From 565efa4f39650d09c05f3895f9a2b16d0f5e7bad Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sun, 11 Jan 2026 14:42:31 +0100 Subject: [PATCH] Squashed 'external/toxcore/c-toxcore/' changes from 1828c5356..c9cdae001 c9cdae001 fix(toxav): remove extra copy of video frame on encode 4f6d4546b test: Improve the fake network library. a2581e700 refactor(toxcore): generate `Friend_Request` and `Dht_Nodes_Response` 2aaa11770 refactor(toxcore): use Tox_Memory in generated events 5c367452b test(toxcore): fix incorrect mutex in tox_scenario_get_time 8f92e710f perf: Add a timed limit of number of cookie requests. 695b6417a test: Add some more simulated network support. 815ae9ce9 test(toxcore): fix thread-safety in scenario framework 6d85c754e test(toxcore): add unit tests for net_crypto 9c22e79cc test(support): add SimulatedEnvironment for deterministic testing f34fcb195 chore: Update windows Dockerfile to debian stable (trixie). ece0e8980 fix(group_moderation): allow validating unsorted sanction list signatures a4fa754d7 refactor: rename struct Packet to struct Net_Packet d6f330f85 cleanup: Fix some warnings from coverity. e206bffa2 fix(group_chats): fix sync packets reverting topics 0e4715598 test: Add new scenario testing framework. 668291f44 refactor(toxcore): decouple Network_Funcs from sockaddr via IP_Port fc4396cef fix: potential division by zero in toxav and unsafe hex parsing 8e8b352ab refactor: Add nullable annotations to struct members. 7740bb421 refactor: decouple net_crypto from DHT 1936d4296 test: add benchmark for toxav audio and video 46bfdc2df fix: correct printf format specifiers for unsigned integers REVERT: 1828c5356 fix(toxav): remove extra copy of video frame on encode git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: c9cdae001341e701fca980c9bb9febfeb95d2902 --- .github/scripts/autotools-linux | 2 +- CMakeLists.txt | 26 +- Makefile.am | 1 + auto_tests/BUILD.bazel | 4 - auto_tests/CMakeLists.txt | 73 +- auto_tests/Makefile.inc | 404 ++++++--- auto_tests/auto_test_support.c | 345 +------- auto_tests/auto_test_support.h | 15 +- auto_tests/bootstrap_test.c | 34 - auto_tests/check_compat.h | 2 + auto_tests/conference_av_test.c | 480 ----------- auto_tests/conference_double_invite_test.c | 94 -- auto_tests/conference_invite_merge_test.c | 182 ---- auto_tests/conference_peer_nick_test.c | 142 ---- auto_tests/conference_simple_test.c | 268 ------ auto_tests/conference_test.c | 441 ---------- auto_tests/conference_two_test.c | 27 - auto_tests/dht_nodes_response_api_test.c | 170 ---- auto_tests/file_streaming_test.c | 272 ------ auto_tests/file_transfer_test.c | 375 -------- auto_tests/forwarding_test.c | 2 +- auto_tests/friend_connection_test.c | 26 - auto_tests/friend_request_spam_test.c | 77 -- auto_tests/friend_request_test.c | 147 ---- auto_tests/group_general_test.c | 475 ----------- auto_tests/group_invite_test.c | 282 ------ auto_tests/group_message_test.c | 622 -------------- auto_tests/group_moderation_test.c | 667 --------------- auto_tests/group_save_test.c | 306 ------- auto_tests/group_state_test.c | 373 -------- auto_tests/group_sync_test.c | 466 ---------- auto_tests/group_tcp_test.c | 259 ------ auto_tests/group_topic_test.c | 358 -------- auto_tests/lan_discovery_test.c | 53 -- auto_tests/lossless_packet_test.c | 71 -- auto_tests/lossy_packet_test.c | 71 -- auto_tests/netprof_test.c | 134 --- auto_tests/onion_test.c | 2 +- auto_tests/overflow_recvq_test.c | 68 -- auto_tests/overflow_sendq_test.c | 46 - auto_tests/reconnect_test.c | 106 --- auto_tests/save_friend_test.c | 183 ---- auto_tests/save_load_test.c | 315 ------- auto_tests/scenarios/BUILD.bazel | 29 + auto_tests/scenarios/CMakeLists.txt | 99 +++ auto_tests/scenarios/framework/README.md | 79 ++ auto_tests/scenarios/framework/framework.c | 800 ++++++++++++++++++ auto_tests/scenarios/framework/framework.h | 215 +++++ auto_tests/scenarios/scenario_avatar_test.c | 144 ++++ .../scenarios/scenario_bootstrap_test.c | 38 + .../scenarios/scenario_conference_av_test.c | 132 +++ .../scenario_conference_double_invite_test.c | 97 +++ .../scenario_conference_invite_merge_test.c | 300 +++++++ .../scenario_conference_offline_test.c | 132 +++ .../scenario_conference_peer_nick_test.c | 111 +++ .../scenario_conference_query_test.c | 131 +++ .../scenario_conference_simple_test.c | 155 ++++ .../scenarios/scenario_conference_test.c | 148 ++++ .../scenarios/scenario_conference_two_test.c | 38 + .../scenario_dht_nodes_response_api_test.c | 195 +++++ auto_tests/scenarios/scenario_events_test.c | 46 + .../scenarios/scenario_file_cancel_test.c | 81 ++ .../scenarios/scenario_file_seek_test.c | 134 +++ .../scenarios/scenario_file_transfer_test.c | 150 ++++ .../scenario_friend_connection_test.c | 46 + .../scenarios/scenario_friend_delete_test.c | 103 +++ .../scenarios/scenario_friend_query_test.c | 72 ++ .../scenario_friend_read_receipt_test.c | 83 ++ .../scenario_friend_request_spam_test.c | 102 +++ .../scenarios/scenario_friend_request_test.c | 78 ++ .../scenarios/scenario_group_general_test.c | 231 +++++ .../scenarios/scenario_group_invite_test.c | 303 +++++++ .../scenarios/scenario_group_message_test.c | 214 +++++ .../scenario_group_moderation_test.c | 367 ++++++++ .../scenarios/scenario_group_save_test.c | 172 ++++ .../scenarios/scenario_group_state_test.c | 186 ++++ .../scenarios/scenario_group_sync_test.c | 263 ++++++ .../scenarios/scenario_group_tcp_test.c | 234 +++++ .../scenario_group_topic_revert_test.c | 241 ++++++ .../scenarios/scenario_group_topic_test.c | 310 +++++++ .../scenarios/scenario_lan_discovery_test.c | 51 ++ .../scenarios/scenario_lossless_packet_test.c | 68 ++ .../scenarios/scenario_lossy_packet_test.c | 52 ++ auto_tests/scenarios/scenario_message_test.c | 65 ++ auto_tests/scenarios/scenario_netprof_test.c | 61 ++ auto_tests/scenarios/scenario_nospam_test.c | 105 +++ .../scenarios/scenario_overflow_recvq_test.c | 125 +++ .../scenarios/scenario_overflow_sendq_test.c | 63 ++ .../scenarios/scenario_reconnect_test.c | 61 ++ .../scenarios/scenario_save_friend_test.c | 93 ++ .../scenarios/scenario_save_load_test.c | 90 ++ .../scenarios/scenario_self_query_test.c | 92 ++ .../scenarios/scenario_send_message_test.c | 98 +++ auto_tests/scenarios/scenario_set_name_test.c | 45 + .../scenario_set_status_message_test.c | 45 + .../scenarios/scenario_tox_many_tcp_test.c | 160 ++++ auto_tests/scenarios/scenario_tox_many_test.c | 102 +++ .../scenarios/scenario_toxav_basic_test.c | 205 +++++ .../scenarios/scenario_toxav_many_test.c | 184 ++++ auto_tests/scenarios/scenario_typing_test.c | 54 ++ .../scenarios/scenario_user_status_test.c | 102 +++ auto_tests/send_message_test.c | 83 -- auto_tests/set_name_test.c | 130 --- auto_tests/set_status_message_test.c | 133 --- auto_tests/tox_events_test.c | 134 --- auto_tests/tox_many_tcp_test.c | 297 ------- auto_tests/tox_many_test.c | 148 ---- auto_tests/toxav_basic_test.c | 566 ------------- auto_tests/toxav_many_test.c | 383 --------- auto_tests/typing_test.c | 67 -- other/analysis/gen-file.sh | 54 +- other/analysis/run-clang | 8 +- other/analysis/run-clang-analyze | 25 +- other/analysis/run-clang-tidy | 8 +- other/analysis/run-cppcheck | 10 +- other/analysis/run-gcc | 4 +- other/bootstrap_daemon/src/config.c | 26 +- other/deploy/apple/Info.plist | 4 +- other/deploy/apple/toxcore.podspec | 2 +- other/deploy/single-file/make_single_file | 4 +- .../autotools.Dockerfile.dockerignore | 1 + other/docker/compcert/Makefile | 4 +- other/docker/compcert/compcert.Dockerfile | 2 +- other/docker/misra/Makefile | 4 + other/docker/modules/check | 275 +++--- other/docker/slimcc/Makefile | 5 +- other/docker/slimcc/slimcc.Dockerfile | 2 +- other/docker/tcc/tcc.Dockerfile | 18 +- other/docker/windows/Dockerfile | 2 +- other/docker/windows/build_toxcore.sh | 2 +- other/docker/windows/get_packages.sh | 8 +- other/event_tooling/generate_event_c.cpp | 167 +++- testing/BUILD.bazel | 1 + testing/CMakeLists.txt | 2 + testing/Messenger_test.c | 2 +- testing/fuzzing/BUILD.bazel | 69 +- testing/fuzzing/CMakeLists.txt | 5 +- testing/fuzzing/bootstrap_fuzz_test.cc | 67 +- testing/fuzzing/e2e_fuzz_test.cc | 64 +- testing/fuzzing/func_conversion.hh | 69 -- testing/fuzzing/fuzz_support.cc | 479 ----------- testing/fuzzing/fuzz_support.hh | 407 --------- testing/fuzzing/fuzz_tox.hh | 17 - testing/fuzzing/protodump.cc | 180 ++-- testing/fuzzing/protodump_reduce.cc | 213 ----- testing/fuzzing/toxsave_fuzz_test.cc | 14 +- testing/support/BUILD.bazel | 111 +++ testing/support/CMakeLists.txt | 72 ++ testing/support/bootstrap_scaling_test.cc | 109 +++ testing/support/doubles/fake_clock.hh | 23 + testing/support/doubles/fake_memory.hh | 51 ++ testing/support/doubles/fake_network_stack.cc | 299 +++++++ testing/support/doubles/fake_network_stack.hh | 56 ++ .../doubles/fake_network_stack_test.cc | 91 ++ testing/support/doubles/fake_random.hh | 45 + testing/support/doubles/fake_sockets.cc | 485 +++++++++++ testing/support/doubles/fake_sockets.hh | 165 ++++ testing/support/doubles/fake_sockets_test.cc | 125 +++ testing/support/doubles/network_universe.cc | 145 ++++ testing/support/doubles/network_universe.hh | 90 ++ .../support/doubles/network_universe_test.cc | 240 ++++++ testing/support/public/clock.hh | 28 + testing/support/public/environment.hh | 47 + testing/support/public/fuzz_data.hh | 183 ++++ testing/support/public/fuzz_helpers.hh | 23 + testing/support/public/memory.hh | 23 + testing/support/public/network.hh | 51 ++ testing/support/public/random.hh | 22 + .../support/public/simulated_environment.hh | 78 ++ testing/support/public/simulation.hh | 112 +++ testing/support/public/tox_network.hh | 82 ++ testing/support/src/clock.cc | 7 + testing/support/src/environment.cc | 7 + testing/support/src/fake_clock.cc | 16 + testing/support/src/fake_memory.cc | 144 ++++ testing/support/src/fake_random.cc | 63 ++ testing/support/src/fuzz_helpers.cc | 96 +++ testing/support/src/memory.cc | 7 + testing/support/src/network.cc | 21 + testing/support/src/random.cc | 7 + testing/support/src/simulated_environment.cc | 76 ++ testing/support/src/simulation.cc | 113 +++ testing/support/src/tox_network.cc | 330 ++++++++ testing/support/tox_network_test.cc | 226 +++++ tools/test-wrapper.sh | 76 ++ toxav/BUILD.bazel | 67 +- toxav/audio.c | 20 +- toxav/audio.h | 22 +- toxav/audio_bench.cc | 199 +++++ toxav/audio_test.cc | 388 ++++++--- toxav/av_test_support.cc | 171 ++++ toxav/av_test_support.hh | 89 ++ toxav/bwcontroller.c | 4 +- toxav/bwcontroller.h | 22 +- toxav/bwcontroller_test.cc | 16 - toxav/groupav.c | 34 +- toxav/groupav.h | 20 +- toxav/msi.c | 40 +- toxav/msi.h | 37 +- toxav/ring_buffer.h | 18 +- toxav/rtp.c | 61 +- toxav/rtp.h | 48 +- toxav/rtp_bench.cc | 73 ++ toxav/rtp_fuzz_test.cc | 5 +- toxav/rtp_test.cc | 5 +- toxav/toxav.c | 127 +-- toxav/video.c | 22 +- toxav/video.h | 28 +- toxav/video_bench.cc | 196 +++++ toxav/video_test.cc | 344 +++++--- toxcore/BUILD.bazel | 164 +++- toxcore/DHT.c | 77 +- toxcore/DHT.h | 4 +- toxcore/DHT_fuzz_test.cc | 19 +- toxcore/DHT_test.cc | 97 ++- toxcore/DHT_test_util.cc | 142 ++++ toxcore/DHT_test_util.hh | 97 +++ toxcore/Messenger.c | 109 ++- toxcore/Messenger.h | 22 +- toxcore/TCP_client.c | 36 +- toxcore/TCP_client.h | 8 + toxcore/TCP_client_test.cc | 365 ++++++++ toxcore/TCP_connection.c | 110 ++- toxcore/TCP_server.c | 18 +- toxcore/TCP_server.h | 9 + toxcore/announce.c | 35 +- toxcore/bin_pack.c | 2 +- toxcore/bin_unpack.c | 4 +- toxcore/crypto_core_test.cc | 43 +- toxcore/crypto_core_test_util.cc | 19 - toxcore/crypto_core_test_util.hh | 32 - toxcore/events/conference_connected.c | 12 +- toxcore/events/conference_invite.c | 23 +- toxcore/events/conference_message.c | 23 +- toxcore/events/conference_peer_list_changed.c | 12 +- toxcore/events/conference_peer_name.c | 23 +- toxcore/events/conference_title.c | 23 +- toxcore/events/dht_nodes_response.c | 62 +- toxcore/events/file_chunk_request.c | 12 +- toxcore/events/file_recv.c | 23 +- toxcore/events/file_recv_chunk.c | 23 +- toxcore/events/file_recv_control.c | 12 +- toxcore/events/friend_connection_status.c | 12 +- toxcore/events/friend_lossless_packet.c | 23 +- toxcore/events/friend_lossy_packet.c | 23 +- toxcore/events/friend_message.c | 23 +- toxcore/events/friend_name.c | 23 +- toxcore/events/friend_read_receipt.c | 12 +- toxcore/events/friend_request.c | 43 +- toxcore/events/friend_status.c | 12 +- toxcore/events/friend_status_message.c | 23 +- toxcore/events/friend_typing.c | 12 +- toxcore/events/group_custom_packet.c | 23 +- toxcore/events/group_custom_private_packet.c | 23 +- toxcore/events/group_invite.c | 33 +- toxcore/events/group_join_fail.c | 12 +- toxcore/events/group_message.c | 23 +- toxcore/events/group_moderation.c | 12 +- toxcore/events/group_password.c | 23 +- toxcore/events/group_peer_exit.c | 33 +- toxcore/events/group_peer_join.c | 12 +- toxcore/events/group_peer_limit.c | 12 +- toxcore/events/group_peer_name.c | 23 +- toxcore/events/group_peer_status.c | 12 +- toxcore/events/group_privacy_state.c | 12 +- toxcore/events/group_private_message.c | 23 +- toxcore/events/group_self_join.c | 12 +- toxcore/events/group_topic.c | 23 +- toxcore/events/group_topic_lock.c | 12 +- toxcore/events/group_voice_state.c | 12 +- toxcore/events/self_connection_status.c | 12 +- toxcore/forwarding.c | 12 +- toxcore/forwarding_fuzz_test.cc | 40 +- toxcore/friend_connection.c | 46 +- toxcore/friend_connection.h | 14 +- toxcore/friend_connection_test.cc | 180 ++++ toxcore/friend_requests.c | 10 +- toxcore/friend_requests.h | 4 +- toxcore/group.c | 38 +- toxcore/group_announce_fuzz_test.cc | 44 +- toxcore/group_announce_test.cc | 46 +- toxcore/group_chats.c | 36 +- toxcore/group_chats.h | 4 +- toxcore/group_common.h | 6 +- toxcore/group_connection.c | 22 +- toxcore/group_moderation.c | 30 +- toxcore/group_moderation_fuzz_test.cc | 16 +- toxcore/group_moderation_test.cc | 108 ++- toxcore/list.c | 2 +- toxcore/logger.c | 8 +- toxcore/mem_test_util.cc | 29 - toxcore/mem_test_util.hh | 39 - toxcore/mono_time.c | 10 +- toxcore/mono_time_test.cc | 79 +- toxcore/mono_time_test_util.cc | 13 + toxcore/mono_time_test_util.hh | 9 + toxcore/net_crypto.c | 130 ++- toxcore/net_crypto.h | 20 +- toxcore/net_crypto_fuzz_test.cc | 62 +- toxcore/net_crypto_test.cc | 577 +++++++++++++ toxcore/network.c | 383 +++------ toxcore/network.h | 99 +-- toxcore/network_test.cc | 13 +- toxcore/network_test_util.cc | 81 -- toxcore/network_test_util.hh | 60 -- toxcore/onion.c | 17 +- toxcore/onion_announce.c | 32 +- toxcore/onion_client.c | 142 ++-- toxcore/onion_client.h | 21 +- toxcore/onion_client_fuzz_test.cc | 390 +++++++++ toxcore/onion_client_test.cc | 579 +++++++++++++ toxcore/ping.c | 17 +- toxcore/ping_array.c | 10 +- toxcore/ping_array_test.cc | 102 ++- toxcore/shared_key_cache.c | 10 +- toxcore/sort_bench.cc | 4 +- toxcore/sort_test.cc | 4 +- toxcore/test_util_test.cc | 20 +- toxcore/tox.c | 52 +- toxcore/tox_dispatch.c | 80 +- toxcore/tox_events_fuzz_test.cc | 14 +- toxcore/tox_events_test.cc | 34 +- toxcore/tox_options.c | 20 + toxcore/tox_options.h | 12 + toxcore/tox_private.h | 67 +- toxcore/tox_struct.h | 90 +- toxcore/tox_test.cc | 23 +- toxencryptsave/toxencryptsave.c | 4 +- 328 files changed, 19057 insertions(+), 13982 deletions(-) delete mode 100644 auto_tests/bootstrap_test.c delete mode 100644 auto_tests/conference_av_test.c delete mode 100644 auto_tests/conference_double_invite_test.c delete mode 100644 auto_tests/conference_invite_merge_test.c delete mode 100644 auto_tests/conference_peer_nick_test.c delete mode 100644 auto_tests/conference_simple_test.c delete mode 100644 auto_tests/conference_test.c delete mode 100644 auto_tests/conference_two_test.c delete mode 100644 auto_tests/dht_nodes_response_api_test.c delete mode 100644 auto_tests/file_streaming_test.c delete mode 100644 auto_tests/file_transfer_test.c delete mode 100644 auto_tests/friend_connection_test.c delete mode 100644 auto_tests/friend_request_spam_test.c delete mode 100644 auto_tests/friend_request_test.c delete mode 100644 auto_tests/group_general_test.c delete mode 100644 auto_tests/group_invite_test.c delete mode 100644 auto_tests/group_message_test.c delete mode 100644 auto_tests/group_moderation_test.c delete mode 100644 auto_tests/group_save_test.c delete mode 100644 auto_tests/group_state_test.c delete mode 100644 auto_tests/group_sync_test.c delete mode 100644 auto_tests/group_tcp_test.c delete mode 100644 auto_tests/group_topic_test.c delete mode 100644 auto_tests/lan_discovery_test.c delete mode 100644 auto_tests/lossless_packet_test.c delete mode 100644 auto_tests/lossy_packet_test.c delete mode 100644 auto_tests/netprof_test.c delete mode 100644 auto_tests/overflow_recvq_test.c delete mode 100644 auto_tests/overflow_sendq_test.c delete mode 100644 auto_tests/reconnect_test.c delete mode 100644 auto_tests/save_friend_test.c delete mode 100644 auto_tests/save_load_test.c create mode 100644 auto_tests/scenarios/BUILD.bazel create mode 100644 auto_tests/scenarios/CMakeLists.txt create mode 100644 auto_tests/scenarios/framework/README.md create mode 100644 auto_tests/scenarios/framework/framework.c create mode 100644 auto_tests/scenarios/framework/framework.h create mode 100644 auto_tests/scenarios/scenario_avatar_test.c create mode 100644 auto_tests/scenarios/scenario_bootstrap_test.c create mode 100644 auto_tests/scenarios/scenario_conference_av_test.c create mode 100644 auto_tests/scenarios/scenario_conference_double_invite_test.c create mode 100644 auto_tests/scenarios/scenario_conference_invite_merge_test.c create mode 100644 auto_tests/scenarios/scenario_conference_offline_test.c create mode 100644 auto_tests/scenarios/scenario_conference_peer_nick_test.c create mode 100644 auto_tests/scenarios/scenario_conference_query_test.c create mode 100644 auto_tests/scenarios/scenario_conference_simple_test.c create mode 100644 auto_tests/scenarios/scenario_conference_test.c create mode 100644 auto_tests/scenarios/scenario_conference_two_test.c create mode 100644 auto_tests/scenarios/scenario_dht_nodes_response_api_test.c create mode 100644 auto_tests/scenarios/scenario_events_test.c create mode 100644 auto_tests/scenarios/scenario_file_cancel_test.c create mode 100644 auto_tests/scenarios/scenario_file_seek_test.c create mode 100644 auto_tests/scenarios/scenario_file_transfer_test.c create mode 100644 auto_tests/scenarios/scenario_friend_connection_test.c create mode 100644 auto_tests/scenarios/scenario_friend_delete_test.c create mode 100644 auto_tests/scenarios/scenario_friend_query_test.c create mode 100644 auto_tests/scenarios/scenario_friend_read_receipt_test.c create mode 100644 auto_tests/scenarios/scenario_friend_request_spam_test.c create mode 100644 auto_tests/scenarios/scenario_friend_request_test.c create mode 100644 auto_tests/scenarios/scenario_group_general_test.c create mode 100644 auto_tests/scenarios/scenario_group_invite_test.c create mode 100644 auto_tests/scenarios/scenario_group_message_test.c create mode 100644 auto_tests/scenarios/scenario_group_moderation_test.c create mode 100644 auto_tests/scenarios/scenario_group_save_test.c create mode 100644 auto_tests/scenarios/scenario_group_state_test.c create mode 100644 auto_tests/scenarios/scenario_group_sync_test.c create mode 100644 auto_tests/scenarios/scenario_group_tcp_test.c create mode 100644 auto_tests/scenarios/scenario_group_topic_revert_test.c create mode 100644 auto_tests/scenarios/scenario_group_topic_test.c create mode 100644 auto_tests/scenarios/scenario_lan_discovery_test.c create mode 100644 auto_tests/scenarios/scenario_lossless_packet_test.c create mode 100644 auto_tests/scenarios/scenario_lossy_packet_test.c create mode 100644 auto_tests/scenarios/scenario_message_test.c create mode 100644 auto_tests/scenarios/scenario_netprof_test.c create mode 100644 auto_tests/scenarios/scenario_nospam_test.c create mode 100644 auto_tests/scenarios/scenario_overflow_recvq_test.c create mode 100644 auto_tests/scenarios/scenario_overflow_sendq_test.c create mode 100644 auto_tests/scenarios/scenario_reconnect_test.c create mode 100644 auto_tests/scenarios/scenario_save_friend_test.c create mode 100644 auto_tests/scenarios/scenario_save_load_test.c create mode 100644 auto_tests/scenarios/scenario_self_query_test.c create mode 100644 auto_tests/scenarios/scenario_send_message_test.c create mode 100644 auto_tests/scenarios/scenario_set_name_test.c create mode 100644 auto_tests/scenarios/scenario_set_status_message_test.c create mode 100644 auto_tests/scenarios/scenario_tox_many_tcp_test.c create mode 100644 auto_tests/scenarios/scenario_tox_many_test.c create mode 100644 auto_tests/scenarios/scenario_toxav_basic_test.c create mode 100644 auto_tests/scenarios/scenario_toxav_many_test.c create mode 100644 auto_tests/scenarios/scenario_typing_test.c create mode 100644 auto_tests/scenarios/scenario_user_status_test.c delete mode 100644 auto_tests/send_message_test.c delete mode 100644 auto_tests/set_name_test.c delete mode 100644 auto_tests/set_status_message_test.c delete mode 100644 auto_tests/tox_events_test.c delete mode 100644 auto_tests/tox_many_tcp_test.c delete mode 100644 auto_tests/tox_many_test.c delete mode 100644 auto_tests/toxav_basic_test.c delete mode 100644 auto_tests/toxav_many_test.c delete mode 100644 auto_tests/typing_test.c delete mode 100644 testing/fuzzing/func_conversion.hh delete mode 100644 testing/fuzzing/fuzz_support.cc delete mode 100644 testing/fuzzing/fuzz_support.hh delete mode 100644 testing/fuzzing/fuzz_tox.hh delete mode 100644 testing/fuzzing/protodump_reduce.cc create mode 100644 testing/support/BUILD.bazel create mode 100644 testing/support/CMakeLists.txt create mode 100644 testing/support/bootstrap_scaling_test.cc create mode 100644 testing/support/doubles/fake_clock.hh create mode 100644 testing/support/doubles/fake_memory.hh create mode 100644 testing/support/doubles/fake_network_stack.cc create mode 100644 testing/support/doubles/fake_network_stack.hh create mode 100644 testing/support/doubles/fake_network_stack_test.cc create mode 100644 testing/support/doubles/fake_random.hh create mode 100644 testing/support/doubles/fake_sockets.cc create mode 100644 testing/support/doubles/fake_sockets.hh create mode 100644 testing/support/doubles/fake_sockets_test.cc create mode 100644 testing/support/doubles/network_universe.cc create mode 100644 testing/support/doubles/network_universe.hh create mode 100644 testing/support/doubles/network_universe_test.cc create mode 100644 testing/support/public/clock.hh create mode 100644 testing/support/public/environment.hh create mode 100644 testing/support/public/fuzz_data.hh create mode 100644 testing/support/public/fuzz_helpers.hh create mode 100644 testing/support/public/memory.hh create mode 100644 testing/support/public/network.hh create mode 100644 testing/support/public/random.hh create mode 100644 testing/support/public/simulated_environment.hh create mode 100644 testing/support/public/simulation.hh create mode 100644 testing/support/public/tox_network.hh create mode 100644 testing/support/src/clock.cc create mode 100644 testing/support/src/environment.cc create mode 100644 testing/support/src/fake_clock.cc create mode 100644 testing/support/src/fake_memory.cc create mode 100644 testing/support/src/fake_random.cc create mode 100644 testing/support/src/fuzz_helpers.cc create mode 100644 testing/support/src/memory.cc create mode 100644 testing/support/src/network.cc create mode 100644 testing/support/src/random.cc create mode 100644 testing/support/src/simulated_environment.cc create mode 100644 testing/support/src/simulation.cc create mode 100644 testing/support/src/tox_network.cc create mode 100644 testing/support/tox_network_test.cc create mode 100755 tools/test-wrapper.sh create mode 100644 toxav/audio_bench.cc create mode 100644 toxav/av_test_support.cc create mode 100644 toxav/av_test_support.hh create mode 100644 toxav/rtp_bench.cc create mode 100644 toxav/video_bench.cc create mode 100644 toxcore/TCP_client_test.cc create mode 100644 toxcore/friend_connection_test.cc delete mode 100644 toxcore/mem_test_util.cc delete mode 100644 toxcore/mem_test_util.hh create mode 100644 toxcore/mono_time_test_util.cc create mode 100644 toxcore/mono_time_test_util.hh create mode 100644 toxcore/net_crypto_test.cc create mode 100644 toxcore/onion_client_fuzz_test.cc create mode 100644 toxcore/onion_client_test.cc diff --git a/.github/scripts/autotools-linux b/.github/scripts/autotools-linux index 7cb30299..2692c376 100755 --- a/.github/scripts/autotools-linux +++ b/.github/scripts/autotools-linux @@ -20,5 +20,5 @@ mkdir -p _build cd _build # pushd ../configure "${CONFIG_FLAGS[@]}" || (cat config.log && false) make "-j$NPROC" -k CFLAGS="$C_FLAGS" LDFLAGS="$LD_FLAGS" -make -j50 -k distcheck DISTCHECK_CONFIGURE_FLAGS="${CONFIG_FLAGS[*]}" || (cat tox-*/_build/build/test-suite.log && false) +make -j50 -k distcheck DISTCHECK_CONFIGURE_FLAGS="${CONFIG_FLAGS[*]}" || (find . -name test-suite.log -exec cat {} + && false) cd - # popd diff --git a/CMakeLists.txt b/CMakeLists.txt index bda1746a..9a061821 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,7 @@ if(MSVC AND MSVC_TOOLSET_VERSION LESS 143) else() set(CMAKE_C_STANDARD 11) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF) @@ -533,6 +533,8 @@ make_version_script(toxcore ${toxcore_API_HEADERS}) # "${CMAKE_INSTALL_INCLUDEDIR}/tox". install_module(toxcore DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tox) +add_subdirectory(testing) + ################################################################################ # # :: Unit tests: no networking, just pure function calls. @@ -545,8 +547,8 @@ if(UNITTEST) toxcore/DHT_test_util.hh toxcore/crypto_core_test_util.cc toxcore/crypto_core_test_util.hh - toxcore/mem_test_util.cc - toxcore/mem_test_util.hh + toxcore/mono_time_test_util.cc + toxcore/mono_time_test_util.hh toxcore/network_test_util.cc toxcore/network_test_util.hh toxcore/test_util.cc @@ -555,7 +557,7 @@ endif() function(unit_test subdir target) add_executable(unit_${target}_test ${subdir}/${target}_test.cc) - target_link_libraries(unit_${target}_test PRIVATE test_util) + target_link_libraries(unit_${target}_test PRIVATE test_util support) if(TARGET toxcore_static) target_link_libraries(unit_${target}_test PRIVATE toxcore_static) else() @@ -570,7 +572,7 @@ function(unit_test subdir target) endif() target_link_libraries(unit_${target}_test PRIVATE GTest::gtest GTest::gtest_main GTest::gmock) set_target_properties(unit_${target}_test PROPERTIES COMPILE_FLAGS "${TEST_CXX_FLAGS}") - add_test(NAME ${target} COMMAND ${CROSSCOMPILING_EMULATOR} unit_${target}_test) + add_test(NAME ${target} COMMAND unit_${target}_test) set_property(TEST ${target} PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw") endfunction() @@ -578,12 +580,24 @@ endfunction() # if(UNITTEST AND TARGET GTest::gtest AND TARGET GTest::gmock) if(BUILD_TOXAV) + add_library(av_test_support STATIC + toxav/av_test_support.cc + toxav/av_test_support.hh) + if(TARGET toxcore_static) + target_link_libraries(av_test_support PRIVATE toxcore_static) + else() + target_link_libraries(av_test_support PRIVATE toxcore_shared) + endif() + target_link_libraries(av_test_support PRIVATE GTest::gtest) + unit_test(toxav audio) + target_link_libraries(unit_audio_test PRIVATE av_test_support) unit_test(toxav bwcontroller) unit_test(toxav msi) unit_test(toxav ring_buffer) unit_test(toxav rtp) unit_test(toxav video) + target_link_libraries(unit_video_test PRIVATE av_test_support) endif() unit_test(toxcore DHT) @@ -600,8 +614,6 @@ if(UNITTEST AND TARGET GTest::gtest AND TARGET GTest::gmock) unit_test(toxcore util) endif() -add_subdirectory(testing) - ################################################################################ # # :: Automated regression tests: create a tox network and run integration tests diff --git a/Makefile.am b/Makefile.am index 97bad771..99b4cacb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,7 @@ EXTRA_DIST = \ libtoxcore.pc.in \ tox.spec \ so.version \ + $(top_srcdir)/tools/test-wrapper.sh \ $(top_srcdir)/docs/updates/Crypto.md \ $(top_srcdir)/docs/updates/Spam-Prevention.md \ $(top_srcdir)/docs/updates/Symmetric-NAT-Transversal.md \ diff --git a/auto_tests/BUILD.bazel b/auto_tests/BUILD.bazel index 6e2cac17..6cfc2f20 100644 --- a/auto_tests/BUILD.bazel +++ b/auto_tests/BUILD.bazel @@ -24,9 +24,6 @@ cc_library( flaky_tests = { "crypto_core_test": True, - "lan_discovery_test": True, - "save_load_test": True, - "tox_many_tcp_test": True, } extra_args = { @@ -39,7 +36,6 @@ extra_data = { [cc_test( name = src[:-2], - timeout = "moderate", srcs = [src], args = ["$(location %s)" % src] + extra_args.get( src[:-2], diff --git a/auto_tests/CMakeLists.txt b/auto_tests/CMakeLists.txt index 41f8b79e..05180861 100644 --- a/auto_tests/CMakeLists.txt +++ b/auto_tests/CMakeLists.txt @@ -32,7 +32,7 @@ function(auto_test target) elseif(TARGET Threads::Threads) target_link_libraries(auto_${target}_test PRIVATE Threads::Threads) endif() - add_test(NAME ${target} COMMAND ${CROSSCOMPILING_EMULATOR} auto_${target}_test) + add_test(NAME ${target} COMMAND auto_${target}_test) set_tests_properties(${target} PROPERTIES TIMEOUT "${TEST_TIMEOUT_SECONDS}") # add the source dir as environment variable, so the testdata can be found set_tests_properties(${target} PROPERTIES ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw;srcdir=${CMAKE_CURRENT_SOURCE_DIR}") @@ -40,88 +40,29 @@ endfunction() auto_test(TCP) auto_test(announce) -auto_test(conference) -auto_test(conference_double_invite) -auto_test(conference_invite_merge) -auto_test(conference_peer_nick) -auto_test(conference_simple) -auto_test(conference_two) auto_test(crypto) #auto_test(dht) # Doesn't work with UNITY_BUILD. -auto_test(dht_nodes_response_api) auto_test(encryptsave) auto_test(file_saving) -auto_test(file_streaming) -auto_test(file_transfer) auto_test(forwarding) -auto_test(friend_connection) -auto_test(friend_request) -auto_test(friend_request_spam) -auto_test(group_general) -auto_test(group_invite) -auto_test(group_message) -auto_test(group_moderation) -auto_test(group_save) -auto_test(group_state) -auto_test(group_sync) -auto_test(group_tcp) -auto_test(group_topic) auto_test(invalid_tcp_proxy) auto_test(invalid_udp_proxy) -auto_test(lan_discovery) -auto_test(lossless_packet) -auto_test(lossy_packet) -auto_test(netprof) auto_test(network) auto_test(onion) -auto_test(overflow_recvq) -auto_test(overflow_sendq) -auto_test(reconnect) -auto_test(save_friend) -auto_test(save_load) -auto_test(send_message) -auto_test(set_name) -auto_test(set_status_message) -auto_test(tox_dispatch) -auto_test(tox_events) -auto_test(tox_many) -auto_test(tox_many_tcp) -auto_test(tox_strncasecmp) -auto_test(typing) -auto_test(version) auto_test(save_compatibility) +auto_test(tox_dispatch) +auto_test(tox_new) +auto_test(tox_strncasecmp) +auto_test(version) + +add_subdirectory(scenarios) target_include_directories(auto_encryptsave_test SYSTEM PRIVATE ${LIBSODIUM_INCLUDE_DIRS}) if(NON_HERMETIC_TESTS) - auto_test(bootstrap) auto_test(tcp_relay) endif() -if(BUILD_TOXAV) - auto_test(conference_av) - auto_test(toxav_basic) - auto_test(toxav_many) - - if(TARGET libvpx::libvpx) - target_link_libraries(auto_toxav_basic_test PRIVATE libvpx::libvpx) - target_link_libraries(auto_toxav_many_test PRIVATE libvpx::libvpx) - elseif(TARGET PkgConfig::VPX) - target_link_libraries(auto_toxav_basic_test PRIVATE PkgConfig::VPX) - target_link_libraries(auto_toxav_many_test PRIVATE PkgConfig::VPX) - else() - target_link_libraries(auto_toxav_basic_test PRIVATE ${VPX_LIBRARIES}) - target_link_directories(auto_toxav_basic_test PRIVATE ${VPX_LIBRARY_DIRS}) - target_include_directories(auto_toxav_basic_test SYSTEM PRIVATE ${VPX_INCLUDE_DIRS}) - target_compile_options(auto_toxav_basic_test PRIVATE ${VPX_CFLAGS_OTHER}) - - target_link_libraries(auto_toxav_many_test PRIVATE ${VPX_LIBRARIES}) - target_link_directories(auto_toxav_many_test PRIVATE ${VPX_LIBRARY_DIRS}) - target_include_directories(auto_toxav_many_test SYSTEM PRIVATE ${VPX_INCLUDE_DIRS}) - target_compile_options(auto_toxav_many_test PRIVATE ${VPX_CFLAGS_OTHER}) - endif() -endif() - if(PROXY_TEST) auto_test(proxy) endif() diff --git a/auto_tests/Makefile.inc b/auto_tests/Makefile.inc index 6e4882d8..f786f2f2 100644 --- a/auto_tests/Makefile.inc +++ b/auto_tests/Makefile.inc @@ -1,48 +1,80 @@ if BUILD_TESTS +LOG_COMPILER = $(top_srcdir)/tools/test-wrapper.sh +AM_LOG_FLAGS = --timeout 60 --retries 3 + noinst_LTLIBRARIES += libauto_test_support.la libauto_test_support_la_SOURCES = ../auto_tests/auto_test_support.c ../auto_tests/auto_test_support.h libauto_test_support_la_LIBADD = libmisc_tools.la libtoxcore.la +noinst_LTLIBRARIES += libscenario_framework.la +libscenario_framework_la_SOURCES = ../auto_tests/scenarios/framework/framework.c ../auto_tests/scenarios/framework/framework.h +libscenario_framework_la_LIBADD = libmisc_tools.la libtoxcore.la + TESTS = \ announce_test \ - conference_double_invite_test \ - conference_invite_merge_test \ - conference_peer_nick_test \ - conference_simple_test \ - conference_test \ - conference_two_test \ crypto_test \ encryptsave_test \ file_saving_test \ - file_streaming_test \ - file_transfer_test \ forwarding_test \ - friend_connection_test \ - friend_request_test \ - group_state_test \ invalid_tcp_proxy_test \ invalid_udp_proxy_test \ - lan_discovery_test \ - lossless_packet_test \ - lossy_packet_test \ network_test \ onion_test \ - overflow_recvq_test \ - overflow_sendq_test \ - reconnect_test \ save_compatibility_test \ - save_friend_test \ - send_message_test \ - set_name_test \ - set_status_message_test \ + scenario_avatar_test \ + scenario_bootstrap_test \ + scenario_conference_double_invite_test \ + scenario_conference_invite_merge_test \ + scenario_conference_offline_test \ + scenario_conference_peer_nick_test \ + scenario_conference_query_test \ + scenario_conference_simple_test \ + scenario_conference_test \ + scenario_conference_two_test \ + scenario_dht_nodes_response_api_test \ + scenario_events_test \ + scenario_file_cancel_test \ + scenario_file_seek_test \ + scenario_file_transfer_test \ + scenario_friend_connection_test \ + scenario_friend_delete_test \ + scenario_friend_query_test \ + scenario_friend_read_receipt_test \ + scenario_friend_request_spam_test \ + scenario_friend_request_test \ + scenario_group_general_test \ + scenario_group_invite_test \ + scenario_group_message_test \ + scenario_group_moderation_test \ + scenario_group_save_test \ + scenario_group_state_test \ + scenario_group_sync_test \ + scenario_group_tcp_test \ + scenario_group_topic_test \ + scenario_lossless_packet_test \ + scenario_lossy_packet_test \ + scenario_message_test \ + scenario_netprof_test \ + scenario_nospam_test \ + scenario_overflow_recvq_test \ + scenario_overflow_sendq_test \ + scenario_reconnect_test \ + scenario_save_friend_test \ + scenario_save_load_test \ + scenario_self_query_test \ + scenario_send_message_test \ + scenario_set_name_test \ + scenario_set_status_message_test \ + scenario_tox_many_test \ + scenario_tox_many_tcp_test \ + scenario_typing_test \ + scenario_user_status_test \ + tcp_relay_test \ TCP_test \ tox_dispatch_test \ - tox_events_test \ - tox_many_tcp_test \ - tox_many_test \ + tox_new_test \ tox_strncasecmp_test \ - typing_test \ version_test AUTOTEST_CFLAGS = \ @@ -58,7 +90,7 @@ AUTOTEST_LDADD = \ if BUILD_AV -TESTS += conference_av_test toxav_basic_test toxav_many_test +TESTS += scenario_conference_av_test scenario_toxav_basic_test scenario_toxav_many_test AUTOTEST_LDADD += libtoxav.la endif @@ -68,30 +100,6 @@ announce_test_SOURCES = ../auto_tests/announce_test.c announce_test_CFLAGS = $(AUTOTEST_CFLAGS) announce_test_LDADD = $(AUTOTEST_LDADD) -conference_double_invite_test_SOURCES = ../auto_tests/conference_double_invite_test.c -conference_double_invite_test_CFLAGS = $(AUTOTEST_CFLAGS) -conference_double_invite_test_LDADD = $(AUTOTEST_LDADD) - -conference_invite_merge_test_SOURCES = ../auto_tests/conference_invite_merge_test.c -conference_invite_merge_test_CFLAGS = $(AUTOTEST_CFLAGS) -conference_invite_merge_test_LDADD = $(AUTOTEST_LDADD) - -conference_peer_nick_test_SOURCES = ../auto_tests/conference_peer_nick_test.c -conference_peer_nick_test_CFLAGS = $(AUTOTEST_CFLAGS) -conference_peer_nick_test_LDADD = $(AUTOTEST_LDADD) - -conference_simple_test_SOURCES = ../auto_tests/conference_simple_test.c -conference_simple_test_CFLAGS = $(AUTOTEST_CFLAGS) -conference_simple_test_LDADD = $(AUTOTEST_LDADD) - -conference_test_SOURCES = ../auto_tests/conference_test.c -conference_test_CFLAGS = $(AUTOTEST_CFLAGS) -conference_test_LDADD = $(AUTOTEST_LDADD) - -conference_two_test_SOURCES = ../auto_tests/conference_two_test.c -conference_two_test_CFLAGS = $(AUTOTEST_CFLAGS) -conference_two_test_LDADD = $(AUTOTEST_LDADD) - crypto_test_SOURCES = ../auto_tests/crypto_test.c crypto_test_CFLAGS = $(AUTOTEST_CFLAGS) crypto_test_LDADD = $(AUTOTEST_LDADD) @@ -104,30 +112,10 @@ file_saving_test_SOURCES = ../auto_tests/file_saving_test.c file_saving_test_CFLAGS = $(AUTOTEST_CFLAGS) file_saving_test_LDADD = $(AUTOTEST_LDADD) -file_streaming_test_SOURCES = ../auto_tests/file_streaming_test.c -file_streaming_test_CFLAGS = $(AUTOTEST_CFLAGS) -file_streaming_test_LDADD = $(AUTOTEST_LDADD) - -file_transfer_test_SOURCES = ../auto_tests/file_transfer_test.c -file_transfer_test_CFLAGS = $(AUTOTEST_CFLAGS) -file_transfer_test_LDADD = $(AUTOTEST_LDADD) - forwarding_test_SOURCES = ../auto_tests/forwarding_test.c forwarding_test_CFLAGS = $(AUTOTEST_CFLAGS) forwarding_test_LDADD = $(AUTOTEST_LDADD) -friend_connection_test_SOURCES = ../auto_tests/friend_connection_test.c -friend_connection_test_CFLAGS = $(AUTOTEST_CFLAGS) -friend_connection_test_LDADD = $(AUTOTEST_LDADD) - -friend_request_test_SOURCES = ../auto_tests/friend_request_test.c -friend_request_test_CFLAGS = $(AUTOTEST_CFLAGS) -friend_request_test_LDADD = $(AUTOTEST_LDADD) - -group_state_test_SOURCES = ../auto_tests/group_state_test.c -group_state_test_CFLAGS = $(AUTOTEST_CFLAGS) -group_state_test_LDADD = $(AUTOTEST_LDADD) - invalid_tcp_proxy_test_SOURCES = ../auto_tests/invalid_tcp_proxy_test.c invalid_tcp_proxy_test_CFLAGS = $(AUTOTEST_CFLAGS) invalid_tcp_proxy_test_LDADD = $(AUTOTEST_LDADD) @@ -136,18 +124,6 @@ invalid_udp_proxy_test_SOURCES = ../auto_tests/invalid_udp_proxy_test.c invalid_udp_proxy_test_CFLAGS = $(AUTOTEST_CFLAGS) invalid_udp_proxy_test_LDADD = $(AUTOTEST_LDADD) -lan_discovery_test_SOURCES = ../auto_tests/lan_discovery_test.c -lan_discovery_test_CFLAGS = $(AUTOTEST_CFLAGS) -lan_discovery_test_LDADD = $(AUTOTEST_LDADD) - -lossless_packet_test_SOURCES = ../auto_tests/lossless_packet_test.c -lossless_packet_test_CFLAGS = $(AUTOTEST_CFLAGS) -lossless_packet_test_LDADD = $(AUTOTEST_LDADD) - -lossy_packet_test_SOURCES = ../auto_tests/lossy_packet_test.c -lossy_packet_test_CFLAGS = $(AUTOTEST_CFLAGS) -lossy_packet_test_LDADD = $(AUTOTEST_LDADD) - network_test_SOURCES = ../auto_tests/network_test.c network_test_CFLAGS = $(AUTOTEST_CFLAGS) network_test_LDADD = $(AUTOTEST_LDADD) @@ -156,37 +132,218 @@ onion_test_SOURCES = ../auto_tests/onion_test.c onion_test_CFLAGS = $(AUTOTEST_CFLAGS) onion_test_LDADD = $(AUTOTEST_LDADD) -overflow_recvq_test_SOURCES = ../auto_tests/overflow_recvq_test.c -overflow_recvq_test_CFLAGS = $(AUTOTEST_CFLAGS) -overflow_recvq_test_LDADD = $(AUTOTEST_LDADD) - -overflow_sendq_test_SOURCES = ../auto_tests/overflow_sendq_test.c -overflow_sendq_test_CFLAGS = $(AUTOTEST_CFLAGS) -overflow_sendq_test_LDADD = $(AUTOTEST_LDADD) - -reconnect_test_SOURCES = ../auto_tests/reconnect_test.c -reconnect_test_CFLAGS = $(AUTO_TEST_CFLAGS) -reconnect_test_LDADD = $(AUTOTEST_LDADD) - save_compatibility_test_SOURCES = ../auto_tests/save_compatibility_test.c save_compatibility_test_CFLAGS = $(AUTOTEST_CFLAGS) save_compatibility_test_LDADD = $(AUTOTEST_LDADD) -save_friend_test_SOURCES = ../auto_tests/save_friend_test.c -save_friend_test_CFLAGS = $(AUTOTEST_CFLAGS) -save_friend_test_LDADD = $(AUTOTEST_LDADD) +tcp_relay_test_SOURCES = ../auto_tests/tcp_relay_test.c +tcp_relay_test_CFLAGS = $(AUTOTEST_CFLAGS) +tcp_relay_test_LDADD = $(AUTOTEST_LDADD) -send_message_test_SOURCES = ../auto_tests/send_message_test.c -send_message_test_CFLAGS = $(AUTOTEST_CFLAGS) -send_message_test_LDADD = $(AUTOTEST_LDADD) +tox_new_test_SOURCES = ../auto_tests/tox_new_test.c +tox_new_test_CFLAGS = $(AUTOTEST_CFLAGS) +tox_new_test_LDADD = $(AUTOTEST_LDADD) -set_name_test_SOURCES = ../auto_tests/set_name_test.c -set_name_test_CFLAGS = $(AUTOTEST_CFLAGS) -set_name_test_LDADD = $(AUTOTEST_LDADD) +scenario_avatar_test_SOURCES = ../auto_tests/scenarios/scenario_avatar_test.c +scenario_avatar_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_avatar_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la -set_status_message_test_SOURCES = ../auto_tests/set_status_message_test.c -set_status_message_test_CFLAGS = $(AUTOTEST_CFLAGS) -set_status_message_test_LDADD = $(AUTOTEST_LDADD) +scenario_conference_simple_test_SOURCES = ../auto_tests/scenarios/scenario_conference_simple_test.c +scenario_conference_simple_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_simple_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_conference_offline_test_SOURCES = ../auto_tests/scenarios/scenario_conference_offline_test.c +scenario_conference_offline_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_offline_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_conference_query_test_SOURCES = ../auto_tests/scenarios/scenario_conference_query_test.c +scenario_conference_query_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_query_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_conference_double_invite_test_SOURCES = ../auto_tests/scenarios/scenario_conference_double_invite_test.c +scenario_conference_double_invite_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_double_invite_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_conference_invite_merge_test_SOURCES = ../auto_tests/scenarios/scenario_conference_invite_merge_test.c +scenario_conference_invite_merge_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_invite_merge_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_conference_peer_nick_test_SOURCES = ../auto_tests/scenarios/scenario_conference_peer_nick_test.c +scenario_conference_peer_nick_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_peer_nick_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_conference_av_test_SOURCES = ../auto_tests/scenarios/scenario_conference_av_test.c +scenario_conference_av_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_av_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_bootstrap_test_SOURCES = ../auto_tests/scenarios/scenario_bootstrap_test.c +scenario_bootstrap_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_bootstrap_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_conference_test_SOURCES = ../auto_tests/scenarios/scenario_conference_test.c +scenario_conference_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_conference_two_test_SOURCES = ../auto_tests/scenarios/scenario_conference_two_test.c +scenario_conference_two_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_conference_two_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_dht_nodes_response_api_test_SOURCES = ../auto_tests/scenarios/scenario_dht_nodes_response_api_test.c +scenario_dht_nodes_response_api_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_dht_nodes_response_api_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_events_test_SOURCES = ../auto_tests/scenarios/scenario_events_test.c +scenario_events_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_events_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_file_cancel_test_SOURCES = ../auto_tests/scenarios/scenario_file_cancel_test.c +scenario_file_cancel_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_file_cancel_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_file_seek_test_SOURCES = ../auto_tests/scenarios/scenario_file_seek_test.c +scenario_file_seek_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_file_seek_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_file_transfer_test_SOURCES = ../auto_tests/scenarios/scenario_file_transfer_test.c +scenario_file_transfer_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_file_transfer_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_friend_connection_test_SOURCES = ../auto_tests/scenarios/scenario_friend_connection_test.c +scenario_friend_connection_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_friend_connection_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_friend_request_test_SOURCES = ../auto_tests/scenarios/scenario_friend_request_test.c +scenario_friend_request_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_friend_request_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_friend_delete_test_SOURCES = ../auto_tests/scenarios/scenario_friend_delete_test.c +scenario_friend_delete_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_friend_delete_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_friend_query_test_SOURCES = ../auto_tests/scenarios/scenario_friend_query_test.c +scenario_friend_query_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_friend_query_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_friend_read_receipt_test_SOURCES = ../auto_tests/scenarios/scenario_friend_read_receipt_test.c +scenario_friend_read_receipt_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_friend_read_receipt_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_friend_request_spam_test_SOURCES = ../auto_tests/scenarios/scenario_friend_request_spam_test.c +scenario_friend_request_spam_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_friend_request_spam_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_general_test_SOURCES = ../auto_tests/scenarios/scenario_group_general_test.c +scenario_group_general_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_general_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_invite_test_SOURCES = ../auto_tests/scenarios/scenario_group_invite_test.c +scenario_group_invite_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_invite_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_message_test_SOURCES = ../auto_tests/scenarios/scenario_group_message_test.c +scenario_group_message_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_message_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_moderation_test_SOURCES = ../auto_tests/scenarios/scenario_group_moderation_test.c +scenario_group_moderation_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_moderation_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_save_test_SOURCES = ../auto_tests/scenarios/scenario_group_save_test.c +scenario_group_save_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_save_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_state_test_SOURCES = ../auto_tests/scenarios/scenario_group_state_test.c +scenario_group_state_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_state_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_sync_test_SOURCES = ../auto_tests/scenarios/scenario_group_sync_test.c +scenario_group_sync_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_sync_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_tcp_test_SOURCES = ../auto_tests/scenarios/scenario_group_tcp_test.c +scenario_group_tcp_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_tcp_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_group_topic_test_SOURCES = ../auto_tests/scenarios/scenario_group_topic_test.c +scenario_group_topic_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_group_topic_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +# A bit flaky, and autotools doesn't do retries. +# scenario_lan_discovery_test_SOURCES = ../auto_tests/scenarios/scenario_lan_discovery_test.c +# scenario_lan_discovery_test_CFLAGS = $(AUTOTEST_CFLAGS) +# scenario_lan_discovery_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_lossless_packet_test_SOURCES = ../auto_tests/scenarios/scenario_lossless_packet_test.c +scenario_lossless_packet_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_lossless_packet_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_lossy_packet_test_SOURCES = ../auto_tests/scenarios/scenario_lossy_packet_test.c +scenario_lossy_packet_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_lossy_packet_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_message_test_SOURCES = ../auto_tests/scenarios/scenario_message_test.c +scenario_message_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_message_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_netprof_test_SOURCES = ../auto_tests/scenarios/scenario_netprof_test.c +scenario_netprof_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_netprof_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_nospam_test_SOURCES = ../auto_tests/scenarios/scenario_nospam_test.c +scenario_nospam_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_nospam_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_reconnect_test_SOURCES = ../auto_tests/scenarios/scenario_reconnect_test.c +scenario_reconnect_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_reconnect_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_save_friend_test_SOURCES = ../auto_tests/scenarios/scenario_save_friend_test.c +scenario_save_friend_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_save_friend_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_self_query_test_SOURCES = ../auto_tests/scenarios/scenario_self_query_test.c +scenario_self_query_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_self_query_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_overflow_recvq_test_SOURCES = ../auto_tests/scenarios/scenario_overflow_recvq_test.c +scenario_overflow_recvq_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_overflow_recvq_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_overflow_sendq_test_SOURCES = ../auto_tests/scenarios/scenario_overflow_sendq_test.c +scenario_overflow_sendq_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_overflow_sendq_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_save_load_test_SOURCES = ../auto_tests/scenarios/scenario_save_load_test.c +scenario_save_load_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_save_load_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_send_message_test_SOURCES = ../auto_tests/scenarios/scenario_send_message_test.c +scenario_send_message_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_send_message_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_set_name_test_SOURCES = ../auto_tests/scenarios/scenario_set_name_test.c +scenario_set_name_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_set_name_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_set_status_message_test_SOURCES = ../auto_tests/scenarios/scenario_set_status_message_test.c +scenario_set_status_message_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_set_status_message_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_tox_many_test_SOURCES = ../auto_tests/scenarios/scenario_tox_many_test.c +scenario_tox_many_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_tox_many_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_tox_many_tcp_test_SOURCES = ../auto_tests/scenarios/scenario_tox_many_tcp_test.c +scenario_tox_many_tcp_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_tox_many_tcp_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_typing_test_SOURCES = ../auto_tests/scenarios/scenario_typing_test.c +scenario_typing_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_typing_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la + +scenario_user_status_test_SOURCES = ../auto_tests/scenarios/scenario_user_status_test.c +scenario_user_status_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_user_status_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la TCP_test_SOURCES = ../auto_tests/TCP_test.c TCP_test_CFLAGS = $(AUTOTEST_CFLAGS) @@ -196,43 +353,23 @@ tox_dispatch_test_SOURCES = ../auto_tests/tox_dispatch_test.c tox_dispatch_test_CFLAGS = $(AUTOTEST_CFLAGS) tox_dispatch_test_LDADD = $(AUTOTEST_LDADD) -tox_events_test_SOURCES = ../auto_tests/tox_events_test.c -tox_events_test_CFLAGS = $(AUTOTEST_CFLAGS) -tox_events_test_LDADD = $(AUTOTEST_LDADD) - -tox_many_tcp_test_SOURCES = ../auto_tests/tox_many_tcp_test.c -tox_many_tcp_test_CFLAGS = $(AUTOTEST_CFLAGS) -tox_many_tcp_test_LDADD = $(AUTOTEST_LDADD) - -tox_many_test_SOURCES = ../auto_tests/tox_many_test.c -tox_many_test_CFLAGS = $(AUTOTEST_CFLAGS) -tox_many_test_LDADD = $(AUTOTEST_LDADD) - tox_strncasecmp_test_SOURCES = ../auto_tests/tox_strncasecmp_test.c tox_strncasecmp_test_CFLAGS = $(AUTOTEST_CFLAGS) tox_strncasecmp_test_LDADD = $(AUTOTEST_LDADD) -typing_test_SOURCES = ../auto_tests/typing_test.c -typing_test_CFLAGS = $(AUTOTEST_CFLAGS) -typing_test_LDADD = $(AUTOTEST_LDADD) - version_test_SOURCES = ../auto_tests/version_test.c version_test_CFLAGS = $(AUTOTEST_CFLAGS) version_test_LDADD = $(AUTOTEST_LDADD) if BUILD_AV -conference_av_test_SOURCES = ../auto_tests/conference_av_test.c -conference_av_test_CFLAGS = $(AUTOTEST_CFLAGS) -conference_av_test_LDADD = $(AUTOTEST_LDADD) +scenario_toxav_basic_test_SOURCES = ../auto_tests/scenarios/scenario_toxav_basic_test.c +scenario_toxav_basic_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_toxav_basic_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la -toxav_basic_test_SOURCES = ../auto_tests/toxav_basic_test.c -toxav_basic_test_CFLAGS = $(AUTOTEST_CFLAGS) -toxav_basic_test_LDADD = $(AUTOTEST_LDADD) $(AV_LIBS) - -toxav_many_test_SOURCES = ../auto_tests/toxav_many_test.c -toxav_many_test_CFLAGS = $(AUTOTEST_CFLAGS) -toxav_many_test_LDADD = $(AUTOTEST_LDADD) +scenario_toxav_many_test_SOURCES = ../auto_tests/scenarios/scenario_toxav_many_test.c +scenario_toxav_many_test_CFLAGS = $(AUTOTEST_CFLAGS) +scenario_toxav_many_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la endif @@ -243,4 +380,5 @@ EXTRA_DIST += \ $(top_srcdir)/auto_tests/data/save.tox.big \ $(top_srcdir)/auto_tests/data/save.tox.little \ $(top_srcdir)/auto_tests/check_compat.h \ - $(top_srcdir)/auto_tests/auto_test_support.h + $(top_srcdir)/auto_tests/auto_test_support.h \ + $(top_srcdir)/auto_tests/scenarios/framework/framework.h diff --git a/auto_tests/auto_test_support.c b/auto_tests/auto_test_support.c index 551c4809..4086f617 100644 --- a/auto_tests/auto_test_support.c +++ b/auto_tests/auto_test_support.c @@ -8,6 +8,8 @@ #include "../toxcore/tox_dispatch.h" #include "../toxcore/tox_events.h" #include "../toxcore/tox_struct.h" +#include "../toxcore/net_crypto.h" +#include "../toxcore/DHT.h" #include "auto_test_support.h" @@ -15,20 +17,31 @@ #define ABORT_ON_LOG_ERROR true #endif +static const uint8_t *auto_test_nc_dht_get_shared_key_sent_wrapper(void *dht, const uint8_t *public_key) +{ + return dht_get_shared_key_sent((DHT *)dht, public_key); +} + +static const uint8_t *auto_test_nc_dht_get_self_public_key_wrapper(const void *dht) +{ + return dht_get_self_public_key((const DHT *)dht); +} + +static const uint8_t *auto_test_nc_dht_get_self_secret_key_wrapper(const void *dht) +{ + return dht_get_self_secret_key((const DHT *)dht); +} + +const Net_Crypto_DHT_Funcs auto_test_dht_funcs = { + auto_test_nc_dht_get_shared_key_sent_wrapper, + auto_test_nc_dht_get_self_public_key_wrapper, + auto_test_nc_dht_get_self_secret_key_wrapper, +}; + #ifndef USE_IPV6 #define USE_IPV6 1 #endif -Run_Auto_Options default_run_auto_options(void) -{ - return (Run_Auto_Options) { - .graph = GRAPH_COMPLETE, - .init_autotox = nullptr, - .tcp_port = 33188, - .events = true, - }; -} - // List of live bootstrap nodes. These nodes should have TCP server enabled. static const struct BootstrapNodes { const char *ip; @@ -92,320 +105,8 @@ void bootstrap_tox_live_network(Tox *tox, bool enable_tcp) } } -bool all_connected(const AutoTox *autotoxes, uint32_t tox_count) -{ - if (tox_count) { - ck_assert(autotoxes != nullptr); - } - - for (uint32_t i = 0; i < tox_count; ++i) { - if (tox_self_get_connection_status(autotoxes[i].tox) == TOX_CONNECTION_NONE) { - return false; - } - } - - return true; -} - -bool all_friends_connected(const AutoTox *autotoxes, uint32_t tox_count) -{ - if (tox_count) { - ck_assert(autotoxes != nullptr); - } - - for (uint32_t i = 0; i < tox_count; ++i) { - const size_t friend_count = tox_self_get_friend_list_size(autotoxes[i].tox); - - for (size_t j = 0; j < friend_count; ++j) { - if (tox_friend_get_connection_status(autotoxes[i].tox, j, nullptr) == TOX_CONNECTION_NONE) { - return false; - } - } - } - - return true; -} - -void iterate_all_wait(AutoTox *autotoxes, uint32_t tox_count, uint32_t wait) -{ - if (tox_count) { - ck_assert(autotoxes != nullptr); - } - - for (uint32_t i = 0; i < tox_count; ++i) { - if (autotoxes[i].alive) { - if (autotoxes[i].events) { - Tox_Err_Events_Iterate err; - Tox_Events *events = tox_events_iterate(autotoxes[i].tox, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(autotoxes[i].dispatch, events, &autotoxes[i]); - tox_events_free(events); - } else { - tox_iterate(autotoxes[i].tox, &autotoxes[i]); - } - autotoxes[i].clock += wait; - } - } - - /* Also actually sleep a little, to allow for local network processing */ - c_sleep(5); -} - -static uint64_t get_state_clock_callback(void *user_data) -{ - const uint64_t *clock = (const uint64_t *)user_data; - return *clock; -} - -void set_mono_time_callback(AutoTox *autotox) -{ - ck_assert(autotox != nullptr); - - Mono_Time *mono_time = autotox->tox->mono_time; - - autotox->clock = current_time_monotonic(mono_time); - ck_assert_msg(autotox->clock >= 1000, - "clock is too low (not initialised?): %lu", (unsigned long)autotox->clock); - mono_time_set_current_time_callback(mono_time, nullptr, nullptr); // set to default first - mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &autotox->clock); -} - -void save_autotox(AutoTox *autotox) -{ - ck_assert(autotox != nullptr); - - fprintf(stderr, "Saving #%u\n", autotox->index); - - free(autotox->save_state); - autotox->save_state = nullptr; - - autotox->save_size = tox_get_savedata_size(autotox->tox); - ck_assert_msg(autotox->save_size > 0, "save is invalid size %u", (unsigned)autotox->save_size); - autotox->save_state = (uint8_t *)malloc(autotox->save_size); - ck_assert_msg(autotox->save_state != nullptr, "malloc failed"); - tox_get_savedata(autotox->tox, autotox->save_state); -} - -void kill_autotox(AutoTox *autotox) -{ - ck_assert(autotox != nullptr); - ck_assert(autotox->alive); - fprintf(stderr, "Killing #%u\n", autotox->index); - autotox->alive = false; - tox_dispatch_free(autotox->dispatch); - tox_kill(autotox->tox); -} - -void reload(AutoTox *autotox) -{ - ck_assert(autotox != nullptr); - - if (autotox->alive) { - kill_autotox(autotox); - } - - fprintf(stderr, "Reloading #%u\n", autotox->index); - ck_assert(autotox->save_state != nullptr); - - struct Tox_Options *const options = tox_options_new(nullptr); - ck_assert(options != nullptr); - tox_options_set_ipv6_enabled(options, USE_IPV6); - tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); - tox_options_set_savedata_data(options, autotox->save_state, autotox->save_size); - autotox->tox = tox_new_log(options, nullptr, &autotox->index); - ck_assert(autotox->tox != nullptr); - autotox->dispatch = tox_dispatch_new(nullptr); - ck_assert(autotox->dispatch != nullptr); - if (autotox->events) { - tox_events_init(autotox->tox); - } - tox_options_free(options); - - set_mono_time_callback(autotox); - autotox->alive = true; -} - -static void initialise_autotox(struct Tox_Options *options, AutoTox *autotox, uint32_t index, uint32_t state_size, - Run_Auto_Options *autotest_opts) -{ - autotox->index = index; - autotox->events = autotest_opts->events; - - Tox_Err_New err = TOX_ERR_NEW_OK; - - if (index == 0) { - struct Tox_Options *default_opts = tox_options_new(nullptr); - ck_assert(default_opts != nullptr); - - tox_options_set_ipv6_enabled(default_opts, USE_IPV6); - - if (options == nullptr) { - options = default_opts; - } - - if (tox_options_get_udp_enabled(options)) { - tox_options_set_tcp_port(options, 0); - autotest_opts->tcp_port = 0; - autotox->tox = tox_new_log(options, &err, &autotox->index); - ck_assert_msg(err == TOX_ERR_NEW_OK, "unexpected tox_new error: %u", err); - } else { - // Try a few ports for the TCP relay. - for (uint16_t tcp_port = autotest_opts->tcp_port; tcp_port < autotest_opts->tcp_port + 200; ++tcp_port) { - tox_options_set_tcp_port(options, tcp_port); - autotox->tox = tox_new_log(options, &err, &autotox->index); - - if (autotox->tox != nullptr) { - autotest_opts->tcp_port = tcp_port; - break; - } - - ck_assert_msg(err == TOX_ERR_NEW_PORT_ALLOC, "unexpected tox_new error (expected PORT_ALLOC): %u", err); - } - } - - tox_options_free(default_opts); - } else { - // No TCP relay enabled for all the other toxes. - if (options != nullptr) { - tox_options_set_tcp_port(options, 0); - } - - autotox->tox = tox_new_log(options, &err, &autotox->index); - } - - ck_assert_msg(autotox->tox != nullptr, "failed to create tox instance #%u (error = %u)", index, err); - - set_mono_time_callback(autotox); - - autotox->dispatch = tox_dispatch_new(nullptr); - ck_assert(autotox->dispatch != nullptr); - if (autotox->events) { - tox_events_init(autotox->tox); - } - - autotox->alive = true; - autotox->save_state = nullptr; - - if (state_size > 0) { - autotox->state = calloc(1, state_size); - ck_assert(autotox->state != nullptr); - ck_assert_msg(autotox->state != nullptr, "failed to allocate state"); - } else { - autotox->state = nullptr; - } - - if (autotest_opts->init_autotox != nullptr) { - autotest_opts->init_autotox(autotox, index); - } -} - -static void autotox_add_friend(AutoTox *autotoxes, uint32_t adding, uint32_t added) -{ - uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_public_key(autotoxes[added].tox, public_key); - Tox_Err_Friend_Add err; - tox_friend_add_norequest(autotoxes[adding].tox, public_key, &err); - ck_assert(err == TOX_ERR_FRIEND_ADD_OK); -} - -static void initialise_friend_graph(Graph_Type graph, uint32_t num_toxes, AutoTox *autotoxes) -{ - if (graph == GRAPH_LINEAR) { - printf("toxes #%d-#%u each add adjacent toxes as friends\n", 0, num_toxes - 1); - - for (uint32_t i = 0; i < num_toxes; ++i) { - for (uint32_t j = i - 1; j != i + 3; j += 2) { - if (j < num_toxes) { - autotox_add_friend(autotoxes, i, j); - } - } - } - } else if (graph == GRAPH_COMPLETE) { - printf("toxes #%d-#%u add each other as friends\n", 0, num_toxes - 1); - - for (uint32_t i = 0; i < num_toxes; ++i) { - for (uint32_t j = 0; j < num_toxes; ++j) { - if (i != j) { - autotox_add_friend(autotoxes, i, j); - } - } - } - } else { - ck_abort_msg("Unknown graph type"); - } -} - -static void bootstrap_autotoxes(const Tox_Options *options, uint32_t tox_count, const Run_Auto_Options *autotest_opts, - AutoTox *autotoxes) -{ - const bool udp_enabled = options != nullptr ? tox_options_get_udp_enabled(options) : true; - - printf("bootstrapping all toxes off tox 0\n"); - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(autotoxes[0].tox, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(autotoxes[0].tox, nullptr); - - for (uint32_t i = 1; i < tox_count; ++i) { - Tox_Err_Bootstrap err; - tox_bootstrap(autotoxes[i].tox, "localhost", dht_port, dht_key, &err); - ck_assert_msg(err == TOX_ERR_BOOTSTRAP_OK, "bootstrap error for port %d: %u", dht_port, err); - } - - if (!udp_enabled) { - ck_assert(autotest_opts->tcp_port != 0); - printf("bootstrapping all toxes to local TCP relay running on port %d\n", autotest_opts->tcp_port); - - for (uint32_t i = 0; i < tox_count; ++i) { - Tox_Err_Bootstrap err; - tox_add_tcp_relay(autotoxes[i].tox, "localhost", autotest_opts->tcp_port, dht_key, &err); - ck_assert(err == TOX_ERR_BOOTSTRAP_OK); - } - } -} - typedef void autotox_test_cb(AutoTox *autotoxes); -void run_auto_test(struct Tox_Options *options, uint32_t tox_count, autotox_test_cb *test, - uint32_t state_size, Run_Auto_Options *autotest_opts) -{ - printf("initialising %u toxes\n", tox_count); - - AutoTox *autotoxes = (AutoTox *)calloc(tox_count, sizeof(AutoTox)); - - ck_assert(autotoxes != nullptr); - - for (uint32_t i = 0; i < tox_count; ++i) { - initialise_autotox(options, &autotoxes[i], i, state_size, autotest_opts); - } - - initialise_friend_graph(autotest_opts->graph, tox_count, autotoxes); - - bootstrap_autotoxes(options, tox_count, autotest_opts, autotoxes); - - do { - iterate_all_wait(autotoxes, tox_count, ITERATION_INTERVAL); - } while (!all_connected(autotoxes, tox_count)); - - printf("toxes are online\n"); - - do { - iterate_all_wait(autotoxes, tox_count, ITERATION_INTERVAL); - } while (!all_friends_connected(autotoxes, tox_count)); - - printf("tox clients connected\n"); - - test(autotoxes); - - for (uint32_t i = 0; i < tox_count; ++i) { - tox_dispatch_free(autotoxes[i].dispatch); - tox_kill(autotoxes[i].tox); - free(autotoxes[i].state); - free(autotoxes[i].save_state); - } - - free(autotoxes); -} - static const char *tox_log_level_name(Tox_Log_Level level) { switch (level) { diff --git a/auto_tests/auto_test_support.h b/auto_tests/auto_test_support.h index cd883e28..844619a5 100644 --- a/auto_tests/auto_test_support.h +++ b/auto_tests/auto_test_support.h @@ -8,6 +8,7 @@ #include "../toxcore/Messenger.h" #include "../toxcore/mono_time.h" #include "../toxcore/tox_dispatch.h" +#include "../toxcore/net_crypto.h" typedef struct AutoTox { Tox *tox; @@ -24,17 +25,10 @@ typedef struct AutoTox { void *state; } AutoTox; -bool all_connected(const AutoTox *autotoxes, uint32_t tox_count); -bool all_friends_connected(const AutoTox *autotoxes, uint32_t tox_count); -void iterate_all_wait(AutoTox *autotoxes, uint32_t tox_count, uint32_t wait); -void save_autotox(AutoTox *autotox); -void kill_autotox(AutoTox *autotox); -void reload(AutoTox *autotox); -void set_mono_time_callback(AutoTox *autotox); typedef enum Graph_Type { GRAPH_COMPLETE = 0, @@ -48,11 +42,6 @@ typedef struct Run_Auto_Options { bool events; } Run_Auto_Options; -Run_Auto_Options default_run_auto_options(void); - -void run_auto_test(struct Tox_Options *options, uint32_t tox_count, void test(AutoTox *autotoxes), - uint32_t state_size, Run_Auto_Options *autotest_opts); - void bootstrap_tox_live_network(Tox *tox, bool enable_tcp); // Use this function when setting the log callback on a Tox* object @@ -66,4 +55,6 @@ void print_debug_logger(void *context, Logger_Level level, const char *file, uin Tox *tox_new_log(struct Tox_Options *options, Tox_Err_New *err, void *log_user_data); Tox *tox_new_log_lan(struct Tox_Options *options, Tox_Err_New *err, void *log_user_data, bool lan_discovery); +extern const Net_Crypto_DHT_Funcs auto_test_dht_funcs; + #endif diff --git a/auto_tests/bootstrap_test.c b/auto_tests/bootstrap_test.c deleted file mode 100644 index 0c5859be..00000000 --- a/auto_tests/bootstrap_test.c +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include "../testing/misc_tools.h" -#include "check_compat.h" - -#include "auto_test_support.h" - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Tox *tox_udp = tox_new_log(nullptr, nullptr, nullptr); - - bootstrap_tox_live_network(tox_udp, false); - - printf("Waiting for connection"); - - do { - printf("."); - fflush(stdout); - - tox_iterate(tox_udp, nullptr); - c_sleep(ITERATION_INTERVAL); - } while (tox_self_get_connection_status(tox_udp) == TOX_CONNECTION_NONE); - - const Tox_Connection status = tox_self_get_connection_status(tox_udp); - ck_assert_msg(status == TOX_CONNECTION_UDP, - "expected connection status to be UDP, but got %u", status); - printf("Connection (UDP): %u\n", tox_self_get_connection_status(tox_udp)); - - tox_kill(tox_udp); - - return 0; -} diff --git a/auto_tests/check_compat.h b/auto_tests/check_compat.h index f60bc7ae..660ff40b 100644 --- a/auto_tests/check_compat.h +++ b/auto_tests/check_compat.h @@ -6,6 +6,7 @@ #include #include +#ifndef ck_assert #define ck_assert(ok) do { \ if (!(ok)) { \ fprintf(stderr, "%s:%d: failed `%s'\n", __FILE__, __LINE__, #ok); \ @@ -28,5 +29,6 @@ fprintf(stderr, "\n"); \ exit(7); \ } while (0) +#endif #endif // C_TOXCORE_AUTO_TESTS_CHECK_COMPAT_H diff --git a/auto_tests/conference_av_test.c b/auto_tests/conference_av_test.c deleted file mode 100644 index 12c6c75c..00000000 --- a/auto_tests/conference_av_test.c +++ /dev/null @@ -1,480 +0,0 @@ -/* Auto Tests: Conferences AV. - */ - -#include -#include -#include -#include - -#include "../toxav/toxav.h" -#include "../toxcore/os_random.h" -#include "check_compat.h" - -#define NUM_AV_GROUP_TOX 16 -#define NUM_AV_DISCONNECT (NUM_AV_GROUP_TOX / 2) -#define NUM_AV_DISABLE (NUM_AV_GROUP_TOX / 2) - -#include "auto_test_support.h" - -typedef struct State { - bool invited_next; - - uint32_t received_audio_peers[NUM_AV_GROUP_TOX]; - uint32_t received_audio_num; -} State; - -static void handle_self_connection_status( - const Tox_Event_Self_Connection_Status *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - - const Tox_Connection connection_status = tox_event_self_connection_status_get_connection_status(event); - if (connection_status != TOX_CONNECTION_NONE) { - printf("tox #%u: is now connected\n", autotox->index); - } else { - printf("tox #%u: is now disconnected\n", autotox->index); - } -} - -static void handle_friend_connection_status( - const Tox_Event_Friend_Connection_Status *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - - const uint32_t friendnumber = tox_event_friend_connection_status_get_friend_number(event); - const Tox_Connection connection_status = tox_event_friend_connection_status_get_connection_status(event); - - if (connection_status != TOX_CONNECTION_NONE) { - printf("tox #%u: is now connected to friend %u\n", autotox->index, friendnumber); - } else { - printf("tox #%u: is now disconnected from friend %u\n", autotox->index, friendnumber); - } -} - -static void audio_callback(void *tox, uint32_t conference_number, uint32_t peer_number, - const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t - sample_rate, void *user_data) -{ - if (samples == 0) { - return; - } - - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - for (uint32_t i = 0; i < state->received_audio_num; ++i) { - if (state->received_audio_peers[i] == peer_number) { - return; - } - } - - ck_assert(state->received_audio_num < NUM_AV_GROUP_TOX); - - state->received_audio_peers[state->received_audio_num] = peer_number; - ++state->received_audio_num; -} - -static void handle_conference_invite( - const Tox_Event_Conference_Invite *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - - const uint32_t friend_number = tox_event_conference_invite_get_friend_number(event); - const Tox_Conference_Type type = tox_event_conference_invite_get_type(event); - const uint8_t *cookie = tox_event_conference_invite_get_cookie(event); - const size_t length = tox_event_conference_invite_get_cookie_length(event); - - ck_assert_msg(type == TOX_CONFERENCE_TYPE_AV, "tox #%u: wrong conference type: %u", autotox->index, type); - - ck_assert_msg(toxav_join_av_groupchat(autotox->tox, friend_number, cookie, length, audio_callback, user_data) == 0, - "tox #%u: failed to join group", autotox->index); -} - -static void handle_conference_connected( - const Tox_Event_Conference_Connected *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - if (state->invited_next || tox_self_get_friend_list_size(autotox->tox) <= 1) { - return; - } - - Tox_Err_Conference_Invite err; - tox_conference_invite(autotox->tox, 1, 0, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %u", autotox->index, - err); - printf("tox #%u: invited next friend\n", autotox->index); - state->invited_next = true; -} - -static bool toxes_are_disconnected_from_group(uint32_t tox_count, AutoTox *autotoxes, - const bool *disconnected) -{ - uint32_t num_disconnected = 0; - - for (uint32_t i = 0; i < tox_count; ++i) { - num_disconnected += disconnected[i]; - } - - for (uint32_t i = 0; i < tox_count; ++i) { - if (disconnected[i]) { - continue; - } - - if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) > tox_count - num_disconnected) { - return false; - } - } - - return true; -} - -static void disconnect_toxes(uint32_t tox_count, AutoTox *autotoxes, - const bool *disconnect, const bool *exclude) -{ - /* Fake a network outage for a set of peers D by iterating only the other - * peers D' until the connections time out according to D', then iterating - * only D until the connections time out according to D. */ - - VLA(bool, disconnect_now, tox_count); - bool invert = false; - - do { - for (uint32_t i = 0; i < tox_count; ++i) { - disconnect_now[i] = exclude[i] || (invert ^ disconnect[i]); - } - - do { - for (uint32_t i = 0; i < tox_count; ++i) { - if (!disconnect_now[i]) { - Tox_Err_Events_Iterate err; - Tox_Events *events = tox_events_iterate(autotoxes[i].tox, true, &err); - tox_dispatch_invoke(autotoxes[i].dispatch, events, &autotoxes[i]); - tox_events_free(events); - autotoxes[i].clock += 1000; - } - } - - c_sleep(20); - } while (!toxes_are_disconnected_from_group(tox_count, autotoxes, disconnect_now)); - - invert = !invert; - } while (invert); -} - -static bool all_connected_to_group(uint32_t tox_count, AutoTox *autotoxes) -{ - for (uint32_t i = 0; i < tox_count; ++i) { - if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) < tox_count) { - return false; - } - } - - return true; -} - -/** - * returns a random index at which a list of booleans is false - * (some such index is required to exist) - */ -static uint32_t random_false_index(const Random *rng, const bool *list, const uint32_t length) -{ - uint32_t index; - - do { - index = random_u32(rng) % length; - } while (list[index]); - - return index; -} - -static bool all_got_audio(AutoTox *autotoxes, const bool *disabled) -{ - uint32_t num_disabled = 0; - - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - num_disabled += disabled[i]; - } - - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - const State *state = (const State *)autotoxes[i].state; - - if (disabled[i] ^ (state->received_audio_num - != NUM_AV_GROUP_TOX - num_disabled - 1)) { - return false; - } - } - - return true; -} - -static void reset_received_audio(AutoTox *autotoxes) -{ - for (uint32_t j = 0; j < NUM_AV_GROUP_TOX; ++j) { - ((State *)autotoxes[j].state)->received_audio_num = 0; - } -} - -#define GROUP_AV_TEST_SAMPLES 960 - -/* must have - * GROUP_AV_AUDIO_ITERATIONS - NUM_AV_GROUP_TOX >= 2^n >= GROUP_JBUF_SIZE - * for some n, to give messages time to be relayed and to let the jitter - * buffers fill up. */ -#define GROUP_AV_AUDIO_ITERATIONS (8 + NUM_AV_GROUP_TOX) - -static bool test_audio(AutoTox *autotoxes, const bool *disabled, bool quiet) -{ - if (!quiet) { - printf("testing sending and receiving audio\n"); - } - - const int16_t pcm[GROUP_AV_TEST_SAMPLES] = {0}; - - reset_received_audio(autotoxes); - - for (uint32_t n = 0; n < GROUP_AV_AUDIO_ITERATIONS; ++n) { - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - if (disabled[i]) { - continue; - } - - if (toxav_group_send_audio(autotoxes[i].tox, 0, pcm, GROUP_AV_TEST_SAMPLES, 1, 48000) != 0) { - if (!quiet) { - ck_abort_msg("#%u failed to send audio", autotoxes[i].index); - } - - return false; - } - } - - iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); - - if (all_got_audio(autotoxes, disabled)) { - return true; - } - } - - if (!quiet) { - ck_abort_msg("group failed to receive audio"); - } - - return false; -} - -static void test_eventual_audio(AutoTox *autotoxes, const bool *disabled, uint64_t timeout) -{ - uint64_t start = autotoxes[0].clock; - - while (autotoxes[0].clock < start + timeout) { - if (!test_audio(autotoxes, disabled, true)) { - continue; - } - - // It needs to succeed twice in a row for the test to pass. - if (test_audio(autotoxes, disabled, true)) { - printf("audio test successful after %d seconds\n", (int)((autotoxes[0].clock - start) / 1000)); - return; - } - } - - printf("audio seems not to be getting through: testing again with errors.\n"); - test_audio(autotoxes, disabled, false); -} - -static void do_audio(AutoTox *autotoxes, uint32_t iterations) -{ - const int16_t pcm[GROUP_AV_TEST_SAMPLES] = {0}; - printf("running audio for %u iterations\n", iterations); - - for (uint32_t f = 0; f < iterations; ++f) { - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - ck_assert_msg(toxav_group_send_audio(autotoxes[i].tox, 0, pcm, GROUP_AV_TEST_SAMPLES, 1, 48000) == 0, - "#%u failed to send audio", autotoxes[i].index); - iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); - } - } -} - -// should agree with value in groupav.c -#define GROUP_JBUF_DEAD_SECONDS 4 - -#define JITTER_SETTLE_TIME (GROUP_JBUF_DEAD_SECONDS*1000 + NUM_AV_GROUP_TOX*ITERATION_INTERVAL*(GROUP_AV_AUDIO_ITERATIONS+1)) - -static void run_conference_tests(AutoTox *autotoxes) -{ - const Random *rng = os_random(); - ck_assert(rng != nullptr); - bool disabled[NUM_AV_GROUP_TOX] = {0}; - - test_audio(autotoxes, disabled, false); - - /* have everyone send audio for a bit so we can test that the audio - * sequnums dropping to 0 on restart isn't a problem */ - do_audio(autotoxes, 20); - - printf("letting random toxes timeout\n"); - bool disconnected[NUM_AV_GROUP_TOX] = {0}; - bool restarting[NUM_AV_GROUP_TOX] = {0}; - - ck_assert(NUM_AV_DISCONNECT < NUM_AV_GROUP_TOX); - - for (uint32_t i = 0; i < NUM_AV_DISCONNECT; ++i) { - uint32_t disconnect = random_false_index(rng, disconnected, NUM_AV_GROUP_TOX); - disconnected[disconnect] = true; - - if (i < NUM_AV_DISCONNECT / 2) { - restarting[disconnect] = true; - printf("Restarting #%u\n", autotoxes[disconnect].index); - } else { - printf("Disconnecting #%u\n", autotoxes[disconnect].index); - } - } - - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - if (restarting[i]) { - save_autotox(&autotoxes[i]); - kill_autotox(&autotoxes[i]); - } - } - - disconnect_toxes(NUM_AV_GROUP_TOX, autotoxes, disconnected, restarting); - - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - if (restarting[i]) { - reload(&autotoxes[i]); - } - } - - printf("reconnecting toxes\n"); - - do { - iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); - } while (!all_connected_to_group(NUM_AV_GROUP_TOX, autotoxes)); - - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - if (restarting[i]) { - ck_assert_msg(!toxav_groupchat_av_enabled(autotoxes[i].tox, 0), - "#%u restarted but av enabled", autotoxes[i].index); - ck_assert_msg(toxav_groupchat_enable_av(autotoxes[i].tox, 0, audio_callback, &autotoxes[i]) == 0, - "#%u failed to re-enable av", autotoxes[i].index); - ck_assert_msg(toxav_groupchat_av_enabled(autotoxes[i].tox, 0), - "#%u av not enabled even after enabling", autotoxes[i].index); - } - } - - printf("testing audio\n"); - - /* Allow time for the jitter buffers to reset and for the group to become - * connected enough for lossy messages to get through - * (all_connected_to_group() only checks lossless connectivity, which is a - * looser condition). */ - test_eventual_audio(autotoxes, disabled, JITTER_SETTLE_TIME + NUM_AV_GROUP_TOX * 1000); - - printf("testing disabling av\n"); - - ck_assert(NUM_AV_DISABLE < NUM_AV_GROUP_TOX); - - for (uint32_t i = 0; i < NUM_AV_DISABLE; ++i) { - uint32_t disable = random_false_index(rng, disabled, NUM_AV_GROUP_TOX); - disabled[disable] = true; - printf("Disabling #%u\n", autotoxes[disable].index); - ck_assert_msg(toxav_groupchat_enable_av(autotoxes[disable].tox, 0, audio_callback, &autotoxes[disable]) != 0, - "#%u could enable already enabled av!", autotoxes[i].index); - ck_assert_msg(toxav_groupchat_disable_av(autotoxes[disable].tox, 0) == 0, - "#%u failed to disable av", autotoxes[i].index); - } - - // Run test without error to clear out messages from now-disabled peers. - test_audio(autotoxes, disabled, true); - - printf("testing audio with some peers having disabled their av\n"); - test_audio(autotoxes, disabled, false); - - for (uint32_t i = 0; i < NUM_AV_DISABLE; ++i) { - if (!disabled[i]) { - continue; - } - - disabled[i] = false; - ck_assert_msg(toxav_groupchat_disable_av(autotoxes[i].tox, 0) != 0, - "#%u could disable already disabled av!", autotoxes[i].index); - ck_assert_msg(!toxav_groupchat_av_enabled(autotoxes[i].tox, 0), - "#%u av enabled after disabling", autotoxes[i].index); - ck_assert_msg(toxav_groupchat_enable_av(autotoxes[i].tox, 0, audio_callback, &autotoxes[i]) == 0, - "#%u failed to re-enable av", autotoxes[i].index); - } - - printf("testing audio after re-enabling all av\n"); - test_eventual_audio(autotoxes, disabled, JITTER_SETTLE_TIME); -} - -static void test_groupav(AutoTox *autotoxes) -{ - const time_t test_start_time = time(nullptr); - - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - tox_events_callback_self_connection_status(autotoxes[i].dispatch, handle_self_connection_status); - tox_events_callback_friend_connection_status(autotoxes[i].dispatch, handle_friend_connection_status); - tox_events_callback_conference_invite(autotoxes[i].dispatch, handle_conference_invite); - tox_events_callback_conference_connected(autotoxes[i].dispatch, handle_conference_connected); - } - - ck_assert_msg(toxav_add_av_groupchat(autotoxes[0].tox, audio_callback, &autotoxes[0]) != -1, - "failed to create group"); - printf("tox #%u: inviting its first friend\n", autotoxes[0].index); - ck_assert_msg(tox_conference_invite(autotoxes[0].tox, 0, 0, nullptr) != 0, "failed to invite friend"); - ((State *)autotoxes[0].state)->invited_next = true; - - printf("waiting for invitations to be made\n"); - uint32_t invited_count = 0; - - do { - iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); - - invited_count = 0; - - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - invited_count += ((State *)autotoxes[i].state)->invited_next; - } - } while (invited_count != NUM_AV_GROUP_TOX - 1); - - uint64_t pregroup_clock = autotoxes[0].clock; - printf("waiting for all toxes to be in the group\n"); - uint32_t fully_connected_count = 0; - - do { - fully_connected_count = 0; - iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); - - for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { - Tox_Err_Conference_Peer_Query err; - uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, &err); - - if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { - peer_count = 0; - } - - fully_connected_count += peer_count == NUM_AV_GROUP_TOX; - } - } while (fully_connected_count != NUM_AV_GROUP_TOX); - - printf("group connected, took %d seconds\n", (int)((autotoxes[0].clock - pregroup_clock) / 1000)); - - run_conference_tests(autotoxes); - - printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - - run_auto_test(nullptr, NUM_AV_GROUP_TOX, test_groupav, sizeof(State), &options); - - return 0; -} diff --git a/auto_tests/conference_double_invite_test.c b/auto_tests/conference_double_invite_test.c deleted file mode 100644 index 722b01c4..00000000 --- a/auto_tests/conference_double_invite_test.c +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include - -typedef struct State { - bool self_online; - bool friend_online; - - bool joined; - uint32_t conference; -} State; - -#include "auto_test_support.h" - -static void handle_conference_invite( - const Tox_Event_Conference_Invite *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - const uint32_t friend_number = tox_event_conference_invite_get_friend_number(event); - const Tox_Conference_Type type = tox_event_conference_invite_get_type(event); - const uint8_t *cookie = tox_event_conference_invite_get_cookie(event); - const size_t length = tox_event_conference_invite_get_cookie_length(event); - - fprintf(stderr, "handle_conference_invite(#%u, %u, %u, uint8_t[%u], _)\n", - autotox->index, friend_number, type, (unsigned)length); - fprintf(stderr, "tox%u joining conference\n", autotox->index); - - ck_assert_msg(!state->joined, "invitation callback generated for already joined conference"); - - if (friend_number != UINT32_MAX) { - Tox_Err_Conference_Join err; - state->conference = tox_conference_join(autotox->tox, friend_number, cookie, length, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, - "attempting to join the conference returned with an error: %u", err); - fprintf(stderr, "tox%u joined conference %u\n", autotox->index, state->conference); - state->joined = true; - } -} - -static void conference_double_invite_test(AutoTox *autotoxes) -{ - // Conference callbacks. - tox_events_callback_conference_invite(autotoxes[0].dispatch, handle_conference_invite); - tox_events_callback_conference_invite(autotoxes[1].dispatch, handle_conference_invite); - - State *state[2]; - state[0] = (State *)autotoxes[0].state; - state[1] = (State *)autotoxes[1].state; - - { - // Create new conference, tox0 is the founder. - Tox_Err_Conference_New err; - state[0]->conference = tox_conference_new(autotoxes[0].tox, &err); - state[0]->joined = true; - ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, - "attempting to create a new conference returned with an error: %u", err); - fprintf(stderr, "Created conference: index=%u\n", state[0]->conference); - } - - { - // Invite friend. - Tox_Err_Conference_Invite err; - tox_conference_invite(autotoxes[0].tox, 0, state[0]->conference, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, - "attempting to invite a friend returned with an error: %u", err); - fprintf(stderr, "tox0 invited tox1\n"); - } - - fprintf(stderr, "Waiting for invitation to arrive\n"); - - do { - iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); - } while (!state[0]->joined || !state[1]->joined); - - fprintf(stderr, "Invitations accepted\n"); - - fprintf(stderr, "Sending second invitation; should be ignored\n"); - tox_conference_invite(autotoxes[0].tox, 0, state[0]->conference, nullptr); - - iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - - run_auto_test(nullptr, 2, conference_double_invite_test, sizeof(State), &options); - - return 0; -} diff --git a/auto_tests/conference_invite_merge_test.c b/auto_tests/conference_invite_merge_test.c deleted file mode 100644 index 7f5afa21..00000000 --- a/auto_tests/conference_invite_merge_test.c +++ /dev/null @@ -1,182 +0,0 @@ -#include -#include -#include - -typedef struct State { - bool connected; - uint32_t conference; -} State; - -#define NUM_INVITE_MERGE_TOX 5 - -#include "auto_test_support.h" - -static void handle_conference_invite( - const Tox_Event_Conference_Invite *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - const uint32_t friend_number = tox_event_conference_invite_get_friend_number(event); - const uint8_t *cookie = tox_event_conference_invite_get_cookie(event); - const size_t length = tox_event_conference_invite_get_cookie_length(event); - - if (friend_number != UINT32_MAX) { - Tox_Err_Conference_Join err; - state->conference = tox_conference_join(autotox->tox, friend_number, cookie, length, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, - "attempting to join the conference returned with an error: %u", err); - fprintf(stderr, "#%u accepted invite to conference %u\n", autotox->index, state->conference); - } -} - -static void handle_conference_connected( - const Tox_Event_Conference_Connected *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - fprintf(stderr, "#%u connected to conference %u\n", autotox->index, state->conference); - state->connected = true; -} - -static void wait_connected(AutoTox *autotoxes, const AutoTox *autotox, uint32_t friendnumber) -{ - do { - iterate_all_wait(autotoxes, NUM_INVITE_MERGE_TOX, ITERATION_INTERVAL); - } while (tox_friend_get_connection_status(autotox->tox, friendnumber, nullptr) == TOX_CONNECTION_NONE); -} - -static void do_invite(AutoTox *autotoxes, AutoTox *inviter, AutoTox *invitee, uint32_t friendnum) -{ - fprintf(stderr, "#%u inviting #%u\n", inviter->index, invitee->index); - - Tox_Err_Conference_Invite err; - tox_conference_invite(inviter->tox, friendnum, ((State *)inviter->state)->conference, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, - "#%u attempting to invite #%u (friendnumber %u) returned with an error: %u", inviter->index, invitee->index, - friendnum, err); - - do { - iterate_all_wait(autotoxes, NUM_INVITE_MERGE_TOX, ITERATION_INTERVAL); - } while (!((State *)invitee->state)->connected); -} - -static bool group_complete(AutoTox *autotoxes) -{ - int c = -1, size = 0; - - for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { - if (!autotoxes[i].alive) { - continue; - } - - const int ct = tox_conference_peer_count(autotoxes[i].tox, ((State *)autotoxes[i].state)->conference, nullptr); - - if (c == -1) { - c = ct; - } else if (c != ct) { - return false; - } - - ++size; - } - - return (c == size); -} - -static void wait_group_complete(AutoTox *autotoxes) -{ - do { - iterate_all_wait(autotoxes, NUM_INVITE_MERGE_TOX, ITERATION_INTERVAL); - } while (!group_complete(autotoxes)); -} - -static void conference_invite_merge_test(AutoTox *autotoxes) -{ - // Test that an explicit invite between peers in different connected - // components will cause a split group to merge - - for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { - tox_events_callback_conference_invite(autotoxes[i].dispatch, handle_conference_invite); - tox_events_callback_conference_connected(autotoxes[i].dispatch, handle_conference_connected); - } - - State *state2 = (State *)autotoxes[2].state; - - { - // Create new conference, tox 2 is the founder. - Tox_Err_Conference_New err; - state2->conference = tox_conference_new(autotoxes[2].tox, &err); - state2->connected = true; - ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, - "attempting to create a new conference returned with an error: %u", err); - fprintf(stderr, "Created conference: index=%u\n", state2->conference); - } - - save_autotox(&autotoxes[2]); - - do_invite(autotoxes, &autotoxes[2], &autotoxes[1], 0); - do_invite(autotoxes, &autotoxes[1], &autotoxes[0], 0); - - save_autotox(&autotoxes[1]); - kill_autotox(&autotoxes[1]); - - do { - iterate_all_wait(autotoxes, NUM_INVITE_MERGE_TOX, ITERATION_INTERVAL); - } while (tox_conference_peer_count(autotoxes[2].tox, state2->conference, nullptr) != 1); - - do_invite(autotoxes, &autotoxes[2], &autotoxes[3], 1); - do_invite(autotoxes, &autotoxes[3], &autotoxes[4], 1); - - kill_autotox(&autotoxes[2]); - - reload(&autotoxes[1]); - - uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_public_key(autotoxes[1].tox, public_key); - tox_friend_add_norequest(autotoxes[3].tox, public_key, nullptr); - tox_self_get_public_key(autotoxes[3].tox, public_key); - tox_friend_add_norequest(autotoxes[1].tox, public_key, nullptr); - wait_connected(autotoxes, &autotoxes[1], 2); - - do_invite(autotoxes, &autotoxes[1], &autotoxes[3], 2); - - fprintf(stderr, "Waiting for group to merge\n"); - - wait_group_complete(autotoxes); - - fprintf(stderr, "Group merged\n"); - - reload(&autotoxes[2]); - wait_connected(autotoxes, &autotoxes[2], 0); - do_invite(autotoxes, &autotoxes[2], &autotoxes[1], 0); - - fprintf(stderr, "Waiting for #2 to rejoin\n"); - - wait_group_complete(autotoxes); - - kill_autotox(&autotoxes[2]); - wait_group_complete(autotoxes); - reload(&autotoxes[2]); - wait_connected(autotoxes, &autotoxes[2], 0); - wait_connected(autotoxes, &autotoxes[1], 1); - - do_invite(autotoxes, &autotoxes[1], &autotoxes[2], 1); - - fprintf(stderr, "Waiting for #2 to rejoin\n"); - - wait_group_complete(autotoxes); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - - run_auto_test(nullptr, NUM_INVITE_MERGE_TOX, conference_invite_merge_test, sizeof(State), &options); - - return 0; -} diff --git a/auto_tests/conference_peer_nick_test.c b/auto_tests/conference_peer_nick_test.c deleted file mode 100644 index 112babe8..00000000 --- a/auto_tests/conference_peer_nick_test.c +++ /dev/null @@ -1,142 +0,0 @@ -#include -#include - -typedef struct State { - bool self_online; - bool friend_online; - bool friend_in_group; - - bool joined; - uint32_t conference; -} State; - -#include "auto_test_support.h" - -static void handle_conference_invite( - const Tox_Event_Conference_Invite *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - const uint32_t friend_number = tox_event_conference_invite_get_friend_number(event); - const Tox_Conference_Type type = tox_event_conference_invite_get_type(event); - const uint8_t *cookie = tox_event_conference_invite_get_cookie(event); - const size_t length = tox_event_conference_invite_get_cookie_length(event); - - fprintf(stderr, "handle_conference_invite(#%u, %u, %u, uint8_t[%u], _)\n", - autotox->index, friend_number, type, (unsigned)length); - fprintf(stderr, "tox%u joining conference\n", autotox->index); - - Tox_Err_Conference_Join err; - state->conference = tox_conference_join(autotox->tox, friend_number, cookie, length, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, - "attempting to join the conference returned with an error: %u", err); - fprintf(stderr, "tox%u joined conference %u\n", autotox->index, state->conference); - state->joined = true; -} - -static void handle_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - const uint32_t conference_number = tox_event_conference_peer_list_changed_get_conference_number(event); - fprintf(stderr, "handle_peer_list_changed(#%u, %u, _)\n", - autotox->index, conference_number); - - Tox_Err_Conference_Peer_Query err; - uint32_t const count = tox_conference_peer_count(autotox->tox, conference_number, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK, - "failed to get conference peer count: err = %u", err); - printf("tox%u has %u peers\n", autotox->index, count); - state->friend_in_group = count == 2; -} - -static void rebuild_peer_list(Tox *tox) -{ - for (uint32_t conference_number = 0; - conference_number < tox_conference_get_chatlist_size(tox); - ++conference_number) { - Tox_Err_Conference_Peer_Query err; - uint32_t const count = tox_conference_peer_count(tox, conference_number, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK, - "failed to get conference peer count for conference %u: err = %u", conference_number, err); - - for (uint32_t peer_number = 0; peer_number < count; peer_number++) { - size_t size = tox_conference_peer_get_name_size(tox, conference_number, peer_number, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK, - "failed to get conference peer %u's name size (conference = %u): err = %u", peer_number, conference_number, err); - - uint8_t *const name = (uint8_t *)malloc(size); - ck_assert(name != nullptr); - tox_conference_peer_get_name(tox, conference_number, peer_number, name, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK, - "failed to get conference peer %u's name (conference = %u): err = %u", peer_number, conference_number, err); - free(name); - } - } -} - -static void conference_peer_nick_test(AutoTox *autotoxes) -{ - // Conference callbacks. - tox_events_callback_conference_invite(autotoxes[0].dispatch, handle_conference_invite); - tox_events_callback_conference_invite(autotoxes[1].dispatch, handle_conference_invite); - tox_events_callback_conference_peer_list_changed(autotoxes[0].dispatch, handle_peer_list_changed); - tox_events_callback_conference_peer_list_changed(autotoxes[1].dispatch, handle_peer_list_changed); - - // Set the names of the toxes. - tox_self_set_name(autotoxes[0].tox, (const uint8_t *)"test-tox-0", 10, nullptr); - tox_self_set_name(autotoxes[1].tox, (const uint8_t *)"test-tox-1", 10, nullptr); - - State *state[2]; - state[0] = (State *)autotoxes[0].state; - state[1] = (State *)autotoxes[1].state; - - { - // Create new conference, tox0 is the founder. - Tox_Err_Conference_New err; - state[0]->conference = tox_conference_new(autotoxes[0].tox, &err); - state[0]->joined = true; - ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, - "attempting to create a new conference returned with an error: %u", err); - fprintf(stderr, "Created conference: index=%u\n", state[0]->conference); - } - - { - // Invite friend. - Tox_Err_Conference_Invite err; - tox_conference_invite(autotoxes[0].tox, 0, state[0]->conference, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, - "attempting to invite a friend returned with an error: %u", err); - fprintf(stderr, "tox0 invited tox1\n"); - } - - fprintf(stderr, "Waiting for invitation to arrive and peers to be in the group\n"); - - do { - iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); - } while (!state[0]->joined || !state[1]->joined || !state[0]->friend_in_group || !state[1]->friend_in_group); - - fprintf(stderr, "Running tox0, but not tox1, waiting for tox1 to drop out\n"); - - do { - iterate_all_wait(autotoxes, 1, 1000); - - // Rebuild peer list after every iteration. - rebuild_peer_list(autotoxes[0].tox); - } while (state[0]->friend_in_group); - - fprintf(stderr, "Invitations accepted\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - run_auto_test(nullptr, 2, conference_peer_nick_test, sizeof(State), &options); - - return 0; -} diff --git a/auto_tests/conference_simple_test.c b/auto_tests/conference_simple_test.c deleted file mode 100644 index 03469087..00000000 --- a/auto_tests/conference_simple_test.c +++ /dev/null @@ -1,268 +0,0 @@ -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/tox.h" -#include "../toxcore/tox_dispatch.h" -#include "../toxcore/tox_events.h" -#include "auto_test_support.h" -#include "check_compat.h" - -typedef struct State { - uint32_t id; - Tox *tox; - bool self_online; - bool friend_online; - bool invited_next; - - bool joined; - uint32_t conference; - - bool received; - - uint32_t peers; -} State; - -static void handle_self_connection_status(const Tox_Event_Self_Connection_Status *event, void *user_data) -{ - State *state = (State *)user_data; - - const Tox_Connection connection_status = tox_event_self_connection_status_get_connection_status(event); - fprintf(stderr, "self_connection_status(#%u, %u, _)\n", state->id, connection_status); - state->self_online = connection_status != TOX_CONNECTION_NONE; -} - -static void handle_friend_connection_status(const Tox_Event_Friend_Connection_Status *event, - void *user_data) -{ - State *state = (State *)user_data; - - const uint32_t friend_number = tox_event_friend_connection_status_get_friend_number(event); - const Tox_Connection connection_status = tox_event_friend_connection_status_get_connection_status(event); - fprintf(stderr, "handle_friend_connection_status(#%u, %u, %u, _)\n", state->id, friend_number, connection_status); - state->friend_online = connection_status != TOX_CONNECTION_NONE; -} - -static void handle_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) -{ - State *state = (State *)user_data; - - const uint32_t friend_number = tox_event_conference_invite_get_friend_number(event); - const Tox_Conference_Type type = tox_event_conference_invite_get_type(event); - const uint8_t *cookie = tox_event_conference_invite_get_cookie(event); - const size_t length = tox_event_conference_invite_get_cookie_length(event); - fprintf(stderr, "handle_conference_invite(#%u, %u, %u, uint8_t[%u], _)\n", - state->id, friend_number, type, (unsigned)length); - fprintf(stderr, "tox%u joining conference\n", state->id); - - { - Tox_Err_Conference_Join err; - state->conference = tox_conference_join(state->tox, friend_number, cookie, length, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "failed to join a conference: err = %u", err); - fprintf(stderr, "tox%u Joined conference %u\n", state->id, state->conference); - state->joined = true; - } -} - -static void handle_conference_message(const Tox_Event_Conference_Message *event, void *user_data) -{ - State *state = (State *)user_data; - - const uint32_t conference_number = tox_event_conference_message_get_conference_number(event); - const uint32_t peer_number = tox_event_conference_message_get_peer_number(event); - const Tox_Message_Type type = tox_event_conference_message_get_type(event); - const uint8_t *message = tox_event_conference_message_get_message(event); - const size_t length = tox_event_conference_message_get_message_length(event); - - fprintf(stderr, "handle_conference_message(#%u, %u, %u, %u, uint8_t[%u], _)\n", - state->id, conference_number, peer_number, type, (unsigned)length); - - fprintf(stderr, "tox%u got message: %s\n", state->id, (const char *)message); - state->received = true; -} - -static void handle_conference_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) -{ - State *state = (State *)user_data; - - const uint32_t conference_number = tox_event_conference_peer_list_changed_get_conference_number(event); - fprintf(stderr, "handle_conference_peer_list_changed(#%u, %u, _)\n", - state->id, conference_number); - - Tox_Err_Conference_Peer_Query err; - uint32_t count = tox_conference_peer_count(state->tox, conference_number, &err); - - if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { - fprintf(stderr, "ERROR: %u\n", err); - exit(EXIT_FAILURE); - } - - fprintf(stderr, "tox%u has %u peers online\n", state->id, count); - state->peers = count; -} - -static void handle_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data) -{ - State *state = (State *)user_data; - - // We're tox2, so now we invite tox3. - if (state->id == 2 && !state->invited_next) { - Tox_Err_Conference_Invite err; - tox_conference_invite(state->tox, 1, state->conference, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox2 failed to invite tox3: err = %u", err); - - state->invited_next = true; - fprintf(stderr, "tox2 invited tox3\n"); - } -} - -static void iterate_one( - Tox *tox, State *state, const Tox_Dispatch *dispatch) -{ - Tox_Err_Events_Iterate err; - Tox_Events *events = tox_events_iterate(tox, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch, events, state); - tox_events_free(events); -} - -static void iterate3_wait( - State *state1, State *state2, State *state3, - const Tox_Dispatch *dispatch, int interval) -{ - iterate_one(state1->tox, state1, dispatch); - iterate_one(state2->tox, state2, dispatch); - iterate_one(state3->tox, state3, dispatch); - - c_sleep(interval); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - State state1 = {1}; - State state2 = {2}; - State state3 = {3}; - - // Create toxes. - state1.tox = tox_new_log(nullptr, nullptr, &state1.id); - state2.tox = tox_new_log(nullptr, nullptr, &state2.id); - state3.tox = tox_new_log(nullptr, nullptr, &state3.id); - - tox_events_init(state1.tox); - tox_events_init(state2.tox); - tox_events_init(state3.tox); - - // tox1 <-> tox2, tox2 <-> tox3 - uint8_t key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_public_key(state2.tox, key); - tox_friend_add_norequest(state1.tox, key, nullptr); // tox1 -> tox2 - tox_self_get_public_key(state1.tox, key); - tox_friend_add_norequest(state2.tox, key, nullptr); // tox2 -> tox1 - tox_self_get_public_key(state3.tox, key); - tox_friend_add_norequest(state2.tox, key, nullptr); // tox2 -> tox3 - tox_self_get_public_key(state2.tox, key); - tox_friend_add_norequest(state3.tox, key, nullptr); // tox3 -> tox2 - - printf("bootstrapping tox2 and tox3 off tox1\n"); - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(state1.tox, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(state1.tox, nullptr); - - tox_bootstrap(state2.tox, "localhost", dht_port, dht_key, nullptr); - tox_bootstrap(state3.tox, "localhost", dht_port, dht_key, nullptr); - - Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); - ck_assert(dispatch != nullptr); - - // Connection callbacks. - tox_events_callback_self_connection_status(dispatch, handle_self_connection_status); - tox_events_callback_friend_connection_status(dispatch, handle_friend_connection_status); - - // Conference callbacks. - tox_events_callback_conference_invite(dispatch, handle_conference_invite); - tox_events_callback_conference_connected(dispatch, handle_conference_connected); - tox_events_callback_conference_message(dispatch, handle_conference_message); - tox_events_callback_conference_peer_list_changed(dispatch, handle_conference_peer_list_changed); - - // Wait for self connection. - fprintf(stderr, "Waiting for toxes to come online\n"); - - do { - iterate3_wait(&state1, &state2, &state3, dispatch, 100); - } while (!state1.self_online || !state2.self_online || !state3.self_online); - - fprintf(stderr, "Toxes are online\n"); - - // Wait for friend connection. - fprintf(stderr, "Waiting for friends to connect\n"); - - do { - iterate3_wait(&state1, &state2, &state3, dispatch, 100); - } while (!state1.friend_online || !state2.friend_online || !state3.friend_online); - - fprintf(stderr, "Friends are connected\n"); - - { - // Create new conference, tox1 is the founder. - Tox_Err_Conference_New err; - state1.conference = tox_conference_new(state1.tox, &err); - state1.joined = true; - ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, "failed to create a conference: err = %u", err); - fprintf(stderr, "Created conference: id = %u\n", state1.conference); - } - - { - // Invite friend. - Tox_Err_Conference_Invite err; - tox_conference_invite(state1.tox, 0, state1.conference, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "failed to invite a friend: err = %u", err); - state1.invited_next = true; - fprintf(stderr, "tox1 invited tox2\n"); - } - - fprintf(stderr, "Waiting for invitation to arrive\n"); - - do { - iterate3_wait(&state1, &state2, &state3, dispatch, 100); - } while (!state1.joined || !state2.joined || !state3.joined); - - fprintf(stderr, "Invitations accepted\n"); - - fprintf(stderr, "Waiting for peers to come online\n"); - - do { - iterate3_wait(&state1, &state2, &state3, dispatch, 100); - } while (state1.peers == 0 || state2.peers == 0 || state3.peers == 0); - - fprintf(stderr, "All peers are online\n"); - - { - fprintf(stderr, "tox1 sends a message to the group: \"hello!\"\n"); - Tox_Err_Conference_Send_Message err; - tox_conference_send_message(state1.tox, state1.conference, TOX_MESSAGE_TYPE_NORMAL, - (const uint8_t *)"hello!", 7, &err); - - if (err != TOX_ERR_CONFERENCE_SEND_MESSAGE_OK) { - fprintf(stderr, "ERROR: %u\n", err); - exit(EXIT_FAILURE); - } - } - - fprintf(stderr, "Waiting for messages to arrive\n"); - - do { - iterate3_wait(&state1, &state2, &state3, dispatch, 100); - c_sleep(100); - } while (!state2.received || !state3.received); - - fprintf(stderr, "Messages received. Test complete.\n"); - - tox_dispatch_free(dispatch); - tox_kill(state3.tox); - tox_kill(state2.tox); - tox_kill(state1.tox); - - return 0; -} diff --git a/auto_tests/conference_test.c b/auto_tests/conference_test.c deleted file mode 100644 index 2f9c6726..00000000 --- a/auto_tests/conference_test.c +++ /dev/null @@ -1,441 +0,0 @@ -/* Auto Tests: Conferences. - */ - -#include -#include -#include -#include - -#include "../toxcore/os_random.h" -#include "../toxcore/util.h" - -#include "check_compat.h" - -#define NUM_GROUP_TOX 16 -#define NUM_DISCONNECT 8 -#define GROUP_MESSAGE "Install Gentoo" - -#define NAMELEN 9 -#define NAME_FORMAT_STR "Tox #%4u" -#define NEW_NAME_FORMAT_STR "New #%4u" - -typedef struct State { - bool invited_next; -} State; - -#include "auto_test_support.h" - -static void handle_self_connection_status( - Tox *tox, Tox_Connection connection_status, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - - if (connection_status != TOX_CONNECTION_NONE) { - printf("tox #%u: is now connected\n", autotox->index); - } else { - printf("tox #%u: is now disconnected\n", autotox->index); - } -} - -static void handle_friend_connection_status( - Tox *tox, uint32_t friendnumber, Tox_Connection connection_status, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - - if (connection_status != TOX_CONNECTION_NONE) { - printf("tox #%u: is now connected to friend %u\n", autotox->index, friendnumber); - } else { - printf("tox #%u: is now disconnected from friend %u\n", autotox->index, friendnumber); - } -} - -static void handle_conference_invite( - Tox *tox, uint32_t friendnumber, Tox_Conference_Type type, - const uint8_t *data, size_t length, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %u", autotox->index, type); - - Tox_Err_Conference_Join err; - uint32_t g_num = tox_conference_join(autotox->tox, friendnumber, data, length, &err); - - ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%u: error joining group: %u", autotox->index, err); - ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", autotox->index); - - // Try joining again. We should only be allowed to join once. - tox_conference_join(autotox->tox, friendnumber, data, length, &err); - ck_assert_msg(err != TOX_ERR_CONFERENCE_JOIN_OK, - "tox #%u: joining groupchat twice should be impossible.", autotox->index); -} - -static void handle_conference_connected( - Tox *tox, uint32_t conference_number, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - if (state->invited_next || tox_self_get_friend_list_size(autotox->tox) <= 1) { - return; - } - - Tox_Err_Conference_Invite err; - tox_conference_invite(autotox->tox, 1, 0, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %u", autotox->index, - err); - printf("tox #%u: invited next friend\n", autotox->index); - state->invited_next = true; -} - -static uint32_t num_recv; - -static void handle_conference_message( - Tox *tox, uint32_t conference_number, uint32_t peer_number, Tox_Message_Type type, - const uint8_t *message, size_t length, void *user_data) -{ - if (length == (sizeof(GROUP_MESSAGE) - 1) && memcmp(message, GROUP_MESSAGE, sizeof(GROUP_MESSAGE) - 1) == 0) { - ++num_recv; - } -} - -static bool toxes_are_disconnected_from_group(uint32_t tox_count, AutoTox *autotoxes, - const bool *disconnected) -{ - uint32_t num_disconnected = 0; - - for (uint32_t i = 0; i < tox_count; ++i) { - num_disconnected += disconnected[i]; - } - - for (uint32_t i = 0; i < tox_count; i++) { - if (disconnected[i]) { - continue; - } - - if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) > tox_count - num_disconnected) { - return false; - } - } - - return true; -} - -static void disconnect_toxes(uint32_t tox_count, AutoTox *autotoxes, - const bool *disconnect, const bool *exclude) -{ - /* Fake a network outage for a set of peers D by iterating only the other - * peers D' until the connections time out according to D', then iterating - * only D until the connections time out according to D. */ - - VLA(bool, disconnect_now, tox_count); - bool invert = false; - - do { - for (uint32_t i = 0; i < tox_count; ++i) { - disconnect_now[i] = exclude[i] || (invert ^ disconnect[i]); - } - - do { - for (uint32_t i = 0; i < tox_count; ++i) { - if (!disconnect_now[i]) { - tox_iterate(autotoxes[i].tox, &autotoxes[i]); - autotoxes[i].clock += 1000; - } - } - - c_sleep(20); - } while (!toxes_are_disconnected_from_group(tox_count, autotoxes, disconnect_now)); - - invert = !invert; - } while (invert); -} - -static bool all_connected_to_group(uint32_t tox_count, AutoTox *autotoxes) -{ - for (uint32_t i = 0; i < tox_count; i++) { - if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) < tox_count) { - return false; - } - } - - return true; -} - -static bool names_propagated(uint32_t tox_count, AutoTox *autotoxes) -{ - for (uint32_t i = 0; i < tox_count; ++i) { - for (uint32_t j = 0; j < tox_count; ++j) { - const size_t len = tox_conference_peer_get_name_size(autotoxes[i].tox, 0, j, nullptr); - - if (len != NAMELEN) { - return false; - } - } - } - - return true; -} - -/** - * returns a random index at which a list of booleans is false - * (some such index is required to exist) - */ -static uint32_t random_false_index(const Random *rng, const bool *list, const uint32_t length) -{ - uint32_t index; - - do { - index = random_u32(rng) % length; - } while (list[index]); - - return index; -} - -static void run_conference_tests(AutoTox *autotoxes) -{ - const Random *rng = os_random(); - ck_assert(rng != nullptr); - /* disabling name change propagation check for now, as it occasionally - * fails due to disconnections too short to trigger freezing */ - const bool check_name_change_propagation = false; - - /* each peer should freeze at least its two friends, but freezing more - * should not be necessary */ - const uint32_t max_frozen = max_u32(2, NUM_DISCONNECT / 2); - printf("restricting number of frozen peers to %u\n", max_frozen); - - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - Tox_Err_Conference_Set_Max_Offline err; - tox_conference_set_max_offline(autotoxes[i].tox, 0, max_frozen, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_OK, - "tox #%u failed to set max offline: err = %u", autotoxes[i].index, err); - } - - printf("letting random toxes timeout\n"); - bool disconnected[NUM_GROUP_TOX] = {0}; - bool restarting[NUM_GROUP_TOX] = {0}; - - ck_assert(NUM_DISCONNECT < NUM_GROUP_TOX); - - for (uint32_t i = 0; i < NUM_DISCONNECT; ++i) { - uint32_t disconnect = random_false_index(rng, disconnected, NUM_GROUP_TOX); - disconnected[disconnect] = true; - - if (i < NUM_DISCONNECT / 2) { - restarting[disconnect] = true; - printf("Restarting #%u\n", autotoxes[disconnect].index); - } else { - printf("Disconnecting #%u\n", autotoxes[disconnect].index); - } - } - - uint8_t *save[NUM_GROUP_TOX]; - size_t save_size[NUM_GROUP_TOX]; - - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - if (restarting[i]) { - save_size[i] = tox_get_savedata_size(autotoxes[i].tox); - ck_assert_msg(save_size[i] != 0, "save is invalid size %u", (unsigned)save_size[i]); - save[i] = (uint8_t *)malloc(save_size[i]); - ck_assert_msg(save[i] != nullptr, "malloc failed"); - tox_get_savedata(autotoxes[i].tox, save[i]); - tox_kill(autotoxes[i].tox); - } - } - - disconnect_toxes(NUM_GROUP_TOX, autotoxes, disconnected, restarting); - - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - if (restarting[i]) { - struct Tox_Options *const options = tox_options_new(nullptr); - ck_assert(options != nullptr); - tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); - tox_options_set_savedata_data(options, save[i], save_size[i]); - autotoxes[i].tox = tox_new_log(options, nullptr, &autotoxes[i].index); - ck_assert(autotoxes[i].tox != nullptr); - tox_options_free(options); - free(save[i]); - - set_mono_time_callback(&autotoxes[i]); - tox_conference_set_max_offline(autotoxes[i].tox, 0, max_frozen, nullptr); - } - } - - if (check_name_change_propagation) { - printf("changing names\n"); - - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - char name[NAMELEN + 1]; - snprintf(name, NAMELEN + 1, NEW_NAME_FORMAT_STR, autotoxes[i].index); - tox_self_set_name(autotoxes[i].tox, (const uint8_t *)name, NAMELEN, nullptr); - } - } - - for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { - const uint32_t num_frozen = tox_conference_offline_peer_count(autotoxes[i].tox, 0, nullptr); - ck_assert_msg(num_frozen <= max_frozen, - "tox #%u has too many offline peers: %u\n", - autotoxes[i].index, num_frozen); - } - - printf("reconnecting toxes\n"); - - do { - iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); - } while (!all_connected_to_group(NUM_GROUP_TOX, autotoxes)); - - printf("running conference tests\n"); - - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - tox_callback_conference_message(autotoxes[i].tox, &handle_conference_message); - - iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); - } - - Tox_Err_Conference_Send_Message err; - ck_assert_msg( - tox_conference_send_message( - autotoxes[random_u32(rng) % NUM_GROUP_TOX].tox, 0, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)GROUP_MESSAGE, - sizeof(GROUP_MESSAGE) - 1, &err) != 0, "failed to send group message"); - ck_assert_msg( - err == TOX_ERR_CONFERENCE_SEND_MESSAGE_OK, "failed to send group message"); - num_recv = 0; - - for (uint8_t j = 0; j < NUM_GROUP_TOX * 2; ++j) { - iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); - } - - ck_assert_msg(num_recv == NUM_GROUP_TOX, "failed to recv group messages"); - - if (check_name_change_propagation) { - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - for (uint32_t j = 0; j < NUM_GROUP_TOX; ++j) { - uint8_t name[NAMELEN]; - tox_conference_peer_get_name(autotoxes[i].tox, 0, j, name, nullptr); - /* Note the toxes will have been reordered */ - ck_assert_msg(memcmp(name, "New", 3) == 0, - "name of #%u according to #%u not updated", autotoxes[j].index, autotoxes[i].index); - } - } - } - - for (uint32_t k = NUM_GROUP_TOX; k != 0 ; --k) { - tox_conference_delete(autotoxes[k - 1].tox, 0, nullptr); - - for (uint8_t j = 0; j < 10 || j < NUM_GROUP_TOX; ++j) { - iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); - } - - for (uint32_t i = 0; i < k - 1; ++i) { - uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, nullptr); - ck_assert_msg(peer_count == (k - 1), "\n\tBad number of group peers (post check)." - "\n\t\t\tExpected: %u but tox_instance(%u) only has: %u\n\n", - k - 1, i, (unsigned)peer_count); - } - } -} - -static void test_many_group(AutoTox *autotoxes) -{ - const time_t test_start_time = time(nullptr); - - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - tox_callback_self_connection_status(autotoxes[i].tox, &handle_self_connection_status); - tox_callback_friend_connection_status(autotoxes[i].tox, &handle_friend_connection_status); - tox_callback_conference_invite(autotoxes[i].tox, &handle_conference_invite); - tox_callback_conference_connected(autotoxes[i].tox, &handle_conference_connected); - - char name[NAMELEN + 1]; - snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, autotoxes[i].index); - tox_self_set_name(autotoxes[i].tox, (const uint8_t *)name, NAMELEN, nullptr); - } - - ck_assert_msg(tox_conference_new(autotoxes[0].tox, nullptr) != UINT32_MAX, "failed to create group"); - printf("tox #%u: inviting its first friend\n", autotoxes[0].index); - ck_assert_msg(tox_conference_invite(autotoxes[0].tox, 0, 0, nullptr) != 0, "failed to invite friend"); - ((State *)autotoxes[0].state)->invited_next = true; - ck_assert_msg(tox_conference_set_title(autotoxes[0].tox, 0, (const uint8_t *)"Gentoo", sizeof("Gentoo") - 1, - nullptr) != 0, - "failed to set group title"); - - printf("waiting for invitations to be made\n"); - uint32_t invited_count = 0; - - do { - iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); - - invited_count = 0; - - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - invited_count += ((State *)autotoxes[i].state)->invited_next; - } - } while (invited_count != NUM_GROUP_TOX - 1); - - uint64_t pregroup_clock = autotoxes[0].clock; - printf("waiting for all toxes to be in the group\n"); - uint32_t fully_connected_count = 0; - - do { - fully_connected_count = 0; - printf("current peer counts: ["); - - iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); - - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - Tox_Err_Conference_Peer_Query err; - uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, &err); - - if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { - peer_count = 0; - } - - fully_connected_count += peer_count == NUM_GROUP_TOX; - - if (i != 0) { - printf(", "); - } - - printf("%u", peer_count); - } - - printf("]\n"); - fflush(stdout); - } while (fully_connected_count != NUM_GROUP_TOX); - - for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { - uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, nullptr); - - ck_assert_msg(peer_count == NUM_GROUP_TOX, "\n\tBad number of group peers (pre check)." - "\n\t\t\tExpected: %d but tox_instance(%u) only has: %u\n\n", - NUM_GROUP_TOX, i, (unsigned)peer_count); - - uint8_t title[2048]; - size_t ret = tox_conference_get_title_size(autotoxes[i].tox, 0, nullptr); - ck_assert_msg(ret == sizeof("Gentoo") - 1, "Wrong title length"); - tox_conference_get_title(autotoxes[i].tox, 0, title, nullptr); - ck_assert_msg(memcmp("Gentoo", title, ret) == 0, "Wrong title"); - } - - printf("waiting for names to propagate\n"); - - do { - iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); - } while (!names_propagated(NUM_GROUP_TOX, autotoxes)); - - printf("group connected, took %d seconds\n", (int)((autotoxes[0].clock - pregroup_clock) / 1000)); - - run_conference_tests(autotoxes); - - printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - options.events = false; - - run_auto_test(nullptr, NUM_GROUP_TOX, test_many_group, sizeof(State), &options); - return 0; -} diff --git a/auto_tests/conference_two_test.c b/auto_tests/conference_two_test.c deleted file mode 100644 index aea5a933..00000000 --- a/auto_tests/conference_two_test.c +++ /dev/null @@ -1,27 +0,0 @@ -// This test checks that we can create two conferences and quit properly. -// -// This test triggers a different code path than if we only allocate a single -// conference. This is the simplest test possible that triggers it. - -#include "../testing/misc_tools.h" -#include "../toxcore/tox.h" -#include "auto_test_support.h" -#include "check_compat.h" - -int main(void) -{ - // Create toxes. - uint32_t id = 1; - Tox *tox1 = tox_new_log(nullptr, nullptr, &id); - - // Create two conferences and then exit. - Tox_Err_Conference_New err; - tox_conference_new(tox1, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, "failed to create conference 1: %u", err); - tox_conference_new(tox1, &err); - ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, "failed to create conference 2: %u", err); - - tox_kill(tox1); - - return 0; -} diff --git a/auto_tests/dht_nodes_response_api_test.c b/auto_tests/dht_nodes_response_api_test.c deleted file mode 100644 index 8acc111f..00000000 --- a/auto_tests/dht_nodes_response_api_test.c +++ /dev/null @@ -1,170 +0,0 @@ -/** - * This autotest creates a small local DHT and makes sure that each peer can crawl - * the entire DHT using the DHT nodes request/response api functions. - */ - -#include -#include -#include - -#include "../toxcore/tox.h" -#include "../toxcore/tox_private.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#define NUM_TOXES 30 -// Maximum number of iterations to wait for all nodes to be crawled. 5 should -// be enough. We pick 10 in case things are slow. This makes the test take -// less time in case it completely fails, so we can retry it. -#define MAX_ITERATIONS 10 - -typedef struct Dht_Node { - uint8_t public_key[TOX_DHT_NODE_PUBLIC_KEY_SIZE]; - char ip[TOX_DHT_NODE_IP_STRING_SIZE]; - uint16_t port; -} Dht_Node; - -typedef struct State { - Dht_Node **nodes; - size_t num_nodes; - uint8_t **public_key_list; -} State; - -static void free_nodes(Dht_Node **nodes, size_t num_nodes) -{ - for (size_t i = 0; i < num_nodes; ++i) { - free(nodes[i]); - } - - free(nodes); -} - -static bool node_crawled(Dht_Node **nodes, size_t num_nodes, const uint8_t *public_key) -{ - for (size_t i = 0; i < num_nodes; ++i) { - if (memcmp(nodes[i]->public_key, public_key, TOX_DHT_NODE_PUBLIC_KEY_SIZE) == 0) { - return true; - } - } - - return false; -} - -static bool all_nodes_crawled(const AutoTox *autotoxes, uint32_t num_toxes, uint8_t **public_key_list) -{ - for (uint32_t i = 0; i < num_toxes; ++i) { - const State *state = (const State *)autotoxes[i].state; - - // make sure each peer has crawled the correct number of nodes - if (state->num_nodes < num_toxes) { - return false; - } - } - - for (uint32_t i = 0; i < num_toxes; ++i) { - const State *state = (const State *)autotoxes[i].state; - - // make sure each peer has the full list of public keys - for (uint32_t j = 0; j < num_toxes; ++j) { - if (!node_crawled(state->nodes, state->num_nodes, public_key_list[j])) { - return false; - } - } - } - - return true; -} - -static void nodes_response_cb(const Tox_Event_Dht_Nodes_Response *event, void *user_data) -{ - ck_assert(user_data != nullptr); - - AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - const uint8_t *public_key = tox_event_dht_nodes_response_get_public_key(event); - const char *ip = (const char *)tox_event_dht_nodes_response_get_ip(event); - const uint16_t port = tox_event_dht_nodes_response_get_port(event); - - if (node_crawled(state->nodes, state->num_nodes, public_key)) { - return; - } - - ck_assert(state->num_nodes < NUM_TOXES); - - Dht_Node *node = (Dht_Node *)calloc(1, sizeof(Dht_Node)); - ck_assert(node != nullptr); - - memcpy(node->public_key, public_key, TOX_DHT_NODE_PUBLIC_KEY_SIZE); - snprintf(node->ip, sizeof(node->ip), "%s", ip); - node->port = port; - - state->nodes[state->num_nodes] = node; - ++state->num_nodes; - - // ask new node to give us their close nodes to every public key - for (size_t i = 0; i < NUM_TOXES; ++i) { - tox_dht_send_nodes_request(autotox->tox, public_key, ip, port, state->public_key_list[i], nullptr); - } -} - -static void test_dht_nodes_request(AutoTox *autotoxes) -{ - ck_assert(NUM_TOXES >= 2); - - uint8_t **public_key_list = (uint8_t **)calloc(NUM_TOXES, sizeof(uint8_t *)); - ck_assert(public_key_list != nullptr); - - for (size_t i = 0; i < NUM_TOXES; ++i) { - State *state = (State *)autotoxes[i].state; - - state->nodes = (Dht_Node **)calloc(NUM_TOXES, sizeof(Dht_Node *)); - ck_assert(state->nodes != nullptr); - - state->num_nodes = 0; - state->public_key_list = public_key_list; - - public_key_list[i] = (uint8_t *)malloc(sizeof(uint8_t) * TOX_PUBLIC_KEY_SIZE); - ck_assert(public_key_list[i] != nullptr); - - tox_self_get_dht_id(autotoxes[i].tox, public_key_list[i]); - tox_events_callback_dht_nodes_response(autotoxes[i].dispatch, nodes_response_cb); - - printf("Peer %zu dht closenode count total/announce-capable: %d/%d\n", - i, - tox_dht_get_num_closelist(autotoxes[i].tox), - tox_dht_get_num_closelist_announce_capable(autotoxes[i].tox)); - } - - bool success = false; - for (size_t i = 0; i < MAX_ITERATIONS; ++i) { - if (all_nodes_crawled(autotoxes, NUM_TOXES, public_key_list)) { - success = true; - break; - } - iterate_all_wait(autotoxes, NUM_TOXES, ITERATION_INTERVAL); - } - ck_assert_msg(success, "Failed to crawl all nodes within %d iterations", MAX_ITERATIONS); - - for (size_t i = 0; i < NUM_TOXES; ++i) { - State *state = (State *)autotoxes[i].state; - free_nodes(state->nodes, state->num_nodes); - free(public_key_list[i]); - } - - free(public_key_list); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - - run_auto_test(nullptr, NUM_TOXES, test_dht_nodes_request, sizeof(State), &options); - - return 0; -} - -#undef NUM_TOXES diff --git a/auto_tests/file_streaming_test.c b/auto_tests/file_streaming_test.c deleted file mode 100644 index 77aa6b59..00000000 --- a/auto_tests/file_streaming_test.c +++ /dev/null @@ -1,272 +0,0 @@ -/* File transfer test: streaming version (no known size). - */ - -#include -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/ccompat.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#ifndef USE_IPV6 -#define USE_IPV6 1 -#endif - -#ifdef TOX_LOCALHOST -#undef TOX_LOCALHOST -#endif -#if USE_IPV6 -#define TOX_LOCALHOST "::1" -#else -#define TOX_LOCALHOST "127.0.0.1" -#endif - -static void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) -{ - if (length == 7 && memcmp("Gentoo", data, 7) == 0) { - tox_friend_add_norequest(m, public_key, nullptr); - } -} - -static uint64_t size_recv; -static uint64_t sending_pos; - -static uint8_t file_cmp_id[TOX_FILE_ID_LENGTH]; -static uint32_t file_accepted; -static uint64_t file_size; -static void tox_file_receive(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t filesize, - const uint8_t *filename, size_t filename_length, void *userdata) -{ - ck_assert_msg(kind == TOX_FILE_KIND_DATA, "bad kind"); - - ck_assert_msg(filename_length == sizeof("Gentoo.exe") - && memcmp(filename, "Gentoo.exe", sizeof("Gentoo.exe")) == 0, "bad filename"); - - uint8_t file_id[TOX_FILE_ID_LENGTH]; - - ck_assert_msg(tox_file_get_file_id(tox, friend_number, file_number, file_id, nullptr), "tox_file_get_file_id error"); - - ck_assert_msg(memcmp(file_id, file_cmp_id, TOX_FILE_ID_LENGTH) == 0, "bad file_id"); - - const uint8_t empty[TOX_FILE_ID_LENGTH] = {0}; - - ck_assert_msg(memcmp(empty, file_cmp_id, TOX_FILE_ID_LENGTH) != 0, "empty file_id"); - - file_size = filesize; - - if (filesize) { - sending_pos = size_recv = 1337; - - Tox_Err_File_Seek err_s; - - ck_assert_msg(tox_file_seek(tox, friend_number, file_number, 1337, &err_s), "tox_file_seek error"); - - ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_OK, "tox_file_seek wrong error"); - - } else { - sending_pos = size_recv = 0; - } - - Tox_Err_File_Control error; - - ck_assert_msg(tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, &error), - "tox_file_control failed. %u", error); - ++file_accepted; - - Tox_Err_File_Seek err_s; - - ck_assert_msg(!tox_file_seek(tox, friend_number, file_number, 1234, &err_s), "tox_file_seek no error"); - - ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_DENIED, "tox_file_seek wrong error"); -} - -static uint32_t sendf_ok; -static void file_print_control(Tox *tox, uint32_t friend_number, uint32_t file_number, Tox_File_Control control, - void *userdata) -{ - /* First send file num is 0.*/ - if (file_number == 0 && control == TOX_FILE_CONTROL_RESUME) { - sendf_ok = 1; - } -} - -static uint64_t max_sending; -static bool m_send_reached; -static uint8_t sending_num; -static bool file_sending_done; -static void tox_file_chunk_request(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, - size_t length, void *user_data) -{ - ck_assert_msg(sendf_ok, "didn't get resume control"); - - ck_assert_msg(sending_pos == position, "bad position %lu", (unsigned long)position); - - if (length == 0) { - ck_assert_msg(!file_sending_done, "file sending already done"); - - file_sending_done = 1; - return; - } - - if (position + length > max_sending) { - ck_assert_msg(!m_send_reached, "requested done file transfer"); - - length = max_sending - position; - m_send_reached = 1; - } - - VLA(uint8_t, f_data, length); - memset(f_data, sending_num, length); - - Tox_Err_File_Send_Chunk error; - tox_file_send_chunk(tox, friend_number, file_number, position, f_data, length, &error); - - ck_assert_msg(error == TOX_ERR_FILE_SEND_CHUNK_OK, - "could not send chunk, error num=%d pos=%d len=%d", (int)error, (int)position, (int)length); - - ++sending_num; - sending_pos += length; -} - -static uint8_t num; -static bool file_recv; -static void write_file(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data, - size_t length, void *user_data) -{ - ck_assert_msg(size_recv == position, "bad position"); - - if (length == 0) { - file_recv = 1; - return; - } - - VLA(uint8_t, f_data, length); - memset(f_data, num, length); - ++num; - - ck_assert_msg(memcmp(f_data, data, length) == 0, "FILE_CORRUPTED"); - - size_recv += length; -} - -static void file_transfer_test(void) -{ - printf("Starting test: few_clients\n"); - uint32_t index[] = { 1, 2, 3 }; - long long unsigned int cur_time = time(nullptr); - Tox_Err_New t_n_error; - Tox *tox1 = tox_new_log(nullptr, &t_n_error, &index[0]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); - Tox *tox2 = tox_new_log(nullptr, &t_n_error, &index[1]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); - Tox *tox3 = tox_new_log(nullptr, &t_n_error, &index[2]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); - - ck_assert_msg(tox1 && tox2 && tox3, "Failed to create 3 tox instances"); - - tox_callback_friend_request(tox2, accept_friend_request); - uint8_t address[TOX_ADDRESS_SIZE]; - tox_self_get_address(tox2, address); - uint32_t test = tox_friend_add(tox3, address, (const uint8_t *)"Gentoo", 7, nullptr); - ck_assert_msg(test == 0, "Failed to add friend error code: %u", test); - - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(tox1, dht_key); - uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); - - tox_bootstrap(tox2, TOX_LOCALHOST, dht_port, dht_key, nullptr); - tox_bootstrap(tox3, TOX_LOCALHOST, dht_port, dht_key, nullptr); - - printf("Waiting for toxes to come online\n"); - - do { - tox_iterate(tox1, nullptr); - tox_iterate(tox2, nullptr); - tox_iterate(tox3, nullptr); - - printf("Connections: self (%u, %u, %u), friends (%u, %u)\n", - tox_self_get_connection_status(tox1), - tox_self_get_connection_status(tox2), - tox_self_get_connection_status(tox3), - tox_friend_get_connection_status(tox2, 0, nullptr), - tox_friend_get_connection_status(tox3, 0, nullptr)); - c_sleep(ITERATION_INTERVAL); - } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(tox3) == TOX_CONNECTION_NONE || - tox_friend_get_connection_status(tox2, 0, nullptr) == TOX_CONNECTION_NONE || - tox_friend_get_connection_status(tox3, 0, nullptr) == TOX_CONNECTION_NONE); - - printf("Starting file transfer test: 100MiB file.\n"); - - file_accepted = file_size = sendf_ok = size_recv = 0; - file_recv = 0; - max_sending = UINT64_MAX; - - printf("Starting file streaming transfer test.\n"); - - file_sending_done = 0; - file_accepted = 0; - file_size = 0; - sendf_ok = 0; - size_recv = 0; - file_recv = 0; - tox_callback_file_recv_chunk(tox3, write_file); - tox_callback_file_recv_control(tox2, file_print_control); - tox_callback_file_chunk_request(tox2, tox_file_chunk_request); - tox_callback_file_recv_control(tox3, file_print_control); - tox_callback_file_recv(tox3, tox_file_receive); - const uint64_t totalf_size = UINT64_MAX; - Tox_File_Number fnum = tox_file_send( - tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr, - (const uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), nullptr); - ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail"); - - Tox_Err_File_Get gfierr; - ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error"); - ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error"); - ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error"); - - max_sending = 100 * 1024; - m_send_reached = 0; - - do { - tox_iterate(tox1, nullptr); - tox_iterate(tox2, nullptr); - tox_iterate(tox3, nullptr); - - uint32_t tox1_interval = tox_iteration_interval(tox1); - uint32_t tox2_interval = tox_iteration_interval(tox2); - uint32_t tox3_interval = tox_iteration_interval(tox3); - - c_sleep(min_u32(tox1_interval, min_u32(tox2_interval, tox3_interval))); - } while (!file_sending_done); - - ck_assert_msg(sendf_ok && file_recv && m_send_reached && totalf_size == file_size && size_recv == max_sending - && sending_pos == size_recv && file_accepted == 1, - "something went wrong in file transfer %u %u %d %d %d %d %d %lu %lu %lu %lu", sendf_ok, file_recv, - m_send_reached, totalf_size == file_size, size_recv == max_sending, sending_pos == size_recv, file_accepted == 1, - (unsigned long)totalf_size, (unsigned long)file_size, - (unsigned long)size_recv, (unsigned long)sending_pos); - - printf("file_transfer_test succeeded, took %llu seconds\n", time(nullptr) - cur_time); - - tox_kill(tox1); - tox_kill(tox2); - tox_kill(tox3); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - file_transfer_test(); - return 0; -} diff --git a/auto_tests/file_transfer_test.c b/auto_tests/file_transfer_test.c deleted file mode 100644 index e1fa1d90..00000000 --- a/auto_tests/file_transfer_test.c +++ /dev/null @@ -1,375 +0,0 @@ -/* File transfer test. - */ - -#include -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/ccompat.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#ifndef USE_IPV6 -#define USE_IPV6 1 -#endif - -#ifdef TOX_LOCALHOST -#undef TOX_LOCALHOST -#endif -#if USE_IPV6 -#define TOX_LOCALHOST "::1" -#else -#define TOX_LOCALHOST "127.0.0.1" -#endif - -static void accept_friend_request(const Tox_Event_Friend_Request *event, void *userdata) -{ - Tox *tox = (Tox *)userdata; - - const uint8_t *public_key = tox_event_friend_request_get_public_key(event); - const uint8_t *data = tox_event_friend_request_get_message(event); - const size_t length = tox_event_friend_request_get_message_length(event); - - if (length == 7 && memcmp("Gentoo", data, 7) == 0) { - tox_friend_add_norequest(tox, public_key, nullptr); - } -} - -static uint64_t size_recv; -static uint64_t sending_pos; - -static uint8_t file_cmp_id[TOX_FILE_ID_LENGTH]; -static uint32_t file_accepted; -static uint64_t file_size; -static void tox_file_receive(const Tox_Event_File_Recv *event, void *userdata) -{ - Tox *state_tox = (Tox *)userdata; - - const uint32_t friend_number = tox_event_file_recv_get_friend_number(event); - const uint32_t file_number = tox_event_file_recv_get_file_number(event); - const uint32_t kind = tox_event_file_recv_get_kind(event); - const uint64_t filesize = tox_event_file_recv_get_file_size(event); - const uint8_t *filename = tox_event_file_recv_get_filename(event); - const size_t filename_length = tox_event_file_recv_get_filename_length(event); - - ck_assert_msg(kind == TOX_FILE_KIND_DATA, "bad kind"); - - ck_assert_msg(filename_length == sizeof("Gentoo.exe") - && memcmp(filename, "Gentoo.exe", sizeof("Gentoo.exe")) == 0, "bad filename"); - - uint8_t file_id[TOX_FILE_ID_LENGTH]; - - ck_assert_msg(tox_file_get_file_id(state_tox, friend_number, file_number, file_id, nullptr), "tox_file_get_file_id error"); - - ck_assert_msg(memcmp(file_id, file_cmp_id, TOX_FILE_ID_LENGTH) == 0, "bad file_id"); - - const uint8_t empty[TOX_FILE_ID_LENGTH] = {0}; - - ck_assert_msg(memcmp(empty, file_cmp_id, TOX_FILE_ID_LENGTH) != 0, "empty file_id"); - - file_size = filesize; - - if (filesize) { - sending_pos = size_recv = 1337; - - Tox_Err_File_Seek err_s; - - ck_assert_msg(tox_file_seek(state_tox, friend_number, file_number, 1337, &err_s), "tox_file_seek error"); - - ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_OK, "tox_file_seek wrong error"); - - } else { - sending_pos = size_recv = 0; - } - - Tox_Err_File_Control error; - - ck_assert_msg(tox_file_control(state_tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, &error), - "tox_file_control failed. %u", error); - ++file_accepted; - - Tox_Err_File_Seek err_s; - - ck_assert_msg(!tox_file_seek(state_tox, friend_number, file_number, 1234, &err_s), "tox_file_seek no error"); - - ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_DENIED, "tox_file_seek wrong error"); -} - -static uint32_t sendf_ok; -static void file_print_control(const Tox_Event_File_Recv_Control *event, - void *userdata) -{ - const uint32_t file_number = tox_event_file_recv_control_get_file_number(event); - const Tox_File_Control control = tox_event_file_recv_control_get_control(event); - - /* First send file num is 0.*/ - if (file_number == 0 && control == TOX_FILE_CONTROL_RESUME) { - sendf_ok = 1; - } -} - -static uint64_t max_sending; -static bool m_send_reached; -static uint8_t sending_num; -static bool file_sending_done; -static void tox_file_chunk_request(const Tox_Event_File_Chunk_Request *event, void *user_data) -{ - Tox *state_tox = (Tox *)user_data; - - const uint32_t friend_number = tox_event_file_chunk_request_get_friend_number(event); - const uint32_t file_number = tox_event_file_chunk_request_get_file_number(event); - const uint64_t position = tox_event_file_chunk_request_get_position(event); - size_t length = tox_event_file_chunk_request_get_length(event); - - ck_assert_msg(sendf_ok, "didn't get resume control"); - - ck_assert_msg(sending_pos == position, "bad position %lu (should be %lu)", (unsigned long)position, (unsigned long)sending_pos); - - if (length == 0) { - ck_assert_msg(!file_sending_done, "file sending already done"); - - file_sending_done = 1; - return; - } - - if (position + length > max_sending) { - ck_assert_msg(!m_send_reached, "requested done file transfer"); - - length = max_sending - position; - m_send_reached = 1; - } - - VLA(uint8_t, f_data, length); - memset(f_data, sending_num, length); - - Tox_Err_File_Send_Chunk error; - tox_file_send_chunk(state_tox, friend_number, file_number, position, f_data, length, &error); - - ck_assert_msg(error == TOX_ERR_FILE_SEND_CHUNK_OK, - "could not send chunk, error num=%d pos=%d len=%d", (int)error, (int)position, (int)length); - - ++sending_num; - sending_pos += length; -} - -static uint8_t num; -static bool file_recv; -static void write_file(const Tox_Event_File_Recv_Chunk *event, void *user_data) -{ - const uint64_t position = tox_event_file_recv_chunk_get_position(event); - const uint8_t *data = tox_event_file_recv_chunk_get_data(event); - const size_t length = tox_event_file_recv_chunk_get_data_length(event); - - ck_assert_msg(size_recv == position, "bad position"); - - if (length == 0) { - file_recv = 1; - return; - } - - VLA(uint8_t, f_data, length); - memset(f_data, num, length); - ++num; - - ck_assert_msg(memcmp(f_data, data, length) == 0, "FILE_CORRUPTED"); - - size_recv += length; -} - -static void iterate_and_dispatch(const Tox_Dispatch *dispatch, Tox *tox) -{ - Tox_Err_Events_Iterate err; - Tox_Events *events; - - events = tox_events_iterate(tox, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch, events, tox); - tox_events_free(events); -} - -static void file_transfer_test(void) -{ - printf("Starting test: few_clients\n"); - uint32_t index[] = { 1, 2, 3 }; - long long unsigned int cur_time = time(nullptr); - Tox_Err_New t_n_error; - Tox *tox1 = tox_new_log(nullptr, &t_n_error, &index[0]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); - Tox *tox2 = tox_new_log(nullptr, &t_n_error, &index[1]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); - Tox *tox3 = tox_new_log(nullptr, &t_n_error, &index[2]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); - - ck_assert_msg(tox1 && tox2 && tox3, "Failed to create 3 tox instances"); - - tox_events_init(tox1); - tox_events_init(tox2); - tox_events_init(tox3); - - Tox_Dispatch *dispatch1 = tox_dispatch_new(nullptr); - ck_assert(dispatch1 != nullptr); - Tox_Dispatch *dispatch2 = tox_dispatch_new(nullptr); - ck_assert(dispatch2 != nullptr); - Tox_Dispatch *dispatch3 = tox_dispatch_new(nullptr); - ck_assert(dispatch3 != nullptr); - - tox_events_callback_friend_request(dispatch2, accept_friend_request); - - uint8_t address[TOX_ADDRESS_SIZE]; - tox_self_get_address(tox2, address); - uint32_t test = tox_friend_add(tox3, address, (const uint8_t *)"Gentoo", 7, nullptr); - ck_assert_msg(test == 0, "Failed to add friend error code: %u", test); - - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(tox1, dht_key); - uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); - - tox_bootstrap(tox2, TOX_LOCALHOST, dht_port, dht_key, nullptr); - tox_bootstrap(tox3, TOX_LOCALHOST, dht_port, dht_key, nullptr); - - printf("Waiting for toxes to come online\n"); - - do { - iterate_and_dispatch(dispatch1, tox1); - iterate_and_dispatch(dispatch2, tox2); - iterate_and_dispatch(dispatch3, tox3); - - printf("Connections: self (%u, %u, %u), friends (%u, %u)\n", - tox_self_get_connection_status(tox1), - tox_self_get_connection_status(tox2), - tox_self_get_connection_status(tox3), - tox_friend_get_connection_status(tox2, 0, nullptr), - tox_friend_get_connection_status(tox3, 0, nullptr)); - c_sleep(ITERATION_INTERVAL); - } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(tox3) == TOX_CONNECTION_NONE || - tox_friend_get_connection_status(tox2, 0, nullptr) == TOX_CONNECTION_NONE || - tox_friend_get_connection_status(tox3, 0, nullptr) == TOX_CONNECTION_NONE); - - printf("Starting file transfer test: 100MiB file.\n"); - - file_accepted = file_size = sendf_ok = size_recv = 0; - file_recv = 0; - max_sending = UINT64_MAX; - uint64_t f_time = time(nullptr); - tox_events_callback_file_recv_chunk(dispatch3, write_file); - tox_events_callback_file_recv_control(dispatch2, file_print_control); - tox_events_callback_file_chunk_request(dispatch2, tox_file_chunk_request); - tox_events_callback_file_recv_control(dispatch3, file_print_control); - tox_events_callback_file_recv(dispatch3, tox_file_receive); - uint64_t totalf_size = 100 * 1024 * 1024; - uint32_t fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr, (const uint8_t *)"Gentoo.exe", - sizeof("Gentoo.exe"), nullptr); - ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail"); - - Tox_Err_File_Get gfierr; - ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error"); - ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error"); - ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error"); - - const size_t max_iterations = INT16_MAX; - - for (size_t i = 0; i < max_iterations; i++) { - iterate_and_dispatch(dispatch1, tox1); - iterate_and_dispatch(dispatch2, tox2); - iterate_and_dispatch(dispatch3, tox3); - - if (file_sending_done) { - ck_assert_msg(sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size && sending_pos == size_recv - && file_accepted == 1, - "Something went wrong in file transfer %u %u %d %d %d %d %lu %lu %lu", - sendf_ok, file_recv, totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, - file_accepted == 1, (unsigned long)totalf_size, (unsigned long)size_recv, - (unsigned long)sending_pos); - break; - } - - uint32_t tox1_interval = tox_iteration_interval(tox1); - uint32_t tox2_interval = tox_iteration_interval(tox2); - uint32_t tox3_interval = tox_iteration_interval(tox3); - - if ((i + 1) % 500 == 0) { - printf("after %u iterations: %.2fMiB done\n", (unsigned int)i + 1, (double)size_recv / 1024 / 1024); - } - - c_sleep(min_u32(tox1_interval, min_u32(tox2_interval, tox3_interval))); - } - - ck_assert_msg(file_sending_done, "file sending did not complete after %u iterations: sendf_ok:%u file_recv:%u " - "totalf_size==file_size:%d size_recv==file_size:%d sending_pos==size_recv:%d file_accepted:%d " - "totalf_size:%lu size_recv:%lu sending_pos:%lu", - (unsigned int)max_iterations, sendf_ok, file_recv, - totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1, - (unsigned long)totalf_size, (unsigned long)size_recv, - (unsigned long)sending_pos); - - printf("100MiB file sent in %lu seconds\n", (unsigned long)(time(nullptr) - f_time)); - - printf("starting file 0 transfer test.\n"); - - file_sending_done = 0; - file_accepted = 0; - file_size = 0; - sendf_ok = 0; - size_recv = 0; - file_recv = 0; - tox_events_callback_file_recv_chunk(dispatch3, write_file); - tox_events_callback_file_recv_control(dispatch2, file_print_control); - tox_events_callback_file_chunk_request(dispatch2, tox_file_chunk_request); - tox_events_callback_file_recv_control(dispatch3, file_print_control); - tox_events_callback_file_recv(dispatch3, tox_file_receive); - totalf_size = 0; - fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr, - (const uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), nullptr); - ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail"); - - ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error"); - ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error"); - ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed"); - ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error"); - - do { - uint32_t tox1_interval = tox_iteration_interval(tox1); - uint32_t tox2_interval = tox_iteration_interval(tox2); - uint32_t tox3_interval = tox_iteration_interval(tox3); - - c_sleep(min_u32(tox1_interval, min_u32(tox2_interval, tox3_interval))); - - iterate_and_dispatch(dispatch1, tox1); - iterate_and_dispatch(dispatch2, tox2); - iterate_and_dispatch(dispatch3, tox3); - } while (!file_sending_done); - - ck_assert_msg(sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size - && sending_pos == size_recv && file_accepted == 1, - "something went wrong in file transfer %u %u %d %d %d %d %llu %llu %llu", sendf_ok, file_recv, - totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1, - (unsigned long long)totalf_size, (unsigned long long)size_recv, - (unsigned long long)sending_pos); - - printf("file_transfer_test succeeded, took %llu seconds\n", time(nullptr) - cur_time); - - tox_dispatch_free(dispatch3); - tox_dispatch_free(dispatch2); - tox_dispatch_free(dispatch1); - tox_kill(tox3); - tox_kill(tox2); - tox_kill(tox1); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - file_transfer_test(); - return 0; -} diff --git a/auto_tests/forwarding_test.c b/auto_tests/forwarding_test.c index 3e5c3071..722f16d0 100644 --- a/auto_tests/forwarding_test.c +++ b/auto_tests/forwarding_test.c @@ -132,7 +132,7 @@ static Forwarding_Subtox *new_forwarding_subtox(const Memory *mem, bool no_udp, ck_assert(subtox->tcp_np != nullptr); const TCP_Proxy_Info inf = {{{{0}}}}; - subtox->c = new_net_crypto(subtox->log, mem, rng, ns, subtox->mono_time, subtox->net, subtox->dht, &inf, subtox->tcp_np); + subtox->c = new_net_crypto(subtox->log, mem, rng, ns, subtox->mono_time, subtox->net, subtox->dht, &auto_test_dht_funcs, &inf, subtox->tcp_np); subtox->forwarding = new_forwarding(subtox->log, mem, rng, subtox->mono_time, subtox->dht, subtox->net); ck_assert(subtox->forwarding != nullptr); diff --git a/auto_tests/friend_connection_test.c b/auto_tests/friend_connection_test.c deleted file mode 100644 index 3a348387..00000000 --- a/auto_tests/friend_connection_test.c +++ /dev/null @@ -1,26 +0,0 @@ -/* Tests that we can make a friend connection. - * - * This is the simplest test that brings up two toxes that can talk to each - * other. It's useful as a copy/pasteable starting point for testing other - * features. - */ - -#include - -#include "auto_test_support.h" - -static void friend_connection_test(AutoTox *toxes) -{ - // Nothing to do here. When copying this test, add test-specific code here. -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - run_auto_test(nullptr, 2, friend_connection_test, 0, &options); - - return 0; -} diff --git a/auto_tests/friend_request_spam_test.c b/auto_tests/friend_request_spam_test.c deleted file mode 100644 index 9e2eabfe..00000000 --- a/auto_tests/friend_request_spam_test.c +++ /dev/null @@ -1,77 +0,0 @@ -/* Tests what happens when spamming friend requests from lots of temporary toxes. - */ - -#include -#include -#include -#include - -#include "../toxcore/ccompat.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" -#include "../testing/misc_tools.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#define FR_MESSAGE "Gentoo" -// TODO(iphydf): Investigate friend request spam: receiving more than 32 at a time means any further -// friend requests are dropped on the floor and aren't seen again. -#define FR_TOX_COUNT 33 - -typedef struct State { - bool unused; -} State; - -static void accept_friend_request(const Tox_Event_Friend_Request *event, - void *userdata) -{ - AutoTox *autotox = (AutoTox *)userdata; - - const uint8_t *public_key = tox_event_friend_request_get_public_key(event); - const uint8_t *data = tox_event_friend_request_get_message(event); - const size_t length = tox_event_friend_request_get_message_length(event); - - ck_assert_msg(length == sizeof(FR_MESSAGE) && memcmp(FR_MESSAGE, data, sizeof(FR_MESSAGE)) == 0, - "unexpected friend request message"); - tox_friend_add_norequest(autotox->tox, public_key, nullptr); -} - -static void test_friend_request(AutoTox *autotoxes) -{ - const time_t con_time = time(nullptr); - - printf("All toxes add tox1 as friend.\n"); - tox_events_callback_friend_request(autotoxes[0].dispatch, accept_friend_request); - - uint8_t address[TOX_ADDRESS_SIZE]; - tox_self_get_address(autotoxes[0].tox, address); - - for (uint32_t i = 2; i < FR_TOX_COUNT; ++i) { - Tox_Err_Friend_Add err; - tox_friend_add(autotoxes[i].tox, address, (const uint8_t *)FR_MESSAGE, sizeof(FR_MESSAGE), &err); - ck_assert_msg(err == TOX_ERR_FRIEND_ADD_OK, "tox %u failed to add friend error code: %u", autotoxes[i].index, err); - } - - for (uint32_t t = 0; t < 100; ++t) { - if (all_friends_connected(autotoxes, FR_TOX_COUNT)) { - break; - } - - iterate_all_wait(autotoxes, FR_TOX_COUNT, ITERATION_INTERVAL); - } - - const size_t size = tox_self_get_friend_list_size(autotoxes[0].tox); - printf("Tox clients connected took %lu seconds; tox1 has %u friends.\n", - (unsigned long)(time(nullptr) - con_time), (unsigned int)size); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - run_auto_test(nullptr, FR_TOX_COUNT, test_friend_request, sizeof(State), &options); - - return 0; -} diff --git a/auto_tests/friend_request_test.c b/auto_tests/friend_request_test.c deleted file mode 100644 index 93802106..00000000 --- a/auto_tests/friend_request_test.c +++ /dev/null @@ -1,147 +0,0 @@ -/* Tests that we can add friends. - */ - -#include -#include -#include -#include - -#include "../toxcore/ccompat.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" -#include "../testing/misc_tools.h" - -#include "auto_test_support.h" -#include "check_compat.h" - -#define REQUEST_MESSAGE "Hello, I would like to be your friend. Please respond." - -typedef struct Callback_Data { - Tox *tox1; // request sender - Tox *tox2; // request receiver - uint8_t message[TOX_MAX_FRIEND_REQUEST_LENGTH]; - uint16_t length; -} Callback_Data; - -static void accept_friend_request(const Tox_Event_Friend_Request *event, void *userdata) -{ - Callback_Data *cb_data = (Callback_Data *)userdata; - - const uint8_t *public_key = tox_event_friend_request_get_public_key(event); - const uint8_t *data = tox_event_friend_request_get_message(event); - const size_t length = tox_event_friend_request_get_message_length(event); - - ck_assert_msg(length == cb_data->length && memcmp(cb_data->message, data, cb_data->length) == 0, - "unexpected friend request message"); - - fprintf(stderr, "Tox2 accepts friend request.\n"); - - Tox_Err_Friend_Add err; - tox_friend_add_norequest(cb_data->tox2, public_key, &err); - - ck_assert_msg(err == TOX_ERR_FRIEND_ADD_OK, "tox_friend_add_norequest failed: %u", err); -} - -static void iterate2_wait(const Tox_Dispatch *dispatch, Callback_Data *cb_data) -{ - Tox_Err_Events_Iterate err; - Tox_Events *events; - - events = tox_events_iterate(cb_data->tox1, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch, events, cb_data); - tox_events_free(events); - - events = tox_events_iterate(cb_data->tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch, events, cb_data); - tox_events_free(events); - - c_sleep(ITERATION_INTERVAL); -} - -static void test_friend_request(const uint8_t *message, uint16_t length) -{ - printf("Initialising 2 toxes.\n"); - uint32_t index[] = { 1, 2 }; - const time_t cur_time = time(nullptr); - Tox *const tox1 = tox_new_log(nullptr, nullptr, &index[0]); - Tox *const tox2 = tox_new_log(nullptr, nullptr, &index[1]); - - ck_assert_msg(tox1 && tox2, "failed to create 2 tox instances"); - - tox_events_init(tox1); - tox_events_init(tox2); - - printf("Bootstrapping Tox2 off Tox1.\n"); - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(tox1, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); - - tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); - - Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); - ck_assert(dispatch != nullptr); - - Callback_Data cb_data = {nullptr}; - cb_data.tox1 = tox1; - cb_data.tox2 = tox2; - - ck_assert(length <= sizeof(cb_data.message)); - memcpy(cb_data.message, message, length); - cb_data.length = length; - - do { - iterate2_wait(dispatch, &cb_data); - } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE); - - printf("Toxes are online, took %lu seconds.\n", (unsigned long)(time(nullptr) - cur_time)); - const time_t con_time = time(nullptr); - - printf("Tox1 adds Tox2 as friend. Waiting for Tox2 to accept.\n"); - tox_events_callback_friend_request(dispatch, accept_friend_request); - - uint8_t address[TOX_ADDRESS_SIZE]; - tox_self_get_address(tox2, address); - - Tox_Err_Friend_Add err; - const uint32_t test = tox_friend_add(tox1, address, message, length, &err); - - ck_assert_msg(err == TOX_ERR_FRIEND_ADD_OK, "tox_friend_add failed: %u", err); - ck_assert_msg(test == 0, "failed to add friend error code: %u", test); - - do { - iterate2_wait(dispatch, &cb_data); - } while (tox_friend_get_connection_status(tox1, 0, nullptr) != TOX_CONNECTION_UDP || - tox_friend_get_connection_status(tox2, 0, nullptr) != TOX_CONNECTION_UDP); - - printf("Tox clients connected took %lu seconds.\n", (unsigned long)(time(nullptr) - con_time)); - printf("friend_request_test succeeded, took %lu seconds.\n", (unsigned long)(time(nullptr) - cur_time)); - - tox_dispatch_free(dispatch); - tox_kill(tox1); - tox_kill(tox2); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - fprintf(stderr, "Testing friend request with the smallest allowed message length.\n"); - test_friend_request((const uint8_t *)"a", 1); - - fprintf(stderr, "Testing friend request with an average sized message length.\n"); - test_friend_request((const uint8_t *)REQUEST_MESSAGE, sizeof(REQUEST_MESSAGE) - 1); - - uint8_t long_message[TOX_MAX_FRIEND_REQUEST_LENGTH]; - - for (uint16_t i = 0; i < sizeof(long_message); ++i) { - long_message[i] = 'a'; - } - - fprintf(stderr, "Testing friend request with the largest allowed message length.\n"); - test_friend_request(long_message, sizeof(long_message)); - - return 0; -} diff --git a/auto_tests/group_general_test.c b/auto_tests/group_general_test.c deleted file mode 100644 index aff8e0d1..00000000 --- a/auto_tests/group_general_test.c +++ /dev/null @@ -1,475 +0,0 @@ -/* - * Tests that we can connect to a public group chat through the DHT and make basic queries - * about the group, other peers, and ourselves. We also make sure we can disconnect and - * reconnect to a group while retaining our credentials. - */ - -#include -#include -#include - -#include "auto_test_support.h" -#include "../toxcore/tox_private.h" - -typedef struct State { - size_t peer_joined_count; - size_t self_joined_count; - size_t peer_exit_count; - bool peer_nick; - bool peer_status; - uint32_t peer_id; - bool is_founder; -} State; - -#define NUM_GROUP_TOXES 2 - -#define GROUP_NAME "NASA Headquarters" -#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) - -#define TOPIC "Funny topic here" -#define TOPIC_LEN (sizeof(TOPIC) - 1) - -#define PEER0_NICK "Lois" -#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) - -#define PEER0_NICK2 "Terry Davis" -#define PEER0_NICK2_LEN (sizeof(PEER0_NICK2) - 1) - -#define PEER1_NICK "Bran" -#define PEER1_NICK_LEN (sizeof(PEER1_NICK) - 1) - -#define EXIT_MESSAGE "Goodbye world" -#define EXIT_MESSAGE_LEN (sizeof(EXIT_MESSAGE) - 1) - -#define PEER_LIMIT 20 - -static void print_ip(const Tox *tox, uint32_t groupnumber, uint32_t peer_id) -{ - Tox_Err_Group_Peer_Query err; - size_t length = tox_group_peer_get_ip_address_size(tox, groupnumber, peer_id, &err); - - ck_assert_msg(err == TOX_ERR_GROUP_PEER_QUERY_OK, "failed to get ip address size: error %u", err); - - uint8_t ip_str[TOX_GROUP_PEER_IP_STRING_MAX_LENGTH]; - tox_group_peer_get_ip_address(tox, groupnumber, peer_id, ip_str, &err); - ip_str[length] = '\0'; - - ck_assert_msg(err == TOX_ERR_GROUP_PEER_QUERY_OK, "failed to get ip address: error %u", err); - - fprintf(stderr, "%s\n", ip_str); -} - -static bool all_group_peers_connected(AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, size_t name_length) -{ - for (size_t i = 0; i < tox_count; ++i) { - // make sure we got an invite response - if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != name_length) { - return false; - } - - // make sure we got a sync response - if (tox_group_get_peer_limit(autotoxes[i].tox, groupnumber, nullptr) != PEER_LIMIT) { - return false; - } - - // make sure we're actually connected - if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) { - return false; - } - } - - return true; -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t groupnumber = tox_event_group_peer_join_get_group_number(event); - const uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); - - // we do a connection test here for fun - Tox_Err_Group_Peer_Query pq_err; - Tox_Connection connection_status = tox_group_peer_get_connection_status(autotox->tox, groupnumber, peer_id, &pq_err); - ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(connection_status != TOX_CONNECTION_NONE); - - Tox_Group_Role role = tox_group_peer_get_role(autotox->tox, groupnumber, peer_id, &pq_err); - ck_assert_msg(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK, "%u", pq_err); - - Tox_User_Status status = tox_group_peer_get_status(autotox->tox, groupnumber, peer_id, &pq_err); - ck_assert_msg(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK, "%u", pq_err); - - size_t peer_name_len = tox_group_peer_get_name_size(autotox->tox, groupnumber, peer_id, &pq_err); - char peer_name[TOX_MAX_NAME_LENGTH + 1]; - - ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); - - tox_group_peer_get_name(autotox->tox, groupnumber, peer_id, (uint8_t *)peer_name, &pq_err); - ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); - - peer_name[peer_name_len] = 0; - - // make sure we see the correct peer state on join - if (!state->is_founder) { - ck_assert_msg(role == TOX_GROUP_ROLE_FOUNDER, "wrong role: %u", role); - - if (state->peer_joined_count == 0) { - ck_assert_msg(status == TOX_USER_STATUS_NONE, "wrong status: %u", status); - ck_assert_msg(peer_name_len == PEER0_NICK_LEN, "wrong nick: %s", peer_name); - ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); - } else { - ck_assert_msg(status == TOX_USER_STATUS_BUSY, "wrong status: %u", status); - ck_assert(peer_name_len == PEER0_NICK2_LEN); - ck_assert(memcmp(peer_name, PEER0_NICK2, peer_name_len) == 0); - } - } else { - ck_assert_msg(role == TOX_GROUP_ROLE_USER, "wrong role: %u", role); - ck_assert(peer_name_len == PEER1_NICK_LEN); - ck_assert(memcmp(peer_name, PEER1_NICK, peer_name_len) == 0); - - if (state->peer_joined_count == 0) { - ck_assert_msg(status == TOX_USER_STATUS_NONE, "wrong status: %u", status); - } else { - ck_assert_msg(status == TOX_USER_STATUS_AWAY, "wrong status: %u", status); - } - } - - fprintf(stderr, "%s joined with IP: ", peer_name); - print_ip(autotox->tox, groupnumber, peer_id); - - state->peer_id = peer_id; - ++state->peer_joined_count; -} - -static void group_peer_self_join_handler(const Tox_Event_Group_Self_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t groupnumber = tox_event_group_self_join_get_group_number(event); - - // make sure we see our own correct peer state on join callback - - Tox_Err_Group_Self_Query sq_err; - size_t self_length = tox_group_self_get_name_size(autotox->tox, groupnumber, &sq_err); - - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - uint8_t self_name[TOX_MAX_NAME_LENGTH]; - tox_group_self_get_name(autotox->tox, groupnumber, self_name, &sq_err); - - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - Tox_User_Status self_status = tox_group_self_get_status(autotox->tox, groupnumber, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - Tox_Group_Role self_role = tox_group_self_get_role(autotox->tox, groupnumber, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - if (state->is_founder) { - // founder doesn't get a self join callback on initial creation of group - ck_assert(self_length == PEER0_NICK2_LEN); - ck_assert(memcmp(self_name, PEER0_NICK2, self_length) == 0); - ck_assert(self_status == TOX_USER_STATUS_BUSY); - ck_assert(self_role == TOX_GROUP_ROLE_FOUNDER); - } else { - ck_assert(self_length == PEER1_NICK_LEN); - ck_assert(memcmp(self_name, PEER1_NICK, self_length) == 0); - ck_assert(self_role == TOX_GROUP_ROLE_USER); - ck_assert(self_status == TOX_USER_STATUS_NONE); - } - - // make sure we see correct group state on join callback - uint8_t group_name[GROUP_NAME_LEN]; - uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH]; - - ck_assert(tox_group_get_peer_limit(autotox->tox, groupnumber, nullptr) == PEER_LIMIT); - ck_assert(tox_group_get_name_size(autotox->tox, groupnumber, nullptr) == GROUP_NAME_LEN); - ck_assert(tox_group_get_topic_size(autotox->tox, groupnumber, nullptr) == TOPIC_LEN); - - Tox_Err_Group_State_Query query_err; - tox_group_get_name(autotox->tox, groupnumber, group_name, &query_err); - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "%u", query_err); - ck_assert(memcmp(group_name, GROUP_NAME, GROUP_NAME_LEN) == 0); - - tox_group_get_topic(autotox->tox, groupnumber, topic, &query_err); - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "%u", query_err); - ck_assert(memcmp(topic, TOPIC, TOPIC_LEN) == 0); - - uint32_t peer_id = tox_group_self_get_peer_id(autotox->tox, groupnumber, nullptr); - - fprintf(stderr, "self joined with IP: "); - print_ip(autotox->tox, groupnumber, peer_id); - - ++state->self_joined_count; -} - -static void group_peer_exit_handler(const Tox_Event_Group_Peer_Exit *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint8_t *part_message = tox_event_group_peer_exit_get_part_message(event); - const size_t length = tox_event_group_peer_exit_get_part_message_length(event); - - ++state->peer_exit_count; - - // first exit is a disconnect. second is a real exit with a part message - if (state->peer_exit_count == 2) { - ck_assert(length == EXIT_MESSAGE_LEN); - ck_assert(memcmp(part_message, EXIT_MESSAGE, length) == 0); - } -} - -static void group_peer_name_handler(const Tox_Event_Group_Peer_Name *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint8_t *name = tox_event_group_peer_name_get_name(event); - const size_t length = tox_event_group_peer_name_get_name_length(event); - - // note: we already test the name_get api call elsewhere - - ck_assert(length == PEER0_NICK2_LEN); - ck_assert(memcmp(name, PEER0_NICK2, length) == 0); - - state->peer_nick = true; -} - -static void group_peer_status_handler(const Tox_Event_Group_Peer_Status *event, - void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t groupnumber = tox_event_group_peer_status_get_group_number(event); - const uint32_t peer_id = tox_event_group_peer_status_get_peer_id(event); - const Tox_User_Status status = tox_event_group_peer_status_get_status(event); - - Tox_Err_Group_Peer_Query err; - Tox_User_Status cur_status = tox_group_peer_get_status(autotox->tox, groupnumber, peer_id, &err); - - ck_assert_msg(cur_status == status, "%u, %u", cur_status, status); - ck_assert(status == TOX_USER_STATUS_BUSY); - - state->peer_status = true; -} - -static void group_announce_test(AutoTox *autotoxes) -{ - ck_assert_msg(NUM_GROUP_TOXES == 2, "NUM_GROUP_TOXES needs to be 2"); - - Tox *tox0 = autotoxes[0].tox; - Tox *tox1 = autotoxes[1].tox; - State *state0 = (State *)autotoxes[0].state; - const State *state1 = (const State *)autotoxes[1].state; - - tox_events_callback_group_peer_join(autotoxes[0].dispatch, group_peer_join_handler); - tox_events_callback_group_peer_join(autotoxes[1].dispatch, group_peer_join_handler); - tox_events_callback_group_self_join(autotoxes[0].dispatch, group_peer_self_join_handler); - tox_events_callback_group_self_join(autotoxes[1].dispatch, group_peer_self_join_handler); - tox_events_callback_group_peer_name(autotoxes[1].dispatch, group_peer_name_handler); - tox_events_callback_group_peer_status(autotoxes[1].dispatch, group_peer_status_handler); - tox_events_callback_group_peer_exit(autotoxes[1].dispatch, group_peer_exit_handler); - - // tox0 makes new group. - Tox_Err_Group_New err_new; - uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *) GROUP_NAME, - GROUP_NAME_LEN, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, - &err_new); - ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); - - state0->is_founder = true; - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - // changes the state (for sync check purposes) - Tox_Err_Group_Set_Peer_Limit limit_set_err; - tox_group_set_peer_limit(tox0, groupnumber, PEER_LIMIT, &limit_set_err); - ck_assert_msg(limit_set_err == TOX_ERR_GROUP_SET_PEER_LIMIT_OK, "failed to set peer limit: %u", limit_set_err); - - Tox_Err_Group_Topic_Set tp_err; - tox_group_set_topic(tox0, groupnumber, (const uint8_t *)TOPIC, TOPIC_LEN, &tp_err); - ck_assert(tp_err == TOX_ERR_GROUP_TOPIC_SET_OK); - - // get the chat id of the new group. - Tox_Err_Group_State_Query err_id; - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - tox_group_get_chat_id(tox0, groupnumber, chat_id, &err_id); - ck_assert(err_id == TOX_ERR_GROUP_STATE_QUERY_OK); - - // tox1 joins it. - Tox_Err_Group_Join err_join; - tox_group_join(tox1, chat_id, (const uint8_t *)PEER1_NICK, PEER1_NICK_LEN, nullptr, 0, &err_join); - ck_assert(err_join == TOX_ERR_GROUP_JOIN_OK); - - // peers see each other and themselves join - while (!state1->peer_joined_count || !state1->self_joined_count || !state0->peer_joined_count) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - // wait for group syncing to finish - while (!all_group_peers_connected(autotoxes, NUM_GROUP_TOXES, groupnumber, GROUP_NAME_LEN)) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - fprintf(stderr, "Peers connected to group\n"); - - // tox 0 changes name - Tox_Err_Group_Self_Name_Set n_err; - tox_group_self_set_name(tox0, groupnumber, (const uint8_t *)PEER0_NICK2, PEER0_NICK2_LEN, &n_err); - ck_assert(n_err == TOX_ERR_GROUP_SELF_NAME_SET_OK); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - Tox_Err_Group_Self_Query sq_err; - size_t self_length = tox_group_self_get_name_size(tox0, groupnumber, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_length == PEER0_NICK2_LEN); - - uint8_t self_name[TOX_MAX_NAME_LENGTH]; - tox_group_self_get_name(tox0, groupnumber, self_name, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(memcmp(self_name, PEER0_NICK2, self_length) == 0); - - fprintf(stderr, "Peer 0 successfully changed nick\n"); - - // tox 0 changes status - Tox_Err_Group_Self_Status_Set s_err; - tox_group_self_set_status(tox0, groupnumber, TOX_USER_STATUS_BUSY, &s_err); - ck_assert(s_err == TOX_ERR_GROUP_SELF_STATUS_SET_OK); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - Tox_User_Status self_status = tox_group_self_get_status(tox0, groupnumber, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_status == TOX_USER_STATUS_BUSY); - - fprintf(stderr, "Peer 0 successfully changed status to %u\n", self_status); - - while (!state1->peer_nick && !state1->peer_status) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - // tox 0 and tox 1 should see the same public key for tox 0 - uint8_t tox0_self_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; - tox_group_self_get_public_key(tox0, groupnumber, tox0_self_pk, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - Tox_Err_Group_Peer_Query pq_err; - uint8_t tox0_pk_query[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; - tox_group_peer_get_public_key(tox1, groupnumber, state1->peer_id, tox0_pk_query, &pq_err); - ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(memcmp(tox0_pk_query, tox0_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0); - - fprintf(stderr, "Peer 0 disconnecting...\n"); - - // tox 0 disconnects then reconnects - Tox_Err_Group_Disconnect d_err; - tox_group_disconnect(tox0, groupnumber, &d_err); - ck_assert(d_err == TOX_ERR_GROUP_DISCONNECT_OK); - - while (state1->peer_exit_count != 1) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - // tox 1 changes status while alone in the group - tox_group_self_set_status(tox1, groupnumber, TOX_USER_STATUS_AWAY, &s_err); - ck_assert(s_err == TOX_ERR_GROUP_SELF_STATUS_SET_OK); - - fprintf(stderr, "Peer 0 reconnecting...\n"); - Tox_Err_Group_Join err_rejoin; - tox_group_join(tox0, chat_id, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, nullptr, 0, &err_rejoin); - ck_assert(err_rejoin == TOX_ERR_GROUP_JOIN_OK); - - while (state1->peer_joined_count != 2 && state0->self_joined_count == 2) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - for (size_t i = 0; i < 100; ++i) { // if we don't do this the exit packet never arrives - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - while (!all_group_peers_connected(autotoxes, NUM_GROUP_TOXES, groupnumber, GROUP_NAME_LEN)) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - // tox 0 should have the same public key and still be founder - uint8_t tox0_self_pk2[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; - tox_group_self_get_public_key(tox0, groupnumber, tox0_self_pk2, &sq_err); - - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(memcmp(tox0_self_pk2, tox0_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0); - - Tox_Group_Role self_role = tox_group_self_get_role(tox0, groupnumber, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - Tox_Group_Role other_role = tox_group_peer_get_role(tox1, groupnumber, state1->peer_id, &pq_err); - ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); - - ck_assert(self_role == other_role && self_role == TOX_GROUP_ROLE_FOUNDER); - - uint32_t num_groups1 = tox_group_get_number_groups(tox0); - uint32_t num_groups2 = tox_group_get_number_groups(tox1); - - ck_assert(num_groups1 == num_groups2 && num_groups2 == 1); - - fprintf(stderr, "Both peers exiting group...\n"); - - Tox_Err_Group_Leave err_exit; - tox_group_leave(tox0, groupnumber, (const uint8_t *)EXIT_MESSAGE, EXIT_MESSAGE_LEN, &err_exit); - ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); - - while (state1->peer_exit_count != 2) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - tox_group_leave(tox1, groupnumber, nullptr, 0, &err_exit); - ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); - - while (num_groups1 != 0 || num_groups2 != 0) { - num_groups1 = tox_group_get_number_groups(tox0); - num_groups2 = tox_group_get_number_groups(tox1); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - printf("All tests passed!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - - run_auto_test(nullptr, NUM_GROUP_TOXES, group_announce_test, sizeof(State), &options); - - return 0; -} - -#undef NUM_GROUP_TOXES -#undef PEER1_NICK -#undef PEER0_NICK -#undef PEER0_NICK_LEN -#undef PEER1_NICK_LEN -#undef GROUP_NAME -#undef GROUP_NAME_LEN -#undef PEER_LIMIT -#undef TOPIC -#undef TOPIC_LEN diff --git a/auto_tests/group_invite_test.c b/auto_tests/group_invite_test.c deleted file mode 100644 index 4abdaae1..00000000 --- a/auto_tests/group_invite_test.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Tests group invites as well as join restrictions, including password protection, privacy state, - * and peer limits. Ensures sure that the peer being blocked from joining successfully receives - * the invite fail packet with the correct message. - * - * This test also checks that many peers can successfully join the group simultaneously. - */ - -#include -#include -#include - -#include "auto_test_support.h" -#include "check_compat.h" - -typedef struct State { - uint32_t num_peers; - bool peer_limit_fail; - bool password_fail; - bool connected; - size_t messages_received; -} State; - -#define NUM_GROUP_TOXES 8 // must be > 7 - -#define PASSWORD "dadada" -#define PASS_LEN (sizeof(PASSWORD) - 1) - -#define WRONG_PASS "dadadada" -#define WRONG_PASS_LEN (sizeof(WRONG_PASS) - 1) - -static bool group_has_full_graph(const AutoTox *autotoxes, uint32_t group_number, uint32_t expected_peer_count) -{ - for (size_t i = 7; i < NUM_GROUP_TOXES; ++i) { - const State *state = (const State *)autotoxes[i].state; - - if (state->num_peers < expected_peer_count) { - return false; - } - } - - const State *state0 = (const State *)autotoxes[0].state; - const State *state1 = (const State *)autotoxes[1].state; - const State *state5 = (const State *)autotoxes[5].state; - - if (state0->num_peers < expected_peer_count || state1->num_peers < expected_peer_count - || state5->num_peers < expected_peer_count) { - return false; - } - - return true; -} - -static void group_join_fail_handler(const Tox_Event_Group_Join_Fail *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const Tox_Group_Join_Fail fail_type = tox_event_group_join_fail_get_fail_type(event); - - switch (fail_type) { - case TOX_GROUP_JOIN_FAIL_PEER_LIMIT: { - state->peer_limit_fail = true; - break; - } - - case TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD: { - state->password_fail = true; - break; - } - - case TOX_GROUP_JOIN_FAIL_UNKNOWN: - - // intentional fallthrough - default: { - ck_assert_msg(false, "Got unknown join fail"); - return; - } - } -} - -static void group_self_join_handler(const Tox_Event_Group_Self_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - state->connected = true; -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - ++state->num_peers; - ck_assert(state->num_peers < NUM_GROUP_TOXES); -} - -static void group_invite_test(AutoTox *autotoxes) -{ - ck_assert_msg(NUM_GROUP_TOXES > 7, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - tox_events_callback_group_peer_join(autotoxes[i].dispatch, group_peer_join_handler); - tox_events_callback_group_join_fail(autotoxes[i].dispatch, group_join_fail_handler); - tox_events_callback_group_self_join(autotoxes[i].dispatch, group_self_join_handler); - } - - Tox *tox0 = autotoxes[0].tox; - Tox *tox1 = autotoxes[1].tox; - Tox *tox2 = autotoxes[2].tox; - Tox *tox3 = autotoxes[3].tox; - Tox *tox4 = autotoxes[4].tox; - Tox *tox5 = autotoxes[5].tox; - Tox *tox6 = autotoxes[6].tox; - - const State *state0 = (const State *)autotoxes[0].state; - const State *state2 = (const State *)autotoxes[2].state; - const State *state3 = (const State *)autotoxes[3].state; - const State *state4 = (const State *)autotoxes[4].state; - const State *state5 = (const State *)autotoxes[5].state; - const State *state6 = (const State *)autotoxes[6].state; - - Tox_Err_Group_New new_err; - uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"test", 4, - (const uint8_t *)"test", 4, &new_err); - ck_assert_msg(new_err == TOX_ERR_GROUP_NEW_OK, "tox_group_new failed: %u", new_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - Tox_Err_Group_State_Query id_err; - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - - tox_group_get_chat_id(tox0, groupnumber, chat_id, &id_err); - ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERY_OK, "%u", id_err); - - // peer 1 joins public group with no password - Tox_Err_Group_Join join_err; - tox_group_join(tox1, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%u", join_err); - - while (state0->num_peers < 1) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - printf("Peer 1 joined group\n"); - - // founder sets a password - Tox_Err_Group_Set_Password pass_set_err; - tox_group_set_password(tox0, groupnumber, (const uint8_t *)PASSWORD, PASS_LEN, &pass_set_err); - ck_assert_msg(pass_set_err == TOX_ERR_GROUP_SET_PASSWORD_OK, "%u", pass_set_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 5000); - - // peer 2 attempts to join with no password - tox_group_join(tox2, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%u", join_err); - - while (!state2->password_fail) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - printf("Peer 2 successfully blocked with no password\n"); - - // peer 3 attempts to join with invalid password - tox_group_join(tox3, chat_id, (const uint8_t *)"Test", 4, (const uint8_t *)WRONG_PASS, WRONG_PASS_LEN, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%u", join_err); - - while (!state3->password_fail) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - printf("Peer 3 successfully blocked with invalid password\n"); - - // founder sets peer limit to 1 - Tox_Err_Group_Set_Peer_Limit limit_set_err; - tox_group_set_peer_limit(tox0, groupnumber, 1, &limit_set_err); - ck_assert_msg(limit_set_err == TOX_ERR_GROUP_SET_PEER_LIMIT_OK, "%u", limit_set_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 5000); - - // peer 4 attempts to join with correct password - tox_group_join(tox4, chat_id, (const uint8_t *)"Test", 4, (const uint8_t *)PASSWORD, PASS_LEN, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%u", join_err); - - while (!state4->peer_limit_fail) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - printf("Peer 4 successfully blocked from joining full group\n"); - - // founder removes password and increases peer limit to 100 - tox_group_set_password(tox0, groupnumber, nullptr, 0, &pass_set_err); - ck_assert_msg(pass_set_err == TOX_ERR_GROUP_SET_PASSWORD_OK, "%u", pass_set_err); - - tox_group_set_peer_limit(tox0, groupnumber, 100, &limit_set_err); - ck_assert_msg(limit_set_err == TOX_ERR_GROUP_SET_PEER_LIMIT_OK, "%u", limit_set_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 5000); - - // peer 5 attempts to join group - tox_group_join(tox5, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%u", join_err); - - while (!state5->connected) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - printf("Peer 5 successfully joined the group\n"); - - // founder makes group private - Tox_Err_Group_Set_Privacy_State priv_err; - tox_group_set_privacy_state(tox0, groupnumber, TOX_GROUP_PRIVACY_STATE_PRIVATE, &priv_err); - ck_assert_msg(priv_err == TOX_ERR_GROUP_SET_PRIVACY_STATE_OK, "%u", priv_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 5000); - - // peer 6 attempts to join group via chat ID - tox_group_join(tox6, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%u", join_err); - - // since we don't receive a fail packet in this case we just wait a while and check if we're in the group - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 20000); - - ck_assert(!state6->connected); - - printf("Peer 6 failed to join private group via chat ID\n"); - - // founder makes group public again - tox_group_set_privacy_state(tox0, groupnumber, TOX_GROUP_PRIVACY_STATE_PUBLIC, &priv_err); - ck_assert_msg(priv_err == TOX_ERR_GROUP_SET_PRIVACY_STATE_OK, "%u", priv_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - const uint32_t num_new_peers = NUM_GROUP_TOXES - 7; - printf("Connecting %u peers at the same time\n", num_new_peers); - - for (size_t i = 7; i < NUM_GROUP_TOXES; ++i) { - tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%u", join_err); - } - - const uint32_t expected_peer_count = num_new_peers + state0->num_peers + 1; - - while (!group_has_full_graph(autotoxes, groupnumber, expected_peer_count)) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - printf("Every peer sees every other peer\n"); - - for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { - Tox_Err_Group_Leave err_exit; - tox_group_leave(autotoxes[i].tox, 0, nullptr, 0, &err_exit); - ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); - } - - printf("All tests passed!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options autotest_opts = default_run_auto_options(); - autotest_opts.graph = GRAPH_COMPLETE; - - run_auto_test(nullptr, NUM_GROUP_TOXES, group_invite_test, sizeof(State), &autotest_opts); - - return 0; -} - -#undef NUM_GROUP_TOXES -#undef PASSWORD -#undef PASS_LEN -#undef WRONG_PASS -#undef WRONG_PASS_LEN diff --git a/auto_tests/group_message_test.c b/auto_tests/group_message_test.c deleted file mode 100644 index 4b8c1d1d..00000000 --- a/auto_tests/group_message_test.c +++ /dev/null @@ -1,622 +0,0 @@ -/* - * Tests message sending capabilities, including: - * - The ability to send/receive plain, action, and custom messages - * - The lossless UDP implementation - * - The packet splitting implementation - * - The ignore feature - */ - -#include -#include -#include - -#include "auto_test_support.h" -#include "check_compat.h" -#include "../toxcore/os_random.h" -#include "../toxcore/util.h" - -typedef struct State { - uint32_t peer_id; - bool peer_joined; - bool message_sent; - bool message_received; - Tox_Group_Message_Id pseudo_msg_id; - bool private_message_received; - size_t custom_packets_received; - size_t custom_private_packets_received; - bool lossless_check; - bool wraparound_check; - int32_t last_msg_recv; -} State; - -#define NUM_GROUP_TOXES 2 -#define MAX_NUM_MESSAGES_LOSSLESS_TEST 300 -#define MAX_NUM_MESSAGES_WRAPAROUND_TEST 9001 - -#define TEST_MESSAGE "Where is it I've read that someone condemned to death says or thinks, an hour before his death, that if he had to live on some high rock, on such a narrow ledge that he'd only room to stand, and the ocean, everlasting darkness, everlasting solitude, everlasting tempest around him, if he had to remain standing on a square yard of space all his life, a thousand years, eternity, it were better to live so than to die at once. Only to live, to live and live! Life, whatever it may be!" -#define TEST_MESSAGE_LEN (sizeof(TEST_MESSAGE) - 1) - -#define TEST_GROUP_NAME "Utah Data Center" -#define TEST_GROUP_NAME_LEN (sizeof(TEST_GROUP_NAME) - 1) - -#define TEST_PRIVATE_MESSAGE "Don't spill yer beans" -#define TEST_PRIVATE_MESSAGE_LEN (sizeof(TEST_PRIVATE_MESSAGE) - 1) - -#define TEST_CUSTOM_PACKET "Why'd ya spill yer beans?" -#define TEST_CUSTOM_PACKET_LEN (sizeof(TEST_CUSTOM_PACKET) - 1) - -#define TEST_CUSTOM_PACKET_LARGE "Where is it I've read that someone condemned to death says or thinks, an hour before his death, that if he had to live on some high rock, on such a narrow ledge that he'd only room to stand, and the ocean, everlasting darkness, everlasting solitude, everlasting tempest around him, if he had to remain standing on a square yard of space all his life, a thousand years, eternity, it were better to live so than to die at once. Only to live, to live and live! Life, whatever it may be! ...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................0123456789" -#define TEST_CUSTOM_PACKET_LARGE_LEN (sizeof(TEST_CUSTOM_PACKET_LARGE) - 1) -static_assert(TEST_CUSTOM_PACKET_LARGE_LEN == TOX_GROUP_MAX_CUSTOM_LOSSY_PACKET_LENGTH, "Should be max"); - -#define TEST_CUSTOM_PRIVATE_PACKET "This is a custom private packet. Enjoy." -#define TEST_CUSTOM_PRIVATE_PACKET_LEN (sizeof(TEST_CUSTOM_PRIVATE_PACKET) - 1) - -#define IGNORE_MESSAGE "Am I bothering you?" -#define IGNORE_MESSAGE_LEN (sizeof(IGNORE_MESSAGE) - 1) - -#define PEER0_NICK "Thomas" -#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) - -#define PEER1_NICK "Winslow" -#define PEER1_NICK_LEN (sizeof(PEER1_NICK) - 1) - -static uint16_t get_message_checksum(const uint8_t *message, uint16_t length) -{ - uint16_t sum = 0; - - for (size_t i = 0; i < length; ++i) { - sum += message[i]; - } - - return sum; -} - -static void group_invite_handler(const Tox_Event_Group_Invite *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t friend_number = tox_event_group_invite_get_friend_number(event); - const uint8_t *invite_data = tox_event_group_invite_get_invite_data(event); - const size_t length = tox_event_group_invite_get_invite_data_length(event); - - printf("invite arrived; accepting\n"); - Tox_Err_Group_Invite_Accept err_accept; - tox_group_invite_accept(autotox->tox, friend_number, invite_data, length, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, - nullptr, 0, &err_accept); - ck_assert(err_accept == TOX_ERR_GROUP_INVITE_ACCEPT_OK); -} - -static void group_join_fail_handler(const Tox_Event_Group_Join_Fail *event, void *user_data) -{ - const Tox_Group_Join_Fail fail_type = tox_event_group_join_fail_get_fail_type(event); - printf("join failed: %u\n", fail_type); -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); - - ck_assert_msg(state->peer_joined == false, "Peer timedout"); - - printf("peer %u joined, sending message\n", peer_id); - state->peer_joined = true; - state->peer_id = peer_id; -} - -static void group_custom_private_packet_handler(const Tox_Event_Group_Custom_Private_Packet *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_custom_private_packet_get_group_number(event); - const uint32_t peer_id = tox_event_group_custom_private_packet_get_peer_id(event); - const uint8_t *data = tox_event_group_custom_private_packet_get_data(event); - const size_t length = tox_event_group_custom_private_packet_get_data_length(event); - - ck_assert_msg(length == TEST_CUSTOM_PRIVATE_PACKET_LEN, - "Failed to receive custom private packet. Invalid length: %zu\n", length); - - char message_buf[TOX_MAX_CUSTOM_PACKET_SIZE + 1]; - memcpy(message_buf, data, length); - message_buf[length] = 0; - - Tox_Err_Group_Peer_Query q_err; - size_t peer_name_len = tox_group_peer_get_name_size(autotox->tox, groupnumber, peer_id, &q_err); - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); - - char peer_name[TOX_MAX_NAME_LENGTH + 1]; - tox_group_peer_get_name(autotox->tox, groupnumber, peer_id, (uint8_t *) peer_name, &q_err); - peer_name[peer_name_len] = 0; - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); - - Tox_Err_Group_Self_Query s_err; - size_t self_name_len = tox_group_self_get_name_size(autotox->tox, groupnumber, &s_err); - ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_name_len <= TOX_MAX_NAME_LENGTH); - - char self_name[TOX_MAX_NAME_LENGTH + 1]; - tox_group_self_get_name(autotox->tox, groupnumber, (uint8_t *)self_name, &s_err); - self_name[self_name_len] = 0; - - ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(memcmp(self_name, PEER1_NICK, self_name_len) == 0); - - printf("%s sent custom private packet to %s: %s\n", peer_name, self_name, message_buf); - ck_assert(memcmp(message_buf, TEST_CUSTOM_PRIVATE_PACKET, length) == 0); - - State *state = (State *)autotox->state; - - ++state->custom_private_packets_received; -} - -static void group_custom_packet_handler(const Tox_Event_Group_Custom_Packet *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_custom_packet_get_group_number(event); - const uint32_t peer_id = tox_event_group_custom_packet_get_peer_id(event); - const uint8_t *data = tox_event_group_custom_packet_get_data(event); - const size_t length = tox_event_group_custom_packet_get_data_length(event); - - ck_assert_msg(length == TEST_CUSTOM_PACKET_LEN, "Failed to receive custom packet. Invalid length: %zu\n", length); - - char message_buf[TOX_MAX_CUSTOM_PACKET_SIZE + 1]; - memcpy(message_buf, data, length); - message_buf[length] = 0; - - Tox_Err_Group_Peer_Query q_err; - size_t peer_name_len = tox_group_peer_get_name_size(autotox->tox, groupnumber, peer_id, &q_err); - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); - - char peer_name[TOX_MAX_NAME_LENGTH + 1]; - tox_group_peer_get_name(autotox->tox, groupnumber, peer_id, (uint8_t *)peer_name, &q_err); - peer_name[peer_name_len] = 0; - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); - - Tox_Err_Group_Self_Query s_err; - size_t self_name_len = tox_group_self_get_name_size(autotox->tox, groupnumber, &s_err); - ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_name_len <= TOX_MAX_NAME_LENGTH); - - char self_name[TOX_MAX_NAME_LENGTH + 1]; - tox_group_self_get_name(autotox->tox, groupnumber, (uint8_t *)self_name, &s_err); - self_name[self_name_len] = 0; - - ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(memcmp(self_name, PEER1_NICK, self_name_len) == 0); - - printf("%s sent custom packet to %s: %s\n", peer_name, self_name, message_buf); - ck_assert(memcmp(message_buf, TEST_CUSTOM_PACKET, length) == 0); - - State *state = (State *)autotox->state; - - ++state->custom_packets_received; -} - -static void group_custom_packet_large_handler(const Tox_Event_Group_Custom_Packet *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint8_t *data = tox_event_group_custom_packet_get_data(event); - const size_t length = tox_event_group_custom_packet_get_data_length(event); - - ck_assert_msg(length == TEST_CUSTOM_PACKET_LARGE_LEN, "Failed to receive large custom packet. Invalid length: %zu\n", length); - - ck_assert(memcmp(data, TEST_CUSTOM_PACKET_LARGE, length) == 0); - - State *state = (State *)autotox->state; - - ++state->custom_packets_received; -} - -static void group_message_handler(const Tox_Event_Group_Message *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_message_get_group_number(event); - const uint32_t peer_id = tox_event_group_message_get_peer_id(event); - const uint8_t *message = tox_event_group_message_get_message(event); - const size_t length = tox_event_group_message_get_message_length(event); - const uint32_t pseudo_msg_id = tox_event_group_message_get_message_id(event); - - ck_assert(!(length == IGNORE_MESSAGE_LEN && memcmp(message, IGNORE_MESSAGE, length) == 0)); - ck_assert_msg(length == TEST_MESSAGE_LEN, "Failed to receive message. Invalid length: %zu\n", length); - - char message_buf[TOX_GROUP_MAX_MESSAGE_LENGTH + 1]; - memcpy(message_buf, message, length); - message_buf[length] = 0; - - Tox_Err_Group_Peer_Query q_err; - size_t peer_name_len = tox_group_peer_get_name_size(autotox->tox, groupnumber, peer_id, &q_err); - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); - - char peer_name[TOX_MAX_NAME_LENGTH + 1]; - tox_group_peer_get_name(autotox->tox, groupnumber, peer_id, (uint8_t *)peer_name, &q_err); - peer_name[peer_name_len] = 0; - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); - - Tox_Err_Group_Self_Query s_err; - size_t self_name_len = tox_group_self_get_name_size(autotox->tox, groupnumber, &s_err); - ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_name_len <= TOX_MAX_NAME_LENGTH); - - char self_name[TOX_MAX_NAME_LENGTH + 1]; - tox_group_self_get_name(autotox->tox, groupnumber, (uint8_t *)self_name, &s_err); - self_name[self_name_len] = 0; - - ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(memcmp(self_name, PEER1_NICK, self_name_len) == 0); - - printf("%s sent message to %s:(id:%u) %s\n", peer_name, self_name, pseudo_msg_id, message_buf); - ck_assert(memcmp(message_buf, TEST_MESSAGE, length) == 0); - - State *state = (State *)autotox->state; - - state->message_received = true; - state->pseudo_msg_id = pseudo_msg_id; -} - -static void group_private_message_handler(const Tox_Event_Group_Private_Message *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_private_message_get_group_number(event); - const uint32_t peer_id = tox_event_group_private_message_get_peer_id(event); - const Tox_Message_Type type = tox_event_group_private_message_get_message_type(event); - const uint8_t *message = tox_event_group_private_message_get_message(event); - const size_t length = tox_event_group_private_message_get_message_length(event); - const Tox_Group_Message_Id pseudo_msg_id = tox_event_group_private_message_get_message_id(event); - - ck_assert_msg(length == TEST_PRIVATE_MESSAGE_LEN, "Failed to receive message. Invalid length: %zu\n", length); - - char message_buf[TOX_GROUP_MAX_MESSAGE_LENGTH + 1]; - memcpy(message_buf, message, length); - message_buf[length] = 0; - - Tox_Err_Group_Peer_Query q_err; - size_t peer_name_len = tox_group_peer_get_name_size(autotox->tox, groupnumber, peer_id, &q_err); - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); - - char peer_name[TOX_MAX_NAME_LENGTH + 1]; - tox_group_peer_get_name(autotox->tox, groupnumber, peer_id, (uint8_t *)peer_name, &q_err); - peer_name[peer_name_len] = 0; - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); - - Tox_Err_Group_Self_Query s_err; - size_t self_name_len = tox_group_self_get_name_size(autotox->tox, groupnumber, &s_err); - ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_name_len <= TOX_MAX_NAME_LENGTH); - - char self_name[TOX_MAX_NAME_LENGTH + 1]; - tox_group_self_get_name(autotox->tox, groupnumber, (uint8_t *)self_name, &s_err); - self_name[self_name_len] = 0; - - ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(memcmp(self_name, PEER1_NICK, self_name_len) == 0); - - printf("%s sent private action to %s:(id: %u) %s\n", peer_name, self_name, pseudo_msg_id, message_buf); - ck_assert(memcmp(message_buf, TEST_PRIVATE_MESSAGE, length) == 0); - - ck_assert(type == TOX_MESSAGE_TYPE_ACTION); - - State *state = (State *)autotox->state; - - state->private_message_received = true; - state->pseudo_msg_id = pseudo_msg_id; -} - -static void group_message_handler_lossless_test(const Tox_Event_Group_Message *event, void *user_data) -{ - const uint8_t *message = tox_event_group_message_get_message(event); - const size_t length = tox_event_group_message_get_message_length(event); - - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - ck_assert(length >= 4 && length <= TOX_GROUP_MAX_MESSAGE_LENGTH); - - uint16_t start; - uint16_t checksum; - memcpy(&start, message, sizeof(uint16_t)); - memcpy(&checksum, message + sizeof(uint16_t), sizeof(uint16_t)); - - ck_assert_msg(start == state->last_msg_recv + 1, "Expected %d, got start %u", state->last_msg_recv + 1, start); - ck_assert_msg(checksum == get_message_checksum(message + 4, length - 4), "Wrong checksum"); - - state->last_msg_recv = start; - - if (state->last_msg_recv == MAX_NUM_MESSAGES_LOSSLESS_TEST) { - state->lossless_check = true; - } -} -static void group_message_handler_wraparound_test(const Tox_Event_Group_Message *event, void *user_data) -{ - const uint8_t *message = tox_event_group_message_get_message(event); - const size_t length = tox_event_group_message_get_message_length(event); - - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - ck_assert(length == 2); - - uint16_t num; - memcpy(&num, message, sizeof(uint16_t)); - - ck_assert_msg(num == state->last_msg_recv + 1, "Expected %d, got start %u", state->last_msg_recv + 1, num); - - state->last_msg_recv = num; - - if (state->last_msg_recv == MAX_NUM_MESSAGES_WRAPAROUND_TEST) { - state->wraparound_check = true; - } -} - -static void group_message_test(AutoTox *autotoxes) -{ - ck_assert_msg(NUM_GROUP_TOXES >= 2, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); - - const Random *rng = os_random(); - ck_assert(rng != nullptr); - - Tox *tox0 = autotoxes[0].tox; - const Tox *tox1 = autotoxes[1].tox; - - State *state0 = (State *)autotoxes[0].state; - State *state1 = (State *)autotoxes[1].state; - - // initialize to different values - state0->pseudo_msg_id = 0; - state1->pseudo_msg_id = 1; - - tox_events_callback_group_invite(autotoxes[1].dispatch, group_invite_handler); - tox_events_callback_group_join_fail(autotoxes[1].dispatch, group_join_fail_handler); - tox_events_callback_group_peer_join(autotoxes[1].dispatch, group_peer_join_handler); - tox_events_callback_group_join_fail(autotoxes[0].dispatch, group_join_fail_handler); - tox_events_callback_group_peer_join(autotoxes[0].dispatch, group_peer_join_handler); - tox_events_callback_group_message(autotoxes[0].dispatch, group_message_handler); - tox_events_callback_group_custom_packet(autotoxes[0].dispatch, group_custom_packet_handler); - tox_events_callback_group_custom_private_packet(autotoxes[0].dispatch, group_custom_private_packet_handler); - tox_events_callback_group_private_message(autotoxes[0].dispatch, group_private_message_handler); - - Tox_Err_Group_Send_Message err_send; - - fprintf(stderr, "Tox 0 creates new group and invites tox1...\n"); - - // tox0 makes new group. - Tox_Err_Group_New err_new; - const uint32_t group_number = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PRIVATE, (const uint8_t *)TEST_GROUP_NAME, - TEST_GROUP_NAME_LEN, (const uint8_t *)PEER1_NICK, PEER1_NICK_LEN, &err_new); - - ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); - - // tox0 invites tox1 - Tox_Err_Group_Invite_Friend err_invite; - tox_group_invite_friend(tox0, group_number, 0, &err_invite); - ck_assert(err_invite == TOX_ERR_GROUP_INVITE_FRIEND_OK); - - while (!state0->message_received) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - if (state1->peer_joined && !state1->message_sent) { - state1->pseudo_msg_id = tox_group_send_message( - tox1, group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)TEST_MESSAGE, - TEST_MESSAGE_LEN, &err_send); - ck_assert(err_send == TOX_ERR_GROUP_SEND_MESSAGE_OK); - state1->message_sent = true; - } - } - - ck_assert_msg(state0->pseudo_msg_id == state1->pseudo_msg_id, "id0:%u id1:%u", - state0->pseudo_msg_id, state1->pseudo_msg_id); - - // Make sure we're still connected to each friend - Tox_Connection conn_1 = tox_friend_get_connection_status(tox0, 0, nullptr); - Tox_Connection conn_2 = tox_friend_get_connection_status(tox1, 0, nullptr); - - ck_assert(conn_1 != TOX_CONNECTION_NONE && conn_2 != TOX_CONNECTION_NONE); - - // tox0 ignores tox1 - Tox_Err_Group_Set_Ignore ig_err; - tox_group_set_ignore(tox0, group_number, state0->peer_id, true, &ig_err); - ck_assert_msg(ig_err == TOX_ERR_GROUP_SET_IGNORE_OK, "%u", ig_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - // tox1 sends group a message which should not be seen by tox0's message handler - tox_group_send_message(tox1, group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)IGNORE_MESSAGE, - IGNORE_MESSAGE_LEN, &err_send); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - // tox0 unignores tox1 - tox_group_set_ignore(tox0, group_number, state0->peer_id, false, &ig_err); - ck_assert_msg(ig_err == TOX_ERR_GROUP_SET_IGNORE_OK, "%u", ig_err); - - fprintf(stderr, "Sending private action...\n"); - - // tox1 sends a private action to tox0 - Tox_Err_Group_Send_Private_Message m_err; - state1->pseudo_msg_id = tox_group_send_private_message(tox1, group_number, state1->peer_id, - TOX_MESSAGE_TYPE_ACTION, (const uint8_t *)TEST_PRIVATE_MESSAGE, - TEST_PRIVATE_MESSAGE_LEN, &m_err); - - ck_assert_msg(m_err == TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK, "%u", m_err); - - while (!state0->private_message_received) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - ck_assert_msg(state0->pseudo_msg_id == state1->pseudo_msg_id, "id0:%u id1:%u", - state0->pseudo_msg_id, state1->pseudo_msg_id); - - fprintf(stderr, "Sending custom packets...\n"); - - // tox0 sends a lossless and lossy custom packet to tox1 - Tox_Err_Group_Send_Custom_Packet c_err; - tox_group_send_custom_packet(tox1, group_number, true, (const uint8_t *)TEST_CUSTOM_PACKET, TEST_CUSTOM_PACKET_LEN, - &c_err); - ck_assert_msg(c_err == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK, "%u", c_err); - - tox_group_send_custom_packet(tox1, group_number, false, (const uint8_t *)TEST_CUSTOM_PACKET, TEST_CUSTOM_PACKET_LEN, - &c_err); - ck_assert_msg(c_err == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK, "%u", c_err); - - fprintf(stderr, "Sending custom private packets...\n"); - - // tox0 sends a lossless and lossy custom private packet to tox1 - Tox_Err_Group_Send_Custom_Private_Packet cperr; - tox_group_send_custom_private_packet(tox1, group_number, state1->peer_id, true, - (const uint8_t *)TEST_CUSTOM_PRIVATE_PACKET, - TEST_CUSTOM_PRIVATE_PACKET_LEN, &cperr); - - ck_assert_msg(cperr == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK, "%u", cperr); - - tox_group_send_custom_private_packet(tox1, group_number, state1->peer_id, false, - (const uint8_t *)TEST_CUSTOM_PRIVATE_PACKET, - TEST_CUSTOM_PRIVATE_PACKET_LEN, &cperr); - - ck_assert_msg(cperr == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK, "%u", cperr); - - while (state0->custom_packets_received < 2 || state0->custom_private_packets_received < 2) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - // tox0 sends a large max sized lossy custom packet - - // overwrite callback for larger packet - tox_events_callback_group_custom_packet(autotoxes[0].dispatch, group_custom_packet_large_handler); - - tox_group_send_custom_packet(tox1, group_number, false, (const uint8_t *)TEST_CUSTOM_PACKET_LARGE, TEST_CUSTOM_PACKET_LARGE_LEN, - &c_err); - ck_assert_msg(c_err == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK, "%u", c_err); - - while (state0->custom_packets_received < 3) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - uint8_t m[TOX_GROUP_MAX_MESSAGE_LENGTH] = {0}; - - fprintf(stderr, "Doing lossless packet test...\n"); - - tox_events_callback_group_message(autotoxes[1].dispatch, group_message_handler_lossless_test); - state1->last_msg_recv = -1; - - // lossless and packet splitting/reassembly test - for (uint16_t i = 0; i <= MAX_NUM_MESSAGES_LOSSLESS_TEST; ++i) { - if (i % 10 == 0) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - uint16_t message_size = min_u16(4 + (random_u16(rng) % TOX_GROUP_MAX_MESSAGE_LENGTH), TOX_GROUP_MAX_MESSAGE_LENGTH); - - memcpy(m, &i, sizeof(uint16_t)); - - for (size_t j = 4; j < message_size; ++j) { - m[j] = random_u32(rng); - } - - const uint16_t checksum = get_message_checksum(m + 4, message_size - 4); - - memcpy(m + 2, &checksum, sizeof(uint16_t)); - - tox_group_send_message(tox0, group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)m, message_size, &err_send); - - ck_assert(err_send == TOX_ERR_GROUP_SEND_MESSAGE_OK); - } - - while (!state1->lossless_check) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - state1->last_msg_recv = -1; - tox_events_callback_group_message(autotoxes[1].dispatch, group_message_handler_wraparound_test); - - fprintf(stderr, "Doing wraparound test...\n"); - - // packet array wrap-around test - for (uint16_t i = 0; i <= MAX_NUM_MESSAGES_WRAPAROUND_TEST; ++i) { - if (i % 10 == 0) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - memcpy(m, &i, sizeof(uint16_t)); - - tox_group_send_message(tox0, group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)m, 2, &err_send); - ck_assert(err_send == TOX_ERR_GROUP_SEND_MESSAGE_OK); - } - - while (!state1->wraparound_check) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { - Tox_Err_Group_Leave err_exit; - tox_group_leave(autotoxes[i].tox, group_number, nullptr, 0, &err_exit); - ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); - } - - fprintf(stderr, "All tests passed!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options autotest_opts = default_run_auto_options(); - autotest_opts.graph = GRAPH_COMPLETE; - - run_auto_test(nullptr, NUM_GROUP_TOXES, group_message_test, sizeof(State), &autotest_opts); - return 0; -} - -#undef NUM_GROUP_TOXES -#undef PEER1_NICK -#undef PEER1_NICK_LEN -#undef PEER0_NICK -#undef PEER0_NICK_LEN -#undef TEST_GROUP_NAME -#undef TEST_GROUP_NAME_LEN -#undef TEST_MESSAGE -#undef TEST_MESSAGE_LEN -#undef TEST_PRIVATE_MESSAGE_LEN -#undef TEST_CUSTOM_PACKET -#undef TEST_CUSTOM_PACKET_LEN -#undef TEST_CUSTOM_PACKET_LARGE -#undef TEST_CUSTOM_PACKET_LARGE_LEN -#undef TEST_CUSTOM_PRIVATE_PACKET -#undef TEST_CUSTOM_PRIVATE_PACKET_LEN -#undef IGNORE_MESSAGE -#undef IGNORE_MESSAGE_LEN -#undef MAX_NUM_MESSAGES_LOSSLESS_TEST -#undef MAX_NUM_MESSAGES_WRAPAROUND_TEST diff --git a/auto_tests/group_moderation_test.c b/auto_tests/group_moderation_test.c deleted file mode 100644 index 855e5f79..00000000 --- a/auto_tests/group_moderation_test.c +++ /dev/null @@ -1,667 +0,0 @@ -/* - * Tests group moderation functionality. - * - * Note that making the peer count too high will break things. This test should not be relied on - * for general group/syncing functionality. - */ - -#include -#include -#include - -#include "auto_test_support.h" -#include "check_compat.h" - -#include "../toxcore/tox.h" - -#define NUM_GROUP_TOXES 5 -#define GROUP_NAME "NASA Headquarters" -#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) - -typedef struct Peer { - char name[TOX_MAX_NAME_LENGTH + 1]; - size_t name_length; - uint32_t peer_id; -} Peer; - -typedef struct State { - char self_name[TOX_MAX_NAME_LENGTH + 1]; - size_t self_name_length; - - uint32_t group_number; - - uint32_t num_peers; - Peer peers[NUM_GROUP_TOXES - 1]; - - bool mod_check; - size_t mod_event_count; - char mod_name1[TOX_MAX_NAME_LENGTH]; - char mod_name2[TOX_MAX_NAME_LENGTH]; - - bool observer_check; - size_t observer_event_count; - char observer_name1[TOX_MAX_NAME_LENGTH]; - char observer_name2[TOX_MAX_NAME_LENGTH]; - - bool user_check; - size_t user_event_count; - - bool kick_check; // mod gets kicked -} State; - -static bool all_peers_connected(AutoTox *autotoxes) -{ - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - const State *state = (const State *)autotoxes[i].state; - - if (state->num_peers != NUM_GROUP_TOXES - 1) { - return false; - } - - if (!tox_group_is_connected(autotoxes[i].tox, state->group_number, nullptr)) { - return false; - } - } - - return true; -} - -/* - * Waits for all peers to receive the mod event. - */ -static void check_mod_event(AutoTox *autotoxes, size_t num_peers, Tox_Group_Mod_Event event) -{ - uint32_t peers_recv_changes = 0; - - do { - peers_recv_changes = 0; - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - for (size_t i = 0; i < num_peers; ++i) { - State *state = (State *)autotoxes[i].state; - bool check = false; - - switch (event) { - case TOX_GROUP_MOD_EVENT_MODERATOR: { - if (state->mod_check) { - check = true; - state->mod_check = false; - } - - break; - } - - case TOX_GROUP_MOD_EVENT_OBSERVER: { - if (state->observer_check) { - check = true; - state->observer_check = false; - } - - break; - } - - case TOX_GROUP_MOD_EVENT_USER: { - if (state->user_check) { - check = true; - state->user_check = false; - } - - break; - } - - case TOX_GROUP_MOD_EVENT_KICK: { - check = state->kick_check; - break; - } - - default: { - ck_assert(0); - } - } - - if (check) { - ++peers_recv_changes; - } - } - } while (peers_recv_changes < num_peers - 1); -} - -static uint32_t get_peer_id_by_nick(const Peer *peers, uint32_t num_peers, const char *name) -{ - ck_assert(name != nullptr); - - for (uint32_t i = 0; i < num_peers; ++i) { - if (memcmp(peers[i].name, name, peers[i].name_length) == 0) { - return peers[i].peer_id; - } - } - - ck_assert_msg(0, "Failed to find peer id"); -} - -static size_t get_state_index_by_nick(const AutoTox *autotoxes, size_t num_peers, const char *name, size_t name_length) -{ - ck_assert(name != nullptr && name_length <= TOX_MAX_NAME_LENGTH); - - for (size_t i = 0; i < num_peers; ++i) { - const State *state = (const State *)autotoxes[i].state; - - if (memcmp(state->self_name, name, name_length) == 0) { - return i; - } - } - - ck_assert_msg(0, "Failed to find index"); -} - -static void group_join_fail_handler(const Tox_Event_Group_Join_Fail *event, void *user_data) -{ - const Tox_Group_Join_Fail fail_type = tox_event_group_join_fail_get_fail_type(event); - fprintf(stderr, "Failed to join group: %u", fail_type); -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t group_number = tox_event_group_peer_join_get_group_number(event); - const uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); - - ck_assert(state->group_number == group_number); - - char peer_name[TOX_MAX_NAME_LENGTH + 1]; - - Tox_Err_Group_Peer_Query q_err; - size_t peer_name_len = tox_group_peer_get_name_size(autotox->tox, group_number, peer_id, &q_err); - - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); - - tox_group_peer_get_name(autotox->tox, group_number, peer_id, (uint8_t *) peer_name, &q_err); - peer_name[peer_name_len] = 0; - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - - Peer *peer = &state->peers[state->num_peers]; - - peer->peer_id = peer_id; - memcpy(peer->name, peer_name, peer_name_len); - peer->name_length = peer_name_len; - - ++state->num_peers; - - ck_assert(state->num_peers < NUM_GROUP_TOXES); -} - -static void handle_mod(State *state, const char *peer_name, size_t peer_name_len) -{ - if (state->mod_event_count == 0) { - ck_assert(memcmp(peer_name, state->mod_name1, peer_name_len) == 0); - } else if (state->mod_event_count == 1) { - ck_assert(memcmp(peer_name, state->mod_name2, peer_name_len) == 0); - } else { - ck_assert(false); - } - - ++state->mod_event_count; - state->mod_check = true; -} - -static void handle_observer(State *state, const char *peer_name, size_t peer_name_len) -{ - if (state->observer_event_count == 0) { - ck_assert(memcmp(peer_name, state->observer_name1, peer_name_len) == 0); - } else if (state->observer_event_count == 1) { - ck_assert(memcmp(peer_name, state->observer_name2, peer_name_len) == 0); - } else { - ck_assert(false); - } - - ++state->observer_event_count; - state->observer_check = true; -} - -static void handle_user(State *state, const char *peer_name, size_t peer_name_len) -{ - // event 1: observer1 gets promoted back to user - // event 2: observer2 gets promoted to moderator - // event 3: moderator 1 gets kicked - // event 4: moderator 2 gets demoted to moderator - if (state->user_event_count == 0) { - ck_assert(memcmp(peer_name, state->observer_name1, peer_name_len) == 0); - } else if (state->user_event_count == 1) { - ck_assert(memcmp(peer_name, state->observer_name2, peer_name_len) == 0); - } else if (state->user_event_count == 2) { - ck_assert(memcmp(peer_name, state->mod_name1, peer_name_len) == 0); - } else if (state->user_event_count == 3) { - ck_assert(memcmp(peer_name, state->mod_name2, peer_name_len) == 0); - } else { - ck_assert(false); - } - - ++state->user_event_count; - state->user_check = true; -} - -static void group_mod_event_handler(const Tox_Event_Group_Moderation *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t group_number = tox_event_group_moderation_get_group_number(event); - const uint32_t target_peer_id = tox_event_group_moderation_get_target_peer_id(event); - const Tox_Group_Mod_Event mod_type = tox_event_group_moderation_get_mod_type(event); - - ck_assert(state->group_number == group_number); - - char peer_name[TOX_MAX_NAME_LENGTH + 1]; - - Tox_Err_Group_Peer_Query q_err; - size_t peer_name_len = tox_group_peer_get_name_size(autotox->tox, group_number, target_peer_id, &q_err); - - if (q_err == TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND) { // may occurr on sync attempts - return; - } - - ck_assert_msg(q_err == TOX_ERR_GROUP_PEER_QUERY_OK, "error %u", q_err); - ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); - - tox_group_peer_get_name(autotox->tox, group_number, target_peer_id, (uint8_t *) peer_name, &q_err); - peer_name[peer_name_len] = 0; - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - - Tox_Group_Role role = tox_group_peer_get_role(autotox->tox, group_number, target_peer_id, &q_err); - ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); - ck_assert(role <= TOX_GROUP_ROLE_OBSERVER); - - fprintf(stderr, "tox%u: got moderator event %u (%s), role = %s\n", - autotox->index, mod_type, tox_group_mod_event_to_string(mod_type), - tox_group_role_to_string(role)); - - switch (mod_type) { - case TOX_GROUP_MOD_EVENT_MODERATOR: { - handle_mod(state, peer_name, peer_name_len); - break; - } - - case TOX_GROUP_MOD_EVENT_OBSERVER: { - handle_observer(state, peer_name, peer_name_len); - break; - } - - case TOX_GROUP_MOD_EVENT_USER: { - handle_user(state, peer_name, peer_name_len); - break; - } - - case TOX_GROUP_MOD_EVENT_KICK: { - ck_assert(memcmp(peer_name, state->mod_name1, peer_name_len) == 0); - state->kick_check = true; - break; - } - - default: { - ck_assert_msg(0, "Got invalid moderator event %u", mod_type); - return; - } - } -} - -/* Checks that `peer_id` sees itself with the role `role`. */ -static void check_self_role(AutoTox *autotoxes, uint32_t peer_id, Tox_Group_Role role) -{ - Tox_Err_Group_Self_Query sq_err; - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - const State *state = (const State *)autotoxes[i].state; - - uint32_t self_peer_id = tox_group_self_get_peer_id(autotoxes[i].tox, state->group_number, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - if (self_peer_id == peer_id) { - Tox_Group_Role self_role = tox_group_self_get_role(autotoxes[i].tox, state->group_number, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_role == role); - return; - } - } -} - -/* Makes sure that a peer's role respects the voice state */ -static void voice_state_message_test(AutoTox *autotox, Tox_Group_Voice_State voice_state) -{ - const State *state = (State *)autotox->state; - - Tox_Err_Group_Self_Query sq_err; - Tox_Group_Role self_role = tox_group_self_get_role(autotox->tox, state->group_number, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - Tox_Err_Group_Send_Message msg_err; - tox_group_send_message(autotox->tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, - (const uint8_t *)"test", 4, &msg_err); - - switch (self_role) { - case TOX_GROUP_ROLE_OBSERVER: { - ck_assert(msg_err == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS); - break; - } - - case TOX_GROUP_ROLE_USER: { - if (voice_state != TOX_GROUP_VOICE_STATE_ALL) { - ck_assert_msg(msg_err == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS, - "%u", msg_err); - } else { - ck_assert(msg_err == TOX_ERR_GROUP_SEND_MESSAGE_OK); - } - - break; - } - - case TOX_GROUP_ROLE_MODERATOR: { - if (voice_state != TOX_GROUP_VOICE_STATE_FOUNDER) { - ck_assert(msg_err == TOX_ERR_GROUP_SEND_MESSAGE_OK); - } else { - ck_assert(msg_err == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS); - } - - break; - } - - case TOX_GROUP_ROLE_FOUNDER: { - ck_assert(msg_err == TOX_ERR_GROUP_SEND_MESSAGE_OK); - break; - } - } -} - -static bool all_peers_got_voice_state_change(AutoTox *autotoxes, uint32_t num_toxes, - Tox_Group_Voice_State expected_voice_state) -{ - Tox_Err_Group_State_Query query_err; - - for (uint32_t i = 0; i < num_toxes; ++i) { - const State *state = (State *)autotoxes[i].state; - - Tox_Group_Voice_State voice_state = tox_group_get_voice_state(autotoxes[i].tox, state->group_number, &query_err); - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - if (voice_state != expected_voice_state) { - return false; - } - } - - return true; -} - -static void check_voice_state(AutoTox *autotoxes, uint32_t num_toxes) -{ - // founder sets voice state to Moderator - const State *state = (State *)autotoxes[0].state; - Tox_Err_Group_Set_Voice_State voice_set_err; - tox_group_set_voice_state(autotoxes[0].tox, state->group_number, TOX_GROUP_VOICE_STATE_MODERATOR, - &voice_set_err); - ck_assert(voice_set_err == TOX_ERR_GROUP_SET_VOICE_STATE_OK); - - for (uint32_t i = 0; i < num_toxes; ++i) { - do { - iterate_all_wait(autotoxes, num_toxes, ITERATION_INTERVAL); - } while (!all_peers_got_voice_state_change(autotoxes, num_toxes, TOX_GROUP_VOICE_STATE_MODERATOR)); - - voice_state_message_test(&autotoxes[i], TOX_GROUP_VOICE_STATE_MODERATOR); - } - - tox_group_set_voice_state(autotoxes[0].tox, state->group_number, TOX_GROUP_VOICE_STATE_FOUNDER, &voice_set_err); - ck_assert(voice_set_err == TOX_ERR_GROUP_SET_VOICE_STATE_OK); - - for (uint32_t i = 0; i < num_toxes; ++i) { - do { - iterate_all_wait(autotoxes, num_toxes, ITERATION_INTERVAL); - } while (!all_peers_got_voice_state_change(autotoxes, num_toxes, TOX_GROUP_VOICE_STATE_FOUNDER)); - - voice_state_message_test(&autotoxes[i], TOX_GROUP_VOICE_STATE_FOUNDER); - } - - tox_group_set_voice_state(autotoxes[0].tox, state->group_number, TOX_GROUP_VOICE_STATE_ALL, &voice_set_err); - ck_assert(voice_set_err == TOX_ERR_GROUP_SET_VOICE_STATE_OK); - - for (uint32_t i = 0; i < num_toxes; ++i) { - do { - iterate_all_wait(autotoxes, num_toxes, ITERATION_INTERVAL); - } while (!all_peers_got_voice_state_change(autotoxes, num_toxes, TOX_GROUP_VOICE_STATE_ALL)); - - voice_state_message_test(&autotoxes[i], TOX_GROUP_VOICE_STATE_ALL); - } -} - -static void group_moderation_test(AutoTox *autotoxes) -{ - ck_assert_msg(NUM_GROUP_TOXES >= 4, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); - ck_assert_msg(NUM_GROUP_TOXES < 10, "NUM_GROUP_TOXES is too big: %d", NUM_GROUP_TOXES); - - uint16_t name_length = 6; - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - State *state = (State *)autotoxes[i].state; - state->self_name_length = name_length; - snprintf(state->self_name, sizeof(state->self_name), "peer_%zu", i); - state->self_name[name_length] = 0; - - tox_events_callback_group_join_fail(autotoxes[i].dispatch, group_join_fail_handler); - tox_events_callback_group_peer_join(autotoxes[i].dispatch, group_peer_join_handler); - tox_events_callback_group_moderation(autotoxes[i].dispatch, group_mod_event_handler); - } - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - fprintf(stderr, "Creating new group\n"); - - /* Founder makes new group */ - State *state0 = (State *)autotoxes[0].state; - Tox *tox0 = autotoxes[0].tox; - - Tox_Err_Group_New err_new; - state0->group_number = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, - GROUP_NAME_LEN, (const uint8_t *)state0->self_name, state0->self_name_length, - &err_new); - - ck_assert_msg(err_new == TOX_ERR_GROUP_NEW_OK, "Failed to create group. error: %u\n", err_new); - - /* Founder gets chat ID */ - Tox_Err_Group_State_Query id_err; - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - tox_group_get_chat_id(tox0, state0->group_number, chat_id, &id_err); - - ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERY_OK, "Failed to get chat ID. error: %u", id_err); - - fprintf(stderr, "Peers attemping to join DHT group via the chat ID\n"); - - for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - State *state = (State *)autotoxes[i].state; - Tox_Err_Group_Join join_err; - state->group_number = tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)state->self_name, - state->self_name_length, - nullptr, 0, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "Peer %s (%zu) failed to join group. error %u", - state->self_name, i, join_err); - - c_sleep(100); - } - - // make sure every peer sees every other peer before we continue - do { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } while (!all_peers_connected(autotoxes)); - - /* manually tell the other peers the names of the peers that will be assigned new roles */ - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - State *state = (State *)autotoxes[i].state; - memcpy(state->mod_name1, state0->peers[0].name, sizeof(state->mod_name1)); - memcpy(state->mod_name2, state0->peers[2].name, sizeof(state->mod_name2)); - memcpy(state->observer_name1, state0->peers[1].name, sizeof(state->observer_name1)); - memcpy(state->observer_name2, state0->peers[2].name, sizeof(state->observer_name2)); - } - - /* founder checks his own role */ - Tox_Err_Group_Self_Query sq_err; - Tox_Group_Role self_role = tox_group_self_get_role(tox0, state0->group_number, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_role == TOX_GROUP_ROLE_FOUNDER); - - /* all peers should be user role except founder */ - for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { - const State *state = (const State *)autotoxes[i].state; - self_role = tox_group_self_get_role(autotoxes[i].tox, state->group_number, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - ck_assert(self_role == TOX_GROUP_ROLE_USER); - } - - /* founder sets first peer to moderator */ - fprintf(stderr, "Founder setting %s to moderator\n", state0->peers[0].name); - - Tox_Err_Group_Set_Role role_err; - tox_group_set_role(tox0, state0->group_number, state0->peers[0].peer_id, TOX_GROUP_ROLE_MODERATOR, &role_err); - ck_assert_msg(role_err == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to set moderator. error: %u", role_err); - - // manually flag the role setter because they don't get a callback - state0->mod_check = true; - ++state0->mod_event_count; - check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_MODERATOR); - - check_self_role(autotoxes, state0->peers[0].peer_id, TOX_GROUP_ROLE_MODERATOR); - - fprintf(stderr, "All peers successfully received mod event\n"); - - /* founder sets second and third peer to observer */ - fprintf(stderr, "Founder setting %s to observer\n", state0->peers[1].name); - - tox_group_set_role(tox0, state0->group_number, state0->peers[1].peer_id, TOX_GROUP_ROLE_OBSERVER, &role_err); - ck_assert_msg(role_err == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to set observer. error: %u", role_err); - - state0->observer_check = true; - ++state0->observer_event_count; - check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_OBSERVER); - - fprintf(stderr, "All peers successfully received observer event 1\n"); - - fprintf(stderr, "Founder setting %s to observer\n", state0->peers[2].name); - - tox_group_set_role(tox0, state0->group_number, state0->peers[2].peer_id, TOX_GROUP_ROLE_OBSERVER, &role_err); - ck_assert_msg(role_err == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to set observer. error: %u", role_err); - - state0->observer_check = true; - ++state0->observer_event_count; - check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_OBSERVER); - - check_self_role(autotoxes, state0->peers[1].peer_id, TOX_GROUP_ROLE_OBSERVER); - - fprintf(stderr, "All peers successfully received observer event 2\n"); - - /* do voice state test here since we have at least one peer of each role */ - check_voice_state(autotoxes, NUM_GROUP_TOXES); - - fprintf(stderr, "Voice state respected by all peers\n"); - - /* New moderator promotes second peer back to user */ - const uint32_t idx = get_state_index_by_nick(autotoxes, NUM_GROUP_TOXES, state0->peers[0].name, - state0->peers[0].name_length); - State *state1 = (State *)autotoxes[idx].state; - Tox *tox1 = autotoxes[idx].tox; - - const uint32_t obs_peer_id = get_peer_id_by_nick(state1->peers, NUM_GROUP_TOXES - 1, state1->observer_name1); - - fprintf(stderr, "%s is promoting %s back to user\n", state1->self_name, state0->peers[1].name); - - tox_group_set_role(tox1, state1->group_number, obs_peer_id, TOX_GROUP_ROLE_USER, &role_err); - ck_assert_msg(role_err == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to promote observer back to user. error: %u", - role_err); - - state1->user_check = true; - ++state1->user_event_count; - check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_USER); - - fprintf(stderr, "All peers successfully received user event\n"); - - /* founder assigns third peer to moderator (this triggers two events: user and moderator) */ - fprintf(stderr, "Founder setting %s to moderator\n", state0->peers[2].name); - - tox_group_set_role(tox0, state0->group_number, state0->peers[2].peer_id, TOX_GROUP_ROLE_MODERATOR, &role_err); - ck_assert_msg(role_err == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to set moderator. error: %u", role_err); - - state0->mod_check = true; - ++state0->mod_event_count; - check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_MODERATOR); - - check_self_role(autotoxes, state0->peers[2].peer_id, TOX_GROUP_ROLE_MODERATOR); - - fprintf(stderr, "All peers successfully received moderator event\n"); - - /* moderator attempts to demote and kick founder */ - uint32_t founder_peer_id = get_peer_id_by_nick(state1->peers, NUM_GROUP_TOXES - 1, state0->self_name); - tox_group_set_role(tox1, state1->group_number, founder_peer_id, TOX_GROUP_ROLE_OBSERVER, &role_err); - ck_assert_msg(role_err != TOX_ERR_GROUP_SET_ROLE_OK, "Mod set founder to observer"); - - Tox_Err_Group_Kick_Peer k_err; - tox_group_kick_peer(tox1, state1->group_number, founder_peer_id, &k_err); - ck_assert_msg(k_err != TOX_ERR_GROUP_KICK_PEER_OK, "Mod kicked founder"); - - /* the moderator about to be kicked changes the topic to trigger the founder to - * re-sign and redistribute it after the kick. - */ - const State *state_x = (const State *)autotoxes[idx].state; - Tox *tox_x = autotoxes[idx].tox; - Tox_Err_Group_Topic_Set topic_err; - tox_group_set_topic(tox_x, state_x->group_number, nullptr, 0, &topic_err); - ck_assert(topic_err == TOX_ERR_GROUP_TOPIC_SET_OK); - - /* founder kicks moderator (this triggers two events: user and kick) */ - fprintf(stderr, "Founder is kicking %s\n", state0->peers[0].name); - - tox_group_kick_peer(tox0, state0->group_number, state0->peers[0].peer_id, &k_err); - ck_assert_msg(k_err == TOX_ERR_GROUP_KICK_PEER_OK, "Failed to kick peer. error: %u", k_err); - - state0->kick_check = true; - check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_KICK); - - fprintf(stderr, "All peers successfully received kick event\n"); - - fprintf(stderr, "Founder is demoting moderator to user\n"); - tox_group_set_role(tox0, state0->group_number, state0->peers[2].peer_id, TOX_GROUP_ROLE_USER, &role_err); - ck_assert_msg(role_err == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to demote peer 3 to User. error: %u", role_err); - - state0->user_check = true; - ++state0->user_event_count; - - check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_USER); - check_self_role(autotoxes, state0->peers[2].peer_id, TOX_GROUP_ROLE_USER); - - for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { - const State *state = (const State *)autotoxes[i].state; - Tox_Err_Group_Leave err_exit; - tox_group_leave(autotoxes[i].tox, state->group_number, nullptr, 0, &err_exit); - ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); - } - - fprintf(stderr, "All tests passed!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_COMPLETE; - - run_auto_test(nullptr, NUM_GROUP_TOXES, group_moderation_test, sizeof(State), &options); - return 0; -} - -#undef NUM_GROUP_TOXES -#undef GROUP_NAME -#undef GROUP_NAME_LEN diff --git a/auto_tests/group_save_test.c b/auto_tests/group_save_test.c deleted file mode 100644 index 26b41d07..00000000 --- a/auto_tests/group_save_test.c +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Tests that we can save a groupchat and load a groupchat with the saved data. - */ - -#include -#include -#include -#include - -#include "auto_test_support.h" - -typedef struct State { - bool peer_joined; -} State; - -#define NUM_GROUP_TOXES 2 -#define GROUP_NAME "The Test Chamber" -#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) -#define TOPIC "They're waiting for you Gordon..." -#define TOPIC_LEN (sizeof(TOPIC) - 1) -#define NEW_PRIV_STATE TOX_GROUP_PRIVACY_STATE_PRIVATE -#define PASSWORD "password123" -#define PASS_LEN (sizeof(PASSWORD) - 1) -#define PEER_LIMIT 69 -#define PEER0_NICK "Mike" -#define PEER0_NICK_LEN (sizeof(PEER0_NICK) -1) -#define NEW_USER_STATUS TOX_USER_STATUS_BUSY - -static void group_invite_handler(const Tox_Event_Group_Invite *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t friend_number = tox_event_group_invite_get_friend_number(event); - const uint8_t *invite_data = tox_event_group_invite_get_invite_data(event); - const size_t length = tox_event_group_invite_get_invite_data_length(event); - - Tox_Err_Group_Invite_Accept err_accept; - tox_group_invite_accept(autotox->tox, friend_number, invite_data, length, (const uint8_t *)"test2", 5, - nullptr, 0, &err_accept); - ck_assert(err_accept == TOX_ERR_GROUP_INVITE_ACCEPT_OK); - -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - state->peer_joined = true; -} - -/* Checks that group has the same state according to the above defines - * - * Returns 0 if state is correct. - * Returns a value < 0 if state is incorrect. - */ -static int has_correct_group_state(const Tox *tox, uint32_t group_number, const uint8_t *expected_chat_id) -{ - Tox_Err_Group_State_Query query_err; - - Tox_Group_Privacy_State priv_state = tox_group_get_privacy_state(tox, group_number, &query_err); - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - if (priv_state != NEW_PRIV_STATE) { - return -1; - } - - size_t pass_len = tox_group_get_password_size(tox, group_number, &query_err); - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - uint8_t password[TOX_GROUP_MAX_PASSWORD_SIZE]; - tox_group_get_password(tox, group_number, password, &query_err); - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - if (pass_len != PASS_LEN || memcmp(password, PASSWORD, pass_len) != 0) { - return -2; - } - - size_t gname_len = tox_group_get_name_size(tox, group_number, &query_err); - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - uint8_t group_name[TOX_GROUP_MAX_GROUP_NAME_LENGTH]; - tox_group_get_name(tox, group_number, group_name, &query_err); - - if (gname_len != GROUP_NAME_LEN || memcmp(group_name, GROUP_NAME, gname_len) != 0) { - return -3; - } - - if (tox_group_get_peer_limit(tox, group_number, nullptr) != PEER_LIMIT) { - return -4; - } - - Tox_Group_Topic_Lock topic_lock = tox_group_get_topic_lock(tox, group_number, &query_err); - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - if (topic_lock != TOX_GROUP_TOPIC_LOCK_DISABLED) { - return -5; - } - - Tox_Err_Group_State_Query id_err; - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - tox_group_get_chat_id(tox, group_number, chat_id, &id_err); - - ck_assert(id_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - if (memcmp(chat_id, expected_chat_id, TOX_GROUP_CHAT_ID_SIZE) != 0) { - return -6; - } - - return 0; -} - -static int has_correct_self_state(const Tox *tox, uint32_t group_number, const uint8_t *expected_self_pk) -{ - Tox_Err_Group_Self_Query sq_err; - size_t self_length = tox_group_self_get_name_size(tox, group_number, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - uint8_t self_name[TOX_MAX_NAME_LENGTH]; - tox_group_self_get_name(tox, group_number, self_name, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - if (self_length != PEER0_NICK_LEN || memcmp(self_name, PEER0_NICK, self_length) != 0) { - return -1; - } - - Tox_User_Status self_status = tox_group_self_get_status(tox, group_number, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - if (self_status != NEW_USER_STATUS) { - return -2; - } - - Tox_Group_Role self_role = tox_group_self_get_role(tox, group_number, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - if (self_role != TOX_GROUP_ROLE_FOUNDER) { - return -3; - } - - uint8_t self_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; - - tox_group_self_get_public_key(tox, group_number, self_pk, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - if (memcmp(self_pk, expected_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) != 0) { - return -4; - } - - return 0; -} - -static void group_save_test(AutoTox *autotoxes) -{ - ck_assert_msg(NUM_GROUP_TOXES > 1, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - tox_events_callback_group_invite(autotoxes[i].dispatch, group_invite_handler); - tox_events_callback_group_peer_join(autotoxes[i].dispatch, group_peer_join_handler); - } - - Tox *tox0 = autotoxes[0].tox; - - const State *state0 = (State *)autotoxes[0].state; - const State *state1 = (State *)autotoxes[1].state; - - Tox_Err_Group_New err_new; - const uint32_t group_number = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PRIVATE, (const uint8_t *)GROUP_NAME, - GROUP_NAME_LEN, (const uint8_t *)"test", 4, &err_new); - - ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); - - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - - Tox_Err_Group_State_Query id_err; - tox_group_get_chat_id(tox0, group_number, chat_id, &id_err); - ck_assert(id_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - uint8_t founder_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; - - Tox_Err_Group_Self_Query sq_err; - tox_group_self_get_public_key(tox0, group_number, founder_pk, &sq_err); - ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); - - Tox_Err_Group_Invite_Friend err_invite; - tox_group_invite_friend(tox0, group_number, 0, &err_invite); - - ck_assert(err_invite == TOX_ERR_GROUP_INVITE_FRIEND_OK); - - while (!state0->peer_joined && !state1->peer_joined) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - printf("tox0 invites tox1 to group\n"); - - // change group state - Tox_Err_Group_Topic_Set top_err; - tox_group_set_topic(tox0, group_number, (const uint8_t *)TOPIC, TOPIC_LEN, &top_err); - ck_assert(top_err == TOX_ERR_GROUP_TOPIC_SET_OK); - - Tox_Err_Group_Set_Topic_Lock lock_set_err; - tox_group_set_topic_lock(tox0, group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, &lock_set_err); - ck_assert(lock_set_err == TOX_ERR_GROUP_SET_TOPIC_LOCK_OK); - - Tox_Err_Group_Set_Privacy_State priv_err; - tox_group_set_privacy_state(tox0, group_number, NEW_PRIV_STATE, &priv_err); - ck_assert(priv_err == TOX_ERR_GROUP_SET_PRIVACY_STATE_OK); - - Tox_Err_Group_Set_Password pass_set_err; - tox_group_set_password(tox0, group_number, (const uint8_t *)PASSWORD, PASS_LEN, &pass_set_err); - ck_assert(pass_set_err == TOX_ERR_GROUP_SET_PASSWORD_OK); - - Tox_Err_Group_Set_Peer_Limit limit_set_err; - tox_group_set_peer_limit(tox0, group_number, PEER_LIMIT, &limit_set_err); - ck_assert(limit_set_err == TOX_ERR_GROUP_SET_PEER_LIMIT_OK); - - // change self state - Tox_Err_Group_Self_Name_Set n_err; - tox_group_self_set_name(tox0, group_number, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, &n_err); - ck_assert(n_err == TOX_ERR_GROUP_SELF_NAME_SET_OK); - - Tox_Err_Group_Self_Status_Set s_err; - tox_group_self_set_status(tox0, group_number, NEW_USER_STATUS, &s_err); - ck_assert(s_err == TOX_ERR_GROUP_SELF_STATUS_SET_OK); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - printf("tox0 changes group state\n"); - - const size_t save_length = tox_get_savedata_size(tox0); - - uint8_t *save = (uint8_t *)malloc(save_length); - - ck_assert(save != nullptr); - - tox_get_savedata(tox0, save); - - for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { - tox_group_leave(autotoxes[i].tox, group_number, nullptr, 0, nullptr); - } - - struct Tox_Options *const options = tox_options_new(nullptr); - - ck_assert(options != nullptr); - - tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); - tox_options_set_savedata_data(options, save, save_length); - tox_options_set_experimental_groups_persistence(options, true); - - Tox *new_tox = tox_new_log(options, nullptr, nullptr); - - ck_assert(new_tox != nullptr); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - printf("tox0 saves group and reloads client\n"); - - const int group_ret = has_correct_group_state(new_tox, group_number, chat_id); - - ck_assert_msg(group_ret == 0, "incorrect group state: %d", group_ret); - - const int self_ret = has_correct_self_state(new_tox, group_number, founder_pk); - - ck_assert_msg(self_ret == 0, "incorrect self state: %d", self_ret); - - tox_group_leave(new_tox, group_number, nullptr, 0, nullptr); - - free(save); - - tox_options_free(options); - - tox_kill(new_tox); - - printf("All tests passed!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options autotest_opts = default_run_auto_options(); - autotest_opts.graph = GRAPH_COMPLETE; - - Tox_Options *opts = tox_options_new(nullptr); - ck_assert(opts != nullptr); - tox_options_set_experimental_groups_persistence(opts, true); - run_auto_test(opts, NUM_GROUP_TOXES, group_save_test, sizeof(State), &autotest_opts); - tox_options_free(opts); - - return 0; -} - -#undef NUM_GROUP_TOXES -#undef GROUP_NAME -#undef GROUP_NAME_LEN -#undef TOPIC -#undef TOPIC_LEN -#undef NEW_PRIV_STATE -#undef PASSWORD -#undef PASS_LEN -#undef PEER_LIMIT -#undef PEER0_NICK -#undef PEER0_NICK_LEN -#undef NEW_USER_STATUS diff --git a/auto_tests/group_state_test.c b/auto_tests/group_state_test.c deleted file mode 100644 index 8cbbdd3e..00000000 --- a/auto_tests/group_state_test.c +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Tests that we can successfully change the group state and that all peers in the group - * receive the correct state changes. - */ - -#include -#include -#include -#include -#include - -#include "auto_test_support.h" -#include "check_compat.h" - -#define NUM_GROUP_TOXES 5 - -#define PEER_LIMIT_1 NUM_GROUP_TOXES -#define PEER_LIMIT_2 50 - -#define PASSWORD "dadada" -#define PASS_LEN (sizeof(PASSWORD) - 1) - -#define GROUP_NAME "The Crystal Palace" -#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) - -#define PEER0_NICK "David" -#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) - -typedef struct State { - size_t num_peers; -} State; - -static bool all_group_peers_connected(const AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, - size_t name_length, uint32_t peer_limit) -{ - for (uint32_t i = 0; i < tox_count; ++i) { - // make sure we got an invite response - if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != name_length) { - return false; - } - - // make sure we got a sync response - if (tox_group_get_peer_limit(autotoxes[i].tox, groupnumber, nullptr) != peer_limit) { - return false; - } - - // make sure we're actually connected - if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) { - return false; - } - - const State *state = (const State *)autotoxes[i].state; - - // make sure all peers are connected to one another - if (state->num_peers < NUM_GROUP_TOXES - 1) { - return false; - } - } - - return true; -} - -static void group_topic_lock_handler(const Tox_Event_Group_Topic_Lock *event, - void *user_data) -{ - const AutoTox *autotox = (const AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_topic_lock_get_group_number(event); - const Tox_Group_Topic_Lock topic_lock = tox_event_group_topic_lock_get_topic_lock(event); - - Tox_Err_Group_State_Query err; - Tox_Group_Topic_Lock current_topic_lock = tox_group_get_topic_lock(autotox->tox, groupnumber, &err); - - ck_assert(err == TOX_ERR_GROUP_STATE_QUERY_OK); - ck_assert_msg(current_topic_lock == topic_lock, "topic locks don't match in callback: %u %u", - topic_lock, current_topic_lock); -} - -static void group_voice_state_handler(const Tox_Event_Group_Voice_State *event, - void *user_data) -{ - const AutoTox *autotox = (const AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_voice_state_get_group_number(event); - const Tox_Group_Voice_State voice_state = tox_event_group_voice_state_get_voice_state(event); - - Tox_Err_Group_State_Query err; - Tox_Group_Voice_State current_voice_state = tox_group_get_voice_state(autotox->tox, groupnumber, &err); - - ck_assert(err == TOX_ERR_GROUP_STATE_QUERY_OK); - ck_assert_msg(current_voice_state == voice_state, "voice states don't match in callback: %u %u", - voice_state, current_voice_state); -} - -static void group_privacy_state_handler(const Tox_Event_Group_Privacy_State *event, - void *user_data) -{ - const AutoTox *autotox = (const AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_privacy_state_get_group_number(event); - const Tox_Group_Privacy_State privacy_state = tox_event_group_privacy_state_get_privacy_state(event); - - Tox_Err_Group_State_Query err; - Tox_Group_Privacy_State current_pstate = tox_group_get_privacy_state(autotox->tox, groupnumber, &err); - - ck_assert(err == TOX_ERR_GROUP_STATE_QUERY_OK); - ck_assert_msg(current_pstate == privacy_state, "privacy states don't match in callback"); -} - -static void group_peer_limit_handler(const Tox_Event_Group_Peer_Limit *event, void *user_data) -{ - const AutoTox *autotox = (const AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_peer_limit_get_group_number(event); - const uint32_t peer_limit = tox_event_group_peer_limit_get_peer_limit(event); - - Tox_Err_Group_State_Query err; - uint32_t current_plimit = tox_group_get_peer_limit(autotox->tox, groupnumber, &err); - - ck_assert(err == TOX_ERR_GROUP_STATE_QUERY_OK); - ck_assert_msg(peer_limit == current_plimit, - "Peer limits don't match in callback: %u, %u\n", peer_limit, current_plimit); -} - -static void group_password_handler(const Tox_Event_Group_Password *event, - void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t groupnumber = tox_event_group_password_get_group_number(event); - const uint8_t *password = tox_event_group_password_get_password(event); - const size_t length = tox_event_group_password_get_password_length(event); - - Tox_Err_Group_State_Query err; - size_t curr_pwlength = tox_group_get_password_size(autotox->tox, groupnumber, &err); - - ck_assert(err == TOX_ERR_GROUP_STATE_QUERY_OK); - ck_assert(length == curr_pwlength); - - uint8_t current_password[TOX_GROUP_MAX_PASSWORD_SIZE]; - tox_group_get_password(autotox->tox, groupnumber, current_password, &err); - - ck_assert(err == TOX_ERR_GROUP_STATE_QUERY_OK); - ck_assert_msg(memcmp(current_password, password, length) == 0, - "Passwords don't match: %s, %s", password, current_password); -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - ++state->num_peers; - ck_assert(state->num_peers < NUM_GROUP_TOXES); -} - -/* Returns 0 if group state is equal to the state passed to this function. - * Returns negative integer if state is invalid. - */ -static int check_group_state(const Tox *tox, uint32_t groupnumber, uint32_t peer_limit, - Tox_Group_Privacy_State priv_state, Tox_Group_Voice_State voice_state, - const uint8_t *password, size_t pass_len, Tox_Group_Topic_Lock topic_lock) -{ - Tox_Err_Group_State_Query query_err; - - Tox_Group_Privacy_State my_priv_state = tox_group_get_privacy_state(tox, groupnumber, &query_err); - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "Failed to get privacy state: %u", query_err); - - if (my_priv_state != priv_state) { - return -1; - } - - uint32_t my_peer_limit = tox_group_get_peer_limit(tox, groupnumber, &query_err); - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "Failed to get peer limit: %u", query_err); - - if (my_peer_limit != peer_limit) { - return -2; - } - - size_t my_pass_len = tox_group_get_password_size(tox, groupnumber, &query_err); - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "Failed to get password size: %u", query_err); - - if (my_pass_len != pass_len) { - return -5; - } - - if (password != nullptr && my_pass_len > 0) { - ck_assert(my_pass_len <= TOX_GROUP_MAX_PASSWORD_SIZE); - - uint8_t my_pass[TOX_GROUP_MAX_PASSWORD_SIZE + 1]; - tox_group_get_password(tox, groupnumber, my_pass, &query_err); - my_pass[my_pass_len] = 0; - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "Failed to get password: %u", query_err); - - if (memcmp(my_pass, password, my_pass_len) != 0) { - return -6; - } - } - - /* Group name should never change */ - size_t my_gname_len = tox_group_get_name_size(tox, groupnumber, &query_err); - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "Failed to get group name size: %u", query_err); - - if (my_gname_len != GROUP_NAME_LEN) { - return -7; - } - - ck_assert(my_gname_len <= TOX_GROUP_MAX_GROUP_NAME_LENGTH); - - uint8_t my_gname[TOX_GROUP_MAX_GROUP_NAME_LENGTH + 1]; - tox_group_get_name(tox, groupnumber, my_gname, &query_err); - my_gname[my_gname_len] = 0; - - if (memcmp(my_gname, (const uint8_t *)GROUP_NAME, my_gname_len) != 0) { - return -8; - } - - Tox_Group_Topic_Lock current_topic_lock = tox_group_get_topic_lock(tox, groupnumber, &query_err); - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "Failed to get topic lock: %u", query_err); - - if (current_topic_lock != topic_lock) { - return -9; - } - - Tox_Group_Voice_State current_voice_state = tox_group_get_voice_state(tox, groupnumber, &query_err); - ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERY_OK, "Failed to get voice state: %u", query_err); - - if (current_voice_state != voice_state) { - return -10; - } - - return 0; -} - -static void set_group_state(Tox *tox, uint32_t groupnumber, uint32_t peer_limit, Tox_Group_Privacy_State priv_state, - Tox_Group_Voice_State voice_state, const uint8_t *password, size_t pass_len, - Tox_Group_Topic_Lock topic_lock) -{ - - Tox_Err_Group_Set_Peer_Limit limit_set_err; - tox_group_set_peer_limit(tox, groupnumber, peer_limit, &limit_set_err); - ck_assert_msg(limit_set_err == TOX_ERR_GROUP_SET_PEER_LIMIT_OK, "failed to set peer limit: %u", limit_set_err); - - Tox_Err_Group_Set_Privacy_State priv_err; - tox_group_set_privacy_state(tox, groupnumber, priv_state, &priv_err); - ck_assert_msg(priv_err == TOX_ERR_GROUP_SET_PRIVACY_STATE_OK, "failed to set privacy state: %u", priv_err); - - Tox_Err_Group_Set_Password pass_set_err; - tox_group_set_password(tox, groupnumber, password, pass_len, &pass_set_err); - ck_assert_msg(pass_set_err == TOX_ERR_GROUP_SET_PASSWORD_OK, "failed to set password: %u", pass_set_err); - - Tox_Err_Group_Set_Topic_Lock lock_set_err; - tox_group_set_topic_lock(tox, groupnumber, topic_lock, &lock_set_err); - ck_assert_msg(lock_set_err == TOX_ERR_GROUP_SET_TOPIC_LOCK_OK, "failed to set topic lock: %u", - lock_set_err); - - Tox_Err_Group_Set_Voice_State voice_set_err; - tox_group_set_voice_state(tox, groupnumber, voice_state, &voice_set_err); - ck_assert_msg(voice_set_err == TOX_ERR_GROUP_SET_VOICE_STATE_OK, "failed to set voice state: %u", - voice_set_err); -} - -static void group_state_test(AutoTox *autotoxes) -{ - ck_assert_msg(NUM_GROUP_TOXES >= 3, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - tox_events_callback_group_privacy_state(autotoxes[i].dispatch, group_privacy_state_handler); - tox_events_callback_group_peer_limit(autotoxes[i].dispatch, group_peer_limit_handler); - tox_events_callback_group_password(autotoxes[i].dispatch, group_password_handler); - tox_events_callback_group_peer_join(autotoxes[i].dispatch, group_peer_join_handler); - tox_events_callback_group_voice_state(autotoxes[i].dispatch, group_voice_state_handler); - tox_events_callback_group_topic_lock(autotoxes[i].dispatch, group_topic_lock_handler); - } - - Tox *tox0 = autotoxes[0].tox; - - /* Tox 0 creates a group and is the founder of a newly created group */ - Tox_Err_Group_New new_err; - uint32_t groupnum = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, - (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, &new_err); - - ck_assert_msg(new_err == TOX_ERR_GROUP_NEW_OK, "tox_group_new failed: %u", new_err); - - /* Founder sets default group state before anyone else joins */ - set_group_state(tox0, groupnum, PEER_LIMIT_1, TOX_GROUP_PRIVACY_STATE_PUBLIC, TOX_GROUP_VOICE_STATE_ALL, - (const uint8_t *)PASSWORD, PASS_LEN, TOX_GROUP_TOPIC_LOCK_ENABLED); - - /* Founder gets the Chat ID and implicitly shares it publicly */ - Tox_Err_Group_State_Query id_err; - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - tox_group_get_chat_id(tox0, groupnum, chat_id, &id_err); - - ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERY_OK, "tox_group_get_chat_id failed %u", id_err); - - /* All other peers join the group using the Chat ID and password */ - for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - Tox_Err_Group_Join join_err; - tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, (const uint8_t *)PASSWORD, PASS_LEN, - &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "tox_group_join failed: %u", join_err); - } - - fprintf(stderr, "Peers attempting to join group\n"); - - /* Keep checking if all instances have connected to the group until test times out */ - while (!all_group_peers_connected(autotoxes, NUM_GROUP_TOXES, groupnum, GROUP_NAME_LEN, PEER_LIMIT_1)) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - /* Change group state and check that all peers received the changes */ - set_group_state(tox0, groupnum, PEER_LIMIT_2, TOX_GROUP_PRIVACY_STATE_PRIVATE, TOX_GROUP_VOICE_STATE_MODERATOR, - nullptr, 0, TOX_GROUP_TOPIC_LOCK_DISABLED); - - fprintf(stderr, "Changing state\n"); - - while (1) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - uint32_t count = 0; - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - if (check_group_state(autotoxes[i].tox, groupnum, PEER_LIMIT_2, TOX_GROUP_PRIVACY_STATE_PRIVATE, - TOX_GROUP_VOICE_STATE_MODERATOR, nullptr, 0, TOX_GROUP_TOPIC_LOCK_DISABLED) == 0) { - ++count; - } - } - - if (count == NUM_GROUP_TOXES) { - fprintf(stderr, "%u peers successfully received state changes\n", count); - break; - } - } - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - Tox_Err_Group_Leave err_exit; - tox_group_leave(autotoxes[i].tox, groupnum, nullptr, 0, &err_exit); - ck_assert_msg(err_exit == TOX_ERR_GROUP_LEAVE_OK, "%u", err_exit); - } - - fprintf(stderr, "All tests passed!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options autotest_opts = default_run_auto_options(); - autotest_opts.graph = GRAPH_COMPLETE; - - run_auto_test(nullptr, NUM_GROUP_TOXES, group_state_test, sizeof(State), &autotest_opts); - - return 0; -} - -#undef PEER0_NICK -#undef PEER0_NICK_LEN -#undef GROUP_NAME_LEN -#undef GROUP_NAME -#undef PASS_LEN -#undef PASSWORD -#undef PEER_LIMIT_2 -#undef PEER_LIMIT_1 -#undef NUM_GROUP_TOXES diff --git a/auto_tests/group_sync_test.c b/auto_tests/group_sync_test.c deleted file mode 100644 index cdd154aa..00000000 --- a/auto_tests/group_sync_test.c +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Tests syncing capabilities of groups: we attempt to have multiple peers change the - * group state in a number of ways and make sure that all peers end up with the same - * resulting state after a short period. - */ - -#include -#include -#include - -#include "auto_test_support.h" - -#include "../toxcore/os_random.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" - -// these should be kept relatively low so integration tests don't always flake out -// but they can be increased for local stress testing -#define NUM_GROUP_TOXES 5 -#define ROLE_SPAM_ITERATIONS 1 -#define TOPIC_SPAM_ITERATIONS 1 - -typedef struct Peers { - uint32_t num_peers; - int64_t *peer_ids; -} Peers; - -typedef struct State { - uint8_t callback_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; - size_t topic_length; - Peers *peers; -} State; - -static int add_peer(Peers *peers, uint32_t peer_id) -{ - const uint32_t new_idx = peers->num_peers; - - int64_t *tmp_list = (int64_t *)realloc(peers->peer_ids, sizeof(int64_t) * (peers->num_peers + 1)); - - if (tmp_list == nullptr) { - return -1; - } - - ++peers->num_peers; - - tmp_list[new_idx] = (int64_t)peer_id; - - peers->peer_ids = tmp_list; - - return 0; -} - -static int del_peer(Peers *peers, uint32_t peer_id) -{ - bool found_peer = false; - int64_t i; - - for (i = 0; i < peers->num_peers; ++i) { - if (peers->peer_ids[i] == peer_id) { - found_peer = true; - break; - } - } - - if (!found_peer) { - return -1; - } - - --peers->num_peers; - - if (peers->num_peers == 0) { - free(peers->peer_ids); - peers->peer_ids = nullptr; - return 0; - } - - if (peers->num_peers != i) { - peers->peer_ids[i] = peers->peer_ids[peers->num_peers]; - } - - peers->peer_ids[peers->num_peers] = -1; - - int64_t *tmp_list = (int64_t *)realloc(peers->peer_ids, sizeof(int64_t) * (peers->num_peers)); - - if (tmp_list == nullptr) { - return -1; - } - - peers->peer_ids = tmp_list; - - return 0; -} - -static void peers_cleanup(Peers *peers) -{ - free(peers->peer_ids); - free(peers); -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); - - ck_assert(add_peer(state->peers, peer_id) == 0); - -} - -static void group_peer_exit_handler(const Tox_Event_Group_Peer_Exit *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t peer_id = tox_event_group_peer_exit_get_peer_id(event); - - ck_assert(del_peer(state->peers, peer_id) == 0); - -} - -static void group_topic_handler(const Tox_Event_Group_Topic *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint8_t *topic = tox_event_group_topic_get_topic(event); - const size_t length = tox_event_group_topic_get_topic_length(event); - - ck_assert(length <= TOX_GROUP_MAX_TOPIC_LENGTH); - - memcpy(state->callback_topic, (const char *)topic, length); - state->topic_length = length; -} - -static bool all_peers_connected(const AutoTox *autotoxes, uint32_t groupnumber) -{ - for (uint32_t i = 0; i < NUM_GROUP_TOXES; ++i) { - // make sure we got an invite response - if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != 4) { - return false; - } - - // make sure we're actually connected - if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) { - return false; - } - - const State *state = (const State *)autotoxes[i].state; - - // make sure all peers are connected to one another - if (state->peers->num_peers == NUM_GROUP_TOXES - 1) { - return false; - } - } - - return true; -} - -static unsigned int get_peer_roles_checksum(const Tox *tox, const State *state, uint32_t groupnumber) -{ - Tox_Group_Role role = tox_group_self_get_role(tox, groupnumber, nullptr); - unsigned int checksum = (unsigned int)role; - - for (size_t i = 0; i < state->peers->num_peers; ++i) { - role = tox_group_peer_get_role(tox, groupnumber, (uint32_t)state->peers->peer_ids[i], nullptr); - checksum += (unsigned int)role; - } - - return checksum; -} - -static bool all_peers_see_same_roles(const AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber) -{ - const State *state0 = (const State *)autotoxes[0].state; - unsigned int expected_checksum = get_peer_roles_checksum(autotoxes[0].tox, state0, groupnumber); - - for (size_t i = 0; i < num_peers; ++i) { - const State *state = (const State *)autotoxes[i].state; - unsigned int checksum = get_peer_roles_checksum(autotoxes[i].tox, state, groupnumber); - - if (checksum != expected_checksum) { - return false; - } - } - - return true; -} - -static void role_spam(const Random *rng, AutoTox *autotoxes, uint32_t num_peers, uint32_t num_demoted, - uint32_t groupnumber) -{ - const State *state0 = (const State *)autotoxes[0].state; - Tox *tox0 = autotoxes[0].tox; - - for (size_t iters = 0; iters < ROLE_SPAM_ITERATIONS; ++iters) { - // founder randomly promotes or demotes one of the non-mods - uint32_t idx = min_u32(random_u32(rng) % num_demoted, state0->peers->num_peers); - Tox_Group_Role f_role = random_u32(rng) % 2 == 0 ? TOX_GROUP_ROLE_MODERATOR : TOX_GROUP_ROLE_USER; - int64_t peer_id = state0->peers->peer_ids[idx]; - - if (peer_id >= 0) { - tox_group_set_role(tox0, groupnumber, (uint32_t)peer_id, f_role, nullptr); - } - - // mods randomly promote or demote one of the non-mods - for (uint32_t i = 1; i < num_peers; ++i) { - const State *state_i = (const State *)autotoxes[i].state; - - for (uint32_t j = num_demoted; j < num_peers; ++j) { - if (i >= state_i->peers->num_peers) { - continue; - } - - const State *state_j = (const State *)autotoxes[j].state; - Tox_Group_Role role = random_u32(rng) % 2 == 0 ? TOX_GROUP_ROLE_USER : TOX_GROUP_ROLE_OBSERVER; - peer_id = state_j->peers->peer_ids[i]; - - if (peer_id >= 0) { - tox_group_set_role(autotoxes[j].tox, groupnumber, (uint32_t)peer_id, role, nullptr); - } - } - } - - iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL); - } - - do { - iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL); - } while (!all_peers_see_same_roles(autotoxes, num_peers, groupnumber)); -} - -/* All peers attempt to set a unique topic. - * - * Return true if all peers successfully changed the topic. - */ -static bool set_topic_all_peers(const Random *rng, AutoTox *autotoxes, size_t num_peers, uint32_t groupnumber) -{ - for (size_t i = 0; i < num_peers; ++i) { - char new_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; - snprintf(new_topic, sizeof(new_topic), "peer %zu's topic %u", i, random_u32(rng)); - const size_t length = strlen(new_topic); - - Tox_Err_Group_Topic_Set err; - tox_group_set_topic(autotoxes[i].tox, groupnumber, (const uint8_t *)new_topic, length, &err); - - if (err != TOX_ERR_GROUP_TOPIC_SET_OK) { - return false; - } - } - - return true; -} - -/* Returns true if all peers have the same topic, and the topic from the get_topic API function - * matches the last topic they received in the topic callback. - */ -static bool all_peers_have_same_topic(const AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber) -{ - uint8_t expected_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; - - Tox_Err_Group_State_Query query_err; - size_t expected_topic_length = tox_group_get_topic_size(autotoxes[0].tox, groupnumber, &query_err); - - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - tox_group_get_topic(autotoxes[0].tox, groupnumber, expected_topic, &query_err); - - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - const State *state0 = (const State *)autotoxes[0].state; - - if (expected_topic_length != state0->topic_length) { - return false; - } - - if (memcmp(state0->callback_topic, expected_topic, expected_topic_length) != 0) { - return false; - } - - for (size_t i = 1; i < num_peers; ++i) { - size_t topic_length = tox_group_get_topic_size(autotoxes[i].tox, groupnumber, &query_err); - - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - if (topic_length != expected_topic_length) { - return false; - } - - uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH]; - tox_group_get_topic(autotoxes[i].tox, groupnumber, topic, &query_err); - - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - if (memcmp(expected_topic, (const char *)topic, topic_length) != 0) { - return false; - } - - const State *state = (const State *)autotoxes[i].state; - - if (topic_length != state->topic_length) { - return false; - } - - if (memcmp(state->callback_topic, (const char *)topic, topic_length) != 0) { - return false; - } - } - - return true; -} - -static void topic_spam(const Random *rng, AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber) -{ - for (size_t i = 0; i < TOPIC_SPAM_ITERATIONS; ++i) { - do { - iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL); - } while (!set_topic_all_peers(rng, autotoxes, num_peers, groupnumber)); - } - - fprintf(stderr, "all peers set the topic at the same time\n"); - - do { - iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL); - } while (!all_peers_have_same_topic(autotoxes, num_peers, groupnumber)); - - fprintf(stderr, "all peers see the same topic\n"); -} - -static void group_sync_test(AutoTox *autotoxes) -{ - ck_assert(NUM_GROUP_TOXES >= 5); - const Random *rng = os_random(); - ck_assert(rng != nullptr); - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - tox_events_callback_group_peer_join(autotoxes[i].dispatch, group_peer_join_handler); - tox_events_callback_group_topic(autotoxes[i].dispatch, group_topic_handler); - tox_events_callback_group_peer_exit(autotoxes[i].dispatch, group_peer_exit_handler); - - State *state = (State *)autotoxes[i].state; - state->peers = (Peers *)calloc(1, sizeof(Peers)); - - ck_assert(state->peers != nullptr); - } - - Tox *tox0 = autotoxes[0].tox; - State *state0 = (State *)autotoxes[0].state; - - Tox_Err_Group_New err_new; - uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *) "test", 4, - (const uint8_t *)"test", 4, &err_new); - - ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); - - fprintf(stderr, "tox0 creats new group and invites all his friends"); - - Tox_Err_Group_State_Query id_err; - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - - tox_group_get_chat_id(tox0, groupnumber, chat_id, &id_err); - ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERY_OK, "%u", id_err); - - for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { - Tox_Err_Group_Join join_err; - tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%u", join_err); - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } - - do { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } while (!all_peers_connected(autotoxes, groupnumber)); - - fprintf(stderr, "%d peers joined the group\n", NUM_GROUP_TOXES); - - Tox_Err_Group_Set_Topic_Lock lock_set_err; - tox_group_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_DISABLED, &lock_set_err); - ck_assert_msg(lock_set_err == TOX_ERR_GROUP_SET_TOPIC_LOCK_OK, "failed to disable topic lock: %u", - lock_set_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - fprintf(stderr, "founder disabled topic lock; all peers try to set the topic\n"); - - topic_spam(rng, autotoxes, NUM_GROUP_TOXES, groupnumber); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - tox_group_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_ENABLED, &lock_set_err); - ck_assert_msg(lock_set_err == TOX_ERR_GROUP_SET_TOPIC_LOCK_OK, "failed to enable topic lock: %u", - lock_set_err); - - do { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } while (!all_peers_have_same_topic(autotoxes, NUM_GROUP_TOXES, groupnumber) - && !all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber) - && state0->peers->num_peers != NUM_GROUP_TOXES - 1); - - Tox_Err_Group_Set_Role role_err; - - for (size_t i = 0; i < state0->peers->num_peers; ++i) { - tox_group_set_role(tox0, groupnumber, (uint32_t)state0->peers->peer_ids[i], TOX_GROUP_ROLE_MODERATOR, - &role_err); - ck_assert_msg(role_err == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to set moderator. error: %u", role_err); - } - - fprintf(stderr, "founder enabled topic lock and set all peers to moderator role\n"); - - do { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } while (!all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber)); - - topic_spam(rng, autotoxes, NUM_GROUP_TOXES, groupnumber); - - const unsigned int num_demoted = state0->peers->num_peers / 2; - - fprintf(stderr, "founder demoting %u moderators to user\n", num_demoted); - - for (size_t i = 0; i < num_demoted; ++i) { - tox_group_set_role(tox0, groupnumber, (uint32_t)state0->peers->peer_ids[i], TOX_GROUP_ROLE_USER, - &role_err); - ck_assert_msg(role_err == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to set user. error: %u", role_err); - } - - do { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - } while (!all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber)); - - fprintf(stderr, "Remaining moderators spam change non-moderator roles\n"); - - role_spam(rng, autotoxes, NUM_GROUP_TOXES, num_demoted, groupnumber); - - fprintf(stderr, "All peers see the same roles\n"); - - for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { - tox_group_leave(autotoxes[i].tox, groupnumber, nullptr, 0, nullptr); - - State *state = (State *)autotoxes[i].state; - peers_cleanup(state->peers); - } - - fprintf(stderr, "All tests passed!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options autotest_opts = default_run_auto_options(); - autotest_opts.graph = GRAPH_COMPLETE; - - run_auto_test(nullptr, NUM_GROUP_TOXES, group_sync_test, sizeof(State), &autotest_opts); - - return 0; -} - -#undef NUM_GROUP_TOXES -#undef ROLE_SPAM_ITERATIONS -#undef TOPIC_SPAM_ITERATIONS diff --git a/auto_tests/group_tcp_test.c b/auto_tests/group_tcp_test.c deleted file mode 100644 index a11bafc3..00000000 --- a/auto_tests/group_tcp_test.c +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Does a basic functionality test for TCP connections. - */ - -#include -#include -#include - -#include "auto_test_support.h" - -#define NUM_GROUP_TOXES 2 -#define CODEWORD "RONALD MCDONALD" -#define CODEWORD_LEN (sizeof(CODEWORD) - 1) - -typedef struct State { - size_t num_peers; - bool got_code; - bool got_second_code; - uint32_t peer_id[NUM_GROUP_TOXES - 1]; -} State; - -static void group_invite_handler(const Tox_Event_Group_Invite *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t friend_number = tox_event_group_invite_get_friend_number(event); - const uint8_t *invite_data = tox_event_group_invite_get_invite_data(event); - const size_t length = tox_event_group_invite_get_invite_data_length(event); - - printf("Accepting friend invite\n"); - - Tox_Err_Group_Invite_Accept err_accept; - tox_group_invite_accept(autotox->tox, friend_number, invite_data, length, (const uint8_t *)"test", 4, - nullptr, 0, &err_accept); - ck_assert(err_accept == TOX_ERR_GROUP_INVITE_ACCEPT_OK); -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - const uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); - - fprintf(stderr, "joined: %zu, %u\n", state->num_peers, peer_id); - ck_assert_msg(state->num_peers < NUM_GROUP_TOXES - 1, "%zu", state->num_peers); - - state->peer_id[state->num_peers++] = peer_id; -} - -static void group_private_message_handler(const Tox_Event_Group_Private_Message *event, void *user_data) -{ - const uint8_t *message = tox_event_group_private_message_get_message(event); - const size_t length = tox_event_group_private_message_get_message_length(event); - - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - ck_assert(length == CODEWORD_LEN); - ck_assert(memcmp(CODEWORD, message, length) == 0); - - printf("Codeword: %s\n", CODEWORD); - - state->got_code = true; -} - -static void group_message_handler(const Tox_Event_Group_Message *event, void *user_data) -{ - const uint8_t *message = tox_event_group_message_get_message(event); - const size_t length = tox_event_group_message_get_message_length(event); - - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - ck_assert(length == CODEWORD_LEN); - ck_assert(memcmp(CODEWORD, message, length) == 0); - - printf("Codeword: %s\n", CODEWORD); - - state->got_second_code = true; -} - -/* - * We need different constants to make TCP run smoothly. TODO(Jfreegman): is this because of the group - * implementation or just an autotest quirk? - */ -#define GROUP_ITERATION_INTERVAL 100 -static void iterate_group(AutoTox *autotoxes, uint32_t num_toxes, size_t interval) -{ - for (uint32_t i = 0; i < num_toxes; i++) { - if (autotoxes[i].alive) { - tox_iterate(autotoxes[i].tox, &autotoxes[i]); - autotoxes[i].clock += interval; - } - } - - c_sleep(50); -} - -static bool all_peers_connected(AutoTox *autotoxes) -{ - iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); - - size_t count = 0; - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - const State *state = (const State *)autotoxes[i].state; - - if (state->num_peers == NUM_GROUP_TOXES - 1) { - ++count; - } - } - - return count == NUM_GROUP_TOXES; -} - -static bool all_peers_got_code(AutoTox *autotoxes) -{ - iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); - - size_t count = 0; - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - const State *state = (const State *)autotoxes[i].state; - - if (state->got_code) { - ++count; - } - } - - return count == NUM_GROUP_TOXES - 1; -} - -static void group_tcp_test(AutoTox *autotoxes) -{ - ck_assert(NUM_GROUP_TOXES >= 2); - - State *state0 = (State *)autotoxes[0].state; - State *state1 = (State *)autotoxes[1].state; - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - tox_events_callback_group_peer_join(autotoxes[i].dispatch, group_peer_join_handler); - tox_events_callback_group_private_message(autotoxes[i].dispatch, group_private_message_handler); - } - - tox_events_callback_group_message(autotoxes[1].dispatch, group_message_handler); - tox_events_callback_group_invite(autotoxes[1].dispatch, group_invite_handler); - - Tox_Err_Group_New new_err; - uint32_t groupnumber = tox_group_new(autotoxes[0].tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"test", 4, - (const uint8_t *)"test", 4, &new_err); - ck_assert_msg(new_err == TOX_ERR_GROUP_NEW_OK, "tox_group_new failed: %u", new_err); - - iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); - - Tox_Err_Group_State_Query id_err; - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - - tox_group_get_chat_id(autotoxes[0].tox, groupnumber, chat_id, &id_err); - ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERY_OK, "%u", id_err); - - printf("Tox 0 created new group...\n"); - - for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { - Tox_Err_Group_Join jerr; - tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"test", 4, nullptr, 0, &jerr); - ck_assert_msg(jerr == TOX_ERR_GROUP_JOIN_OK, "%u", jerr); - iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL * 10); - } - - while (!all_peers_connected(autotoxes)) - ; - - printf("%d peers successfully joined. Waiting for code...\n", NUM_GROUP_TOXES); - printf("Tox 0 sending secret code to all peers\n"); - - for (size_t i = 0; i < NUM_GROUP_TOXES - 1; ++i) { - - Tox_Err_Group_Send_Private_Message perr; - tox_group_send_private_message(autotoxes[0].tox, groupnumber, state0->peer_id[i], - TOX_MESSAGE_TYPE_NORMAL, - (const uint8_t *)CODEWORD, CODEWORD_LEN, &perr); - ck_assert_msg(perr == TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK, "%u", perr); - } - - while (!all_peers_got_code(autotoxes)) - ; - - Tox_Err_Group_Leave err_exit; - tox_group_leave(autotoxes[1].tox, groupnumber, nullptr, 0, &err_exit); - ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); - - iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); - - state0->num_peers = 0; - state1->num_peers = 0; - - // now do a friend invite to make sure the TCP-specific logic for friend invites is okay - - printf("Tox1 leaves group and Tox0 does a friend group invite for tox1\n"); - - Tox_Err_Group_Invite_Friend err_invite; - tox_group_invite_friend(autotoxes[0].tox, groupnumber, 0, &err_invite); - ck_assert(err_invite == TOX_ERR_GROUP_INVITE_FRIEND_OK); - - while (state0->num_peers == 0 && state1->num_peers == 0) { - iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); - } - - printf("Tox 1 successfully joined. Waiting for code...\n"); - - Tox_Err_Group_Send_Message merr; - tox_group_send_message(autotoxes[0].tox, groupnumber, TOX_MESSAGE_TYPE_NORMAL, - (const uint8_t *)CODEWORD, CODEWORD_LEN, &merr); - ck_assert(merr == TOX_ERR_GROUP_SEND_MESSAGE_OK); - - while (!state1->got_second_code) { - iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); - } - - for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { - tox_group_leave(autotoxes[i].tox, groupnumber, nullptr, 0, &err_exit); - ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); - } - - printf("Test passed!\n"); -} - -int main(int argc, char **argv) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - struct Tox_Options *options = tox_options_new(nullptr); - ck_assert(options != nullptr); - - tox_options_set_udp_enabled(options, false); - - Run_Auto_Options autotest_opts = default_run_auto_options(); - autotest_opts.graph = GRAPH_COMPLETE; - - // TODO(JFreegman): Fix this test and remove the "if". - if (argc > 2) { - run_auto_test(options, NUM_GROUP_TOXES, group_tcp_test, sizeof(State), &autotest_opts); - } - - tox_options_free(options); - return 0; -} - -#undef CODEWORD_LEN -#undef CODEWORD -#undef NUM_GROUP_TOXES diff --git a/auto_tests/group_topic_test.c b/auto_tests/group_topic_test.c deleted file mode 100644 index 44b76547..00000000 --- a/auto_tests/group_topic_test.c +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Tests that we can successfully change the group topic, that all peers receive topic changes - * and that the topic lock works as intended. - */ - -#include -#include -#include - -#include "auto_test_support.h" -#include "check_compat.h" - -#include "../toxcore/os_random.h" -#include "../toxcore/tox.h" - -#define NUM_GROUP_TOXES 3 - -#define TOPIC "They're waiting for you Gordon...in the test chamber" -#define TOPIC_LEN (sizeof(TOPIC) - 1) - -#define TOPIC2 "They're waiting for you Gordon...in the test chamber 2.0" -#define TOPIC_LEN2 (sizeof(TOPIC2) - 1) - -#define GROUP_NAME "The Test Chamber" -#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) - -#define PEER0_NICK "Koresh" -#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) - -typedef struct State { - uint32_t peer_id; // the id of the peer we set to observer -} State; - -static bool all_group_peers_connected(const AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, - size_t name_length, uint32_t peer_limit) -{ - for (uint32_t i = 0; i < tox_count; ++i) { - // make sure we got an invite - if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != name_length) { - return false; - } - - // make sure we got a sync response - if (peer_limit != 0 && tox_group_get_peer_limit(autotoxes[i].tox, groupnumber, nullptr) != peer_limit) { - return false; - } - - // make sure we're actually connected - if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) { - return false; - } - } - - return true; -} - -static void group_peer_join_handler(const Tox_Event_Group_Peer_Join *event, void *user_data) -{ - //const uint32_t group_number = tox_event_group_peer_join_get_group_number(event); - const uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); - - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - State *state = (State *)autotox->state; - - state->peer_id = peer_id; -} - -static void group_topic_handler(const Tox_Event_Group_Topic *event, void *user_data) -{ - AutoTox *autotox = (AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t group_number = tox_event_group_topic_get_group_number(event); - //const uint32_t peer_id = tox_event_group_topic_get_peer_id(event); - const uint8_t *topic = tox_event_group_topic_get_topic(event); - const uint32_t topic_length = tox_event_group_topic_get_topic_length(event); - - ck_assert(topic_length <= TOX_GROUP_MAX_TOPIC_LENGTH); - - Tox_Err_Group_State_Query query_err; - uint8_t topic2[TOX_GROUP_MAX_TOPIC_LENGTH]; - tox_group_get_topic(autotox->tox, group_number, topic2, &query_err); - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - - size_t topic_length_getter = tox_group_get_topic_size(autotox->tox, group_number, &query_err); - ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); - ck_assert_msg(topic_length_getter == topic_length && memcmp(topic, topic2, topic_length) == 0, - "topic differs in callback: %s, %s", topic, topic2); -} - -static void group_topic_lock_handler(const Tox_Event_Group_Topic_Lock *event, void *user_data) -{ - const AutoTox *autotox = (const AutoTox *)user_data; - ck_assert(autotox != nullptr); - - const uint32_t group_number = tox_event_group_topic_lock_get_group_number(event); - const Tox_Group_Topic_Lock topic_lock = tox_event_group_topic_lock_get_topic_lock(event); - - Tox_Err_Group_State_Query err; - Tox_Group_Topic_Lock current_lock = tox_group_get_topic_lock(autotox->tox, group_number, &err); - - ck_assert(err == TOX_ERR_GROUP_STATE_QUERY_OK); - ck_assert_msg(topic_lock == current_lock, "topic locks differ in callback"); -} - -/* Sets group topic. - * - * Return true on success. - */ -static bool set_topic(Tox *tox, uint32_t groupnumber, const char *topic, size_t length) -{ - Tox_Err_Group_Topic_Set err; - tox_group_set_topic(tox, groupnumber, (const uint8_t *)topic, length, &err); - - return err == TOX_ERR_GROUP_TOPIC_SET_OK; -} - -/* Returns 0 if group topic matches expected topic. - * Returns a value < 0 on failure. - */ -static int check_topic(const Tox *tox, uint32_t groupnumber, const char *expected_topic, size_t expected_length) -{ - Tox_Err_Group_State_Query query_err; - size_t topic_length = tox_group_get_topic_size(tox, groupnumber, &query_err); - - if (query_err != TOX_ERR_GROUP_STATE_QUERY_OK) { - return -1; - } - - if (expected_length != topic_length) { - return -2; - } - - uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH]; - tox_group_get_topic(tox, groupnumber, topic, &query_err); - - if (query_err != TOX_ERR_GROUP_STATE_QUERY_OK) { - return -3; - } - - if (memcmp(expected_topic, (const char *)topic, topic_length) != 0) { - return -4; - } - - return 0; -} - -static void wait_topic_lock(AutoTox *autotoxes, uint32_t groupnumber, Tox_Group_Topic_Lock expected_lock) -{ - while (1) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - uint32_t count = 0; - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - Tox_Err_Group_State_Query err; - Tox_Group_Topic_Lock topic_lock = tox_group_get_topic_lock(autotoxes[i].tox, groupnumber, &err); - ck_assert(err == TOX_ERR_GROUP_STATE_QUERY_OK); - - if (topic_lock == expected_lock) { - ++count; - } - } - - if (count == NUM_GROUP_TOXES) { - break; - } - } -} - -/* Waits for all peers in group to see the same topic */ -static void wait_state_topic(AutoTox *autotoxes, uint32_t groupnumber, const char *topic, size_t length) -{ - while (1) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - uint32_t count = 0; - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - const int c_ret = check_topic(autotoxes[i].tox, groupnumber, topic, length); - - if (c_ret == 0) { - ++count; - } - } - - if (count == NUM_GROUP_TOXES) { - break; - } - } -} - -/* All peers attempt to set the topic. - * - * Returns the number of peers who succeeeded. - */ -static uint32_t set_topic_all_peers(const Random *rng, AutoTox *autotoxes, size_t num_peers, uint32_t groupnumber) -{ - uint32_t change_count = 0; - - for (size_t i = 0; i < num_peers; ++i) { - char new_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; - snprintf(new_topic, sizeof(new_topic), "peer %zu changes topic %u", i, random_u32(rng)); - size_t length = strlen(new_topic); - - if (set_topic(autotoxes[i].tox, groupnumber, new_topic, length)) { - wait_state_topic(autotoxes, groupnumber, new_topic, length); - ++change_count; - } else { - fprintf(stderr, "Peer %zu couldn't set the topic\n", i); - } - } - - return change_count; -} - -static void group_topic_test(AutoTox *autotoxes) -{ - ck_assert_msg(NUM_GROUP_TOXES >= 3, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); - - const Random *rng = os_random(); - ck_assert(rng != nullptr); - - Tox *tox0 = autotoxes[0].tox; - Tox_Dispatch *dispatch0 = autotoxes[0].dispatch; - const State *state0 = (const State *)autotoxes[0].state; - - tox_events_callback_group_peer_join(dispatch0, group_peer_join_handler); - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - tox_events_callback_group_topic(autotoxes[i].dispatch, group_topic_handler); - tox_events_callback_group_topic_lock(autotoxes[i].dispatch, group_topic_lock_handler); - } - - /* Tox1 creates a group and is the founder of a newly created group */ - Tox_Err_Group_New new_err; - const uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, - GROUP_NAME_LEN, - (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, &new_err); - - ck_assert_msg(new_err == TOX_ERR_GROUP_NEW_OK, "tox_group_new failed: %u", new_err); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - /* Founder sets group topic before anyone else joins */ - const bool s_ret = set_topic(tox0, groupnumber, TOPIC, TOPIC_LEN); - ck_assert_msg(s_ret, "Founder failed to set topic"); - - /* Founder gets the Chat ID and implicitly shares it publicly */ - Tox_Err_Group_State_Query id_err; - uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; - tox_group_get_chat_id(tox0, groupnumber, chat_id, &id_err); - - ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERY_OK, "tox_group_get_chat_id failed %u", id_err); - - /* All other peers join the group using the Chat ID */ - for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - Tox_Err_Group_Join join_err; - tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); - ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "tox_group_join failed: %u", join_err); - - c_sleep(100); - } - - fprintf(stderr, "Peers attempting to join group\n"); - - all_group_peers_connected(autotoxes, NUM_GROUP_TOXES, groupnumber, GROUP_NAME_LEN, MAX_GC_PEERS_DEFAULT); - - wait_state_topic(autotoxes, groupnumber, TOPIC, TOPIC_LEN); - - /* Founder disables topic lock */ - Tox_Err_Group_Set_Topic_Lock lock_set_err; - tox_group_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_DISABLED, &lock_set_err); - ck_assert_msg(lock_set_err == TOX_ERR_GROUP_SET_TOPIC_LOCK_OK, "failed to disable topic lock: %u", - lock_set_err); - - fprintf(stderr, "Topic lock disabled\n"); - - /* make sure every peer sees the topic lock state change */ - wait_topic_lock(autotoxes, groupnumber, TOX_GROUP_TOPIC_LOCK_DISABLED); - - /* All peers should be able to change the topic now */ - uint32_t change_count = set_topic_all_peers(rng, autotoxes, NUM_GROUP_TOXES, groupnumber); - - ck_assert_msg(change_count == NUM_GROUP_TOXES, "%u peers changed the topic with topic lock disabled", change_count); - - /* founder silences the last peer he saw join */ - Tox_Err_Group_Set_Role merr; - tox_group_set_role(tox0, groupnumber, state0->peer_id, TOX_GROUP_ROLE_OBSERVER, &merr); - ck_assert_msg(merr == TOX_ERR_GROUP_SET_ROLE_OK, "Failed to set %u to observer role: %u", state0->peer_id, merr); - - fprintf(stderr, "Random peer is set to observer\n"); - - iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); - - /* All peers except one should now be able to change the topic */ - change_count = set_topic_all_peers(rng, autotoxes, NUM_GROUP_TOXES, groupnumber); - - ck_assert_msg(change_count == NUM_GROUP_TOXES - 1, "%u peers changed the topic with a silenced peer", change_count); - - /* Founder enables topic lock and sets topic back to original */ - tox_group_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_ENABLED, &lock_set_err); - ck_assert_msg(lock_set_err == TOX_ERR_GROUP_SET_TOPIC_LOCK_OK, "failed to enable topic lock: %u", - lock_set_err); - - fprintf(stderr, "Topic lock enabled\n"); - - /* Wait for all peers to get topic lock state change */ - wait_topic_lock(autotoxes, groupnumber, TOX_GROUP_TOPIC_LOCK_ENABLED); - - const bool s3_ret = set_topic(tox0, groupnumber, TOPIC2, TOPIC_LEN2); - ck_assert_msg(s3_ret, "Founder failed to set topic second time"); - - wait_state_topic(autotoxes, groupnumber, TOPIC2, TOPIC_LEN2); - - /* No peer excluding the founder should be able to set the topic */ - - change_count = set_topic_all_peers(rng, &autotoxes[1], NUM_GROUP_TOXES - 1, groupnumber); - - ck_assert_msg(change_count == 0, "%u peers changed the topic with topic lock enabled", change_count); - - /* A final check that the topic is unchanged */ - wait_state_topic(autotoxes, groupnumber, TOPIC2, TOPIC_LEN2); - - for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { - Tox_Err_Group_Leave err_exit; - tox_group_leave(autotoxes[i].tox, groupnumber, nullptr, 0, &err_exit); - ck_assert_msg(err_exit == TOX_ERR_GROUP_LEAVE_OK, "%u", err_exit); - } - - fprintf(stderr, "All tests passed!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options autotest_opts = default_run_auto_options(); - autotest_opts.graph = GRAPH_COMPLETE; - - run_auto_test(nullptr, NUM_GROUP_TOXES, group_topic_test, sizeof(State), &autotest_opts); - - return 0; -} - -#undef TOPIC -#undef TOPIC_LEN -#undef TOPIC2 -#undef TOPIC_LEN2 -#undef NUM_GROUP_TOXES -#undef GROUP_NAME -#undef GROUP_NAME_LEN -#undef PEER0_NICK -#undef PEER0_NICK_LEN diff --git a/auto_tests/lan_discovery_test.c b/auto_tests/lan_discovery_test.c deleted file mode 100644 index 103220c3..00000000 --- a/auto_tests/lan_discovery_test.c +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/ccompat.h" -#include "../toxcore/tox_struct.h" -#include "auto_test_support.h" - -static uint64_t get_state_clock_callback(void *user_data) -{ - const uint64_t *clock = (const uint64_t *)user_data; - return *clock; -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Tox *tox1 = tox_new_log_lan(nullptr, nullptr, nullptr, /* lan_discovery */true); - Tox *tox2 = tox_new_log_lan(nullptr, nullptr, nullptr, /* lan_discovery */true); - ck_assert(tox1 != nullptr); - ck_assert(tox2 != nullptr); - - uint64_t clock = current_time_monotonic(tox1->mono_time); - Mono_Time *mono_time; - - mono_time = tox1->mono_time; - mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &clock); - - mono_time = tox2->mono_time; - mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &clock); - - printf("Waiting for LAN discovery. This loop will attempt to run until successful."); - - do { - printf("."); - fflush(stdout); - - tox_iterate(tox1, nullptr); - tox_iterate(tox2, nullptr); - c_sleep(5); - clock += 100; - } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE); - - printf(" %u <-> %u\n", - tox_self_get_connection_status(tox1), - tox_self_get_connection_status(tox2)); - - tox_kill(tox2); - tox_kill(tox1); - return 0; -} diff --git a/auto_tests/lossless_packet_test.c b/auto_tests/lossless_packet_test.c deleted file mode 100644 index 91a6a59f..00000000 --- a/auto_tests/lossless_packet_test.c +++ /dev/null @@ -1,71 +0,0 @@ -/* Tests that we can send lossless packets. - */ - -#include -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/util.h" -#include "check_compat.h" - -typedef struct State { - bool custom_packet_received; -} State; - -#include "auto_test_support.h" - -#define LOSSLESS_PACKET_FILLER 160 - -static void handle_lossless_packet(const Tox_Event_Friend_Lossless_Packet *event, void *user_data) -{ - //const uint32_t friend_number = tox_event_friend_lossless_packet_get_friend_number(event); - const uint8_t *data = tox_event_friend_lossless_packet_get_data(event); - const uint32_t data_length = tox_event_friend_lossless_packet_get_data_length(event); - - uint8_t *cmp_packet = (uint8_t *)malloc(tox_max_custom_packet_size()); - ck_assert(cmp_packet != nullptr); - memset(cmp_packet, LOSSLESS_PACKET_FILLER, tox_max_custom_packet_size()); - - if (data_length == tox_max_custom_packet_size() && memcmp(data, cmp_packet, tox_max_custom_packet_size()) == 0) { - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - state->custom_packet_received = true; - } - - free(cmp_packet); -} - -static void test_lossless_packet(AutoTox *autotoxes) -{ - tox_events_callback_friend_lossless_packet(autotoxes[1].dispatch, &handle_lossless_packet); - const size_t packet_size = tox_max_custom_packet_size() + 1; - uint8_t *packet = (uint8_t *)malloc(packet_size); - ck_assert(packet != nullptr); - memset(packet, LOSSLESS_PACKET_FILLER, packet_size); - - bool ret = tox_friend_send_lossless_packet(autotoxes[0].tox, 0, packet, packet_size, nullptr); - ck_assert_msg(ret == false, "should not be able to send custom packets this big %i", ret); - - ret = tox_friend_send_lossless_packet(autotoxes[0].tox, 0, packet, tox_max_custom_packet_size(), nullptr); - ck_assert_msg(ret == true, "tox_friend_send_lossless_packet fail %i", ret); - - free(packet); - - do { - iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); - } while (!((State *)autotoxes[1].state)->custom_packet_received); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - - run_auto_test(nullptr, 2, test_lossless_packet, sizeof(State), &options); - - return 0; -} diff --git a/auto_tests/lossy_packet_test.c b/auto_tests/lossy_packet_test.c deleted file mode 100644 index 200977d1..00000000 --- a/auto_tests/lossy_packet_test.c +++ /dev/null @@ -1,71 +0,0 @@ -/* Tests that we can send lossy packets. - */ - -#include -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/util.h" -#include "check_compat.h" - -typedef struct State { - bool custom_packet_received; -} State; - -#include "auto_test_support.h" - -#define LOSSY_PACKET_FILLER 200 - -static void handle_lossy_packet(const Tox_Event_Friend_Lossy_Packet *event, void *user_data) -{ - //const uint32_t friend_number = tox_event_friend_lossy_packet_get_friend_number(event); - const uint8_t *data = tox_event_friend_lossy_packet_get_data(event); - const uint32_t data_length = tox_event_friend_lossy_packet_get_data_length(event); - - uint8_t *cmp_packet = (uint8_t *)malloc(tox_max_custom_packet_size()); - ck_assert(cmp_packet != nullptr); - memset(cmp_packet, LOSSY_PACKET_FILLER, tox_max_custom_packet_size()); - - if (data_length == tox_max_custom_packet_size() && memcmp(data, cmp_packet, tox_max_custom_packet_size()) == 0) { - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - state->custom_packet_received = true; - } - - free(cmp_packet); -} - -static void test_lossy_packet(AutoTox *autotoxes) -{ - tox_events_callback_friend_lossy_packet(autotoxes[1].dispatch, &handle_lossy_packet); - const size_t packet_size = tox_max_custom_packet_size() + 1; - uint8_t *packet = (uint8_t *)malloc(packet_size); - ck_assert(packet != nullptr); - memset(packet, LOSSY_PACKET_FILLER, packet_size); - - bool ret = tox_friend_send_lossy_packet(autotoxes[0].tox, 0, packet, packet_size, nullptr); - ck_assert_msg(ret == false, "should not be able to send custom packets this big %i", ret); - - ret = tox_friend_send_lossy_packet(autotoxes[0].tox, 0, packet, tox_max_custom_packet_size(), nullptr); - ck_assert_msg(ret == true, "tox_friend_send_lossy_packet fail %i", ret); - - free(packet); - - do { - iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); - } while (!((State *)autotoxes[1].state)->custom_packet_received); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - - run_auto_test(nullptr, 2, test_lossy_packet, sizeof(State), &options); - - return 0; -} diff --git a/auto_tests/netprof_test.c b/auto_tests/netprof_test.c deleted file mode 100644 index 578b0c32..00000000 --- a/auto_tests/netprof_test.c +++ /dev/null @@ -1,134 +0,0 @@ -/** Auto Tests: basic network profile functionality test (UDP only) - */ - -#include -#include -#include - -#include "../toxcore/tox_private.h" -#include "../toxcore/util.h" - -#include "auto_test_support.h" -#include "check_compat.h" - -#define NUM_TOXES 2 - -static void test_netprof(AutoTox *autotoxes) -{ - // Send some messages to create fake traffic - for (size_t i = 0; i < 256; ++i) { - for (uint32_t j = 0; j < NUM_TOXES; ++j) { - tox_friend_send_message(autotoxes[j].tox, 0, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"test", 4, nullptr); - } - - iterate_all_wait(autotoxes, NUM_TOXES, ITERATION_INTERVAL); - } - - // idle traffic for a while - for (size_t i = 0; i < 100; ++i) { - iterate_all_wait(autotoxes, NUM_TOXES, ITERATION_INTERVAL); - } - - const Tox *tox1 = autotoxes[0].tox; - - const uint64_t UDP_count_sent1 = tox_netprof_get_packet_total_count(tox1, TOX_NETPROF_PACKET_TYPE_UDP, - TOX_NETPROF_DIRECTION_SENT); - const uint64_t UDP_count_recv1 = tox_netprof_get_packet_total_count(tox1, TOX_NETPROF_PACKET_TYPE_UDP, - TOX_NETPROF_DIRECTION_RECV); - const uint64_t TCP_count_sent1 = tox_netprof_get_packet_total_count(tox1, TOX_NETPROF_PACKET_TYPE_TCP, - TOX_NETPROF_DIRECTION_SENT); - const uint64_t TCP_count_recv1 = tox_netprof_get_packet_total_count(tox1, TOX_NETPROF_PACKET_TYPE_TCP, - TOX_NETPROF_DIRECTION_RECV); - - const uint64_t UDP_bytes_sent1 = tox_netprof_get_packet_total_bytes(tox1, TOX_NETPROF_PACKET_TYPE_UDP, - TOX_NETPROF_DIRECTION_SENT); - const uint64_t UDP_bytes_recv1 = tox_netprof_get_packet_total_bytes(tox1, TOX_NETPROF_PACKET_TYPE_UDP, - TOX_NETPROF_DIRECTION_RECV); - const uint64_t TCP_bytes_sent1 = tox_netprof_get_packet_total_bytes(tox1, TOX_NETPROF_PACKET_TYPE_TCP, - TOX_NETPROF_DIRECTION_SENT); - const uint64_t TCP_bytes_recv1 = tox_netprof_get_packet_total_bytes(tox1, TOX_NETPROF_PACKET_TYPE_TCP, - TOX_NETPROF_DIRECTION_RECV); - - ck_assert(UDP_count_recv1 > 0 && UDP_count_sent1 > 0); - ck_assert(UDP_bytes_recv1 > 0 && UDP_bytes_sent1 > 0); - - (void)TCP_count_sent1; - (void)TCP_bytes_sent1; - (void)TCP_bytes_recv1; - (void)TCP_count_recv1; - - uint64_t total_sent_count = 0; - uint64_t total_recv_count = 0; - uint64_t total_sent_bytes = 0; - uint64_t total_recv_bytes = 0; - - // tox1 makes sure the sum value of all packet ID's is equal to the totals - for (size_t i = 0; i < 256; ++i) { - // this id isn't valid for UDP packets but we still want to call the - // functions and make sure they return some non-zero value - if (i == TOX_NETPROF_PACKET_ID_TCP_DATA) { - ck_assert(tox_netprof_get_packet_id_count(tox1, TOX_NETPROF_PACKET_TYPE_UDP, i, - TOX_NETPROF_DIRECTION_SENT) > 0); - ck_assert(tox_netprof_get_packet_id_bytes(tox1, TOX_NETPROF_PACKET_TYPE_UDP, i, - TOX_NETPROF_DIRECTION_SENT) > 0); - ck_assert(tox_netprof_get_packet_id_bytes(tox1, TOX_NETPROF_PACKET_TYPE_UDP, i, - TOX_NETPROF_DIRECTION_SENT) > 0); - ck_assert(tox_netprof_get_packet_id_bytes(tox1, TOX_NETPROF_PACKET_TYPE_UDP, i, - TOX_NETPROF_DIRECTION_RECV) > 0); - continue; - } - - total_sent_count += tox_netprof_get_packet_id_count(tox1, TOX_NETPROF_PACKET_TYPE_UDP, i, - TOX_NETPROF_DIRECTION_SENT); - total_recv_count += tox_netprof_get_packet_id_count(tox1, TOX_NETPROF_PACKET_TYPE_UDP, i, - TOX_NETPROF_DIRECTION_RECV); - - total_sent_bytes += tox_netprof_get_packet_id_bytes(tox1, TOX_NETPROF_PACKET_TYPE_UDP, i, - TOX_NETPROF_DIRECTION_SENT); - total_recv_bytes += tox_netprof_get_packet_id_bytes(tox1, TOX_NETPROF_PACKET_TYPE_UDP, i, - TOX_NETPROF_DIRECTION_RECV); - } - - const uint64_t total_packets = total_sent_count + total_recv_count; - ck_assert_msg(total_packets == UDP_count_sent1 + UDP_count_recv1, - "%" PRIu64 "does not match %" PRIu64 "\n", total_packets, UDP_count_sent1 + UDP_count_recv1); - - ck_assert_msg(total_sent_count == UDP_count_sent1, "%" PRIu64 " does not match %" PRIu64 "\n", total_sent_count, UDP_count_sent1); - ck_assert_msg(total_recv_count == UDP_count_recv1, "%" PRIu64 " does not match %" PRIu64"\n", total_recv_count, UDP_count_recv1); - - - const uint64_t total_bytes = total_sent_bytes + total_recv_bytes; - ck_assert_msg(total_bytes == UDP_bytes_sent1 + UDP_bytes_recv1, - "%" PRIu64 "does not match %" PRIu64 "\n", total_bytes, UDP_bytes_sent1 + UDP_bytes_recv1); - - ck_assert_msg(total_sent_bytes == UDP_bytes_sent1, "%" PRIu64 " does not match %" PRIu64 "\n", total_sent_bytes, UDP_bytes_sent1); - ck_assert_msg(total_recv_bytes == UDP_bytes_recv1, "%" PRIu64 " does not match %" PRIu64 "\n", total_recv_bytes, UDP_bytes_recv1); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Tox_Err_Options_New options_err; - struct Tox_Options *tox_opts = tox_options_new(&options_err); - - ck_assert_msg(options_err == TOX_ERR_OPTIONS_NEW_OK, "Failed to initialize tox options: %u\n", options_err); - - tox_options_default(tox_opts); - tox_options_set_udp_enabled(tox_opts, true); - - Run_Auto_Options autotox_opts = default_run_auto_options(); - autotox_opts.graph = GRAPH_COMPLETE; - - run_auto_test(tox_opts, NUM_TOXES, test_netprof, 0, &autotox_opts); - - // TODO(Jfreegman): uncomment this when TCP autotests are fixed - // tox_options_set_udp_enabled(tox_opts, false); - // run_auto_test(tox_opts, NUM_TOXES, test_netprof, 0, &autotox_opts); - - tox_options_free(tox_opts); - - return 0; -} - -#undef NUM_TOXES diff --git a/auto_tests/onion_test.c b/auto_tests/onion_test.c index f4cbeff7..875bbe86 100644 --- a/auto_tests/onion_test.c +++ b/auto_tests/onion_test.c @@ -492,7 +492,7 @@ static Onions *new_onions(const Memory *mem, const Random *rng, uint16_t port, u } TCP_Proxy_Info inf = {{{{0}}}}; - on->nc = new_net_crypto(on->log, mem, rng, ns, on->mono_time, net, dht, &inf, on->tcp_np); + on->nc = new_net_crypto(on->log, mem, rng, ns, on->mono_time, net, dht, &auto_test_dht_funcs, &inf, on->tcp_np); on->onion_c = new_onion_client(on->log, mem, rng, on->mono_time, on->nc, dht, net); if (!on->onion_c) { diff --git a/auto_tests/overflow_recvq_test.c b/auto_tests/overflow_recvq_test.c deleted file mode 100644 index b2a30f23..00000000 --- a/auto_tests/overflow_recvq_test.c +++ /dev/null @@ -1,68 +0,0 @@ -/* Try to overflow the net_crypto packet buffer. - */ - -#include - -typedef struct State { - uint32_t recv_count; -} State; - -#include "auto_test_support.h" - -#define NUM_MSGS 40000 - -static void handle_friend_message(const Tox_Event_Friend_Message *event, void *user_data) -{ - //const uint32_t friend_number = tox_event_friend_message_get_friend_number(event); - //const Tox_Message_Type type = tox_event_friend_message_get_type(event); - //const uint8_t *message = tox_event_friend_message_get_message(event); - //const uint32_t message_length = tox_event_friend_message_get_message_length(event); - - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - state->recv_count++; -} - -static void net_crypto_overflow_test(AutoTox *autotoxes) -{ - tox_events_callback_friend_message(autotoxes[0].dispatch, handle_friend_message); - - printf("sending many messages to tox0\n"); - - for (uint32_t tox_index = 1; tox_index < 3; tox_index++) { - for (uint32_t i = 0; i < NUM_MSGS; i++) { - uint8_t message[128] = {0}; - snprintf((char *)message, sizeof(message), "%u-%u", tox_index, i); - - Tox_Err_Friend_Send_Message err; - tox_friend_send_message(autotoxes[tox_index].tox, 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof message, &err); - - if (err == TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ) { - printf("tox%u sent %u messages to friend 0\n", tox_index, i); - break; - } - - ck_assert_msg(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK, - "tox%u failed to send message number %u: %u", tox_index, i, err); - } - } - - // TODO(iphydf): Wait until all messages have arrived. Currently, not all - // messages arrive, so this test would always fail. - for (uint32_t i = 0; i < 200; i++) { - iterate_all_wait(autotoxes, 3, ITERATION_INTERVAL); - } - - printf("tox%u received %u messages\n", autotoxes[0].index, ((State *)autotoxes[0].state)->recv_count); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - run_auto_test(nullptr, 3, net_crypto_overflow_test, sizeof(State), &options); - - return 0; -} diff --git a/auto_tests/overflow_sendq_test.c b/auto_tests/overflow_sendq_test.c deleted file mode 100644 index d9b60894..00000000 --- a/auto_tests/overflow_sendq_test.c +++ /dev/null @@ -1,46 +0,0 @@ -/* Try to overflow the net_crypto packet buffer. - */ - -#include - -#include "auto_test_support.h" - -#define NUM_MSGS 40000 - -static void net_crypto_overflow_test(AutoTox *autotoxes) -{ - const uint8_t message[] = {0}; - bool errored = false; - - for (uint32_t i = 0; i < NUM_MSGS; i++) { - Tox_Err_Friend_Send_Message err; - tox_friend_send_message(autotoxes[0].tox, 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof message, &err); - - if (err != TOX_ERR_FRIEND_SEND_MESSAGE_OK) { - errored = true; - } - - if (errored) { - // As soon as we get the first error, we expect the same error (SENDQ) - // every time we try to send. - ck_assert_msg(err == TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ, - "expected SENDQ error on message %u, but got %u", i, err); - } else { - ck_assert_msg(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK, - "failed to send message number %u: %u", i, err); - } - } - - ck_assert_msg(errored, "expected SENDQ error at some point (increase NUM_MSGS?)"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - run_auto_test(nullptr, 2, net_crypto_overflow_test, 0, &options); - - return 0; -} diff --git a/auto_tests/reconnect_test.c b/auto_tests/reconnect_test.c deleted file mode 100644 index 4661ec8f..00000000 --- a/auto_tests/reconnect_test.c +++ /dev/null @@ -1,106 +0,0 @@ -/* Auto Tests: Reconnection. - * - * This test checks that when a tox instance is suspended for long enough that - * its friend connections time out, those connections are promptly - * re-established when the instance is resumed. - */ - -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/friend_connection.h" -#include "../toxcore/os_random.h" -#include "../toxcore/tox.h" -#include "check_compat.h" - -#define TOX_COUNT 2 -#define RECONNECT_TIME_MAX (FRIEND_CONNECTION_TIMEOUT + 3) - -#include "auto_test_support.h" - -static uint32_t tox_connected_count(uint32_t tox_count, AutoTox *autotoxes, uint32_t index) -{ - const size_t friend_count = tox_self_get_friend_list_size(autotoxes[index].tox); - uint32_t connected_count = 0; - - for (size_t j = 0; j < friend_count; j++) { - if (tox_friend_get_connection_status(autotoxes[index].tox, j, nullptr) != TOX_CONNECTION_NONE) { - ++connected_count; - } - } - - return connected_count; -} - -static bool all_disconnected_from(uint32_t tox_count, AutoTox *autotoxes, uint32_t index) -{ - for (uint32_t i = 0; i < tox_count; i++) { - if (i == index) { - continue; - } - - if (tox_connected_count(tox_count, autotoxes, i) >= tox_count - 1) { - return false; - } - } - - return true; -} - -static void test_reconnect(AutoTox *autotoxes) -{ - const Random *rng = os_random(); - ck_assert(rng != nullptr); - const time_t test_start_time = time(nullptr); - - printf("letting connections settle\n"); - - do { - iterate_all_wait(autotoxes, TOX_COUNT, ITERATION_INTERVAL); - } while (time(nullptr) - test_start_time < 2); - - const uint16_t disconnect = random_u16(rng) % TOX_COUNT; - printf("disconnecting #%u\n", autotoxes[disconnect].index); - - do { - for (uint16_t i = 0; i < TOX_COUNT; ++i) { - if (i != disconnect) { - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(autotoxes[i].tox, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(autotoxes[i].dispatch, events, &autotoxes[i]); - tox_events_free(events); - - autotoxes[i].clock += 1000; - } - } - - c_sleep(20); - } while (!all_disconnected_from(TOX_COUNT, autotoxes, disconnect)); - - const uint64_t reconnect_start_time = autotoxes[0].clock; - - printf("reconnecting\n"); - - do { - iterate_all_wait(autotoxes, TOX_COUNT, ITERATION_INTERVAL); - } while (!all_friends_connected(autotoxes, TOX_COUNT)); - - const uint64_t reconnect_time = autotoxes[0].clock - reconnect_start_time; - ck_assert_msg(reconnect_time <= RECONNECT_TIME_MAX * 1000, "reconnection took %d seconds; expected at most %d seconds", - (int)(reconnect_time / 1000), RECONNECT_TIME_MAX); - - printf("test_reconnect succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - run_auto_test(nullptr, TOX_COUNT, test_reconnect, 0, &options); - - return 0; -} diff --git a/auto_tests/save_friend_test.c b/auto_tests/save_friend_test.c deleted file mode 100644 index 5b82c634..00000000 --- a/auto_tests/save_friend_test.c +++ /dev/null @@ -1,183 +0,0 @@ -/* Auto Tests: Save and load friends. - */ - -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/ccompat.h" -#include "../toxcore/crypto_core.h" -#include "../toxcore/os_random.h" -#include "../toxcore/tox.h" -#include "auto_test_support.h" -#include "check_compat.h" - -struct test_data { - uint8_t *name; - uint8_t *status_message; - bool received_name; - bool received_status_message; -}; - -static void set_random(Tox *m, const Random *rng, bool (*setter)(Tox *, const uint8_t *, size_t, Tox_Err_Set_Info *), size_t length) -{ - VLA(uint8_t, text, length); - - for (uint32_t i = 0; i < length; ++i) { - text[i] = random_u08(rng); - } - - setter(m, text, length, nullptr); -} - -static void alloc_string(uint8_t **to, size_t length) -{ - free(*to); - *to = (uint8_t *)malloc(length); - ck_assert(*to != nullptr); -} - -static void set_string(uint8_t **to, const uint8_t *from, size_t length) -{ - alloc_string(to, length); - memcpy(*to, from, length); -} - -static void namechange_callback(const Tox_Event_Friend_Name *event, void *user_data) -{ - //const uint32_t friend_number = tox_event_friend_name_get_friend_number(event); - const uint8_t *name = tox_event_friend_name_get_name(event); - const uint32_t name_length = tox_event_friend_name_get_name_length(event); - - struct test_data *to_compare = (struct test_data *)user_data; - set_string(&to_compare->name, name, name_length); - to_compare->received_name = true; -} - -static void statuschange_callback(const Tox_Event_Friend_Status_Message *event, void *user_data) -{ - //const uint32_t friend_number = tox_event_friend_status_message_get_friend_number(event); - const uint8_t *message = tox_event_friend_status_message_get_message(event); - const uint32_t message_length = tox_event_friend_status_message_get_message_length(event); - - struct test_data *to_compare = (struct test_data *)user_data; - set_string(&to_compare->status_message, message, message_length); - to_compare->received_status_message = true; -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Tox *const tox1 = tox_new_log(nullptr, nullptr, nullptr); - Tox *const tox2 = tox_new_log(nullptr, nullptr, nullptr); - ck_assert(tox1 != nullptr); - ck_assert(tox2 != nullptr); - - tox_events_init(tox1); - Tox_Dispatch *dispatch1 = tox_dispatch_new(nullptr); - ck_assert(dispatch1 != nullptr); - - printf("bootstrapping tox2 off tox1\n"); - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(tox1, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); - - tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); - - struct test_data to_compare = {nullptr}; - - uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_public_key(tox1, public_key); - tox_friend_add_norequest(tox2, public_key, nullptr); - tox_self_get_public_key(tox2, public_key); - tox_friend_add_norequest(tox1, public_key, nullptr); - - uint8_t *reference_name = (uint8_t *)malloc(tox_max_name_length()); - uint8_t *reference_status = (uint8_t *)malloc(tox_max_status_message_length()); - ck_assert(reference_name != nullptr); - ck_assert(reference_status != nullptr); - - const Random *rng = os_random(); - ck_assert(rng != nullptr); - set_random(tox1, rng, tox_self_set_name, tox_max_name_length()); - set_random(tox2, rng, tox_self_set_name, tox_max_name_length()); - set_random(tox1, rng, tox_self_set_status_message, tox_max_status_message_length()); - set_random(tox2, rng, tox_self_set_status_message, tox_max_status_message_length()); - - tox_self_get_name(tox2, reference_name); - tox_self_get_status_message(tox2, reference_status); - - tox_events_callback_friend_name(dispatch1, namechange_callback); - tox_events_callback_friend_status_message(dispatch1, statuschange_callback); - - while (true) { - if (tox_self_get_connection_status(tox1) && - tox_self_get_connection_status(tox2) && - tox_friend_get_connection_status(tox1, 0, nullptr) == TOX_CONNECTION_UDP) { - printf("Connected.\n"); - break; - } - - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox1, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch1, events, &to_compare); - tox_events_free(events); - - tox_iterate(tox2, nullptr); - - c_sleep(tox_iteration_interval(tox1)); - } - - while (true) { - if (to_compare.received_name && to_compare.received_status_message) { - printf("Exchanged names and status messages.\n"); - break; - } - - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox1, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch1, events, &to_compare); - tox_events_free(events); - - tox_iterate(tox2, nullptr); - - c_sleep(tox_iteration_interval(tox1)); - } - - size_t save_size = tox_get_savedata_size(tox1); - uint8_t *savedata = (uint8_t *)malloc(save_size); - tox_get_savedata(tox1, savedata); - - struct Tox_Options *const options = tox_options_new(nullptr); - tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); - tox_options_set_savedata_data(options, savedata, save_size); - - Tox *const tox_to_compare = tox_new_log(options, nullptr, nullptr); - - alloc_string(&to_compare.name, tox_friend_get_name_size(tox_to_compare, 0, nullptr)); - tox_friend_get_name(tox_to_compare, 0, to_compare.name, nullptr); - alloc_string(&to_compare.status_message, tox_friend_get_status_message_size(tox_to_compare, 0, nullptr)); - tox_friend_get_status_message(tox_to_compare, 0, to_compare.status_message, nullptr); - - ck_assert_msg(memcmp(reference_name, to_compare.name, tox_max_name_length()) == 0, - "incorrect name: should be all zeroes"); - ck_assert_msg(memcmp(reference_status, to_compare.status_message, tox_max_status_message_length()) == 0, - "incorrect status message: should be all zeroes"); - - tox_options_free(options); - tox_dispatch_free(dispatch1); - tox_kill(tox1); - tox_kill(tox2); - tox_kill(tox_to_compare); - free(savedata); - free(to_compare.name); - free(to_compare.status_message); - free(reference_status); - free(reference_name); - - return 0; -} diff --git a/auto_tests/save_load_test.c b/auto_tests/save_load_test.c deleted file mode 100644 index 26f29fea..00000000 --- a/auto_tests/save_load_test.c +++ /dev/null @@ -1,315 +0,0 @@ -/* Tests that we can save and load Tox data. - */ - -#include -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/ccompat.h" -#include "../toxcore/tox.h" -#include "../toxcore/tox_struct.h" -#include "../toxcore/util.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#ifndef USE_IPV6 -#define USE_IPV6 1 -#endif - -#ifdef TOX_LOCALHOST -#undef TOX_LOCALHOST -#endif -#if USE_IPV6 -#define TOX_LOCALHOST "::1" -#else -#define TOX_LOCALHOST "127.0.0.1" -#endif - -#ifdef TCP_RELAY_PORT -#undef TCP_RELAY_PORT -#endif -#define TCP_RELAY_PORT 33431 - -static void accept_friend_request(const Tox_Event_Friend_Request *event, void *userdata) -{ - Tox *tox = (Tox *)userdata; - - const uint8_t *public_key = tox_event_friend_request_get_public_key(event); - const uint8_t *message = tox_event_friend_request_get_message(event); - uint32_t message_length = tox_event_friend_request_get_message_length(event); - - if (message_length == 7 && memcmp("Gentoo", message, 7) == 0) { - tox_friend_add_norequest(tox, public_key, nullptr); - } -} - -static unsigned int connected_t1; -static void tox_connection_status(const Tox_Event_Self_Connection_Status *event, void *user_data) -{ - const Tox_Connection connection_status = tox_event_self_connection_status_get_connection_status(event); - - if (connected_t1 && !connection_status) { - ck_abort_msg("Tox went offline"); - } - - ck_assert_msg(connection_status != TOX_CONNECTION_NONE, "wrong status %u", connection_status); - - connected_t1 = connection_status; -} - -/* validate that: - * a) saving stays within the confined space - * b) a saved state can be loaded back successfully - * c) a second save is of equal size - * d) the second save is of equal content */ -static void reload_tox(Tox **tox, struct Tox_Options *const in_opts, void *user_data) -{ - const size_t extra = 64; - const size_t save_size1 = tox_get_savedata_size(*tox); - ck_assert_msg(save_size1 != 0, "save is invalid size %u", (unsigned)save_size1); - printf("%u\n", (unsigned)save_size1); - - uint8_t *buffer = (uint8_t *)malloc(save_size1 + 2 * extra); - ck_assert_msg(buffer != nullptr, "malloc failed"); - memset(buffer, 0xCD, extra); - memset(buffer + extra + save_size1, 0xCD, extra); - tox_get_savedata(*tox, buffer + extra); - tox_kill(*tox); - - for (size_t i = 0; i < extra; ++i) { - ck_assert_msg(buffer[i] == 0xCD, "Buffer underwritten from tox_get_savedata() @%u", (unsigned)i); - ck_assert_msg(buffer[extra + save_size1 + i] == 0xCD, "Buffer overwritten from tox_get_savedata() @%u", (unsigned)i); - } - - struct Tox_Options *const options = (in_opts == nullptr) ? tox_options_new(nullptr) : in_opts; - tox_options_set_ipv6_enabled(options, USE_IPV6); - - tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); - - tox_options_set_savedata_data(options, buffer + extra, save_size1); - - *tox = tox_new_log(options, nullptr, user_data); - - if (in_opts == nullptr) { - tox_options_free(options); - } - - ck_assert_msg(*tox != nullptr, "Failed to load back stored buffer"); - - const size_t save_size2 = tox_get_savedata_size(*tox); - - ck_assert_msg(save_size1 == save_size2, "Tox save data changed in size from a store/load cycle: %u -> %u", - (unsigned)save_size1, (unsigned)save_size2); - - uint8_t *buffer2 = (uint8_t *)malloc(save_size2); - - ck_assert_msg(buffer2 != nullptr, "malloc failed"); - - tox_get_savedata(*tox, buffer2); - - ck_assert_msg(!memcmp(buffer + extra, buffer2, save_size2), "Tox state changed by store/load/store cycle"); - - free(buffer2); - - free(buffer); -} - -typedef struct Time_Data { - pthread_mutex_t lock; - uint64_t clock; -} Time_Data; - -static uint64_t get_state_clock_callback(void *user_data) -{ - Time_Data *time_data = (Time_Data *)user_data; - pthread_mutex_lock(&time_data->lock); - uint64_t clock = time_data->clock; - pthread_mutex_unlock(&time_data->lock); - return clock; -} - -static void increment_clock(Time_Data *time_data, uint64_t count) -{ - pthread_mutex_lock(&time_data->lock); - time_data->clock += count; - pthread_mutex_unlock(&time_data->lock); -} - -static void set_current_time_callback(Tox *tox, Time_Data *time_data) -{ - Mono_Time *mono_time = tox->mono_time; - mono_time_set_current_time_callback(mono_time, get_state_clock_callback, time_data); -} - -static void test_few_clients(void) -{ - uint32_t index[] = { 1, 2, 3 }; - time_t con_time = 0, cur_time = time(nullptr); - - struct Tox_Options *opts1 = tox_options_new(nullptr); - tox_options_set_ipv6_enabled(opts1, USE_IPV6); - tox_options_set_tcp_port(opts1, TCP_RELAY_PORT); - Tox_Err_New t_n_error; - Tox *tox1 = tox_new_log(opts1, &t_n_error, &index[0]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "Failed to create tox instance: %u", t_n_error); - tox_options_free(opts1); - tox_events_init(tox1); - Tox_Dispatch *dispatch1 = tox_dispatch_new(nullptr); - ck_assert(dispatch1 != nullptr); - - struct Tox_Options *opts2 = tox_options_new(nullptr); - tox_options_set_ipv6_enabled(opts2, USE_IPV6); - tox_options_set_udp_enabled(opts2, false); - tox_options_set_local_discovery_enabled(opts2, false); - Tox *tox2 = tox_new_log(opts2, &t_n_error, &index[1]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "Failed to create tox instance: %u", t_n_error); - tox_events_init(tox2); - Tox_Dispatch *dispatch2 = tox_dispatch_new(nullptr); - ck_assert(dispatch2 != nullptr); - - struct Tox_Options *opts3 = tox_options_new(nullptr); - tox_options_set_ipv6_enabled(opts3, USE_IPV6); - tox_options_set_local_discovery_enabled(opts3, false); - Tox *tox3 = tox_new_log(opts3, &t_n_error, &index[2]); - ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "Failed to create tox instance: %u", t_n_error); - - ck_assert_msg(tox1 && tox2 && tox3, "Failed to create 3 tox instances"); - - Time_Data time_data; - ck_assert_msg(pthread_mutex_init(&time_data.lock, nullptr) == 0, "Failed to init time_data mutex"); - time_data.clock = current_time_monotonic(tox1->mono_time); - set_current_time_callback(tox1, &time_data); - set_current_time_callback(tox2, &time_data); - set_current_time_callback(tox3, &time_data); - - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(tox1, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); - - printf("using tox1 as tcp relay for tox2\n"); - tox_add_tcp_relay(tox2, TOX_LOCALHOST, TCP_RELAY_PORT, dht_key, nullptr); - - printf("bootstrapping toxes off tox1\n"); - tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); - tox_bootstrap(tox3, "localhost", dht_port, dht_key, nullptr); - - connected_t1 = 0; - tox_events_callback_self_connection_status(dispatch1, tox_connection_status); - tox_events_callback_friend_request(dispatch2, accept_friend_request); - uint8_t address[TOX_ADDRESS_SIZE]; - tox_self_get_address(tox2, address); - uint32_t test = tox_friend_add(tox3, address, (const uint8_t *)"Gentoo", 7, nullptr); - ck_assert_msg(test == 0, "Failed to add friend error code: %u", test); - - uint8_t off = 1; - - while (true) { - { - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox1, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch1, events, tox1); - tox_events_free(events); - } - { - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch2, events, tox2); - tox_events_free(events); - } - tox_iterate(tox3, nullptr); - - if (tox_self_get_connection_status(tox1) && tox_self_get_connection_status(tox2) - && tox_self_get_connection_status(tox3)) { - if (off) { - printf("Toxes are online, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); - con_time = time(nullptr); - off = 0; - } - - if (tox_friend_get_connection_status(tox2, 0, nullptr) == TOX_CONNECTION_TCP - && tox_friend_get_connection_status(tox3, 0, nullptr) == TOX_CONNECTION_TCP) { - break; - } - } - - increment_clock(&time_data, 200); - c_sleep(5); - } - - ck_assert_msg(connected_t1, "Tox1 isn't connected. %u", connected_t1); - printf("tox clients connected took %lu seconds\n", (unsigned long)(time(nullptr) - con_time)); - - // We're done with this callback, so unset it to ensure we don't fail the - // test if tox1 goes offline while tox2 and 3 are reloaded. - tox_events_callback_self_connection_status(dispatch1, nullptr); - - reload_tox(&tox2, opts2, &index[1]); - tox_events_init(tox2); - - reload_tox(&tox3, opts3, &index[2]); - - cur_time = time(nullptr); - - off = 1; - - while (true) { - { - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox1, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch1, events, tox1); - tox_events_free(events); - } - { - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch2, events, tox2); - tox_events_free(events); - } - tox_iterate(tox3, nullptr); - - if (tox_self_get_connection_status(tox1) && tox_self_get_connection_status(tox2) - && tox_self_get_connection_status(tox3)) { - if (off) { - printf("Toxes are online again after reloading, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); - con_time = time(nullptr); - off = 0; - } - - if (tox_friend_get_connection_status(tox2, 0, nullptr) == TOX_CONNECTION_TCP - && tox_friend_get_connection_status(tox3, 0, nullptr) == TOX_CONNECTION_TCP) { - break; - } - } - - increment_clock(&time_data, 100); - c_sleep(5); - } - - printf("tox clients connected took %lu seconds\n", (unsigned long)(time(nullptr) - con_time)); - - printf("test_few_clients succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); - - tox_dispatch_free(dispatch1); - tox_dispatch_free(dispatch2); - - tox_kill(tox1); - tox_kill(tox2); - tox_kill(tox3); - - tox_options_free(opts2); - tox_options_free(opts3); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - test_few_clients(); - return 0; -} diff --git a/auto_tests/scenarios/BUILD.bazel b/auto_tests/scenarios/BUILD.bazel new file mode 100644 index 00000000..05da628a --- /dev/null +++ b/auto_tests/scenarios/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +cc_library( + name = "scenario_framework", + testonly = True, + srcs = ["framework/framework.c"], + hdrs = ["framework/framework.h"], + visibility = ["//visibility:public"], + deps = [ + "//c-toxcore/testing:misc_tools", + "//c-toxcore/toxcore:mono_time", + "//c-toxcore/toxcore:network", + "//c-toxcore/toxcore:tox", + "//c-toxcore/toxcore:tox_dispatch", + "//c-toxcore/toxcore:tox_events", + ], +) + +[cc_test( + name = src[len("scenario_"):-2], + size = "small", + srcs = [src], + deps = [ + ":scenario_framework", + "//c-toxcore/toxav", + "//c-toxcore/toxcore:tox", + "//c-toxcore/toxcore:tox_events", + ], +) for src in glob(["scenario_*_test.c"])] diff --git a/auto_tests/scenarios/CMakeLists.txt b/auto_tests/scenarios/CMakeLists.txt new file mode 100644 index 00000000..ee70e420 --- /dev/null +++ b/auto_tests/scenarios/CMakeLists.txt @@ -0,0 +1,99 @@ +add_library(scenario_framework + framework/framework.c + framework/framework.h) +target_link_libraries(scenario_framework PUBLIC misc_tools) +if(TARGET toxcore_static) + target_link_libraries(scenario_framework PUBLIC toxcore_static) +else() + target_link_libraries(scenario_framework PUBLIC toxcore_shared) +endif() +if(TARGET pthreads4w::pthreads4w) + target_link_libraries(scenario_framework PUBLIC pthreads4w::pthreads4w) +elseif(TARGET PThreads4W::PThreads4W) + target_link_libraries(scenario_framework PUBLIC PThreads4W::PThreads4W) +elseif(TARGET Threads::Threads) + target_link_libraries(scenario_framework PUBLIC Threads::Threads) +endif() + +function(scenario_test target) + add_executable(auto_${target}_test ${target}_test.c) + target_link_libraries(auto_${target}_test PRIVATE misc_tools scenario_framework) + add_test(NAME ${target} COMMAND auto_${target}_test) + set_tests_properties(${target} PROPERTIES TIMEOUT "${TEST_TIMEOUT_SECONDS}") + # add the source dir as environment variable, so the testdata can be found + set_tests_properties(${target} PROPERTIES ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw;srcdir=${CMAKE_CURRENT_SOURCE_DIR}/..") +endfunction() + +scenario_test(scenario_avatar) +scenario_test(scenario_bootstrap) +scenario_test(scenario_conference) +scenario_test(scenario_conference_double_invite) +scenario_test(scenario_conference_invite_merge) +scenario_test(scenario_conference_offline) +scenario_test(scenario_conference_peer_nick) +scenario_test(scenario_conference_query) +scenario_test(scenario_conference_simple) +scenario_test(scenario_conference_two) +scenario_test(scenario_dht_nodes_response_api) +scenario_test(scenario_events) +scenario_test(scenario_file_cancel) +scenario_test(scenario_file_seek) +scenario_test(scenario_file_transfer) +scenario_test(scenario_friend_connection) +scenario_test(scenario_friend_delete) +scenario_test(scenario_friend_query) +scenario_test(scenario_friend_read_receipt) +scenario_test(scenario_friend_request) +scenario_test(scenario_friend_request_spam) +scenario_test(scenario_group_general) +scenario_test(scenario_group_invite) +scenario_test(scenario_group_message) +scenario_test(scenario_group_moderation) +scenario_test(scenario_group_save) +scenario_test(scenario_group_state) +scenario_test(scenario_group_sync) +scenario_test(scenario_group_tcp) +scenario_test(scenario_group_topic) +scenario_test(scenario_lan_discovery) +scenario_test(scenario_lossless_packet) +scenario_test(scenario_lossy_packet) +scenario_test(scenario_message) +scenario_test(scenario_netprof) +scenario_test(scenario_nospam) +scenario_test(scenario_overflow_recvq) +scenario_test(scenario_overflow_sendq) +scenario_test(scenario_reconnect) +scenario_test(scenario_save_friend) +scenario_test(scenario_save_load) +scenario_test(scenario_self_query) +scenario_test(scenario_send_message) +scenario_test(scenario_set_name) +scenario_test(scenario_set_status_message) +scenario_test(scenario_tox_many) +scenario_test(scenario_tox_many_tcp) +scenario_test(scenario_typing) +scenario_test(scenario_user_status) + +if(BUILD_TOXAV) + scenario_test(scenario_toxav_basic) + scenario_test(scenario_toxav_many) + scenario_test(scenario_conference_av) + + if(TARGET libvpx::libvpx) + target_link_libraries(auto_scenario_toxav_basic_test PRIVATE libvpx::libvpx) + target_link_libraries(auto_scenario_toxav_many_test PRIVATE libvpx::libvpx) + elseif(TARGET PkgConfig::VPX) + target_link_libraries(auto_scenario_toxav_basic_test PRIVATE PkgConfig::VPX) + target_link_libraries(auto_scenario_toxav_many_test PRIVATE PkgConfig::VPX) + else() + target_link_libraries(auto_scenario_toxav_basic_test PRIVATE ${VPX_LIBRARIES}) + target_link_directories(auto_scenario_toxav_basic_test PRIVATE ${VPX_LIBRARY_DIRS}) + target_include_directories(auto_scenario_toxav_basic_test SYSTEM PRIVATE ${VPX_INCLUDE_DIRS}) + target_compile_options(auto_scenario_toxav_basic_test PRIVATE ${VPX_CFLAGS_OTHER}) + + target_link_libraries(auto_scenario_toxav_many_test PRIVATE ${VPX_LIBRARIES}) + target_link_directories(auto_scenario_toxav_many_test PRIVATE ${VPX_LIBRARY_DIRS}) + target_include_directories(auto_scenario_toxav_many_test SYSTEM PRIVATE ${VPX_INCLUDE_DIRS}) + target_compile_options(auto_scenario_toxav_many_test PRIVATE ${VPX_CFLAGS_OTHER}) + endif() +endif() diff --git a/auto_tests/scenarios/framework/README.md b/auto_tests/scenarios/framework/README.md new file mode 100644 index 00000000..8c6eadc3 --- /dev/null +++ b/auto_tests/scenarios/framework/README.md @@ -0,0 +1,79 @@ +# Tox Scenario Framework + +Tests should read like protocol specifications. Instead of managing manual loops +and shared global state, each "node" in a test is assigned a script (sequence of +tasks) that it executes concurrently with other nodes in a simulated +environment. + +### 1. `ToxScenario` + +The container for a single test case. + +- Manages a collection of `ToxNode` instances. +- Owns the **Virtual Clock**. All nodes in a scenario share the same timeline, + making timeouts and delays deterministic. +- Handles the orchestration of the event loop (`tox_iterate`). + +### 2. `ToxNode` + +A wrapper around a `Tox` instance and its associated state. + +- Each node has its own `Tox_Dispatch`. +- Nodes are identified by a simple index or a string alias (e.g., "Alice", + "Bob"). +- Encapsulates the node's progress through its assigned Script. + +### 3. `tox_node_script_cb` + +A C function that defines what a node does. Because it runs in its own thread, +you can use standard C control flow, local variables, and loops. + +## Example Usage + +```c +void alice_script(ToxNode *self, void *ctx) { + // Step 1: Wait for DHT connection + WAIT_UNTIL(tox_node_is_self_connected(self)); + + // Step 2: Send a message to Bob (friend 0) + uint8_t msg[] = "Hello Bob"; + tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), NULL); + + // Step 3: Wait for a specific response event + WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE); +} + +void test_simple_interaction() { + ToxScenario *s = tox_scenario_new(argc, argv, 10000); // 10s virtual timeout + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, NULL); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, NULL); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + ck_assert(res == TOX_SCENARIO_DONE); + tox_scenario_free(s); +} +``` + +## Execution Model: Cooperative Multi-threading + +The scenario framework uses `pthread` to provide a natural programming model +while maintaining a deterministic virtual clock: + +1. **Runner**: Orchestrates the scenario in "Ticks" (default 10ms). +2. **Nodes**: Run concurrently in their own threads but **synchronize** at + every tick. +3. **Yielding**: When a script calls `WAIT_UNTIL`, `WAIT_FOR_EVENT`, or + `tox_scenario_yield`, it yields control back to the runner. +4. **Time Advancement**: The runner advances the virtual clock and signals all + nodes to proceed only after all active nodes have yielded. +5. **Barriers**: Nodes can synchronize their progress using + `tox_scenario_barrier_wait(self)`. This function blocks the calling node + until all other active nodes have also reached the barrier. This is useful + for ensuring setup steps (like group creation or connection establishment) + are complete across all nodes before proceeding to the next phase of the + test. diff --git a/auto_tests/scenarios/framework/framework.c b/auto_tests/scenarios/framework/framework.c new file mode 100644 index 00000000..dfb18caa --- /dev/null +++ b/auto_tests/scenarios/framework/framework.c @@ -0,0 +1,800 @@ +#include "framework.h" + +#include +#include +#include +#include +#include +#include + +#include "../../../testing/misc_tools.h" +#include "../../../toxcore/tox_struct.h" +#include "../../../toxcore/network.h" + +#define MAX_NODES 128 + +typedef struct { + Tox_Connection connection_status; + bool finished; + bool offline; + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + uint8_t dht_id[TOX_PUBLIC_KEY_SIZE]; + uint8_t address[TOX_ADDRESS_SIZE]; + uint16_t udp_port; +} ToxNodeMirror; + +struct ToxNode { + Tox *tox; + Tox_Options *options; + Tox_Dispatch *dispatch; + uint32_t index; + char *alias; + + ToxScenario *scenario; + pthread_t thread; + tox_node_script_cb *script; + void *script_ctx; + size_t script_ctx_size; + + void *mirrored_ctx; + void *mirrored_ctx_public; + ToxNodeMirror mirror; + ToxNodeMirror mirror_public; + + bool finished; + bool offline; + uint32_t barrier_index; + uint64_t last_tick; + bool seen_events[256]; +}; + +struct ToxScenario { + ToxNode *nodes[MAX_NODES]; + uint32_t num_nodes; + uint32_t num_active; + uint32_t num_ready; + + uint64_t virtual_clock; + uint64_t timeout_ms; + uint64_t tick_count; + + pthread_mutex_t mutex; + pthread_mutex_t clock_mutex; + pthread_cond_t cond_runner; + pthread_cond_t cond_nodes; + + bool run_started; + + bool trace_enabled; + bool event_log_enabled; + + struct { + const char *name; + uint32_t count; + } barrier; +}; + +static uint64_t get_scenario_clock(void *user_data) +{ + ToxScenario *s = (ToxScenario *)user_data; + pthread_mutex_lock(&s->clock_mutex); + uint64_t time = s->virtual_clock; + pthread_mutex_unlock(&s->clock_mutex); + return time; +} + +static void framework_debug_log(Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, + const char *func, const char *message, void *user_data) +{ + ToxNode *node = (ToxNode *)user_data; + ck_assert(node != nullptr); + + if (level == TOX_LOG_LEVEL_TRACE) { + if (node == nullptr || !node->scenario->trace_enabled) { + return; + } + } + + const char *level_name = "UNKNOWN"; + switch (level) { + case TOX_LOG_LEVEL_TRACE: + level_name = "TRACE"; + break; + case TOX_LOG_LEVEL_DEBUG: + level_name = "DEBUG"; + break; + case TOX_LOG_LEVEL_INFO: + level_name = "INFO"; + break; + case TOX_LOG_LEVEL_WARNING: + level_name = "WARN"; + break; + case TOX_LOG_LEVEL_ERROR: + level_name = "ERROR"; + break; + } + + const uint64_t relative_time = node ? (get_scenario_clock(node->scenario) - 1000) : 0; + fprintf(stderr, "[%08lu] [%s] %s %s:%u %s: %s\n", (unsigned long)relative_time, + node != nullptr ? node->alias : "Unknown", level_name, file, line, func, message); +} + +void tox_node_log(ToxNode *node, const char *format, ...) +{ + ck_assert(node != nullptr); + va_list args; + va_start(args, format); + uint64_t relative_time = get_scenario_clock(node->scenario) - 1000; + fprintf(stderr, "[%08lu] [%s] ", (unsigned long)relative_time, node->alias ? node->alias : "Unknown"); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + va_end(args); +} + +void tox_scenario_log(const ToxScenario *s, const char *format, ...) +{ + va_list args; + va_start(args, format); + uint64_t relative_time = get_scenario_clock((void *)(uintptr_t)s) - 1000; + fprintf(stderr, "[%08lu] [Runner] ", (unsigned long)relative_time); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + va_end(args); +} + +ToxScenario *tox_scenario_new(int argc, char *const argv[], uint64_t timeout_ms) +{ + static bool seeded = false; + if (!seeded) { + srand(time(nullptr)); + seeded = true; + } + + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--wait-time") == 0 && i + 1 < argc) { + timeout_ms = (uint64_t)atoll(argv[i + 1]) * 1000; + break; + } + } + + ToxScenario *s = (ToxScenario *)calloc(1, sizeof(ToxScenario)); + ck_assert(s != nullptr); + s->timeout_ms = timeout_ms; + s->virtual_clock = 1000; + s->trace_enabled = (getenv("TOX_TRACE") != nullptr); + s->event_log_enabled = (getenv("TOX_EVENT_LOG") != nullptr); + + pthread_mutex_init(&s->mutex, nullptr); + pthread_mutex_init(&s->clock_mutex, nullptr); + pthread_cond_init(&s->cond_runner, nullptr); + pthread_cond_init(&s->cond_nodes, nullptr); + return s; +} + +void tox_scenario_free(ToxScenario *s) +{ + for (uint32_t i = 0; i < s->num_nodes; ++i) { + ToxNode *node = s->nodes[i]; + ck_assert(node != nullptr); + tox_dispatch_free(node->dispatch); + tox_kill(node->tox); + tox_options_free(node->options); + free(node->alias); + free(node->mirrored_ctx); + free(node->mirrored_ctx_public); + free(node); + } + pthread_mutex_destroy(&s->mutex); + pthread_mutex_destroy(&s->clock_mutex); + pthread_cond_destroy(&s->cond_runner); + pthread_cond_destroy(&s->cond_nodes); + free(s); +} + +Tox *tox_node_get_tox(const ToxNode *node) +{ + ck_assert(node != nullptr); + ToxScenario *s = node->scenario; + pthread_mutex_lock(&s->mutex); + Tox *tox = node->tox; + pthread_mutex_unlock(&s->mutex); + return tox; +} + +void tox_node_get_address(const ToxNode *node, uint8_t *address) +{ + ck_assert(node != nullptr); + ck_assert(address != nullptr); + memcpy(address, node->mirror_public.address, TOX_ADDRESS_SIZE); +} + +ToxScenario *tox_node_get_scenario(const ToxNode *node) +{ + ck_assert(node != nullptr); + return node->scenario; +} + +Tox_Connection tox_node_get_connection_status(const ToxNode *node) +{ + ck_assert(node != nullptr); + return node->mirror_public.connection_status; +} + +Tox_Connection tox_node_get_friend_connection_status(const ToxNode *node, uint32_t friend_number) +{ + ck_assert(node != nullptr); + // Note: Friend status is not currently mirrored. + // This is safe if called by the node on itself. + // If called on a peer, it might still race. + Tox_Err_Friend_Query err; + Tox_Connection conn = tox_friend_get_connection_status(node->tox, friend_number, &err); + if (err != TOX_ERR_FRIEND_QUERY_OK) { + return TOX_CONNECTION_NONE; + } + return conn; +} + +bool tox_node_is_self_connected(const ToxNode *node) +{ + return tox_node_get_connection_status(node) != TOX_CONNECTION_NONE; +} + +bool tox_node_is_friend_connected(const ToxNode *node, uint32_t friend_number) +{ + return tox_node_get_friend_connection_status(node, friend_number) != TOX_CONNECTION_NONE; +} + +uint32_t tox_node_get_conference_peer_count(const ToxNode *node, uint32_t conference_number) +{ + ck_assert(node != nullptr); + Tox_Err_Conference_Peer_Query err; + uint32_t count = tox_conference_peer_count(node->tox, conference_number, &err); + if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { + return 0; + } + return count; +} + +void tox_node_set_offline(ToxNode *node, bool offline) +{ + ck_assert(node != nullptr); + node->offline = offline; +} + +bool tox_node_is_offline(const ToxNode *node) +{ + ck_assert(node != nullptr); + return node->mirror_public.offline; +} + +bool tox_node_is_finished(const ToxNode *node) +{ + ck_assert(node != nullptr); + ToxScenario *s = node->scenario; + pthread_mutex_lock(&s->mutex); + bool finished = node->mirror_public.finished; + pthread_mutex_unlock(&s->mutex); + return finished; +} + +bool tox_scenario_is_running(ToxNode *self) +{ + bool running; + pthread_mutex_lock(&self->scenario->mutex); + running = self->scenario->run_started; + pthread_mutex_unlock(&self->scenario->mutex); + return running; +} + +bool tox_node_friend_name_is(const ToxNode *node, uint32_t friend_number, const uint8_t *name, size_t length) +{ + ck_assert(node != nullptr); + Tox_Err_Friend_Query err; + size_t actual_len = tox_friend_get_name_size(node->tox, friend_number, &err); + if (err != TOX_ERR_FRIEND_QUERY_OK || actual_len != length) { + return false; + } + uint8_t *actual_name = (uint8_t *)malloc(length); + ck_assert(actual_name != nullptr); + tox_friend_get_name(node->tox, friend_number, actual_name, nullptr); + bool match = (memcmp(actual_name, name, length) == 0); + free(actual_name); + return match; +} + +bool tox_node_friend_status_message_is(const ToxNode *node, uint32_t friend_number, const uint8_t *msg, size_t length) +{ + ck_assert(node != nullptr); + Tox_Err_Friend_Query err; + size_t actual_len = tox_friend_get_status_message_size(node->tox, friend_number, &err); + if (err != TOX_ERR_FRIEND_QUERY_OK || actual_len != length) { + return false; + } + uint8_t *actual_msg = (uint8_t *)malloc(length); + ck_assert(actual_msg != nullptr); + tox_friend_get_status_message(node->tox, friend_number, actual_msg, nullptr); + bool match = (memcmp(actual_msg, msg, length) == 0); + free(actual_msg); + return match; +} + +bool tox_node_friend_typing_is(const ToxNode *node, uint32_t friend_number, bool is_typing) +{ + ck_assert(node != nullptr); + Tox_Err_Friend_Query err; + bool actual = tox_friend_get_typing(node->tox, friend_number, &err); + return (err == TOX_ERR_FRIEND_QUERY_OK && actual == is_typing); +} + +void tox_scenario_yield(ToxNode *self) +{ + // 1. Poll Tox (Strictly in the node's thread), only if not offline + if (!tox_node_is_offline(self)) { + Tox_Err_Events_Iterate ev_err; + Tox_Events *events = tox_events_iterate(self->tox, false, &ev_err); + if (events != nullptr) { + uint32_t size = tox_events_get_size(events); + for (uint32_t j = 0; j < size; ++j) { + const Tox_Event *ev = tox_events_get(events, j); + const Tox_Event_Type type = tox_event_get_type(ev); + self->seen_events[type] = true; + if (self->scenario->event_log_enabled) { + tox_node_log(self, "Received event: %s (%u)", tox_event_type_to_string(type), type); + } + } + tox_dispatch_invoke(self->dispatch, events, self); + tox_events_free(events); + } + } + + ToxScenario *s = self->scenario; + + // 2. Update mirror (Outside lock to reduce contention) + self->mirror.connection_status = tox_self_get_connection_status(self->tox); + self->mirror.offline = self->offline; + tox_self_get_public_key(self->tox, self->mirror.public_key); + tox_self_get_dht_id(self->tox, self->mirror.dht_id); + tox_self_get_address(self->tox, self->mirror.address); + Tox_Err_Get_Port port_err; + self->mirror.udp_port = tox_self_get_udp_port(self->tox, &port_err); + + if (self->mirrored_ctx != nullptr && self->script_ctx != nullptr) { + memcpy(self->mirrored_ctx, self->script_ctx, self->script_ctx_size); + } + + pthread_mutex_lock(&s->mutex); + + self->mirror.finished = self->finished; + self->last_tick = s->tick_count; + s->num_ready++; + + // Wake runner + pthread_cond_signal(&s->cond_runner); + + // Wait for next tick + while (self->last_tick == s->tick_count && s->run_started) { + pthread_cond_wait(&s->cond_nodes, &s->mutex); + } + + pthread_mutex_unlock(&s->mutex); + + // Give the OS a chance to deliver UDP packets + c_sleep(1); +} + +void tox_scenario_wait_for_event(ToxNode *self, Tox_Event_Type type) +{ + while (!self->seen_events[type] && tox_scenario_is_running(self)) { + tox_scenario_yield(self); + } +} + +void tox_scenario_barrier_wait(ToxNode *self) +{ + ToxScenario *s = self->scenario; + pthread_mutex_lock(&s->mutex); + uint32_t my_barrier_index = ++self->barrier_index; + pthread_mutex_unlock(&s->mutex); + + while (tox_scenario_is_running(self)) { + // Even if all nodes have reached the barrier, we MUST yield at least once + // to ensure the runner has a chance to synchronize the snapshots for this barrier. + tox_scenario_yield(self); + + pthread_mutex_lock(&s->mutex); + uint32_t reached = 0; + for (uint32_t i = 0; i < s->num_nodes; ++i) { + if (s->nodes[i]->barrier_index >= my_barrier_index || s->nodes[i]->finished) { + reached++; + } + } + const bool done = reached >= s->num_nodes; + pthread_mutex_unlock(&s->mutex); + + if (done) { + break; + } + } +} + +void tox_node_wait_for_self_connected(ToxNode *self) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); +} + +void tox_node_wait_for_friend_connected(ToxNode *self, uint32_t friend_number) +{ + WAIT_UNTIL(tox_node_is_friend_connected(self, friend_number)); +} + +static void *node_thread_wrapper(void *arg) +{ + ToxNode *node = (ToxNode *)arg; + ck_assert(node != nullptr); + ToxScenario *s = node->scenario; + + // Wait for the starting signal from runner + pthread_mutex_lock(&s->mutex); + s->num_ready++; + pthread_cond_signal(&s->cond_runner); + while (s->tick_count == 0 && s->run_started) { + pthread_cond_wait(&s->cond_nodes, &s->mutex); + } + pthread_mutex_unlock(&s->mutex); + + // Run the actual script + srand(time(nullptr) + node->index); + node->script(node, node->script_ctx); + + // After the script is done, keep polling until the scenario is over + pthread_mutex_lock(&s->mutex); + node->finished = true; + s->num_active--; + uint32_t active = s->num_active; + pthread_mutex_unlock(&s->mutex); + + tox_node_log(node, "finished script, active nodes remaining: %u", active); + + pthread_mutex_lock(&s->mutex); + pthread_cond_signal(&s->cond_runner); + + while (s->run_started) { + uint64_t tick_at_start = s->tick_count; + s->num_ready++; + pthread_cond_signal(&s->cond_runner); + + while (tick_at_start == s->tick_count && s->run_started) { + pthread_cond_wait(&s->cond_nodes, &s->mutex); + } + pthread_mutex_unlock(&s->mutex); + + // 2. Update mirror outside lock + node->mirror.connection_status = tox_self_get_connection_status(node->tox); + node->mirror.finished = node->finished; + node->mirror.offline = node->offline; + + if (node->mirrored_ctx != nullptr && node->script_ctx != nullptr) { + memcpy(node->mirrored_ctx, node->script_ctx, node->script_ctx_size); + } + + // Poll Tox even when script is "finished" + Tox_Err_Events_Iterate ev_err; + Tox_Events *events = tox_events_iterate(node->tox, false, &ev_err); + if (events != nullptr) { + tox_dispatch_invoke(node->dispatch, events, node); + tox_events_free(events); + } + pthread_mutex_lock(&s->mutex); + } + pthread_mutex_unlock(&s->mutex); + + return nullptr; +} + +static Tox *create_tox_with_port_retry(Tox_Options *opts, void *log_user_data, Tox_Err_New *out_err) +{ + tox_options_set_log_user_data(opts, log_user_data); + + if (tox_options_get_start_port(opts) == 0 && tox_options_get_end_port(opts) == 0) { + tox_options_set_start_port(opts, TOX_PORT_DEFAULT); + tox_options_set_end_port(opts, 65535); + } + + uint16_t tcp_port = tox_options_get_tcp_port(opts); + Tox *tox = nullptr; + + for (int i = 0; i < 50; ++i) { + tox = tox_new(opts, out_err); + + if (*out_err != TOX_ERR_NEW_PORT_ALLOC || tcp_port == 0) { + break; + } + + tcp_port++; + tox_options_set_tcp_port(opts, tcp_port); + } + + return tox; +} + +ToxNode *tox_scenario_add_node_ex(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size, const Tox_Options *options) +{ + if (s->num_nodes >= MAX_NODES) { + return nullptr; + } + + ToxNode *node = (ToxNode *)calloc(1, sizeof(ToxNode)); + ck_assert(node != nullptr); + if (alias != nullptr) { + size_t len = strlen(alias) + 1; + node->alias = (char *)malloc(len); + ck_assert(node->alias != nullptr); + memcpy(node->alias, alias, len); + } + node->index = s->num_nodes; + node->scenario = s; + node->script = script; + node->script_ctx = ctx; + node->script_ctx_size = ctx_size; + + if (ctx_size > 0) { + node->mirrored_ctx = calloc(1, ctx_size); + ck_assert(node->mirrored_ctx != nullptr); + node->mirrored_ctx_public = calloc(1, ctx_size); + ck_assert(node->mirrored_ctx_public != nullptr); + if (ctx != nullptr) { + memcpy(node->mirrored_ctx, ctx, ctx_size); + memcpy(node->mirrored_ctx_public, ctx, ctx_size); + } + } + + Tox_Options *opts = tox_options_new(nullptr); + if (options != nullptr) { + tox_options_copy(opts, options); + } else { + tox_options_set_ipv6_enabled(opts, false); + tox_options_set_local_discovery_enabled(opts, false); + } + + tox_options_set_log_callback(opts, framework_debug_log); + Tox_Err_New err; + node->tox = create_tox_with_port_retry(opts, node, &err); + + if (err == TOX_ERR_NEW_OK) { + ck_assert(node->tox != nullptr); + node->options = tox_options_new(nullptr); + tox_options_copy(node->options, opts); + } + + tox_options_free(opts); + + + if (err != TOX_ERR_NEW_OK) { + tox_scenario_log(s, "Failed to create Tox instance for node %s: %u", alias, err); + free(node); + return nullptr; + } + + node->dispatch = tox_dispatch_new(nullptr); + tox_events_init(node->tox); + mono_time_set_current_time_callback(node->tox->mono_time, get_scenario_clock, s); + + // Initial mirror population + node->mirror.connection_status = tox_self_get_connection_status(node->tox); + tox_self_get_public_key(node->tox, node->mirror.public_key); + tox_self_get_dht_id(node->tox, node->mirror.dht_id); + tox_self_get_address(node->tox, node->mirror.address); + Tox_Err_Get_Port port_err; + node->mirror.udp_port = tox_self_get_udp_port(node->tox, &port_err); + + node->mirror_public = node->mirror; + + s->nodes[s->num_nodes++] = node; + return node; +} + +ToxNode *tox_scenario_add_node(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size) +{ + return tox_scenario_add_node_ex(s, alias, script, ctx, ctx_size, nullptr); +} + +ToxNode *tox_scenario_get_node(ToxScenario *s, uint32_t index) +{ + if (index >= s->num_nodes) { + return nullptr; + } + return s->nodes[index]; +} + +const char *tox_node_get_alias(const ToxNode *node) +{ + ck_assert(node != nullptr); + return node->alias; +} + +uint32_t tox_node_get_index(const ToxNode *node) +{ + ck_assert(node != nullptr); + return node->index; +} + +void *tox_node_get_script_ctx(const ToxNode *node) +{ + ck_assert(node != nullptr); + return node->script_ctx; +} + +const void *tox_node_get_peer_ctx(const ToxNode *node) +{ + ck_assert(node != nullptr); + return node->mirrored_ctx_public; +} + +Tox_Dispatch *tox_node_get_dispatch(const ToxNode *node) +{ + ck_assert(node != nullptr); + return node->dispatch; +} + +uint64_t tox_scenario_get_time(ToxScenario *s) +{ + pthread_mutex_lock(&s->clock_mutex); + uint64_t time = s->virtual_clock; + pthread_mutex_unlock(&s->clock_mutex); + return time; +} + +ToxScenarioStatus tox_scenario_run(ToxScenario *s) +{ + pthread_mutex_lock(&s->mutex); + s->num_active = s->num_nodes; + s->num_ready = 0; + s->tick_count = 0; + s->run_started = true; + pthread_mutex_unlock(&s->mutex); + + // Start all node threads + for (uint32_t i = 0; i < s->num_nodes; ++i) { + pthread_create(&s->nodes[i]->thread, nullptr, node_thread_wrapper, s->nodes[i]); + } + + pthread_mutex_lock(&s->mutex); + + uint64_t start_clock = s->virtual_clock; + uint64_t deadline = start_clock + s->timeout_ms; + + while (s->num_active > 0 && s->virtual_clock < deadline) { + // 1. Wait until all nodes (including finished ones) have reached the barrier + while (s->num_ready < s->num_nodes) { + pthread_cond_wait(&s->cond_runner, &s->mutex); + } + + // 2. Synchronize Snapshots + for (uint32_t i = 0; i < s->num_nodes; ++i) { + ToxNode *node = s->nodes[i]; + ck_assert(node != nullptr); + node->mirror_public = node->mirror; + if (node->mirrored_ctx_public && node->mirrored_ctx) { + memcpy(node->mirrored_ctx_public, node->mirrored_ctx, node->script_ctx_size); + } + } + + // 3. Advance tick and clock + s->num_ready = 0; + s->tick_count++; + pthread_mutex_lock(&s->clock_mutex); + s->virtual_clock += TOX_SCENARIO_TICK_MS; + pthread_mutex_unlock(&s->clock_mutex); + + if (s->tick_count % 100 == 0) { + uint64_t tick = s->tick_count; + uint32_t active = s->num_active; + pthread_mutex_unlock(&s->mutex); + tox_scenario_log(s, "Tick %lu, active nodes: %u", (unsigned long)tick, active); + pthread_mutex_lock(&s->mutex); + } + + // 4. Release nodes for the new tick + pthread_cond_broadcast(&s->cond_nodes); + } + + ToxScenarioStatus result = (s->num_active == 0) ? TOX_SCENARIO_DONE : TOX_SCENARIO_TIMEOUT; + if (result == TOX_SCENARIO_TIMEOUT) { + tox_scenario_log(s, "Scenario TIMEOUT after %lu ms (virtual time)", (unsigned long)(s->virtual_clock - start_clock)); + } + + // Stop nodes + s->run_started = false; + pthread_cond_broadcast(&s->cond_nodes); + pthread_mutex_unlock(&s->mutex); + + for (uint32_t i = 0; i < s->num_nodes; ++i) { + pthread_join(s->nodes[i]->thread, nullptr); + } + + return result; +} + +void tox_node_bootstrap(ToxNode *a, ToxNode *b) +{ + uint8_t pk[TOX_PUBLIC_KEY_SIZE]; + memcpy(pk, b->mirror_public.dht_id, TOX_PUBLIC_KEY_SIZE); + const uint16_t port = b->mirror_public.udp_port; + + Tox_Err_Bootstrap err; + tox_bootstrap(a->tox, "127.0.0.1", port, pk, &err); + if (err != TOX_ERR_BOOTSTRAP_OK) { + tox_node_log(a, "Error bootstrapping from %s: %u", b->alias, err); + } +} + +void tox_node_friend_add(ToxNode *a, ToxNode *b) +{ + uint8_t pk[TOX_PUBLIC_KEY_SIZE]; + memcpy(pk, b->mirror_public.public_key, TOX_PUBLIC_KEY_SIZE); + Tox_Err_Friend_Add err; + tox_friend_add_norequest(a->tox, pk, &err); + if (err != TOX_ERR_FRIEND_ADD_OK) { + tox_node_log(a, "Error adding friend %s: %u", b->alias, err); + } +} + +void tox_node_reload(ToxNode *node) +{ + ck_assert(node != nullptr); + ToxScenario *s = node->scenario; + pthread_mutex_lock(&s->mutex); + + // 1. Save data + size_t size = tox_get_savedata_size(node->tox); + uint8_t *data = (uint8_t *)malloc(size); + ck_assert(data != nullptr); + tox_get_savedata(node->tox, data); + + Tox *old_tox = node->tox; + Tox_Dispatch *old_dispatch = node->dispatch; + + // Invalidate node state while reloading + node->tox = nullptr; + node->dispatch = nullptr; + + pthread_mutex_unlock(&s->mutex); + + // 2. Kill old instance + tox_dispatch_free(old_dispatch); + tox_kill(old_tox); + + // 3. Create new instance from save + Tox_Options *opts = tox_options_new(nullptr); + ck_assert(opts != nullptr); + tox_options_copy(opts, node->options); + tox_options_set_savedata_type(opts, TOX_SAVEDATA_TYPE_TOX_SAVE); + tox_options_set_savedata_data(opts, data, size); + + Tox_Err_New err; + Tox *new_tox = create_tox_with_port_retry(opts, node, &err); + tox_options_free(opts); + free(data); + + if (err != TOX_ERR_NEW_OK) { + tox_node_log(node, "Failed to reload Tox instance: %u", err); + return; + } + + ck_assert_msg(new_tox != nullptr, "tox_new said OK but returned NULL"); + Tox_Dispatch *new_dispatch = tox_dispatch_new(nullptr); + tox_events_init(new_tox); + mono_time_set_current_time_callback(new_tox->mono_time, get_scenario_clock, s); + + pthread_mutex_lock(&s->mutex); + node->tox = new_tox; + node->dispatch = new_dispatch; + pthread_mutex_unlock(&s->mutex); + + // Re-bootstrap from siblings + for (uint32_t i = 0; i < s->num_nodes; ++i) { + if (i != node->index) { + tox_node_bootstrap(node, s->nodes[i]); + } + } +} diff --git a/auto_tests/scenarios/framework/framework.h b/auto_tests/scenarios/framework/framework.h new file mode 100644 index 00000000..bde450c2 --- /dev/null +++ b/auto_tests/scenarios/framework/framework.h @@ -0,0 +1,215 @@ +#ifndef TOX_TEST_FRAMEWORK_H +#define TOX_TEST_FRAMEWORK_H + +#include +#include +#include +#include + +#include "../../../toxcore/attributes.h" +#include "../../../toxcore/ccompat.h" +#include "../../../toxcore/tox.h" +#include "../../../toxcore/tox_dispatch.h" + +// --- Constants --- +#define TOX_SCENARIO_TICK_MS 50 + +// --- Opaque Types --- +typedef struct ToxNode ToxNode; +typedef struct ToxScenario ToxScenario; + +typedef enum { + TOX_SCENARIO_OK, + TOX_SCENARIO_RUNNING, + TOX_SCENARIO_DONE, + TOX_SCENARIO_ERROR, + TOX_SCENARIO_TIMEOUT +} ToxScenarioStatus; + +/** + * A script function that defines the behavior of a node. + */ +typedef void tox_node_script_cb(ToxNode *self, void *ctx); + +// --- Core API --- + +ToxScenario *tox_scenario_new(int argc, char *const argv[], uint64_t timeout_ms); +void tox_scenario_free(ToxScenario *s); + +/** + * Adds a node to the scenario and assigns it a script. + * @param ctx_size The size of the context struct to be mirrored (snapshot) at each tick. + */ +ToxNode *tox_scenario_add_node(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size); + +/** + * Extended version of add_node that accepts custom Tox_Options. + */ +ToxNode *tox_scenario_add_node_ex(ToxScenario *s, const char *alias, tox_node_script_cb *script, void *ctx, size_t ctx_size, const Tox_Options *options); + +/** + * Returns a node by its index in the scenario. + */ +ToxNode *tox_scenario_get_node(ToxScenario *s, uint32_t index); + +/** + * Runs the scenario until all nodes complete their scripts or timeout occurs. + */ +ToxScenarioStatus tox_scenario_run(ToxScenario *s); + +// --- Logging API --- + +/** + * Logs a message with the node's alias/index and current virtual time. + */ +void tox_node_log(ToxNode *node, const char *format, ...) GNU_PRINTF(2, 3); + +/** + * Logs a message from the scenario runner with current virtual time. + */ +void tox_scenario_log(const ToxScenario *s, const char *format, ...) GNU_PRINTF(2, 3); + +// --- Script API (Callable from within scripts) --- + +/** + * Returns the underlying Tox instance. + */ +Tox *tox_node_get_tox(const ToxNode *node); + +/** + * Returns the mirrored address of the node. + */ +void tox_node_get_address(const ToxNode *node, uint8_t *address); + +/** + * Returns the scenario this node belongs to. + */ +ToxScenario *tox_node_get_scenario(const ToxNode *node); + +/** + * Returns the alias of the node. + */ +const char *tox_node_get_alias(const ToxNode *node); + +/** + * Returns the index of the node in the scenario. + */ +uint32_t tox_node_get_index(const ToxNode *node); + +/** + * Returns the script context associated with this node. + */ +void *tox_node_get_script_ctx(const ToxNode *node); + +/** + * Returns a read-only snapshot of a peer's context. + * The snapshot is updated at every tick. + */ +const void *tox_node_get_peer_ctx(const ToxNode *node); + +/** + * Returns the event dispatcher associated with this node. + */ +Tox_Dispatch *tox_node_get_dispatch(const ToxNode *node); + +/** + * Predicate helpers for common wait conditions. + */ +bool tox_node_is_self_connected(const ToxNode *node); +bool tox_node_is_friend_connected(const ToxNode *node, uint32_t friend_number); + +Tox_Connection tox_node_get_connection_status(const ToxNode *node); +Tox_Connection tox_node_get_friend_connection_status(const ToxNode *node, uint32_t friend_number); + +/** + * Conference helpers. + */ +uint32_t tox_node_get_conference_peer_count(const ToxNode *node, uint32_t conference_number); + +/** + * Network simulation. + */ +void tox_node_set_offline(ToxNode *node, bool offline); +bool tox_node_is_offline(const ToxNode *node); + +/** + * Returns true if the node has finished its script. + */ +bool tox_node_is_finished(const ToxNode *node); + +/** + * High-level predicates for specific state changes. + */ +bool tox_node_friend_name_is(const ToxNode *node, uint32_t friend_number, const uint8_t *name, size_t length); +bool tox_node_friend_status_message_is(const ToxNode *node, uint32_t friend_number, const uint8_t *msg, size_t length); +bool tox_node_friend_typing_is(const ToxNode *node, uint32_t friend_number, bool is_typing); + +/** + * Yields execution to the next tick. + */ +void tox_scenario_yield(ToxNode *self); + +/** + * Returns the current virtual time in milliseconds. + */ +uint64_t tox_scenario_get_time(ToxScenario *s); + +/** + * Returns true if the scenario is still running. + */ +bool tox_scenario_is_running(ToxNode *self); + +/** + * Blocks until a condition is true. Polls every tick. + */ +#define WAIT_UNTIL(cond) do { while(!(cond) && tox_scenario_is_running(self)) { tox_scenario_yield(self); } } while(0) + +/** + * Blocks until a specific event is observed. + */ +void tox_scenario_wait_for_event(ToxNode *self, Tox_Event_Type type); + +#define WAIT_FOR_EVENT(type) tox_scenario_wait_for_event(self, type) + +/** + * Synchronization barriers. + * Blocks until all active nodes have reached this barrier. + */ +void tox_scenario_barrier_wait(ToxNode *self); + +/** + * High-level wait helpers. + */ +void tox_node_wait_for_self_connected(ToxNode *self); +void tox_node_wait_for_friend_connected(ToxNode *self, uint32_t friend_number); + +// --- High-Level Helpers --- + +void tox_node_bootstrap(ToxNode *a, ToxNode *b); +void tox_node_friend_add(ToxNode *a, ToxNode *b); + +/** + * Reloads a node from its own savedata. + * This simulates a client restart. + */ +void tox_node_reload(ToxNode *node); + +#ifndef ck_assert +#define ck_assert(ok) do { \ + if (!(ok)) { \ + fprintf(stderr, "%s:%d: failed `%s'\n", __FILE__, __LINE__, #ok); \ + exit(7); \ + } \ +} while (0) + +#define ck_assert_msg(ok, ...) do { \ + if (!(ok)) { \ + fprintf(stderr, "%s:%d: failed `%s': ", __FILE__, __LINE__, #ok); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + exit(7); \ + } \ +} while (0) +#endif + +#endif // TOX_TEST_FRAMEWORK_H diff --git a/auto_tests/scenarios/scenario_avatar_test.c b/auto_tests/scenarios/scenario_avatar_test.c new file mode 100644 index 00000000..4080af13 --- /dev/null +++ b/auto_tests/scenarios/scenario_avatar_test.c @@ -0,0 +1,144 @@ +#include "framework/framework.h" +#include +#include +#include + +#define AVATAR_SIZE 100 +static const uint8_t avatar_data[AVATAR_SIZE] = "This is a fake avatar image data. It should be longer but for testing this is enough."; + +typedef struct { + uint8_t avatar_hash[TOX_HASH_LENGTH]; + bool transfer_finished; +} AvatarState; + +static void on_file_chunk_request(const Tox_Event_File_Chunk_Request *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + AvatarState *state = (AvatarState *)tox_node_get_script_ctx(self); + + uint32_t friend_number = tox_event_file_chunk_request_get_friend_number(event); + uint32_t file_number = tox_event_file_chunk_request_get_file_number(event); + uint64_t position = tox_event_file_chunk_request_get_position(event); + size_t length = tox_event_file_chunk_request_get_length(event); + + if (length == 0) { + state->transfer_finished = true; + return; + } + + if (position >= AVATAR_SIZE) { + tox_file_send_chunk(tox_node_get_tox(self), friend_number, file_number, position, nullptr, 0, nullptr); + state->transfer_finished = true; + return; + } + + size_t to_send = AVATAR_SIZE - (size_t)position; + if (to_send > length) { + to_send = length; + } + + tox_file_send_chunk(tox_node_get_tox(self), friend_number, file_number, position, avatar_data + position, to_send, nullptr); +} + +static void alice_script(ToxNode *self, void *ctx) +{ + AvatarState *state = (AvatarState *)ctx; + tox_events_callback_file_chunk_request(tox_node_get_dispatch(self), on_file_chunk_request); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + Tox *tox = tox_node_get_tox(self); + tox_hash(state->avatar_hash, avatar_data, AVATAR_SIZE); + + tox_node_log(self, "Sending avatar request to Bob..."); + Tox_Err_File_Send err_send; + tox_file_send(tox, 0, TOX_FILE_KIND_AVATAR, AVATAR_SIZE, state->avatar_hash, (const uint8_t *)"avatar.png", 10, &err_send); + ck_assert(err_send == TOX_ERR_FILE_SEND_OK); + + WAIT_UNTIL(state->transfer_finished); + tox_node_log(self, "Avatar transfer finished from Alice side."); +} + +typedef struct { + bool avatar_received; + uint8_t received_data[AVATAR_SIZE]; + size_t received_len; +} BobState; + +static void on_file_recv(const Tox_Event_File_Recv *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + uint32_t kind = tox_event_file_recv_get_kind(event); + uint32_t friend_number = tox_event_file_recv_get_friend_number(event); + uint32_t file_number = tox_event_file_recv_get_file_number(event); + + if (kind == TOX_FILE_KIND_AVATAR) { + tox_node_log(self, "Receiving avatar from Alice, resuming..."); + tox_file_control(tox_node_get_tox(self), friend_number, file_number, TOX_FILE_CONTROL_RESUME, nullptr); + } +} + +static void on_file_recv_chunk(const Tox_Event_File_Recv_Chunk *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + BobState *state = (BobState *)tox_node_get_script_ctx(self); + size_t length = tox_event_file_recv_chunk_get_data_length(event); + uint64_t position = tox_event_file_recv_chunk_get_position(event); + + if (length == 0) { + state->avatar_received = true; + return; + } + + if (position + length <= AVATAR_SIZE) { + memcpy(state->received_data + position, tox_event_file_recv_chunk_get_data(event), length); + state->received_len += length; + } +} + +static void bob_script(ToxNode *self, void *ctx) +{ + const BobState *state = (const BobState *)ctx; + tox_events_callback_file_recv(tox_node_get_dispatch(self), on_file_recv); + tox_events_callback_file_recv_chunk(tox_node_get_dispatch(self), on_file_recv_chunk); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + WAIT_UNTIL(state->avatar_received); + tox_node_log(self, "Avatar received. Verifying data..."); + + ck_assert(state->received_len == AVATAR_SIZE); + ck_assert(memcmp(state->received_data, avatar_data, AVATAR_SIZE) == 0); + + tox_node_log(self, "Avatar verification successful!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + AvatarState alice_state = {{0}, false}; + BobState bob_state = {false, {0}, 0}; + + tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AvatarState)); + tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState)); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + diff --git a/auto_tests/scenarios/scenario_bootstrap_test.c b/auto_tests/scenarios/scenario_bootstrap_test.c new file mode 100644 index 00000000..05d6149e --- /dev/null +++ b/auto_tests/scenarios/scenario_bootstrap_test.c @@ -0,0 +1,38 @@ +#include "framework/framework.h" +#include + +static void alice_script(ToxNode *self, void *ctx) +{ + ToxScenario *s = tox_node_get_scenario(self); + ToxNode *bob = tox_scenario_get_node(s, 1); + // Alice just waits for Bob to finish his bootstrap test + WAIT_UNTIL(tox_node_is_finished(bob)); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_log(self, "Waiting for DHT connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Connected to DHT!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 30000); + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + + if (res == TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Scenario completed successfully!"); + } else { + tox_scenario_log(s, "Scenario failed with status: %u", res); + } + + tox_scenario_free(s); + return (res == TOX_SCENARIO_DONE) ? 0 : 1; +} diff --git a/auto_tests/scenarios/scenario_conference_av_test.c b/auto_tests/scenarios/scenario_conference_av_test.c new file mode 100644 index 00000000..22c6d359 --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_av_test.c @@ -0,0 +1,132 @@ +#include "framework/framework.h" +#include "../../toxav/toxav.h" +#include +#include +#include + +#define NUM_NODES 4 + +typedef struct { + bool invited_next; + uint32_t audio_received_mask; + uint32_t group_number; + bool joined; +} State; + +static void audio_callback(void *tox, uint32_t group_number, uint32_t peer_number, const int16_t *pcm, + unsigned int samples, uint8_t channels, uint32_t sample_rate, void *user_data) +{ + (void)tox; + (void)group_number; + (void)sample_rate; + (void)channels; + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + if (samples > 0) { + state->audio_received_mask |= (1 << peer_number); + } +} + +static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + uint32_t friend_number = tox_event_conference_invite_get_friend_number(event); + const uint8_t *cookie = tox_event_conference_invite_get_cookie(event); + size_t length = tox_event_conference_invite_get_cookie_length(event); + + tox_node_log(self, "Received conference invite from friend %u", friend_number); + state->group_number = toxav_join_av_groupchat(tox_node_get_tox(self), friend_number, cookie, length, audio_callback, self); + ck_assert(state->group_number != (uint32_t) -1); + state->joined = true; +} + +static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + uint32_t group_number = tox_event_conference_connected_get_conference_number(event); + + if (state->invited_next) { + return; + } + + // In a linear graph, friend 0 is i-1, friend 1 is i+1 (if exists) + // We want to invite the next peer. + uint32_t friend_count = tox_self_get_friend_list_size(tox_node_get_tox(self)); + if (friend_count > 1 || tox_node_get_index(self) == 0) { + uint32_t friend_to_invite = (tox_node_get_index(self) == 0) ? 0 : 1; + if (friend_to_invite < friend_count) { + Tox_Err_Conference_Invite err; + tox_conference_invite(tox_node_get_tox(self), friend_to_invite, group_number, &err); + if (err == TOX_ERR_CONFERENCE_INVITE_OK) { + tox_node_log(self, "Invited next friend (%u)", friend_to_invite); + state->invited_next = true; + } + } + } +} + +static void peer_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_conference_invite(dispatch, on_conference_invite); + tox_events_callback_conference_connected(dispatch, on_conference_connected); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + + if (tox_node_get_index(self) == 0) { + tox_node_log(self, "Creating AV group..."); + state->group_number = toxav_add_av_groupchat(tox, audio_callback, self); + ck_assert(state->group_number != (uint32_t) -1); + state->joined = true; + + // Peer 0 has only one friend (Peer 1) + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_conference_invite(tox, 0, state->group_number, nullptr); + state->invited_next = true; + } + + // Wait until joined and everyone is in the group + WAIT_UNTIL(state->joined); + while (tox_node_get_conference_peer_count(self, state->group_number) < (uint32_t)NUM_NODES) { + tox_scenario_yield(self); + } + tox_node_log(self, "All %u peers in group!", (uint32_t)NUM_NODES); + + // Send audio for a bit + const int16_t pcm[960] = {0}; + for (int i = 0; i < 40; i++) { + toxav_group_send_audio(tox, state->group_number, pcm, 960, 1, 48000); + tox_scenario_yield(self); + } + + tox_node_log(self, "Audio sent. Received audio mask: %u", state->audio_received_mask); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + State states[NUM_NODES] = {0}; + ToxNode *nodes[NUM_NODES]; + + for (uint32_t i = 0; i < NUM_NODES; i++) { + char name[32]; + snprintf(name, sizeof(name), "Peer-%u", i); + nodes[i] = tox_scenario_add_node(s, name, peer_script, &states[i], sizeof(State)); + } + + // Linear graph + for (uint32_t i = 0; i < NUM_NODES - 1; i++) { + tox_node_bootstrap(nodes[i + 1], nodes[i]); + tox_node_friend_add(nodes[i], nodes[i + 1]); + tox_node_friend_add(nodes[i + 1], nodes[i]); + } + + ToxScenarioStatus res = tox_scenario_run(s); + tox_scenario_free(s); + return (res == TOX_SCENARIO_DONE) ? 0 : 1; +} diff --git a/auto_tests/scenarios/scenario_conference_double_invite_test.c b/auto_tests/scenarios/scenario_conference_double_invite_test.c new file mode 100644 index 00000000..e09a2c42 --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_double_invite_test.c @@ -0,0 +1,97 @@ +#include "framework/framework.h" +#include +#include + +typedef struct { + bool joined; + uint32_t conference; +} State; + +static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + + if (state->joined) { + tox_node_log(self, "ERROR! Received second invite for already joined conference"); + return; + } + + Tox_Err_Conference_Join err; + state->conference = tox_conference_join(tox_node_get_tox(self), + tox_event_conference_invite_get_friend_number(event), + tox_event_conference_invite_get_cookie(event), + tox_event_conference_invite_get_cookie_length(event), + &err); + if (err == TOX_ERR_CONFERENCE_JOIN_OK) { + state->joined = true; + tox_node_log(self, "Joined conference %u", state->conference); + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + State *state = (State *)ctx; + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Creating conference..."); + Tox_Err_Conference_New err_new; + state->conference = tox_conference_new(tox, &err_new); + state->joined = true; + + tox_node_log(self, "Inviting Bob (1st time)..."); + tox_conference_invite(tox, 0, state->conference, nullptr); + + // Wait for Bob to join + const ToxNode *bob = tox_scenario_get_node(tox_node_get_scenario(self), 1); + const State *bob_view = (const State *)tox_node_get_peer_ctx(bob); + WAIT_UNTIL(bob_view->joined); + + tox_node_log(self, "Inviting Bob (2nd time); should be ignored by Bob's script logic..."); + tox_conference_invite(tox, 0, state->conference, nullptr); + + // Wait some ticks to see if Bob's script logs an error + for (int i = 0; i < 20; ++i) { + tox_scenario_yield(self); + } +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite); + WAIT_UNTIL(tox_node_is_self_connected(self)); + + const State *state = (const State *)ctx; + WAIT_UNTIL(state->joined); + + // Stay alive for Alice's second invite + for (int i = 0; i < 50; ++i) { + tox_scenario_yield(self); + } +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + State s1 = {0}, s2 = {0}; + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, &s1, sizeof(State)); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &s2, sizeof(State)); + + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + tox_node_bootstrap(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_conference_invite_merge_test.c b/auto_tests/scenarios/scenario_conference_invite_merge_test.c new file mode 100644 index 00000000..8feafd3f --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_invite_merge_test.c @@ -0,0 +1,300 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + uint32_t conference; + bool connected; + uint32_t peer_count; + bool n1_should_be_offline; + bool coordinator_should_be_offline; +} State; + +static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + + state->conference = tox_conference_join(tox_node_get_tox(self), + tox_event_conference_invite_get_friend_number(event), + tox_event_conference_invite_get_cookie(event), + tox_event_conference_invite_get_cookie_length(event), + nullptr); + tox_node_log(self, "Joined conference %u", state->conference); +} + +static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + state->connected = true; + tox_node_log(self, "Connected to conference"); +} + +static void node_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + const Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_conference_invite(dispatch, on_conference_invite); + tox_events_callback_conference_connected(dispatch, on_conference_connected); + + tox_node_log(self, "Waiting for self connection..."); + tox_node_wait_for_self_connected(self); + tox_node_log(self, "Connected to DHT!"); + + // Wait for all nodes to be DHT-connected + for (int i = 0; i < 5; ++i) { + ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i); + WAIT_UNTIL(tox_node_is_self_connected(node)); + } + tox_node_log(self, "All nodes connected to DHT!"); + + // Nodes 0, 1, 3, 4 just wait to be invited and connect. + // Coordination is handled by Node 2 (Founder/Coordinator). + + tox_node_log(self, "Waiting for conference connection..."); + WAIT_UNTIL(state->connected); + tox_node_log(self, "Connected to conference!"); + + // In this test, nodes participate in multiple phases. + // They just stay alive until the group is fully merged. + WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5); + tox_node_log(self, "Finished!"); +} + +static void coordinator_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_conference_invite(dispatch, on_conference_invite); + tox_events_callback_conference_connected(dispatch, on_conference_connected); + + tox_node_log(self, "Waiting for self connection..."); + tox_node_wait_for_self_connected(self); + tox_node_log(self, "Connected to DHT!"); + + // Wait for all nodes to be DHT-connected + for (int i = 0; i < 5; ++i) { + ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i); + WAIT_UNTIL(tox_node_is_self_connected(node)); + } + tox_node_log(self, "All nodes connected to DHT!"); + + // 1. Create conference + state->conference = tox_conference_new(tox, nullptr); + state->connected = true; + tox_node_log(self, "Created conference %u", state->conference); + + // 2. Invite Node 1 + tox_node_log(self, "Waiting for friend 0 (Node1) to connect..."); + tox_node_wait_for_friend_connected(self, 0); // Node 1 is friend 0 + tox_node_log(self, "Friend 0 (Node1) connected! Inviting..."); + tox_conference_invite(tox, 0, state->conference, nullptr); + + const ToxNode *n1 = tox_scenario_get_node(tox_node_get_scenario(self), 1); + const State *s1_view = (const State *)tox_node_get_peer_ctx(n1); + WAIT_UNTIL(s1_view->connected); + + // 3. Node 1 invites Node 0 + // Wait for Node 0 to join conference + const ToxNode *n0 = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const State *s0_view = (const State *)tox_node_get_peer_ctx(n0); + tox_node_log(self, "Waiting for Node 0 to join conference..."); + WAIT_UNTIL(s0_view->connected); + tox_node_log(self, "Node 0 joined!"); + + // 4. Split: Node 1 goes offline + tox_node_log(self, "Splitting group. Node 1 goes offline."); + state->n1_should_be_offline = true; + + // Wait for Coordinator to see Node 1 is gone (Safe read via mirror) + tox_node_log(self, "Waiting for Node 1 to be seen as gone from conference..."); + WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 2); // Only Coordinator and Node 0 left + + // 5. Coordinator invites Node 3 + tox_node_log(self, "Waiting for friend 1 (Node3) to connect..."); + tox_node_wait_for_friend_connected(self, 1); // Node 3 is friend 1 + tox_node_log(self, "Friend 1 (Node3) connected! Inviting..."); + tox_conference_invite(tox, 1, state->conference, nullptr); + + const ToxNode *n3 = tox_scenario_get_node(tox_node_get_scenario(self), 3); + const State *s3_view = (const State *)tox_node_get_peer_ctx(n3); + WAIT_UNTIL(s3_view->connected); + + // 6. Node 3 invites Node 4 + const ToxNode *n4 = tox_scenario_get_node(tox_node_get_scenario(self), 4); + const State *s4_view = (const State *)tox_node_get_peer_ctx(n4); + tox_node_log(self, "Waiting for Node 4 to join conference..."); + WAIT_UNTIL(s4_view->connected); + tox_node_log(self, "Node 4 joined!"); + + // 7. Coordinator goes offline, Node 1 comes back online + tox_node_log(self, "Coordinator goes offline, Node 1 comes back online."); + state->n1_should_be_offline = false; + tox_node_set_offline(self, true); + + // Wait in offline mode + for (int i = 0; i < 200; ++i) { + tox_scenario_yield(self); + } + + // 8. Node 1 merges groups by inviting Node 3 + // This is handled in Node 1's script after it observes n1_should_be_offline == false. + + // 9. Coordinator comes back online + tox_node_log(self, "Coming back online."); + tox_node_set_offline(self, false); + + // Coordinator rejoins + // In original test: reload and invite again. + // Here we just re-sync. + + // Wait for all nodes to be in one group + WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5); + tox_node_log(self, "Group merged successfully!"); +} + +static void n1_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_conference_invite(dispatch, on_conference_invite); + tox_events_callback_conference_connected(dispatch, on_conference_connected); + + tox_node_log(self, "Waiting for self connection..."); + tox_node_wait_for_self_connected(self); + tox_node_log(self, "Connected to DHT!"); + + // Wait for all nodes to be DHT-connected + for (int i = 0; i < 5; ++i) { + ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i); + WAIT_UNTIL(tox_node_is_self_connected(node)); + } + tox_node_log(self, "All nodes connected to DHT!"); + + // Phase 1: Wait to join + tox_node_log(self, "Waiting for conference connection..."); + WAIT_UNTIL(state->connected); + tox_node_log(self, "Connected to conference!"); + + // Phase 2: Invite Node 0 + tox_node_log(self, "Waiting for friend 0 (Node0) to connect..."); + tox_node_wait_for_friend_connected(self, 0); // Node 0 is friend 0 + tox_node_log(self, "Friend 0 (Node0) connected! Inviting..."); + tox_conference_invite(tox, 0, state->conference, nullptr); + + // Phase 3: Split (we will be set offline by Coordinator) + // Observe coordinator's request for our offline status + const ToxNode *coordinator = tox_scenario_get_node(tox_node_get_scenario(self), 2); + const State *coord_view = (const State *)tox_node_get_peer_ctx(coordinator); + + WAIT_UNTIL(coord_view->n1_should_be_offline); + tox_node_log(self, "Going offline as requested by coordinator."); + tox_node_set_offline(self, true); + + WAIT_UNTIL(!coord_view->n1_should_be_offline); + tox_node_log(self, "Coming back online as requested by coordinator."); + tox_node_set_offline(self, false); + + // Phase 4: Merge groups + // Node 3 is friend 2 for Node 1 + tox_node_log(self, "Waiting for friend 2 (Node3) to connect..."); + tox_node_wait_for_friend_connected(self, 2); + tox_node_log(self, "Friend 2 (Node3) connected! Inviting to merge..."); + tox_node_log(self, "Inviting Node 3 to merge groups."); + tox_conference_invite(tox, 2, state->conference, nullptr); + + WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5); + tox_node_log(self, "Finished!"); +} + +static void n3_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_conference_invite(dispatch, on_conference_invite); + tox_events_callback_conference_connected(dispatch, on_conference_connected); + + tox_node_log(self, "Waiting for self connection..."); + tox_node_wait_for_self_connected(self); + tox_node_log(self, "Connected to DHT!"); + + // Wait for all nodes to be DHT-connected + for (int i = 0; i < 5; ++i) { + ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i); + WAIT_UNTIL(tox_node_is_self_connected(node)); + } + tox_node_log(self, "All nodes connected to DHT!"); + + // Phase 2: Wait to join + tox_node_log(self, "Waiting for conference connection..."); + WAIT_UNTIL(state->connected); + tox_node_log(self, "Connected to conference!"); + + // Phase 3: Invite Node 4 + // Node 3 invites Node 4 when Coordinator is ready. + // We can just wait for Node 1 to go offline, which signals the split. + ToxNode *n1 = tox_scenario_get_node(tox_node_get_scenario(self), 1); + WAIT_UNTIL(tox_node_is_offline(n1)); + + tox_node_log(self, "Waiting for friend 1 (Node4) to connect..."); + tox_node_wait_for_friend_connected(self, 1); // Node 4 is friend 1 + tox_node_log(self, "Friend 1 (Node4) connected! Inviting..."); + tox_conference_invite(tox, 1, state->conference, nullptr); + + WAIT_UNTIL(tox_conference_peer_count(tox, state->conference, nullptr) == 5); + tox_node_log(self, "Finished!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + State states[5] = {0}; + ToxNode *nodes[5]; + + nodes[0] = tox_scenario_add_node(s, "Node0", node_script, &states[0], sizeof(State)); + nodes[1] = tox_scenario_add_node(s, "Node1", n1_script, &states[1], sizeof(State)); + nodes[2] = tox_scenario_add_node(s, "Coordinator", coordinator_script, &states[2], sizeof(State)); + nodes[3] = tox_scenario_add_node(s, "Node3", n3_script, &states[3], sizeof(State)); + nodes[4] = tox_scenario_add_node(s, "Node4", node_script, &states[4], sizeof(State)); + + // Topology: 0-1-2-3-4 + tox_node_friend_add(nodes[0], nodes[1]); + tox_node_friend_add(nodes[1], nodes[0]); + tox_node_friend_add(nodes[1], nodes[2]); + tox_node_friend_add(nodes[2], nodes[1]); + tox_node_friend_add(nodes[2], nodes[3]); + tox_node_friend_add(nodes[3], nodes[2]); + tox_node_friend_add(nodes[3], nodes[4]); + tox_node_friend_add(nodes[4], nodes[3]); + + // Merge connection: 1-3 + tox_node_friend_add(nodes[1], nodes[3]); + tox_node_friend_add(nodes[3], nodes[1]); + + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 5; ++j) { + if (i != j) { + tox_node_bootstrap(nodes[i], nodes[j]); + } + } + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_conference_offline_test.c b/auto_tests/scenarios/scenario_conference_offline_test.c new file mode 100644 index 00000000..f9b8db21 --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_offline_test.c @@ -0,0 +1,132 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + uint32_t conf_num; + bool bob_joined; +} AliceState; + +static void alice_on_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + AliceState *state = (AliceState *)tox_node_get_script_ctx(self); + uint32_t conf_num = tox_event_conference_peer_list_changed_get_conference_number(event); + + if (conf_num != state->conf_num) { + return; + } + + uint32_t count = tox_conference_peer_count(tox_node_get_tox(self), conf_num, nullptr); + if (count == 2) { + state->bob_joined = true; + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + AliceState *state = (AliceState *)ctx; + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), alice_on_peer_list_changed); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + Tox *tox = tox_node_get_tox(self); + Tox_Err_Conference_New err_new; + state->conf_num = tox_conference_new(tox, &err_new); + ck_assert(err_new == TOX_ERR_CONFERENCE_NEW_OK); + + tox_conference_set_max_offline(tox, state->conf_num, 10, nullptr); + + tox_node_log(self, "Inviting Bob to conference %u", state->conf_num); + tox_conference_invite(tox, 0, state->conf_num, nullptr); + + WAIT_UNTIL(state->bob_joined); + tox_node_log(self, "Bob joined. Reloading Alice..."); + + tox_node_reload(self); + tox_node_log(self, "Alice reloaded."); + + // Re-register callback after reload + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), alice_on_peer_list_changed); + + // After reload, we need to get the conference number again + uint32_t chatlist[1]; + tox_conference_get_chatlist(tox_node_get_tox(self), chatlist); + state->conf_num = chatlist[0]; + + tox_node_log(self, "Checking offline peer list for conference %u...", state->conf_num); + + Tox_Err_Conference_Peer_Query q_err; + uint32_t offline_count = tox_conference_offline_peer_count(tox_node_get_tox(self), state->conf_num, &q_err); + ck_assert(q_err == TOX_ERR_CONFERENCE_PEER_QUERY_OK); + tox_node_log(self, "Offline peer count: %u", offline_count); + + // Bob should be offline now because we just started and haven't connected to him in the conference yet. + ck_assert(offline_count >= 1); + + tox_node_log(self, "Conference offline peer tests passed!"); +} + +typedef struct { + bool invited; + uint32_t conf_num; + uint8_t cookie[512]; + size_t cookie_len; +} BobState; + +static void bob_on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + BobState *state = (BobState *)tox_node_get_script_ctx(self); + + state->invited = true; + state->cookie_len = tox_event_conference_invite_get_cookie_length(event); + memcpy(state->cookie, tox_event_conference_invite_get_cookie(event), state->cookie_len); + tox_node_log(self, "Received conference invite"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + const BobState *state = (const BobState *)ctx; + tox_events_callback_conference_invite(tox_node_get_dispatch(self), bob_on_conference_invite); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + WAIT_UNTIL(state->invited); + + tox_conference_join(tox_node_get_tox(self), 0, state->cookie, state->cookie_len, nullptr); + + ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0); + WAIT_UNTIL(tox_node_is_finished(alice)); +} + + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + AliceState alice_state = {0}; + BobState bob_state = {0}; + + tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState)); + tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState)); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_conference_peer_nick_test.c b/auto_tests/scenarios/scenario_conference_peer_nick_test.c new file mode 100644 index 00000000..966cf352 --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_peer_nick_test.c @@ -0,0 +1,111 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + bool joined; + uint32_t conference; + bool friend_in_group; +} State; + +static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + + Tox_Err_Conference_Join err; + state->conference = tox_conference_join(tox_node_get_tox(self), + tox_event_conference_invite_get_friend_number(event), + tox_event_conference_invite_get_cookie(event), + tox_event_conference_invite_get_cookie_length(event), + &err); + if (err == TOX_ERR_CONFERENCE_JOIN_OK) { + state->joined = true; + } +} + +static void on_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + + uint32_t count = tox_conference_peer_count(tox_node_get_tox(self), tox_event_conference_peer_list_changed_get_conference_number(event), nullptr); + state->friend_in_group = (count == 2); +} + +static void alice_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + State *state = (State *)ctx; + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_peer_list_changed); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_self_set_name(tox, (const uint8_t *)"Alice", 5, nullptr); + + tox_node_log(self, "Creating conference..."); + Tox_Err_Conference_New err_new; + state->conference = tox_conference_new(tox, &err_new); + state->joined = true; + + tox_node_log(self, "Inviting Bob..."); + tox_conference_invite(tox, 0, state->conference, nullptr); + + tox_node_log(self, "Waiting for Bob to join..."); + WAIT_UNTIL(state->friend_in_group); + tox_node_log(self, "Bob joined!"); + + // Wait for Bob to drop out (simulated by Bob finishing) + tox_node_log(self, "Waiting for Bob to leave..."); + WAIT_UNTIL(!state->friend_in_group); + tox_node_log(self, "Bob left!"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + const State *state = (const State *)ctx; + tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite); + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_peer_list_changed); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_self_set_name(tox, (const uint8_t *)"Bob", 3, nullptr); + + WAIT_UNTIL(state->joined); + tox_node_log(self, "Joined conference!"); + + WAIT_UNTIL(state->friend_in_group); + + // Stay a bit then leave + for (int i = 0; i < 20; ++i) { + tox_scenario_yield(self); + } + + tox_node_log(self, "Leaving conference..."); + tox_conference_delete(tox, state->conference, nullptr); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + State s1 = {0}, s2 = {0}; + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, &s1, sizeof(State)); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &s2, sizeof(State)); + + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + tox_node_bootstrap(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_conference_query_test.c b/auto_tests/scenarios/scenario_conference_query_test.c new file mode 100644 index 00000000..9985033c --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_query_test.c @@ -0,0 +1,131 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + uint32_t conf_num; + bool peer_joined; +} AliceState; + +static void on_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + AliceState *state = (AliceState *)tox_node_get_script_ctx(self); + if (tox_conference_peer_count(tox_node_get_tox(self), state->conf_num, nullptr) == 2) { + state->peer_joined = true; + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + AliceState *state = (AliceState *)ctx; + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_peer_list_changed); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + Tox *tox = tox_node_get_tox(self); + Tox_Err_Conference_New err_new; + state->conf_num = tox_conference_new(tox, &err_new); + ck_assert(err_new == TOX_ERR_CONFERENCE_NEW_OK); + + tox_node_log(self, "Created conference %u", state->conf_num); + tox_conference_invite(tox, 0, state->conf_num, nullptr); + + WAIT_UNTIL(state->peer_joined); + tox_node_log(self, "Bob joined. Querying peer info..."); + + // Test tox_conference_peer_number_is_ours + // Our own peer number should be 0 or 1. + Tox_Err_Conference_Peer_Query q_err; + bool p0_ours = tox_conference_peer_number_is_ours(tox, state->conf_num, 0, &q_err); + ck_assert(q_err == TOX_ERR_CONFERENCE_PEER_QUERY_OK); + bool p1_ours = tox_conference_peer_number_is_ours(tox, state->conf_num, 1, &q_err); + ck_assert(q_err == TOX_ERR_CONFERENCE_PEER_QUERY_OK); + + ck_assert(p0_ours != p1_ours); // One must be ours, the other must not be. + + uint32_t our_peer_num = p0_ours ? 0 : 1; + uint32_t bobs_peer_num = p0_ours ? 1 : 0; + + // Test tox_conference_peer_get_public_key + uint8_t our_pk[TOX_PUBLIC_KEY_SIZE]; + uint8_t our_self_pk[TOX_PUBLIC_KEY_SIZE]; + tox_conference_peer_get_public_key(tox, state->conf_num, our_peer_num, our_pk, nullptr); + tox_self_get_public_key(tox, our_self_pk); + ck_assert(memcmp(our_pk, our_self_pk, TOX_PUBLIC_KEY_SIZE) == 0); + + uint8_t bobs_pk[TOX_PUBLIC_KEY_SIZE]; + uint8_t bobs_self_pk[TOX_PUBLIC_KEY_SIZE]; + tox_conference_peer_get_public_key(tox, state->conf_num, bobs_peer_num, bobs_pk, nullptr); + + ToxNode *bob_node = tox_scenario_get_node(tox_node_get_scenario(self), 1); + tox_self_get_public_key(tox_node_get_tox(bob_node), bobs_self_pk); + ck_assert(memcmp(bobs_pk, bobs_self_pk, TOX_PUBLIC_KEY_SIZE) == 0); + + // Test tox_conference_get_type + Tox_Err_Conference_Get_Type gt_err; + Tox_Conference_Type type = tox_conference_get_type(tox, state->conf_num, >_err); + ck_assert(gt_err == TOX_ERR_CONFERENCE_GET_TYPE_OK); + ck_assert(type == TOX_CONFERENCE_TYPE_TEXT); + + tox_node_log(self, "Conference query tests passed!"); +} + +typedef struct { + bool invited; + uint8_t cookie[512]; + size_t cookie_len; +} BobState; + +static void bob_on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + BobState *state = (BobState *)tox_node_get_script_ctx(self); + state->invited = true; + state->cookie_len = tox_event_conference_invite_get_cookie_length(event); + memcpy(state->cookie, tox_event_conference_invite_get_cookie(event), state->cookie_len); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + const BobState *state = (const BobState *)ctx; + tox_events_callback_conference_invite(tox_node_get_dispatch(self), bob_on_conference_invite); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + WAIT_UNTIL(state->invited); + tox_conference_join(tox_node_get_tox(self), 0, state->cookie, state->cookie_len, nullptr); + + ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0); + WAIT_UNTIL(tox_node_is_finished(alice)); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + AliceState alice_state = {0, false}; + BobState bob_state = {0}; + + tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState)); + tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState)); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_conference_simple_test.c b/auto_tests/scenarios/scenario_conference_simple_test.c new file mode 100644 index 00000000..995a8aa5 --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_simple_test.c @@ -0,0 +1,155 @@ +#include "framework/framework.h" +#include +#include + +typedef struct { + bool joined; + bool connected; + uint32_t conference; + bool received; + uint32_t peers; +} State; + +static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + state->connected = true; + tox_node_log(self, "Connected to conference %u", tox_event_conference_connected_get_conference_number(event)); +} + +static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + + tox_node_log(self, "Received conference invite from friend %u", tox_event_conference_invite_get_friend_number(event)); + Tox_Err_Conference_Join err; + state->conference = tox_conference_join(tox_node_get_tox(self), + tox_event_conference_invite_get_friend_number(event), + tox_event_conference_invite_get_cookie(event), + tox_event_conference_invite_get_cookie_length(event), + &err); + if (err == TOX_ERR_CONFERENCE_JOIN_OK) { + state->joined = true; + tox_node_log(self, "Joined conference %u", state->conference); + } else { + tox_node_log(self, "Failed to join conference: %u", err); + } +} + +static void on_conference_message(const Tox_Event_Conference_Message *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + const uint8_t *msg = tox_event_conference_message_get_message(event); + if (memcmp(msg, "hello!", 6) == 0) { + state->received = true; + } +} + +static void on_conference_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + + state->peers = tox_conference_peer_count(tox_node_get_tox(self), tox_event_conference_peer_list_changed_get_conference_number(event), nullptr); +} + +static void tox1_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + State *state = (State *)ctx; + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_conference_peer_list_changed); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Creating conference..."); + Tox_Err_Conference_New err_new; + state->conference = tox_conference_new(tox, &err_new); + state->joined = true; + + tox_node_log(self, "Inviting Bob..."); + Tox_Err_Conference_Invite err_inv; + tox_conference_invite(tox, 0, state->conference, &err_inv); + + WAIT_UNTIL(state->peers == 3); + tox_node_log(self, "All peers joined!"); + + Tox_Err_Conference_Send_Message err_msg; + tox_conference_send_message(tox, state->conference, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello!", 6, &err_msg); +} + +static void tox2_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + State *state = (State *)ctx; + tox_events_callback_conference_connected(tox_node_get_dispatch(self), on_conference_connected); + tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite); + tox_events_callback_conference_message(tox_node_get_dispatch(self), on_conference_message); + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_conference_peer_list_changed); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Waiting for join..."); + WAIT_UNTIL(state->joined); + tox_node_log(self, "Joined conference %u. Waiting for connection...", state->conference); + WAIT_UNTIL(state->connected); + tox_node_log(self, "Connected. Waiting for Charlie to connect..."); + + WAIT_UNTIL(tox_node_is_friend_connected(self, 1)); + tox_node_log(self, "Charlie connected. Inviting Charlie..."); + + Tox_Err_Conference_Invite err_inv; + bool ok = tox_conference_invite(tox, 1, state->conference, &err_inv); + if (ok) { + tox_node_log(self, "Successfully invited Charlie"); + } else { + tox_node_log(self, "Failed to invite Charlie: %u", err_inv); + } + + WAIT_UNTIL(state->received); + tox_node_log(self, "Received message!"); +} + +static void tox3_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite); + tox_events_callback_conference_message(tox_node_get_dispatch(self), on_conference_message); + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_conference_peer_list_changed); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Waiting for message..."); + WAIT_UNTIL(state->received); + tox_node_log(self, "Received message!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 600000); // 10m virtual timeout + State s1 = {0}, s2 = {0}, s3 = {0}; + + ToxNode *t1 = tox_scenario_add_node(s, "Alice", tox1_script, &s1, sizeof(State)); + ToxNode *t2 = tox_scenario_add_node(s, "Bob", tox2_script, &s2, sizeof(State)); + ToxNode *t3 = tox_scenario_add_node(s, "Charlie", tox3_script, &s3, sizeof(State)); + + // t1 <-> t2, t2 <-> t3 + tox_node_friend_add(t1, t2); + tox_node_friend_add(t2, t1); + tox_node_friend_add(t2, t3); + tox_node_friend_add(t3, t2); + + tox_node_bootstrap(t2, t1); + tox_node_bootstrap(t3, t1); // All bootstrap from Alice + tox_node_bootstrap(t3, t2); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_conference_test.c b/auto_tests/scenarios/scenario_conference_test.c new file mode 100644 index 00000000..eb19684d --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_test.c @@ -0,0 +1,148 @@ +#include "framework/framework.h" +#include +#include +#include + +#define NUM_PEERS 16 +#define GROUP_MESSAGE "Install Gentoo" + +typedef struct { + bool joined; + bool connected; + uint32_t conference; + uint32_t peer_count; + uint32_t messages_received; +} PeerState; + +static void on_conference_invite(const Tox_Event_Conference_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + PeerState *state = (PeerState *)tox_node_get_script_ctx(self); + + state->conference = tox_conference_join(tox_node_get_tox(self), + tox_event_conference_invite_get_friend_number(event), + tox_event_conference_invite_get_cookie(event), + tox_event_conference_invite_get_cookie_length(event), + nullptr); + tox_node_log(self, "Joined conference %u", state->conference); + state->joined = true; +} + +static void on_conference_connected(const Tox_Event_Conference_Connected *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + PeerState *state = (PeerState *)tox_node_get_script_ctx(self); + state->connected = true; + tox_node_log(self, "Connected to conference %u", tox_event_conference_connected_get_conference_number(event)); +} + +static void on_peer_list_changed(const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + PeerState *state = (PeerState *)tox_node_get_script_ctx(self); + state->peer_count = tox_conference_peer_count(tox_node_get_tox(self), tox_event_conference_peer_list_changed_get_conference_number(event), nullptr); + tox_node_log(self, "Peer count changed: %u", state->peer_count); +} + +static void on_conference_message(const Tox_Event_Conference_Message *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + PeerState *state = (PeerState *)tox_node_get_script_ctx(self); + const uint8_t *msg = tox_event_conference_message_get_message(event); + if (memcmp(msg, GROUP_MESSAGE, sizeof(GROUP_MESSAGE) - 1) == 0) { + state->messages_received++; + tox_node_log(self, "Received message %u", state->messages_received); + } +} + +static void peer_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + PeerState *state = (PeerState *)ctx; + uint32_t my_index = tox_node_get_index(self); + + tox_events_callback_conference_invite(tox_node_get_dispatch(self), on_conference_invite); + tox_events_callback_conference_connected(tox_node_get_dispatch(self), on_conference_connected); + tox_events_callback_conference_peer_list_changed(tox_node_get_dispatch(self), on_peer_list_changed); + tox_events_callback_conference_message(tox_node_get_dispatch(self), on_conference_message); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Self connected"); + + // Founder (Node 0) creates group + if (my_index == 0) { + state->conference = tox_conference_new(tox, nullptr); + tox_node_log(self, "Created conference %u", state->conference); + state->joined = true; + state->connected = true; + + // Invite friend 0 (Node 1) + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Friend 0 (Node 1) connected, inviting..."); + tox_conference_invite(tox, 0, state->conference, nullptr); + } else { + WAIT_UNTIL(state->joined); + WAIT_UNTIL(state->connected); + + // Invite next friends if any + // In linear topology, each node has 2 friends (except ends) + // We invite friend 1 (the next node in line) + if (my_index < NUM_PEERS - 1) { + WAIT_UNTIL(tox_node_is_friend_connected(self, 1)); + tox_node_log(self, "Friend 1 (Node %u) connected, inviting...", my_index + 1); + Tox_Err_Conference_Invite err; + if (!tox_conference_invite(tox, 1, state->conference, &err)) { + tox_node_log(self, "Failed to invite Node %u: %u", my_index + 1, err); + } + } + } + + // Wait for all to join + WAIT_UNTIL(state->peer_count == NUM_PEERS); + tox_node_log(self, "All peers joined"); + + // One peer sends a message + if (my_index == 0) { + tox_node_log(self, "Sending message"); + tox_conference_send_message(tox, state->conference, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)GROUP_MESSAGE, sizeof(GROUP_MESSAGE) - 1, nullptr); + } + + // Wait for message propagation + WAIT_UNTIL(state->messages_received > 0); + tox_node_log(self, "Done"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + PeerState states[NUM_PEERS] = {0}; + + ToxNode *nodes[NUM_PEERS]; + for (int i = 0; i < NUM_PEERS; ++i) { + char alias[16]; + snprintf(alias, sizeof(alias), "Peer%d", i); + nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(PeerState)); + } + + // Linear topology + for (int i = 0; i < NUM_PEERS; ++i) { + if (i > 0) { + tox_node_friend_add(nodes[i], nodes[i - 1]); + tox_node_bootstrap(nodes[i], nodes[i - 1]); + } + if (i < NUM_PEERS - 1) { + tox_node_friend_add(nodes[i], nodes[i + 1]); + } + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef NUM_PEERS diff --git a/auto_tests/scenarios/scenario_conference_two_test.c b/auto_tests/scenarios/scenario_conference_two_test.c new file mode 100644 index 00000000..3383e784 --- /dev/null +++ b/auto_tests/scenarios/scenario_conference_two_test.c @@ -0,0 +1,38 @@ +#include "framework/framework.h" +#include + +static void tox_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + + tox_node_log(self, "Creating conference 1..."); + Tox_Err_Conference_New err; + tox_conference_new(tox, &err); + if (err != TOX_ERR_CONFERENCE_NEW_OK) { + tox_node_log(self, "Failed to create conference 1: %u", err); + return; + } + + tox_node_log(self, "Creating conference 2..."); + tox_conference_new(tox, &err); + if (err != TOX_ERR_CONFERENCE_NEW_OK) { + tox_node_log(self, "Failed to create conference 2: %u", err); + return; + } +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + tox_scenario_add_node(s, "Node", tox_script, nullptr, 0); + + // Just run it until completion + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_dht_nodes_response_api_test.c b/auto_tests/scenarios/scenario_dht_nodes_response_api_test.c new file mode 100644 index 00000000..4ef9203b --- /dev/null +++ b/auto_tests/scenarios/scenario_dht_nodes_response_api_test.c @@ -0,0 +1,195 @@ +#include "framework/framework.h" +#include "../../toxcore/tox_private.h" +#include +#include +#include + +#define NUM_TOXES 30 + +typedef struct { + uint8_t public_key[TOX_DHT_NODE_PUBLIC_KEY_SIZE]; + char ip[TOX_DHT_NODE_IP_STRING_SIZE]; + uint16_t port; +} Dht_Node; + +typedef struct { + Dht_Node *nodes[NUM_TOXES]; + size_t num_nodes; + uint8_t public_key_list[NUM_TOXES][TOX_PUBLIC_KEY_SIZE]; +} State; + +static bool node_crawled(const State *state, const uint8_t *public_key) +{ + for (size_t i = 0; i < state->num_nodes; ++i) { + if (memcmp(state->nodes[i]->public_key, public_key, TOX_DHT_NODE_PUBLIC_KEY_SIZE) == 0) { + return true; + } + } + + return false; +} + +static void on_dht_nodes_response(const Tox_Event_Dht_Nodes_Response *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + + const uint8_t *public_key = tox_event_dht_nodes_response_get_public_key(event); + const char *ip = (const char *)tox_event_dht_nodes_response_get_ip(event); + const uint16_t port = tox_event_dht_nodes_response_get_port(event); + + if (node_crawled(state, public_key)) { + return; + } + + if (state->num_nodes >= NUM_TOXES) { + return; + } + + Dht_Node *node = (Dht_Node *)calloc(1, sizeof(Dht_Node)); + ck_assert(node != nullptr); + + memcpy(node->public_key, public_key, TOX_DHT_NODE_PUBLIC_KEY_SIZE); + snprintf(node->ip, sizeof(node->ip), "%s", ip); + node->port = port; + + state->nodes[state->num_nodes] = node; + ++state->num_nodes; + + // ask new node to give us their close nodes to every public key + for (size_t i = 0; i < NUM_TOXES; ++i) { + tox_dht_send_nodes_request(tox_node_get_tox(self), public_key, ip, port, state->public_key_list[i], nullptr); + } +} + +static void peer_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + tox_events_callback_dht_nodes_response(tox_node_get_dispatch(self), on_dht_nodes_response); + + // Initial bootstrap: ask the node we bootstrapped from for all other nodes + // Wait for self connection first + WAIT_UNTIL(tox_node_is_self_connected(self)); + + // After connecting, we should start receiving responses because we bootstrapped + // but the original test calls tox_dht_send_nodes_request for all nodes. + // In our case, we bootstrap linearly, so Peer-i bootstraps from Peer-(i-1). + // Peer-0 doesn't bootstrap from anyone. + + // To kick off crawling, each node can ask its bootstrap source for all nodes. + uint32_t my_index = tox_node_get_index(self); + if (my_index > 0) { + ToxNode *bootstrap_source = tox_scenario_get_node(tox_node_get_scenario(self), my_index - 1); + uint8_t bs_pk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox_node_get_tox(bootstrap_source), bs_pk); + + Tox_Err_Get_Port port_err; + uint16_t bs_port = tox_self_get_udp_port(tox_node_get_tox(bootstrap_source), &port_err); + + for (size_t i = 0; i < NUM_TOXES; ++i) { + tox_dht_send_nodes_request(tox_node_get_tox(self), bs_pk, "127.0.0.1", bs_port, state->public_key_list[i], nullptr); + } + } + + // Wait until we have crawled all nodes + uint64_t last_log_time = 0; + uint64_t last_request_time = 0; + while (state->num_nodes < NUM_TOXES) { + tox_scenario_yield(self); + uint64_t now = tox_scenario_get_time(tox_node_get_scenario(self)); + + if (now - last_request_time >= 2000) { + last_request_time = now; + + // Retry strategy: ask a random known node for missing nodes + if (state->num_nodes > 0) { + int idx = rand() % state->num_nodes; + const Dht_Node *n = state->nodes[idx]; + for (int i = 0; i < NUM_TOXES; ++i) { + if (!node_crawled(state, state->public_key_list[i])) { + tox_dht_send_nodes_request(tox_node_get_tox(self), + n->public_key, n->ip, n->port, state->public_key_list[i], nullptr); + } + } + } else if (my_index > 0) { + // Fallback: if we haven't found anyone yet, ask bootstrap peer again + ToxNode *bootstrap_source = tox_scenario_get_node(tox_node_get_scenario(self), my_index - 1); + uint8_t bs_pk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox_node_get_tox(bootstrap_source), bs_pk); + Tox_Err_Get_Port port_err; + uint16_t bs_port = tox_self_get_udp_port(tox_node_get_tox(bootstrap_source), &port_err); + + for (int i = 0; i < NUM_TOXES; ++i) { + tox_dht_send_nodes_request(tox_node_get_tox(self), bs_pk, "127.0.0.1", bs_port, state->public_key_list[i], nullptr); + } + } + } + + if (now - last_log_time >= 5000) { + last_log_time = now; + tox_node_log(self, "Still crawling... found %zu/%d nodes", state->num_nodes, NUM_TOXES); + if (state->num_nodes < NUM_TOXES) { + char missing[256] = {0}; + size_t pos = 0; + for (int i = 0; i < NUM_TOXES; ++i) { + if (!node_crawled(state, state->public_key_list[i])) { + pos += snprintf(missing + pos, sizeof(missing) - pos, "%d ", i); + if (pos >= sizeof(missing) - 1) { + break; + } + } + } + tox_node_log(self, "Missing nodes: %s", missing); + } + } + } + + tox_node_log(self, "Finished crawling all %u nodes.", (unsigned int)state->num_nodes); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + State *states = (State *)calloc(NUM_TOXES, sizeof(State)); + + ToxNode *nodes[NUM_TOXES]; + + for (uint32_t i = 0; i < NUM_TOXES; ++i) { + char alias[32]; + snprintf(alias, sizeof(alias), "Peer-%u", i); + nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(State)); + } + + // Fill the public_key_list for all nodes + uint8_t public_keys[NUM_TOXES][TOX_PUBLIC_KEY_SIZE]; + for (uint32_t i = 0; i < NUM_TOXES; ++i) { + tox_self_get_dht_id(tox_node_get_tox(nodes[i]), public_keys[i]); + } + + for (uint32_t i = 0; i < NUM_TOXES; ++i) { + memcpy(states[i].public_key_list, public_keys, sizeof(public_keys)); + } + + // Create a linear graph + for (uint32_t i = 1; i < NUM_TOXES; ++i) { + tox_node_bootstrap(nodes[i], nodes[i - 1]); + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + for (uint32_t i = 0; i < NUM_TOXES; ++i) { + for (size_t j = 0; j < states[i].num_nodes; ++j) { + free(states[i].nodes[j]); + } + } + + free(states); + tox_scenario_free(s); + return 0; +} + +#undef NUM_TOXES diff --git a/auto_tests/scenarios/scenario_events_test.c b/auto_tests/scenarios/scenario_events_test.c new file mode 100644 index 00000000..40abc58a --- /dev/null +++ b/auto_tests/scenarios/scenario_events_test.c @@ -0,0 +1,46 @@ +#include "framework/framework.h" +#include +#include +#include + +static void alice_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + tox_node_log(self, "Sending message to Bob..."); + uint8_t msg[] = "hello"; + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err); + ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + tox_node_log(self, "Waiting for message from Alice..."); + WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE); + tox_node_log(self, "Received message!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_file_cancel_test.c b/auto_tests/scenarios/scenario_file_cancel_test.c new file mode 100644 index 00000000..d0009f25 --- /dev/null +++ b/auto_tests/scenarios/scenario_file_cancel_test.c @@ -0,0 +1,81 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + bool cancelled; +} AliceState; + +static void on_file_recv_control(const Tox_Event_File_Recv_Control *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + AliceState *state = (AliceState *)tox_node_get_script_ctx(self); + + if (tox_event_file_recv_control_get_control(event) == TOX_FILE_CONTROL_CANCEL) { + state->cancelled = true; + tox_node_log(self, "Bob cancelled the file transfer."); + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + const AliceState *state = (const AliceState *)ctx; + tox_events_callback_file_recv_control(tox_node_get_dispatch(self), on_file_recv_control); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + Tox *tox = tox_node_get_tox(self); + tox_file_send(tox, 0, TOX_FILE_KIND_DATA, 1000, nullptr, (const uint8_t *)"test.txt", 8, nullptr); + + WAIT_UNTIL(state->cancelled); + tox_node_log(self, "File cancellation verified from Alice side."); +} + +static void on_file_recv(const Tox_Event_File_Recv *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + uint32_t friend_number = tox_event_file_recv_get_friend_number(event); + uint32_t file_number = tox_event_file_recv_get_file_number(event); + + tox_node_log(self, "Received file request, rejecting (CANCEL)..."); + tox_file_control(tox_node_get_tox(self), friend_number, file_number, TOX_FILE_CONTROL_CANCEL, nullptr); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_events_callback_file_recv(tox_node_get_dispatch(self), on_file_recv); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0); + WAIT_UNTIL(tox_node_is_finished(alice)); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + AliceState alice_state = {false}; + + tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState)); + tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_file_seek_test.c b/auto_tests/scenarios/scenario_file_seek_test.c new file mode 100644 index 00000000..9261fbca --- /dev/null +++ b/auto_tests/scenarios/scenario_file_seek_test.c @@ -0,0 +1,134 @@ +#include "framework/framework.h" +#include +#include +#include + +#define FILE_SIZE (1024 * 1024) +#define SEEK_POS (512 * 1024) + +typedef struct { + bool seek_happened; + bool finished; +} SenderState; + +static void on_file_chunk_request(const Tox_Event_File_Chunk_Request *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + SenderState *state = (SenderState *)tox_node_get_script_ctx(self); + + uint32_t friend_number = tox_event_file_chunk_request_get_friend_number(event); + uint32_t file_number = tox_event_file_chunk_request_get_file_number(event); + uint64_t position = tox_event_file_chunk_request_get_position(event); + size_t length = tox_event_file_chunk_request_get_length(event); + + if (length == 0) { + state->finished = true; + return; + } + + if (position >= SEEK_POS) { + state->seek_happened = true; + } + + uint8_t data[TOX_MAX_CUSTOM_PACKET_SIZE]; + memset(data, (uint8_t)(position % 256), length); + tox_file_send_chunk(tox_node_get_tox(self), friend_number, file_number, position, data, length, nullptr); +} + +static void sender_script(ToxNode *self, void *ctx) +{ + SenderState *state = (SenderState *)ctx; + tox_events_callback_file_chunk_request(tox_node_get_dispatch(self), on_file_chunk_request); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + Tox *tox = tox_node_get_tox(self); + tox_file_send(tox, 0, TOX_FILE_KIND_DATA, FILE_SIZE, nullptr, (const uint8_t *)"seek_test.bin", 13, nullptr); + + WAIT_UNTIL(state->finished); + tox_node_log(self, "Sender finished. Seek happened: %s", state->seek_happened ? "YES" : "NO"); + ck_assert(state->seek_happened); +} + +typedef struct { + uint32_t file_number; + bool file_recv_called; + bool finished; +} ReceiverState; + +static void on_file_recv(const Tox_Event_File_Recv *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + ReceiverState *state = (ReceiverState *)tox_node_get_script_ctx(self); + state->file_number = tox_event_file_recv_get_file_number(event); + state->file_recv_called = true; + tox_node_log(self, "Received file request, file_number = %u", state->file_number); +} + +static void on_file_recv_chunk(const Tox_Event_File_Recv_Chunk *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + ReceiverState *state = (ReceiverState *)tox_node_get_script_ctx(self); + size_t length = tox_event_file_recv_chunk_get_data_length(event); + if (length == 0) { + state->finished = true; + } +} + +static void receiver_script(ToxNode *self, void *ctx) +{ + ReceiverState *state = (ReceiverState *)ctx; + tox_events_callback_file_recv(tox_node_get_dispatch(self), on_file_recv); + tox_events_callback_file_recv_chunk(tox_node_get_dispatch(self), on_file_recv_chunk); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + WAIT_UNTIL(state->file_recv_called); + + tox_node_log(self, "Seeking to %d before resuming...", SEEK_POS); + Tox_Err_File_Seek seek_err; + bool seek_res = tox_file_seek(tox_node_get_tox(self), 0, state->file_number, SEEK_POS, &seek_err); + if (!seek_res) { + tox_node_log(self, "tox_file_seek failed: %s", tox_err_file_seek_to_string(seek_err)); + } + ck_assert(seek_res); + + tox_node_log(self, "Resuming file %u...", state->file_number); + Tox_Err_File_Control ctrl_err; + bool resume_res = tox_file_control(tox_node_get_tox(self), 0, state->file_number, TOX_FILE_CONTROL_RESUME, &ctrl_err); + ck_assert(resume_res); + + WAIT_UNTIL(state->finished); + tox_node_log(self, "Receiver finished!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + SenderState sender_state = {false, false}; + ReceiverState receiver_state = {0, false, false}; + + tox_scenario_add_node(s, "Sender", sender_script, &sender_state, sizeof(SenderState)); + tox_scenario_add_node(s, "Receiver", receiver_script, &receiver_state, sizeof(ReceiverState)); + + ToxNode *sender = tox_scenario_get_node(s, 0); + ToxNode *receiver = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(sender, receiver); + tox_node_friend_add(sender, receiver); + tox_node_friend_add(receiver, sender); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef FILE_SIZE diff --git a/auto_tests/scenarios/scenario_file_transfer_test.c b/auto_tests/scenarios/scenario_file_transfer_test.c new file mode 100644 index 00000000..c6e44422 --- /dev/null +++ b/auto_tests/scenarios/scenario_file_transfer_test.c @@ -0,0 +1,150 @@ +#include "framework/framework.h" +#include +#include +#include + +#define FILE_SIZE (1024 * 1024) +#define FILENAME "Gentoo.exe" + +typedef struct { + bool file_received; + bool file_sending_done; + uint64_t size_recv; + uint64_t sending_pos; + uint8_t file_id[TOX_FILE_ID_LENGTH]; + uint32_t file_accepted; + uint64_t file_size; + bool sendf_ok; + uint8_t num; + uint8_t sending_num; + bool m_send_reached; +} FileTransferState; + +static void on_file_receive(const Tox_Event_File_Recv *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + FileTransferState *state = (FileTransferState *)tox_node_get_script_ctx(self); + + const uint32_t friend_number = tox_event_file_recv_get_friend_number(event); + const uint32_t file_number = tox_event_file_recv_get_file_number(event); + const uint64_t filesize = tox_event_file_recv_get_file_size(event); + + state->file_size = filesize; + state->sending_pos = state->size_recv = 0; + + Tox_Err_File_Control error; + tox_file_control(tox_node_get_tox(self), friend_number, file_number, TOX_FILE_CONTROL_RESUME, &error); + state->file_accepted++; + tox_node_log(self, "File receive: %u bytes, accepted", (uint32_t)filesize); +} + +static void on_file_recv_control(const Tox_Event_File_Recv_Control *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + FileTransferState *state = (FileTransferState *)tox_node_get_script_ctx(self); + const Tox_File_Control control = tox_event_file_recv_control_get_control(event); + + if (control == TOX_FILE_CONTROL_RESUME) { + state->sendf_ok = true; + tox_node_log(self, "Received RESUME control"); + } +} + +static void on_file_chunk_request(const Tox_Event_File_Chunk_Request *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + FileTransferState *state = (FileTransferState *)tox_node_get_script_ctx(self); + + const uint32_t friend_number = tox_event_file_chunk_request_get_friend_number(event); + const uint32_t file_number = tox_event_file_chunk_request_get_file_number(event); + const uint64_t position = tox_event_file_chunk_request_get_position(event); + size_t length = tox_event_file_chunk_request_get_length(event); + + if (length == 0) { + state->file_sending_done = true; + tox_node_log(self, "File sending done"); + return; + } + + uint8_t *f_data = (uint8_t *)malloc(length); + memset(f_data, state->sending_num, length); + + tox_file_send_chunk(tox_node_get_tox(self), friend_number, file_number, position, f_data, length, nullptr); + free(f_data); + + state->sending_num++; + state->sending_pos += length; +} + +static void on_file_recv_chunk(const Tox_Event_File_Recv_Chunk *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + FileTransferState *state = (FileTransferState *)tox_node_get_script_ctx(self); + + const size_t length = tox_event_file_recv_chunk_get_data_length(event); + + if (length == 0) { + state->file_received = true; + tox_node_log(self, "File reception done"); + return; + } + + state->num++; + state->size_recv += length; +} + +static void sender_script(ToxNode *self, void *ctx) +{ + const FileTransferState *state = (const FileTransferState *)ctx; + + tox_events_callback_file_recv_control(tox_node_get_dispatch(self), on_file_recv_control); + tox_events_callback_file_chunk_request(tox_node_get_dispatch(self), on_file_chunk_request); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + uint32_t fnum = tox_file_send(tox_node_get_tox(self), 0, TOX_FILE_KIND_DATA, FILE_SIZE, nullptr, (const uint8_t *)FILENAME, sizeof(FILENAME), nullptr); + tox_node_log(self, "Started sending file %u", fnum); + + WAIT_UNTIL(state->file_sending_done); + tox_node_log(self, "Done"); +} + +static void receiver_script(ToxNode *self, void *ctx) +{ + const FileTransferState *state = (const FileTransferState *)ctx; + + tox_events_callback_file_recv(tox_node_get_dispatch(self), on_file_receive); + tox_events_callback_file_recv_chunk(tox_node_get_dispatch(self), on_file_recv_chunk); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + + WAIT_UNTIL(state->file_received); + tox_node_log(self, "Done"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + FileTransferState state_sender = {0}; + FileTransferState state_receiver = {0}; + + ToxNode *sender = tox_scenario_add_node(s, "Sender", sender_script, &state_sender, sizeof(FileTransferState)); + ToxNode *receiver = tox_scenario_add_node(s, "Receiver", receiver_script, &state_receiver, sizeof(FileTransferState)); + + tox_node_friend_add(sender, receiver); + tox_node_friend_add(receiver, sender); + tox_node_bootstrap(sender, receiver); + tox_node_bootstrap(receiver, sender); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef FILE_SIZE diff --git a/auto_tests/scenarios/scenario_friend_connection_test.c b/auto_tests/scenarios/scenario_friend_connection_test.c new file mode 100644 index 00000000..96896f5f --- /dev/null +++ b/auto_tests/scenarios/scenario_friend_connection_test.c @@ -0,0 +1,46 @@ +#include "framework/framework.h" +#include + +static void alice_script(ToxNode *self, void *ctx) +{ + tox_node_log(self, "Waiting for DHT connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Connected to DHT. Waiting for friend connection..."); + + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to Bob!"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_log(self, "Waiting for DHT connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Connected to DHT. Waiting for friend connection..."); + + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to Alice!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); // 60s virtual timeout + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + tox_scenario_log(s, "Starting scenario..."); + ToxScenarioStatus res = tox_scenario_run(s); + + if (res == TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Scenario completed successfully!"); + } else { + tox_scenario_log(s, "Scenario failed with status: %u", res); + } + + tox_scenario_free(s); + return (res == TOX_SCENARIO_DONE) ? 0 : 1; +} diff --git a/auto_tests/scenarios/scenario_friend_delete_test.c b/auto_tests/scenarios/scenario_friend_delete_test.c new file mode 100644 index 00000000..d63ce1ac --- /dev/null +++ b/auto_tests/scenarios/scenario_friend_delete_test.c @@ -0,0 +1,103 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + bool bob_went_offline; +} AliceState; + +static void on_alice_friend_connection_status(const Tox_Event_Friend_Connection_Status *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + AliceState *state = (AliceState *)tox_node_get_script_ctx(self); + + Tox_Connection status = tox_event_friend_connection_status_get_connection_status(event); + tox_node_log(self, "Friend connection status changed to %u", status); + + if (status == TOX_CONNECTION_NONE) { + state->bob_went_offline = true; + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + tox_events_callback_friend_connection_status(tox_node_get_dispatch(self), on_alice_friend_connection_status); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + tox_node_log(self, "Connected to Bob. Deleting Bob now..."); + + Tox_Err_Friend_Delete err; + bool success = tox_friend_delete(tox_node_get_tox(self), 0, &err); + ck_assert(success); + ck_assert(err == TOX_ERR_FRIEND_DELETE_OK); + + tox_node_log(self, "Bob deleted. Bob should see Alice as offline."); + + // Wait for Bob to finish + ToxNode *bob = tox_scenario_get_node(tox_node_get_scenario(self), 1); + WAIT_UNTIL(tox_node_is_finished(bob)); +} + +typedef struct { + bool alice_went_offline; + bool alice_connected; +} BobState; + +static void on_bob_friend_connection_status(const Tox_Event_Friend_Connection_Status *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + BobState *state = (BobState *)tox_node_get_script_ctx(self); + + Tox_Connection status = tox_event_friend_connection_status_get_connection_status(event); + tox_node_log(self, "Friend connection status changed to %u", status); + + if (status != TOX_CONNECTION_NONE) { + state->alice_connected = true; + } + + if (status == TOX_CONNECTION_NONE) { + state->alice_went_offline = true; + } +} + +static void bob_script(ToxNode *self, void *ctx) +{ + const BobState *state = (const BobState *)ctx; + tox_events_callback_friend_connection_status(tox_node_get_dispatch(self), on_bob_friend_connection_status); + + tox_node_wait_for_self_connected(self); + WAIT_UNTIL(state->alice_connected); + tox_node_log(self, "Connected to Alice. Waiting for Alice to delete me..."); + + WAIT_UNTIL(state->alice_went_offline); + tox_node_log(self, "Alice went offline as expected!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + AliceState alice_state = {false}; + BobState bob_state = {false, false}; + + tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState)); + tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState)); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_friend_query_test.c b/auto_tests/scenarios/scenario_friend_query_test.c new file mode 100644 index 00000000..59cf3196 --- /dev/null +++ b/auto_tests/scenarios/scenario_friend_query_test.c @@ -0,0 +1,72 @@ +#include "framework/framework.h" +#include +#include +#include + +static void alice_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + Tox *tox = tox_node_get_tox(self); + + // Test tox_friend_exists + ck_assert(tox_friend_exists(tox, 0)); + ck_assert(!tox_friend_exists(tox, 1)); + + // Test tox_friend_get_public_key + uint8_t pk[TOX_PUBLIC_KEY_SIZE]; + Tox_Err_Friend_Get_Public_Key pk_err; + bool pk_success = tox_friend_get_public_key(tox, 0, pk, &pk_err); + ck_assert(pk_success); + ck_assert(pk_err == TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK); + + // Test tox_friend_by_public_key + Tox_Err_Friend_By_Public_Key by_pk_err; + uint32_t friend_num = tox_friend_by_public_key(tox, pk, &by_pk_err); + ck_assert(by_pk_err == TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK); + ck_assert(friend_num == 0); + + // Test tox_friend_get_last_online + Tox_Err_Friend_Get_Last_Online lo_err; + uint64_t last_online = tox_friend_get_last_online(tox, 0, &lo_err); + ck_assert(lo_err == TOX_ERR_FRIEND_GET_LAST_ONLINE_OK); + // Since they are currently connected, last_online should be recent (non-zero) + ck_assert(last_online > 0); + + tox_node_log(self, "Friend query tests passed!"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + // Bob stays connected while Alice runs her tests + ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0); + WAIT_UNTIL(tox_node_is_finished(alice)); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_friend_read_receipt_test.c b/auto_tests/scenarios/scenario_friend_read_receipt_test.c new file mode 100644 index 00000000..ad25787f --- /dev/null +++ b/auto_tests/scenarios/scenario_friend_read_receipt_test.c @@ -0,0 +1,83 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + uint32_t message_id; + bool received_receipt; +} AliceState; + +static void on_read_receipt(const Tox_Event_Friend_Read_Receipt *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + AliceState *state = (AliceState *)tox_node_get_script_ctx(self); + + uint32_t friend_number = tox_event_friend_read_receipt_get_friend_number(event); + uint32_t message_id = tox_event_friend_read_receipt_get_message_id(event); + + tox_node_log(self, "Received read receipt for friend %u, message %u", friend_number, message_id); + + if (friend_number == 0 && message_id == state->message_id) { + state->received_receipt = true; + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + AliceState *state = (AliceState *)ctx; + tox_events_callback_friend_read_receipt(tox_node_get_dispatch(self), on_read_receipt); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + tox_node_log(self, "Sending message to Bob..."); + uint8_t msg[] = "Hello Bob!"; + Tox_Err_Friend_Send_Message err; + state->message_id = tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err); + ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); + tox_node_log(self, "Message sent with ID %u, waiting for read receipt...", state->message_id); + + WAIT_UNTIL(state->received_receipt); + tox_node_log(self, "Read receipt received successfully!"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + tox_node_log(self, "Waiting for message from Alice..."); + WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE); + tox_node_log(self, "Received message! Tox will automatically send a read receipt."); + + // We stay here to allow Alice to receive the receipt + // We wait until alice is finished + ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0); + WAIT_UNTIL(tox_node_is_finished(alice)); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + AliceState alice_state = {0, false}; + tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState)); + tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_friend_request_spam_test.c b/auto_tests/scenarios/scenario_friend_request_spam_test.c new file mode 100644 index 00000000..db85b075 --- /dev/null +++ b/auto_tests/scenarios/scenario_friend_request_spam_test.c @@ -0,0 +1,102 @@ +#include "framework/framework.h" +#include +#include +#include + +#define FR_MESSAGE "Gentoo" +#define FR_TOX_COUNT 33 + +typedef struct { + uint32_t requests_received; +} ReceiverState; + +static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + ReceiverState *state = (ReceiverState *)tox_node_get_script_ctx(self); + + const uint8_t *public_key = tox_event_friend_request_get_public_key(event); + const uint8_t *data = tox_event_friend_request_get_message(event); + const size_t length = tox_event_friend_request_get_message_length(event); + + if (length == sizeof(FR_MESSAGE) && memcmp(FR_MESSAGE, data, sizeof(FR_MESSAGE)) == 0) { + tox_friend_add_norequest(tox_node_get_tox(self), public_key, nullptr); + state->requests_received++; + tox_node_log(self, "Friend request received: %u", state->requests_received); + } +} + +static void receiver_script(ToxNode *self, void *ctx) +{ + const ReceiverState *state = (const ReceiverState *)ctx; + tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + + // Wait for all requests to be received and friends connected + WAIT_UNTIL(state->requests_received == FR_TOX_COUNT - 1); + + // Also wait until they are all connected as friends + bool all_connected = false; + while (!all_connected && tox_scenario_is_running(self)) { + all_connected = true; + for (uint32_t i = 0; i < FR_TOX_COUNT - 1; ++i) { + if (!tox_node_is_friend_connected(self, i)) { + all_connected = false; + break; + } + } + if (!all_connected) { + tox_scenario_yield(self); + } + } + + tox_node_log(self, "All friends connected"); +} + +static void sender_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + + ToxNode *receiver = (ToxNode *)ctx; + uint8_t address[TOX_ADDRESS_SIZE]; + tox_node_get_address(receiver, address); + + Tox_Err_Friend_Add err; + tox_friend_add(tox_node_get_tox(self), address, (const uint8_t *)FR_MESSAGE, sizeof(FR_MESSAGE), &err); + if (err != TOX_ERR_FRIEND_ADD_OK) { + tox_node_log(self, "Failed to add friend: %u", err); + return; + } + + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to receiver"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + ReceiverState receiver_state = {0}; + + ToxNode *receiver = tox_scenario_add_node(s, "Receiver", receiver_script, &receiver_state, sizeof(ReceiverState)); + ToxNode *senders[FR_TOX_COUNT - 1]; + + for (int i = 0; i < FR_TOX_COUNT - 1; ++i) { + char alias[16]; + snprintf(alias, sizeof(alias), "Sender%d", i); + senders[i] = tox_scenario_add_node(s, alias, sender_script, receiver, 0); + tox_node_bootstrap(senders[i], receiver); + tox_node_bootstrap(receiver, senders[i]); + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef FR_MESSAGE diff --git a/auto_tests/scenarios/scenario_friend_request_test.c b/auto_tests/scenarios/scenario_friend_request_test.c new file mode 100644 index 00000000..b14d8e56 --- /dev/null +++ b/auto_tests/scenarios/scenario_friend_request_test.c @@ -0,0 +1,78 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + const uint8_t *message; + size_t length; +} RequestData; + +static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + Tox *tox = tox_node_get_tox(self); + const RequestData *data = (const RequestData *)tox_node_get_script_ctx(self); + + const uint8_t *msg = tox_event_friend_request_get_message(event); + size_t len = tox_event_friend_request_get_message_length(event); + + if (len == data->length && memcmp(msg, data->message, len) == 0) { + tox_friend_add_norequest(tox, tox_event_friend_request_get_public_key(event), nullptr); + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + RequestData *data = (RequestData *)ctx; + + WAIT_UNTIL(tox_node_is_self_connected(self)); + + ToxNode *bob = tox_scenario_get_node(tox_node_get_scenario(self), 1); + uint8_t bob_addr[TOX_ADDRESS_SIZE]; + tox_node_get_address(bob, bob_addr); + + tox_friend_add(tox, bob_addr, data->message, data->length, nullptr); + + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); +} + +static void test_with_message(int argc, char *argv[], const char *label, const uint8_t *message, size_t length) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + tox_scenario_log(s, "Testing friend request: %s (length %zu)", label, length); + RequestData data = {message, length}; + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, &data, sizeof(RequestData)); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &data, sizeof(RequestData)); + + tox_node_bootstrap(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + exit(1); + } + tox_scenario_free(s); +} + +int main(int argc, char *argv[]) +{ + test_with_message(argc, argv, "Short", (const uint8_t *)"a", 1); + test_with_message(argc, argv, "Medium", (const uint8_t *)"Hello, let\'s be friends!", 24); + + uint8_t long_msg[TOX_MAX_FRIEND_REQUEST_LENGTH]; + memset(long_msg, 'F', sizeof(long_msg)); + test_with_message(argc, argv, "Max length", long_msg, sizeof(long_msg)); + + return 0; +} diff --git a/auto_tests/scenarios/scenario_group_general_test.c b/auto_tests/scenarios/scenario_group_general_test.c new file mode 100644 index 00000000..6b7630b5 --- /dev/null +++ b/auto_tests/scenarios/scenario_group_general_test.c @@ -0,0 +1,231 @@ +#include "framework/framework.h" +#include +#include +#include + +#define GROUP_NAME "NASA Headquarters" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) +#define TOPIC "Funny topic here" +#define TOPIC_LEN (sizeof(TOPIC) - 1) +#define PEER0_NICK "Lois" +#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) +#define PEER0_NICK2 "Terry Davis" +#define PEER0_NICK2_LEN (sizeof(PEER0_NICK2) - 1) +#define PEER1_NICK "Bran" +#define PEER1_NICK_LEN (sizeof(PEER1_NICK) - 1) +#define EXIT_MESSAGE "Goodbye world" +#define EXIT_MESSAGE_LEN (sizeof(EXIT_MESSAGE) - 1) +#define PEER_LIMIT 20 + +typedef struct { + uint32_t group_number; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + uint32_t peer_joined_count; + uint32_t self_joined_count; + uint32_t peer_exit_count; + bool peer_nick_updated; + bool peer_status_updated; + uint32_t last_peer_id; + bool is_founder; + bool synced; +} GroupState; + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->peer_joined_count++; + state->last_peer_id = tox_event_group_peer_join_get_peer_id(event); + tox_node_log(self, "Peer joined: %u (total %u)", state->last_peer_id, state->peer_joined_count); +} + +static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->self_joined_count++; + tox_node_log(self, "Self joined (total %u)", state->self_joined_count); +} + +static void on_group_peer_exit(const Tox_Event_Group_Peer_Exit *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->peer_exit_count++; + tox_node_log(self, "Peer exited (total %u)", state->peer_exit_count); + + if (state->peer_exit_count == 2) { + size_t len = tox_event_group_peer_exit_get_part_message_length(event); + const uint8_t *msg = tox_event_group_peer_exit_get_part_message(event); + ck_assert(len == EXIT_MESSAGE_LEN); + ck_assert(memcmp(msg, EXIT_MESSAGE, len) == 0); + } +} + +static void on_group_peer_name(const Tox_Event_Group_Peer_Name *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + size_t len = tox_event_group_peer_name_get_name_length(event); + const uint8_t *name = tox_event_group_peer_name_get_name(event); + if (len == PEER0_NICK2_LEN && memcmp(name, PEER0_NICK2, len) == 0) { + state->peer_nick_updated = true; + } +} + +static void on_group_peer_status(const Tox_Event_Group_Peer_Status *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + if (tox_event_group_peer_status_get_status(event) == TOX_USER_STATUS_BUSY) { + state->peer_status_updated = true; + } +} + +static void founder_script(ToxNode *self, void *ctx) +{ + GroupState *state = (GroupState *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_self_join(dispatch, on_group_self_join); + + tox_node_log(self, "Waiting for self connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Connected!"); + + Tox_Err_Group_New err_new; + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + tox_node_log(self, "Group created: %u", state->group_number); + + tox_group_set_peer_limit(tox, state->group_number, PEER_LIMIT, nullptr); + tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC, TOPIC_LEN, nullptr); + + tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr); + + // Signal Peer 1 that group is created + tox_scenario_barrier_wait(self); + + // Wait for Peer 1 to join + tox_node_log(self, "Waiting for peer to join..."); + WAIT_UNTIL(state->peer_joined_count == 1); + tox_node_log(self, "Peer joined!"); + + // Sync check + WAIT_UNTIL(tox_group_is_connected(tox, state->group_number, nullptr)); + + // Change name + tox_group_self_set_name(tox, state->group_number, (const uint8_t *)PEER0_NICK2, PEER0_NICK2_LEN, nullptr); + // Change status + tox_group_self_set_status(tox, state->group_number, TOX_USER_STATUS_BUSY, nullptr); + + // Disconnect + tox_node_log(self, "Disconnecting..."); + tox_group_disconnect(tox, state->group_number, nullptr); + + // Wait some time + for (int i = 0; i < 50; ++i) { + tox_scenario_yield(self); + } + + // Reconnect + tox_node_log(self, "Reconnecting..."); + tox_group_join(tox, state->chat_id, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, nullptr, 0, nullptr); + + WAIT_UNTIL(tox_group_is_connected(tox, state->group_number, nullptr)); + + // Wait for Peer 1 to see us + for (int i = 0; i < 100; ++i) { + tox_scenario_yield(self); + } + + // Leave + tox_node_log(self, "Leaving with message..."); + tox_group_leave(tox, state->group_number, (const uint8_t *)EXIT_MESSAGE, EXIT_MESSAGE_LEN, nullptr); +} + +static void peer1_script(ToxNode *self, void *ctx) +{ + const GroupState *state = (const GroupState *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_self_join(dispatch, on_group_self_join); + tox_events_callback_group_peer_name(dispatch, on_group_peer_name); + tox_events_callback_group_peer_status(dispatch, on_group_peer_status); + tox_events_callback_group_peer_exit(dispatch, on_group_peer_exit); + + tox_node_log(self, "Waiting for self connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Connected!"); + + // Wait for Founder to create group and get chat_id + tox_node_log(self, "Waiting for founder to create group..."); + tox_scenario_barrier_wait(self); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const GroupState *founder_view = (const GroupState *)tox_node_get_peer_ctx(founder); + + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + memcpy(chat_id, founder_view->chat_id, TOX_GROUP_CHAT_ID_SIZE); + tox_node_log(self, "Got chat ID from founder!"); + + tox_node_log(self, "Joining group..."); + tox_group_join(tox, chat_id, (const uint8_t *)PEER1_NICK, PEER1_NICK_LEN, nullptr, 0, nullptr); + + WAIT_UNTIL(state->self_joined_count == 1); + WAIT_UNTIL(state->peer_joined_count == 1); + WAIT_UNTIL(tox_group_is_connected(tox, 0, nullptr)); + + WAIT_UNTIL(state->peer_nick_updated); + WAIT_UNTIL(state->peer_status_updated); + + // Founder will disconnect + WAIT_UNTIL(state->peer_exit_count == 1); + + // Change status while alone + tox_group_self_set_status(tox, 0, TOX_USER_STATUS_AWAY, nullptr); + + // Founder will reconnect + WAIT_UNTIL(state->peer_joined_count == 2); + + // Founder will leave with message + WAIT_UNTIL(state->peer_exit_count == 2); + + tox_group_leave(tox, 0, nullptr, 0, nullptr); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + GroupState s0 = { .group_number = UINT32_MAX, .is_founder = true }; + GroupState s1 = { .group_number = UINT32_MAX, .is_founder = false }; + + ToxNode *n0 = tox_scenario_add_node(s, "Founder", founder_script, &s0, sizeof(GroupState)); + ToxNode *n1 = tox_scenario_add_node(s, "Peer1", peer1_script, &s1, sizeof(GroupState)); + + tox_node_bootstrap(n1, n0); + // Note: No friend add needed for public groups, they find each other via DHT + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef PEER_LIMIT +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef TOPIC +#undef TOPIC_LEN +#undef PEER0_NICK +#undef PEER0_NICK_LEN +#undef PEER1_NICK +#undef PEER1_NICK_LEN diff --git a/auto_tests/scenarios/scenario_group_invite_test.c b/auto_tests/scenarios/scenario_group_invite_test.c new file mode 100644 index 00000000..1e713bf0 --- /dev/null +++ b/auto_tests/scenarios/scenario_group_invite_test.c @@ -0,0 +1,303 @@ +#include "framework/framework.h" +#include +#include +#include + +#define PASSWORD "dadada" +#define PASS_LEN (sizeof(PASSWORD) - 1) +#define WRONG_PASS "dadadada" +#define WRONG_PASS_LEN (sizeof(WRONG_PASS) - 1) +#define PEER_LIMIT 1 + +typedef struct { + uint32_t group_number; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + bool peer_limit_fail; + bool password_fail; + bool connected; + uint32_t peer_count; +} State; + +static void on_group_join_fail(const Tox_Event_Group_Join_Fail *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + Tox_Group_Join_Fail fail_type = tox_event_group_join_fail_get_fail_type(event); + + if (fail_type == TOX_GROUP_JOIN_FAIL_PEER_LIMIT) { + state->peer_limit_fail = true; + } else if (fail_type == TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD) { + state->password_fail = true; + } +} + +static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + state->connected = true; +} + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + state->peer_count++; +} + +static void founder_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_self_join(dispatch, on_group_self_join); + + tox_node_wait_for_self_connected(self); + + Tox_Err_Group_New err_new; + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"test", 4, (const uint8_t *)"test", 4, &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr); + + // Phase 1: Peer 1 joins with no password + tox_scenario_barrier_wait(self); // Barrier 1: Founder created group + WAIT_UNTIL(state->peer_count == 1); + tox_node_log(self, "Peer 1 joined."); + + // Phase 2: Set password + tox_group_set_password(tox, state->group_number, (const uint8_t *)PASSWORD, PASS_LEN, nullptr); + tox_scenario_barrier_wait(self); // Barrier 2: Founder set password + + // Phase 3: Peer 2 attempts with no password (and fails) + // Phase 4: Peer 3 attempts with wrong password (and fails) + tox_scenario_barrier_wait(self); // Barrier 3: Peers 2 and 3 finished attempts + + // Phase 5: Set peer limit + tox_group_set_peer_limit(tox, state->group_number, PEER_LIMIT, nullptr); + tox_scenario_barrier_wait(self); // Barrier 4: Founder set peer limit + + // Phase 6: Peer 4 attempts with correct password (and fails due to limit) + tox_scenario_barrier_wait(self); // Barrier 5: Peer 4 finished attempt + + // Phase 7: Remove password and increase limit + tox_group_set_password(tox, state->group_number, nullptr, 0, nullptr); + tox_group_set_peer_limit(tox, state->group_number, 100, nullptr); + tox_scenario_barrier_wait(self); // Barrier 6: Founder relaxed restrictions + + // Phase 8: Peer 5 joins + WAIT_UNTIL(state->peer_count >= 2); + + // Phase 9: Set private state + tox_group_set_privacy_state(tox, state->group_number, TOX_GROUP_PRIVACY_STATE_PRIVATE, nullptr); + tox_scenario_barrier_wait(self); // Barrier 7: Founder made group private + + // Phase 10: Peer 6 attempts join (and fails because it's private) + tox_scenario_barrier_wait(self); // Barrier 8: Peer 6 finished wait + + // Phase 11: Make group public again + tox_group_set_privacy_state(tox, state->group_number, TOX_GROUP_PRIVACY_STATE_PUBLIC, nullptr); + tox_scenario_barrier_wait(self); // Barrier 9: Founder made group public again + + // Final phase: Everyone leaves + WAIT_UNTIL(state->peer_count >= 2); // At least Peer 1 and 5 are here + tox_scenario_barrier_wait(self); // Barrier 10: Ready to leave + tox_group_leave(tox, state->group_number, nullptr, 0, nullptr); +} + +static void peer1_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + tox_node_wait_for_self_connected(self); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const State *founder_view = (const State *)tox_node_get_peer_ctx(founder); + tox_scenario_barrier_wait(self); // Barrier 1: Founder created group + + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer1", 5, nullptr, 0, nullptr); + + tox_scenario_barrier_wait(self); // Barrier 2 + tox_scenario_barrier_wait(self); // Barrier 3 + tox_scenario_barrier_wait(self); // Barrier 4 + tox_scenario_barrier_wait(self); // Barrier 5 + tox_scenario_barrier_wait(self); // Barrier 6 + tox_scenario_barrier_wait(self); // Barrier 7 + tox_scenario_barrier_wait(self); // Barrier 8 + tox_scenario_barrier_wait(self); // Barrier 9 + tox_scenario_barrier_wait(self); // Barrier 10 + tox_group_leave(tox, 0, nullptr, 0, nullptr); +} + +static void peer2_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + Tox *tox = tox_node_get_tox(self); + tox_events_callback_group_join_fail(tox_node_get_dispatch(self), on_group_join_fail); + tox_node_wait_for_self_connected(self); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const State *founder_view = (const State *)tox_node_get_peer_ctx(founder); + tox_scenario_barrier_wait(self); // 1 + tox_scenario_barrier_wait(self); // 2: Password set + + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer2", 5, nullptr, 0, nullptr); + + WAIT_UNTIL(state->password_fail); + tox_node_log(self, "Blocked by password as expected."); + + tox_scenario_barrier_wait(self); // 3 + tox_scenario_barrier_wait(self); // 4 + tox_scenario_barrier_wait(self); // 5 + tox_scenario_barrier_wait(self); // 6 + tox_scenario_barrier_wait(self); // 7 + tox_scenario_barrier_wait(self); // 8 + tox_scenario_barrier_wait(self); // 9 + tox_scenario_barrier_wait(self); // 10 +} + +static void peer3_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + Tox *tox = tox_node_get_tox(self); + tox_events_callback_group_join_fail(tox_node_get_dispatch(self), on_group_join_fail); + tox_node_wait_for_self_connected(self); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const State *founder_view = (const State *)tox_node_get_peer_ctx(founder); + tox_scenario_barrier_wait(self); // 1 + tox_scenario_barrier_wait(self); // 2 + + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer3", 5, (const uint8_t *)WRONG_PASS, WRONG_PASS_LEN, nullptr); + + WAIT_UNTIL(state->password_fail); + tox_node_log(self, "Blocked by wrong password as expected."); + + tox_scenario_barrier_wait(self); // 3 + tox_scenario_barrier_wait(self); // 4 + tox_scenario_barrier_wait(self); // 5 + tox_scenario_barrier_wait(self); // 6 + tox_scenario_barrier_wait(self); // 7 + tox_scenario_barrier_wait(self); // 8 + tox_scenario_barrier_wait(self); // 9 + tox_scenario_barrier_wait(self); // 10 +} + +static void peer4_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + Tox *tox = tox_node_get_tox(self); + tox_events_callback_group_join_fail(tox_node_get_dispatch(self), on_group_join_fail); + tox_node_wait_for_self_connected(self); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const State *founder_view = (const State *)tox_node_get_peer_ctx(founder); + tox_scenario_barrier_wait(self); // 1 + tox_scenario_barrier_wait(self); // 2 + tox_scenario_barrier_wait(self); // 3 + tox_scenario_barrier_wait(self); // 4: Peer limit set + + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer4", 5, (const uint8_t *)PASSWORD, PASS_LEN, nullptr); + + WAIT_UNTIL(state->peer_limit_fail); + tox_node_log(self, "Blocked by peer limit as expected."); + + tox_scenario_barrier_wait(self); // 5 + tox_scenario_barrier_wait(self); // 6 + tox_scenario_barrier_wait(self); // 7 + tox_scenario_barrier_wait(self); // 8 + tox_scenario_barrier_wait(self); // 9 + tox_scenario_barrier_wait(self); // 10 +} + +static void peer5_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + Tox *tox = tox_node_get_tox(self); + tox_events_callback_group_self_join(tox_node_get_dispatch(self), on_group_self_join); + tox_node_wait_for_self_connected(self); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const State *founder_view = (const State *)tox_node_get_peer_ctx(founder); + tox_scenario_barrier_wait(self); // 1 + tox_scenario_barrier_wait(self); // 2 + tox_scenario_barrier_wait(self); // 3 + tox_scenario_barrier_wait(self); // 4 + tox_scenario_barrier_wait(self); // 5 + tox_scenario_barrier_wait(self); // 6: Restrictions relaxed + + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer5", 5, nullptr, 0, nullptr); + + WAIT_UNTIL(state->connected); + tox_node_log(self, "Joined group."); + + tox_scenario_barrier_wait(self); // 7 + tox_scenario_barrier_wait(self); // 8 + tox_scenario_barrier_wait(self); // 9 + tox_scenario_barrier_wait(self); // 10 + tox_group_leave(tox, 0, nullptr, 0, nullptr); +} + +static void peer6_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + Tox *tox = tox_node_get_tox(self); + tox_events_callback_group_self_join(tox_node_get_dispatch(self), on_group_self_join); + tox_node_wait_for_self_connected(self); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const State *founder_view = (const State *)tox_node_get_peer_ctx(founder); + tox_scenario_barrier_wait(self); // 1 + tox_scenario_barrier_wait(self); // 2 + tox_scenario_barrier_wait(self); // 3 + tox_scenario_barrier_wait(self); // 4 + tox_scenario_barrier_wait(self); // 5 + tox_scenario_barrier_wait(self); // 6 + tox_scenario_barrier_wait(self); // 7: Private group + + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer6", 5, nullptr, 0, nullptr); + + // Wait some time to be sure we are NOT connected + for (int i = 0; i < 2000 / TOX_SCENARIO_TICK_MS; ++i) { + ck_assert(!state->connected); + tox_scenario_yield(self); + } + tox_node_log(self, "Could not join private group via chat ID as expected."); + + tox_scenario_barrier_wait(self); // 8 + tox_scenario_barrier_wait(self); // 9 + tox_scenario_barrier_wait(self); // 10 +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + State states[7] = {0}; + + ToxNode *nodes[7]; + nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(State)); + nodes[1] = tox_scenario_add_node(s, "Peer1", peer1_script, &states[1], sizeof(State)); + nodes[2] = tox_scenario_add_node(s, "Peer2", peer2_script, &states[2], sizeof(State)); + nodes[3] = tox_scenario_add_node(s, "Peer3", peer3_script, &states[3], sizeof(State)); + nodes[4] = tox_scenario_add_node(s, "Peer4", peer4_script, &states[4], sizeof(State)); + nodes[5] = tox_scenario_add_node(s, "Peer5", peer5_script, &states[5], sizeof(State)); + nodes[6] = tox_scenario_add_node(s, "Peer6", peer6_script, &states[6], sizeof(State)); + + for (int i = 1; i < 7; ++i) { + tox_node_bootstrap(nodes[i], nodes[0]); + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef PASSWORD +#undef PASS_LEN +#undef PEER_LIMIT diff --git a/auto_tests/scenarios/scenario_group_message_test.c b/auto_tests/scenarios/scenario_group_message_test.c new file mode 100644 index 00000000..2e6cf020 --- /dev/null +++ b/auto_tests/scenarios/scenario_group_message_test.c @@ -0,0 +1,214 @@ +#include "framework/framework.h" +#include +#include +#include + +#define PEER0_NICK "Thomas" +#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) +#define PEER1_NICK "Winslow" +#define PEER1_NICK_LEN (sizeof(PEER1_NICK) - 1) +#define TEST_GROUP_NAME "Utah Data Center" +#define TEST_GROUP_NAME_LEN (sizeof(TEST_GROUP_NAME) - 1) +#define TEST_MESSAGE "Where is it I've read that someone condemned to death says or thinks..." +#define TEST_MESSAGE_LEN (sizeof(TEST_MESSAGE) - 1) +#define TEST_PRIVATE_MESSAGE "Don't spill yer beans" +#define TEST_PRIVATE_MESSAGE_LEN (sizeof(TEST_PRIVATE_MESSAGE) - 1) +#define TEST_CUSTOM_PACKET "Why'd ya spill yer beans?" +#define TEST_CUSTOM_PACKET_LEN (sizeof(TEST_CUSTOM_PACKET) - 1) +#define TEST_CUSTOM_PRIVATE_PACKET "This is a custom private packet. Enjoy." +#define TEST_CUSTOM_PRIVATE_PACKET_LEN (sizeof(TEST_CUSTOM_PRIVATE_PACKET) - 1) +#define MAX_NUM_MESSAGES_LOSSLESS_TEST 100 + +typedef struct { + uint32_t group_number; + uint32_t peer_id; + bool peer_joined; + bool message_received; + bool private_message_received; + size_t custom_packets_received; + size_t custom_private_packets_received; + int32_t last_msg_recv; + bool lossless_done; +} GroupState; + +static uint16_t get_message_checksum(const uint8_t *message, uint16_t length) +{ + uint16_t sum = 0; + for (size_t i = 0; i < length; ++i) { + sum += message[i]; + } + return sum; +} + +static void on_group_invite(const Tox_Event_Group_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + Tox_Err_Group_Invite_Accept err; + state->group_number = tox_group_invite_accept(tox_node_get_tox(self), + tox_event_group_invite_get_friend_number(event), + tox_event_group_invite_get_invite_data(event), + tox_event_group_invite_get_invite_data_length(event), + (const uint8_t *)PEER1_NICK, PEER1_NICK_LEN, nullptr, 0, &err); + tox_node_log(self, "Accepted group invite, group %u", state->group_number); +} + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->peer_joined = true; + state->peer_id = tox_event_group_peer_join_get_peer_id(event); + tox_node_log(self, "Peer %u joined group", state->peer_id); +} + +static void on_group_message(const Tox_Event_Group_Message *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + const uint8_t *msg = tox_event_group_message_get_message(event); + size_t len = tox_event_group_message_get_message_length(event); + + if (len == TEST_MESSAGE_LEN && memcmp(msg, TEST_MESSAGE, len) == 0) { + state->message_received = true; + tox_node_log(self, "Received group message"); + } else if (len >= 4) { + uint16_t start, checksum; + memcpy(&start, msg, 2); + memcpy(&checksum, msg + 2, 2); + if (checksum == get_message_checksum(msg + 4, len - 4)) { + state->last_msg_recv = start; + if (start == MAX_NUM_MESSAGES_LOSSLESS_TEST) { + state->lossless_done = true; + } + } + } +} + +static void on_group_private_message(const Tox_Event_Group_Private_Message *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + const uint8_t *msg = tox_event_group_private_message_get_message(event); + size_t len = tox_event_group_private_message_get_message_length(event); + + if (len == TEST_PRIVATE_MESSAGE_LEN && memcmp(msg, TEST_PRIVATE_MESSAGE, len) == 0) { + state->private_message_received = true; + tox_node_log(self, "Received private group message"); + } +} + +static void on_group_custom_packet(const Tox_Event_Group_Custom_Packet *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->custom_packets_received++; + tox_node_log(self, "Received custom packet %zu", state->custom_packets_received); +} + +static void on_group_custom_private_packet(const Tox_Event_Group_Custom_Private_Packet *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->custom_private_packets_received++; + tox_node_log(self, "Received custom private packet %zu", state->custom_private_packets_received); +} + +static void peer0_script(ToxNode *self, void *ctx) +{ + GroupState *state = (GroupState *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_message(dispatch, on_group_message); + tox_events_callback_group_private_message(dispatch, on_group_private_message); + tox_events_callback_group_custom_packet(dispatch, on_group_custom_packet); + tox_events_callback_group_custom_private_packet(dispatch, on_group_custom_private_packet); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PRIVATE, (const uint8_t *)TEST_GROUP_NAME, TEST_GROUP_NAME_LEN, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, nullptr); + tox_group_invite_friend(tox, state->group_number, 0, nullptr); + tox_node_log(self, "Created group and invited friend"); + + WAIT_UNTIL(state->peer_joined); + WAIT_UNTIL(state->message_received); + WAIT_UNTIL(state->private_message_received); + WAIT_UNTIL(state->custom_packets_received >= 2); + WAIT_UNTIL(state->custom_private_packets_received >= 2); + + // Lossless test + uint8_t m[TOX_GROUP_MAX_MESSAGE_LENGTH]; + for (uint16_t i = 0; i <= MAX_NUM_MESSAGES_LOSSLESS_TEST; ++i) { + uint32_t size = 4 + (rand() % 100); + memcpy(m, &i, 2); + for (size_t j = 4; j < size; ++j) { + uint32_t val = rand() % 256; + m[j] = (uint8_t)val; + } + uint16_t checksum = get_message_checksum(m + 4, (uint16_t)(size - 4)); + memcpy(m + 2, &checksum, 2); + tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, m, size, nullptr); + if (i % 10 == 0) { + tox_scenario_yield(self); + } + } + + tox_node_log(self, "Done"); +} + +static void peer1_script(ToxNode *self, void *ctx) +{ + const GroupState *state = (const GroupState *)ctx; + const Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_group_invite(dispatch, on_group_invite); + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_message(dispatch, on_group_message); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(state->peer_joined); + + tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)TEST_MESSAGE, TEST_MESSAGE_LEN, nullptr); + tox_group_send_private_message(tox, state->group_number, state->peer_id, TOX_MESSAGE_TYPE_ACTION, (const uint8_t *)TEST_PRIVATE_MESSAGE, TEST_PRIVATE_MESSAGE_LEN, nullptr); + + tox_group_send_custom_packet(tox, state->group_number, true, (const uint8_t *)TEST_CUSTOM_PACKET, TEST_CUSTOM_PACKET_LEN, nullptr); + tox_group_send_custom_packet(tox, state->group_number, false, (const uint8_t *)TEST_CUSTOM_PACKET, TEST_CUSTOM_PACKET_LEN, nullptr); + + tox_group_send_custom_private_packet(tox, state->group_number, state->peer_id, true, (const uint8_t *)TEST_CUSTOM_PRIVATE_PACKET, TEST_CUSTOM_PRIVATE_PACKET_LEN, nullptr); + tox_group_send_custom_private_packet(tox, state->group_number, state->peer_id, false, (const uint8_t *)TEST_CUSTOM_PRIVATE_PACKET, TEST_CUSTOM_PRIVATE_PACKET_LEN, nullptr); + + WAIT_UNTIL(state->lossless_done); + tox_node_log(self, "Done"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + GroupState s0 = { .last_msg_recv = -1 }, s1 = { .last_msg_recv = -1 }; + + ToxNode *n0 = tox_scenario_add_node(s, "Peer0", peer0_script, &s0, sizeof(GroupState)); + ToxNode *n1 = tox_scenario_add_node(s, "Peer1", peer1_script, &s1, sizeof(GroupState)); + + tox_node_friend_add(n0, n1); + tox_node_friend_add(n1, n0); + tox_node_bootstrap(n0, n1); + tox_node_bootstrap(n1, n0); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef PEER0_NICK +#undef PEER0_NICK_LEN +#undef PEER1_NICK +#undef PEER1_NICK_LEN diff --git a/auto_tests/scenarios/scenario_group_moderation_test.c b/auto_tests/scenarios/scenario_group_moderation_test.c new file mode 100644 index 00000000..938932bb --- /dev/null +++ b/auto_tests/scenarios/scenario_group_moderation_test.c @@ -0,0 +1,367 @@ +#include "framework/framework.h" +#include +#include +#include + +#define NUM_PEERS 5 +#define GROUP_NAME "Moderation Test Group" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) + +typedef struct { + uint32_t group_number; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + bool chat_id_ready; + uint32_t peer_count; + bool connected; + Tox_Group_Role self_role; + Tox_Group_Voice_State voice_state; + bool kicked; + uint32_t peer_ids[NUM_PEERS]; // Map node index to group peer_id + Tox_Group_Mod_Event last_mod_event; + uint32_t last_mod_target; +} ModState; + +static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + ModState *state = (ModState *)tox_node_get_script_ctx(self); + state->connected = true; + uint32_t group_number = tox_event_group_self_join_get_group_number(event); + uint32_t self_id = tox_group_self_get_peer_id(tox_node_get_tox(self), group_number, nullptr); + state->self_role = tox_group_self_get_role(tox_node_get_tox(self), group_number, nullptr); + tox_node_log(self, "Joined group %u (Peer ID: %u) with role %s", group_number, self_id, tox_group_role_to_string(state->self_role)); +} + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + ModState *state = (ModState *)tox_node_get_script_ctx(self); + uint32_t group_number = tox_event_group_peer_join_get_group_number(event); + uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); + + tox_node_log(self, "Peer %u joined the group", peer_id); + state->peer_count++; + + Tox_Err_Group_Peer_Query q_err; + size_t length = tox_group_peer_get_name_size(tox_node_get_tox(self), group_number, peer_id, &q_err); + if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK && length > 0) { + uint8_t name[TOX_MAX_NAME_LENGTH]; + tox_group_peer_get_name(tox_node_get_tox(self), group_number, peer_id, name, &q_err); + if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK) { + tox_node_log(self, "Peer %u name identified: %.*s", peer_id, (int)length, name); + if (length == 7 && memcmp(name, "Founder", 7) == 0) { + state->peer_ids[0] = peer_id; + } else if (length >= 5 && memcmp(name, "Peer", 4) == 0) { + int idx = atoi((const char *)name + 4); + if (idx > 0 && idx < NUM_PEERS) { + state->peer_ids[idx] = peer_id; + } + } + } + } +} + +static void on_group_moderation(const Tox_Event_Group_Moderation *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + ModState *state = (ModState *)tox_node_get_script_ctx(self); + state->last_mod_event = tox_event_group_moderation_get_mod_type(event); + state->last_mod_target = tox_event_group_moderation_get_target_peer_id(event); + + Tox_Err_Group_Self_Query err; + state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, &err); + + if (state->last_mod_event == TOX_GROUP_MOD_EVENT_KICK && state->last_mod_target == tox_group_self_get_peer_id(tox_node_get_tox(self), state->group_number, nullptr)) { + state->kicked = true; + } + + tox_node_log(self, "Moderation event: %s on peer %u. My role is now %s", + tox_group_mod_event_to_string(state->last_mod_event), + state->last_mod_target, + tox_group_role_to_string(state->self_role)); +} + +static void on_group_voice_state(const Tox_Event_Group_Voice_State *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + ModState *state = (ModState *)tox_node_get_script_ctx(self); + state->voice_state = tox_event_group_voice_state_get_voice_state(event); + tox_node_log(self, "Voice state updated: %u", state->voice_state); +} + +static void common_init(ToxNode *self, ModState *state) +{ + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + tox_events_callback_group_self_join(dispatch, on_group_self_join); + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_moderation(dispatch, on_group_moderation); + tox_events_callback_group_voice_state(dispatch, on_group_voice_state); + + for (int i = 0; i < NUM_PEERS; ++i) { + state->peer_ids[i] = UINT32_MAX; + } + + tox_node_log(self, "Waiting for self connection..."); + tox_node_wait_for_self_connected(self); + tox_node_log(self, "Connected!"); +} + +static void wait_for_peer_role(ToxNode *self, uint32_t peer_idx, Tox_Group_Role expected_role) +{ + const ToxNode *peer = tox_scenario_get_node(tox_node_get_scenario(self), peer_idx); + const ModState *peer_view = (const ModState *)tox_node_get_peer_ctx(peer); + + tox_node_log(self, "Waiting for Peer %u to have role %s", peer_idx, tox_group_role_to_string(expected_role)); + WAIT_UNTIL(peer_view->self_role == expected_role); + tox_node_log(self, "Peer %u now has role %s", peer_idx, tox_group_role_to_string(expected_role)); +} + +static void founder_script(ToxNode *self, void *ctx) +{ + ModState *state = (ModState *)ctx; + Tox *tox = tox_node_get_tox(self); + common_init(self, state); + + Tox_Err_Group_New err_new; + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Founder", 7, &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + state->self_role = TOX_GROUP_ROLE_FOUNDER; + state->peer_ids[0] = tox_group_self_get_peer_id(tox, state->group_number, nullptr); + + tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr); + state->chat_id_ready = true; + + // Barrier 1: Wait for all peers to join and be seen by everyone + tox_scenario_barrier_wait(self); + WAIT_UNTIL(state->peer_count == NUM_PEERS - 1); + tox_node_log(self, "All peers joined"); + + // Wait until we know all peer IDs + for (int i = 1; i < NUM_PEERS; ++i) { + WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX); + } + tox_node_log(self, "All peer IDs identified"); + + tox_scenario_barrier_wait(self); // Sync point after everyone sees everyone + + // Barrier 2: Peer 1 becomes Moderator + tox_group_set_role(tox, state->group_number, state->peer_ids[1], TOX_GROUP_ROLE_MODERATOR, nullptr); + for (int i = 0; i < NUM_PEERS; ++i) { + wait_for_peer_role(self, 1, TOX_GROUP_ROLE_MODERATOR); + } + tox_scenario_barrier_wait(self); + + // Barrier 3: Peer 2 and 3 become Observer + tox_group_set_role(tox, state->group_number, state->peer_ids[2], TOX_GROUP_ROLE_OBSERVER, nullptr); + tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_OBSERVER, nullptr); + for (int i = 0; i < NUM_PEERS; ++i) { + wait_for_peer_role(self, 2, TOX_GROUP_ROLE_OBSERVER); + wait_for_peer_role(self, 3, TOX_GROUP_ROLE_OBSERVER); + } + tox_scenario_barrier_wait(self); + + // Barrier 4: Voice State tests + tox_node_log(self, "Setting voice state to MODERATOR"); + tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_MODERATOR, nullptr); + tox_scenario_barrier_wait(self); // Phase 1 set + tox_scenario_barrier_wait(self); // Phase 1 done + + tox_node_log(self, "Setting voice state to FOUNDER"); + tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_FOUNDER, nullptr); + tox_scenario_barrier_wait(self); // Phase 2 set + tox_scenario_barrier_wait(self); // Phase 2 done + + tox_node_log(self, "Setting voice state to ALL"); + tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_ALL, nullptr); + tox_scenario_barrier_wait(self); // Phase 3 set + tox_scenario_barrier_wait(self); // Phase 3 done + + // Barrier 5: Peer 1 (Mod) promotes Peer 2 back to User + tox_scenario_barrier_wait(self); + wait_for_peer_role(self, 2, TOX_GROUP_ROLE_USER); + + // Barrier 6: Founder promotes Peer 3 to Moderator + tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_MODERATOR, nullptr); + wait_for_peer_role(self, 3, TOX_GROUP_ROLE_MODERATOR); + tox_scenario_barrier_wait(self); + + // Barrier 7: Moderator (Peer 1) attempts to kick/demote Founder (should fail) + tox_scenario_barrier_wait(self); + + // Barrier 8: Founder kicks Moderator (Peer 1) + tox_group_kick_peer(tox, state->group_number, state->peer_ids[1], nullptr); + tox_scenario_barrier_wait(self); + + // Barrier 9: Founder demotes Moderator (Peer 3) to User + tox_group_set_role(tox, state->group_number, state->peer_ids[3], TOX_GROUP_ROLE_USER, nullptr); + wait_for_peer_role(self, 3, TOX_GROUP_ROLE_USER); + tox_scenario_barrier_wait(self); + + tox_scenario_barrier_wait(self); // Done +} + +static void peer_script(ToxNode *self, void *ctx) +{ + ModState *state = (ModState *)ctx; + Tox *tox = tox_node_get_tox(self); + common_init(self, state); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const ModState *founder_view = (const ModState *)tox_node_get_peer_ctx(founder); + + while (!founder_view->chat_id_ready) { + tox_scenario_yield(self); + } + tox_node_log(self, "Got chat ID from founder"); + + char name[16]; + snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self)); + Tox_Err_Group_Join err_join; + state->group_number = tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, &err_join); + if (state->group_number == UINT32_MAX) { + tox_node_log(self, "tox_group_join failed with error %u", err_join); + } + ck_assert(state->group_number != UINT32_MAX); + + WAIT_UNTIL(state->connected); + + uint32_t self_id = tox_group_self_get_peer_id(tox, state->group_number, nullptr); + state->peer_ids[tox_node_get_index(self)] = self_id; + + tox_scenario_barrier_wait(self); // Barrier 1: Joined + + // Wait until we know all peer IDs + for (uint32_t i = 0; i < NUM_PEERS; ++i) { + if (tox_node_get_index(self) != i) { + WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX); + } + } + + tox_scenario_barrier_wait(self); // Sync point after everyone sees everyone + + tox_scenario_barrier_wait(self); // Barrier 2: Peer 1 Moderator + wait_for_peer_role(self, 1, TOX_GROUP_ROLE_MODERATOR); + + tox_scenario_barrier_wait(self); // Barrier 3: Peer 2/3 Observer + wait_for_peer_role(self, 2, TOX_GROUP_ROLE_OBSERVER); + wait_for_peer_role(self, 3, TOX_GROUP_ROLE_OBSERVER); + + // Barrier 4: Voice State tests + // Sub-phase 1: MODERATOR + tox_scenario_barrier_wait(self); // Phase 1 set + WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_MODERATOR); + Tox_Err_Group_Send_Message err_msg; + tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg); + if (state->self_role == TOX_GROUP_ROLE_MODERATOR || state->self_role == TOX_GROUP_ROLE_FOUNDER) { + if (err_msg != TOX_ERR_GROUP_SEND_MESSAGE_OK) { + tox_node_log(self, "Expected OK, got %u. Role: %s", err_msg, tox_group_role_to_string(state->self_role)); + } + ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK); + } else { + if (err_msg != TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS) { + tox_node_log(self, "Expected PERMISSIONS, got %u. Role: %s", err_msg, tox_group_role_to_string(state->self_role)); + } + ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS); + } + tox_scenario_barrier_wait(self); // Phase 1 done + + // Sub-phase 2: FOUNDER + tox_scenario_barrier_wait(self); // Phase 2 set + WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_FOUNDER); + tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg); + if (state->self_role == TOX_GROUP_ROLE_FOUNDER) { + ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK); + } else { + ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS); + } + tox_scenario_barrier_wait(self); // Phase 2 done + + // Sub-phase 3: ALL + tox_scenario_barrier_wait(self); // Phase 3 set + WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_ALL); + tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"hello", 5, &err_msg); + if (state->self_role == TOX_GROUP_ROLE_OBSERVER) { + ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS); + } else { + ck_assert(err_msg == TOX_ERR_GROUP_SEND_MESSAGE_OK); + } + tox_scenario_barrier_wait(self); // Phase 3 done + + // Barrier 5: Peer 1 (Mod) promotes Peer 2 back to User + if (tox_node_get_index(self) == 1) { // Peer 1 + uint32_t peer2_id = state->peer_ids[2]; + tox_group_set_role(tox, state->group_number, peer2_id, TOX_GROUP_ROLE_USER, nullptr); + } + tox_scenario_barrier_wait(self); + wait_for_peer_role(self, 2, TOX_GROUP_ROLE_USER); + + // Barrier 6: Founder promotes Peer 3 to Moderator + tox_scenario_barrier_wait(self); + wait_for_peer_role(self, 3, TOX_GROUP_ROLE_MODERATOR); + + // Barrier 7: Moderator (Peer 1) attempts to kick/demote Founder (should fail) + if (tox_node_get_index(self) == 1) { + Tox_Err_Group_Kick_Peer err_kick; + uint32_t founder_peer_id = state->peer_ids[0]; + tox_group_kick_peer(tox, state->group_number, founder_peer_id, &err_kick); + ck_assert(err_kick != TOX_ERR_GROUP_KICK_PEER_OK); + + Tox_Err_Group_Set_Role err_role; + tox_group_set_role(tox, state->group_number, founder_peer_id, TOX_GROUP_ROLE_USER, &err_role); + ck_assert(err_role != TOX_ERR_GROUP_SET_ROLE_OK); + } + tox_scenario_barrier_wait(self); + + // Barrier 8: Founder kicks Moderator (Peer 1) + tox_scenario_barrier_wait(self); + if (tox_node_get_index(self) == 1) { + WAIT_UNTIL(state->kicked); + return; // Exit script + } + + // Barrier 9: Founder demotes Moderator (Peer 3) to User + tox_scenario_barrier_wait(self); + wait_for_peer_role(self, 3, TOX_GROUP_ROLE_USER); + + tox_scenario_barrier_wait(self); // Done +} + +int main(int argc, char *argv[]) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + setvbuf(stderr, nullptr, _IONBF, 0); + + ToxScenario *s = tox_scenario_new(argc, argv, 300000); // 5 virtual minutes + ModState states[NUM_PEERS] = {0}; + ToxNode *nodes[NUM_PEERS]; + + nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(ModState)); + static char aliases[NUM_PEERS][16]; + for (int i = 1; i < NUM_PEERS; ++i) { + snprintf(aliases[i], sizeof(aliases[i]), "Peer%d", i); + nodes[i] = tox_scenario_add_node(s, aliases[i], peer_script, &states[i], sizeof(ModState)); + } + + for (int i = 0; i < NUM_PEERS; ++i) { + for (int j = 0; j < NUM_PEERS; ++j) { + if (i != j) { + tox_node_bootstrap(nodes[i], nodes[j]); + tox_node_friend_add(nodes[i], nodes[j]); + } + } + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef NUM_PEERS diff --git a/auto_tests/scenarios/scenario_group_save_test.c b/auto_tests/scenarios/scenario_group_save_test.c new file mode 100644 index 00000000..bc734e0e --- /dev/null +++ b/auto_tests/scenarios/scenario_group_save_test.c @@ -0,0 +1,172 @@ +#include "framework/framework.h" +#include +#include +#include + +#define GROUP_NAME "The Test Chamber" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) +#define TOPIC "They're waiting for you Gordon..." +#define TOPIC_LEN (sizeof(TOPIC) - 1) +#define NEW_PRIV_STATE TOX_GROUP_PRIVACY_STATE_PRIVATE +#define PASSWORD "password123" +#define PASS_LEN (sizeof(PASSWORD) - 1) +#define PEER_LIMIT 69 +#define PEER0_NICK "Mike" +#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) +#define NEW_USER_STATUS TOX_USER_STATUS_BUSY + +typedef struct { + bool peer_joined; +} State; + +static void on_group_invite(const Tox_Event_Group_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + uint32_t friend_number = tox_event_group_invite_get_friend_number(event); + const uint8_t *invite_data = tox_event_group_invite_get_invite_data(event); + size_t length = tox_event_group_invite_get_invite_data_length(event); + + tox_group_invite_accept(tox_node_get_tox(self), friend_number, invite_data, length, (const uint8_t *)"Bob", 3, nullptr, 0, nullptr); +} + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + state->peer_joined = true; +} + +static void check_founder_state(ToxNode *self, uint32_t group_number, const uint8_t *expected_chat_id, const uint8_t *expected_self_pk) +{ + Tox *tox = tox_node_get_tox(self); + Tox_Err_Group_State_Query query_err; + + // Group state + ck_assert(tox_group_get_privacy_state(tox, group_number, &query_err) == NEW_PRIV_STATE); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); + + uint8_t password[TOX_GROUP_MAX_PASSWORD_SIZE]; + size_t pass_len = tox_group_get_password_size(tox, group_number, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); + tox_group_get_password(tox, group_number, password, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); + ck_assert(pass_len == PASS_LEN && memcmp(password, PASSWORD, pass_len) == 0); + + uint8_t gname[TOX_GROUP_MAX_GROUP_NAME_LENGTH]; + size_t gname_len = tox_group_get_name_size(tox, group_number, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERY_OK); + tox_group_get_name(tox, group_number, gname, &query_err); + ck_assert(gname_len == GROUP_NAME_LEN && memcmp(gname, GROUP_NAME, gname_len) == 0); + + ck_assert(tox_group_get_peer_limit(tox, group_number, nullptr) == PEER_LIMIT); + ck_assert(tox_group_get_topic_lock(tox, group_number, nullptr) == TOX_GROUP_TOPIC_LOCK_DISABLED); + + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + tox_group_get_chat_id(tox, group_number, chat_id, nullptr); + ck_assert(memcmp(chat_id, expected_chat_id, TOX_GROUP_CHAT_ID_SIZE) == 0); + + // Self state + Tox_Err_Group_Self_Query sq_err; + uint8_t self_name[TOX_MAX_NAME_LENGTH]; + size_t self_len = tox_group_self_get_name_size(tox, group_number, &sq_err); + tox_group_self_get_name(tox, group_number, self_name, nullptr); + ck_assert(self_len == PEER0_NICK_LEN && memcmp(self_name, PEER0_NICK, self_len) == 0); + + ck_assert(tox_group_self_get_status(tox, group_number, nullptr) == NEW_USER_STATUS); + ck_assert(tox_group_self_get_role(tox, group_number, nullptr) == TOX_GROUP_ROLE_FOUNDER); + + uint8_t self_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; + tox_group_self_get_public_key(tox, group_number, self_pk, nullptr); + ck_assert(memcmp(self_pk, expected_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0); +} + +static void founder_script(ToxNode *self, void *ctx) +{ + const State *state = (const State *)ctx; + Tox *tox = tox_node_get_tox(self); + tox_events_callback_group_peer_join(tox_node_get_dispatch(self), on_group_peer_join); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + Tox_Err_Group_New err_new; + uint32_t group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PRIVATE, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"test", 4, &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + tox_group_get_chat_id(tox, group_number, chat_id, nullptr); + + uint8_t founder_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; + tox_group_self_get_public_key(tox, group_number, founder_pk, nullptr); + + tox_group_invite_friend(tox, group_number, 0, nullptr); + WAIT_UNTIL(state->peer_joined); + + tox_node_log(self, "Bob joined. Changing group state..."); + tox_group_set_topic(tox, group_number, (const uint8_t *)TOPIC, TOPIC_LEN, nullptr); + tox_group_set_topic_lock(tox, group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr); + tox_group_set_privacy_state(tox, group_number, NEW_PRIV_STATE, nullptr); + tox_group_set_password(tox, group_number, (const uint8_t *)PASSWORD, PASS_LEN, nullptr); + tox_group_set_peer_limit(tox, group_number, PEER_LIMIT, nullptr); + tox_group_self_set_name(tox, group_number, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, nullptr); + tox_group_self_set_status(tox, group_number, NEW_USER_STATUS, nullptr); + + tox_scenario_yield(self); + + tox_node_log(self, "Saving and reloading..."); + tox_node_reload(self); + tox_node_log(self, "Reloaded."); + + tox_node_wait_for_self_connected(self); + check_founder_state(self, group_number, chat_id, founder_pk); + tox_node_log(self, "State verified after reload."); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_events_callback_group_invite(tox_node_get_dispatch(self), on_group_invite); + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + tox_node_log(self, "Waiting for founder to finish..."); + WAIT_UNTIL(tox_node_is_finished(founder)); + tox_node_log(self, "Founder finished, Bob exiting."); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + State states[2] = {{0}}; + + Tox_Options *opts = tox_options_new(nullptr); + tox_options_set_experimental_groups_persistence(opts, true); + tox_options_set_ipv6_enabled(opts, false); + tox_options_set_local_discovery_enabled(opts, false); + + ToxNode *alice = tox_scenario_add_node_ex(s, "Alice", founder_script, &states[0], sizeof(State), opts); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &states[1], sizeof(State)); + tox_options_free(opts); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef PASSWORD +#undef PASS_LEN +#undef PEER_LIMIT +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef TOPIC +#undef TOPIC_LEN +#undef PEER0_NICK +#undef PEER0_NICK_LEN diff --git a/auto_tests/scenarios/scenario_group_state_test.c b/auto_tests/scenarios/scenario_group_state_test.c new file mode 100644 index 00000000..6e0533ae --- /dev/null +++ b/auto_tests/scenarios/scenario_group_state_test.c @@ -0,0 +1,186 @@ +#include "framework/framework.h" +#include +#include +#include + +#define NUM_PEERS 5 +#define GROUP_NAME "The Crystal Palace" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) +#define PASSWORD "dadada" +#define PASS_LEN (sizeof(PASSWORD) - 1) +#define PEER_LIMIT_1 NUM_PEERS +#define PEER_LIMIT_2 50 + +typedef struct { + uint32_t group_number; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + uint32_t peer_count; + Tox_Group_Privacy_State privacy_state; + Tox_Group_Voice_State voice_state; + Tox_Group_Topic_Lock topic_lock; + uint32_t peer_limit; + uint8_t password[TOX_GROUP_MAX_PASSWORD_SIZE]; + size_t password_len; +} GroupState; + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->peer_count++; +} + +static void on_group_privacy_state(const Tox_Event_Group_Privacy_State *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->privacy_state = tox_event_group_privacy_state_get_privacy_state(event); +} + +static void on_group_voice_state(const Tox_Event_Group_Voice_State *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->voice_state = tox_event_group_voice_state_get_voice_state(event); +} + +static void on_group_topic_lock(const Tox_Event_Group_Topic_Lock *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->topic_lock = tox_event_group_topic_lock_get_topic_lock(event); +} + +static void on_group_peer_limit(const Tox_Event_Group_Peer_Limit *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->peer_limit = tox_event_group_peer_limit_get_peer_limit(event); +} + +static void on_group_password(const Tox_Event_Group_Password *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + GroupState *state = (GroupState *)tox_node_get_script_ctx(self); + state->password_len = tox_event_group_password_get_password_length(event); + if (state->password_len > 0) { + memcpy(state->password, tox_event_group_password_get_password(event), state->password_len); + } +} + +static void founder_script(ToxNode *self, void *ctx) +{ + GroupState *state = (GroupState *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + + tox_node_wait_for_self_connected(self); + + Tox_Err_Group_New err_new; + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, + (const uint8_t *)"Founder", 7, &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr); + + tox_group_set_peer_limit(tox, state->group_number, PEER_LIMIT_1, nullptr); + tox_group_set_privacy_state(tox, state->group_number, TOX_GROUP_PRIVACY_STATE_PUBLIC, nullptr); + tox_group_set_password(tox, state->group_number, (const uint8_t *)PASSWORD, PASS_LEN, nullptr); + tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_ENABLED, nullptr); + tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_ALL, nullptr); + + tox_scenario_barrier_wait(self); // Barrier 1: Founder set initial state + + WAIT_UNTIL(state->peer_count == NUM_PEERS - 1); + tox_node_log(self, "All peers joined."); + + tox_scenario_barrier_wait(self); // Barrier 2: All peers joined + + tox_node_log(self, "Changing group state..."); + tox_group_set_peer_limit(tox, state->group_number, PEER_LIMIT_2, nullptr); + tox_group_set_privacy_state(tox, state->group_number, TOX_GROUP_PRIVACY_STATE_PRIVATE, nullptr); + tox_group_set_password(tox, state->group_number, nullptr, 0, nullptr); + tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr); + tox_group_set_voice_state(tox, state->group_number, TOX_GROUP_VOICE_STATE_MODERATOR, nullptr); + + tox_scenario_barrier_wait(self); // Barrier 3: State changed + + tox_scenario_barrier_wait(self); // Barrier 4: All peers verified state +} + +static void peer_script(ToxNode *self, void *ctx) +{ + const GroupState *state = (const GroupState *)ctx; + Tox *tox = tox_node_get_tox(self); + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_privacy_state(dispatch, on_group_privacy_state); + tox_events_callback_group_voice_state(dispatch, on_group_voice_state); + tox_events_callback_group_topic_lock(dispatch, on_group_topic_lock); + tox_events_callback_group_peer_limit(dispatch, on_group_peer_limit); + tox_events_callback_group_password(dispatch, on_group_password); + + tox_node_wait_for_self_connected(self); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const GroupState *founder_view = (const GroupState *)tox_node_get_peer_ctx(founder); + + tox_scenario_barrier_wait(self); // Barrier 1: Founder set initial state + + Tox_Err_Group_Join join_err; + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer", 4, (const uint8_t *)PASSWORD, PASS_LEN, &join_err); + if (join_err != TOX_ERR_GROUP_JOIN_OK) { + tox_node_log(self, "tox_group_join failed with error: %s", tox_err_group_join_to_string(join_err)); + } + ck_assert(join_err == TOX_ERR_GROUP_JOIN_OK); + + WAIT_UNTIL(tox_group_is_connected(tox, 0, nullptr)); + + tox_scenario_barrier_wait(self); // Barrier 2: All peers joined + + tox_scenario_barrier_wait(self); // Barrier 3: State changed + + WAIT_UNTIL(state->privacy_state == TOX_GROUP_PRIVACY_STATE_PRIVATE); + WAIT_UNTIL(state->voice_state == TOX_GROUP_VOICE_STATE_MODERATOR); + WAIT_UNTIL(state->topic_lock == TOX_GROUP_TOPIC_LOCK_DISABLED); + WAIT_UNTIL(state->peer_limit == PEER_LIMIT_2); + WAIT_UNTIL(state->password_len == 0); + + tox_node_log(self, "State verified."); + + tox_scenario_barrier_wait(self); // Barrier 4: All peers verified state +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + GroupState states[NUM_PEERS] = {{0}}; + + ToxNode *nodes[NUM_PEERS]; + nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(GroupState)); + for (int i = 1; i < NUM_PEERS; ++i) { + char alias[16]; + snprintf(alias, sizeof(alias), "Peer%d", i); + nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(GroupState)); + } + + for (int i = 1; i < NUM_PEERS; ++i) { + tox_node_bootstrap(nodes[i], nodes[0]); + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef PASSWORD +#undef PASS_LEN +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef NUM_PEERS diff --git a/auto_tests/scenarios/scenario_group_sync_test.c b/auto_tests/scenarios/scenario_group_sync_test.c new file mode 100644 index 00000000..f5f40be2 --- /dev/null +++ b/auto_tests/scenarios/scenario_group_sync_test.c @@ -0,0 +1,263 @@ +#include "framework/framework.h" +#include +#include +#include + +#define NUM_PEERS 5 +#define GROUP_NAME "Sync Test Group" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) + +typedef struct { + uint32_t group_number; + uint32_t peer_count; + bool connected; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + size_t topic_len; + uint32_t peers[NUM_PEERS]; // Track peer IDs we've seen + Tox_Group_Role self_role; +} SyncState; + +static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + SyncState *state = (SyncState *)tox_node_get_script_ctx(self); + state->connected = true; + tox_node_log(self, "Joined group"); +} + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + SyncState *state = (SyncState *)tox_node_get_script_ctx(self); + uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); + + // Check if it's ourselves + Tox_Err_Group_Self_Query err_peer; + uint32_t self_id = tox_group_self_get_peer_id(tox_node_get_tox(self), state->group_number, &err_peer); + if (err_peer == TOX_ERR_GROUP_SELF_QUERY_OK && peer_id == self_id) { + return; + } + + for (uint32_t i = 0; i < state->peer_count; ++i) { + if (state->peers[i] == peer_id) { + return; + } + } + + if (state->peer_count < NUM_PEERS) { + tox_node_log(self, "Peer joined: %u", peer_id); + state->peers[state->peer_count++] = peer_id; + } +} + +static void on_group_topic(const Tox_Event_Group_Topic *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + SyncState *state = (SyncState *)tox_node_get_script_ctx(self); + state->topic_len = tox_event_group_topic_get_topic_length(event); + memcpy(state->topic, tox_event_group_topic_get_topic(event), state->topic_len); + state->topic[state->topic_len] = '\0'; + tox_node_log(self, "Topic updated to: %s", state->topic); +} + +static void common_init(ToxNode *self, SyncState *state) +{ + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + tox_events_callback_group_self_join(dispatch, on_group_self_join); + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_topic(dispatch, on_group_topic); + + tox_node_log(self, "Waiting for self connection..."); + tox_node_wait_for_self_connected(self); + tox_node_log(self, "Connected!"); +} + +static void founder_script(ToxNode *self, void *ctx) +{ + SyncState *state = (SyncState *)ctx; + Tox *tox = tox_node_get_tox(self); + common_init(self, state); + + Tox_Err_Group_New err_new; + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Founder", 7, &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + state->self_role = TOX_GROUP_ROLE_FOUNDER; + + tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr); + + // Phase 1: Wait for everyone to join + tox_scenario_barrier_wait(self); // Barrier 1: Created + + tox_node_log(self, "Waiting for peers to join (current count: %u)...", state->peer_count); + WAIT_UNTIL(state->peer_count >= NUM_PEERS - 1); + tox_node_log(self, "All peers joined."); + + // Phase 2: Topic Sync + tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr); + tox_scenario_barrier_wait(self); // Barrier 2: Lock disabled + + // Everyone spams topic + tox_node_log(self, "Spamming topic..."); + for (uint32_t i = 0; i < tox_node_get_index(self); ++i) { + tox_scenario_yield(self); + } + char topic[64]; + snprintf(topic, sizeof(topic), "Founder Topic %d", rand()); + bool ok = tox_group_set_topic(tox, state->group_number, (const uint8_t *)topic, strlen(topic), nullptr); + ck_assert(ok); + + // Manually update state because Tox might not call on_group_topic for self + state->topic_len = strlen(topic); + memcpy(state->topic, topic, state->topic_len); + state->topic[state->topic_len] = '\0'; + + tox_scenario_barrier_wait(self); // Barrier 3: Topic spam done + + // Wait for topic convergence + tox_node_log(self, "Waiting for topic convergence..."); + uint64_t last_log = 0; + while (1) { + bool converged = true; + for (int i = 0; i < NUM_PEERS; ++i) { + const ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i); + const SyncState *s_view = (const SyncState *)tox_node_get_peer_ctx(node); + if (s_view->topic_len != state->topic_len || memcmp(s_view->topic, state->topic, state->topic_len) != 0) { + converged = false; + if (tox_scenario_get_time(tox_node_get_scenario(self)) > last_log + 5000) { + tox_node_log(self, "Still waiting for %s to converge topic. Expected: %s, Got: %s", + tox_node_get_alias(node), state->topic, s_view->topic); + } + break; + } + } + if (converged) { + break; + } + if (tox_scenario_get_time(tox_node_get_scenario(self)) > last_log + 5000) { + last_log = tox_scenario_get_time(tox_node_get_scenario(self)); + } + tox_scenario_yield(self); + } + tox_node_log(self, "Topics converged!"); + + // Phase 3: Role Sync + // Promote everyone to Moderator + tox_node_log(self, "Promoting everyone to Moderator..."); + for (uint32_t i = 0; i < state->peer_count; ++i) { + tox_group_set_role(tox, state->group_number, state->peers[i], TOX_GROUP_ROLE_MODERATOR, nullptr); + } + + tox_scenario_barrier_wait(self); // Barrier 4: Roles set + + // Wait for role convergence + tox_node_log(self, "Waiting for role convergence..."); + while (1) { + bool converged = true; + for (int i = 0; i < NUM_PEERS; ++i) { + ToxNode *node = tox_scenario_get_node(tox_node_get_scenario(self), i); + const SyncState *s_view = (const SyncState *)tox_node_get_peer_ctx(node); + + Tox_Group_Role expected = (i == 0 ? TOX_GROUP_ROLE_FOUNDER : TOX_GROUP_ROLE_MODERATOR); + if (s_view->self_role != expected) { + converged = false; + break; + } + } + if (converged) { + break; + } + tox_scenario_yield(self); + } + tox_node_log(self, "Roles converged!"); + + tox_scenario_barrier_wait(self); // Barrier 5: Done +} + +static void peer_script(ToxNode *self, void *ctx) +{ + SyncState *state = (SyncState *)ctx; + Tox *tox = tox_node_get_tox(self); + common_init(self, state); + + const ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + tox_scenario_barrier_wait(self); // Barrier 1: Created + + const SyncState *founder_view = (const SyncState *)tox_node_get_peer_ctx(founder); + + char name[16]; + snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self)); + state->group_number = tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, nullptr); + ck_assert(state->group_number != UINT32_MAX); + + WAIT_UNTIL(state->connected); + tox_node_log(self, "Joined and connected to group."); + + tox_scenario_barrier_wait(self); // Barrier 2: Lock disabled + + // Spam topic + for (uint32_t i = 0; i < tox_node_get_index(self); ++i) { + tox_scenario_yield(self); + } + char topic[64]; + snprintf(topic, sizeof(topic), "Peer%u Topic %d", tox_node_get_index(self), rand()); + tox_node_log(self, "Setting topic: %s", topic); + tox_group_set_topic(tox, state->group_number, (const uint8_t *)topic, strlen(topic), nullptr); + + // Manually update state because Tox might not call on_group_topic for self + state->topic_len = strlen(topic); + memcpy(state->topic, topic, state->topic_len); + state->topic[state->topic_len] = '\0'; + + tox_scenario_barrier_wait(self); // Barrier 3: Topic spam done + + // Observe and publish role for Phase 3 + tox_node_log(self, "Waiting for Moderator role..."); + while (tox_scenario_is_running(self)) { + state->self_role = tox_group_self_get_role(tox, state->group_number, nullptr); + if (state->self_role == TOX_GROUP_ROLE_MODERATOR) { + break; + } + tox_scenario_yield(self); + } + tox_node_log(self, "Got Moderator role!"); + + tox_scenario_barrier_wait(self); // Barrier 4: Roles set + + tox_scenario_barrier_wait(self); // Barrier 5: Done +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + SyncState states[NUM_PEERS] = {0}; + ToxNode *nodes[NUM_PEERS]; + + nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(SyncState)); + static char aliases[NUM_PEERS][16]; + for (int i = 1; i < NUM_PEERS; ++i) { + snprintf(aliases[i], sizeof(aliases[i]), "Peer%d", i); + nodes[i] = tox_scenario_add_node(s, aliases[i], peer_script, &states[i], sizeof(SyncState)); + } + + for (int i = 0; i < NUM_PEERS; ++i) { + for (int j = 0; j < NUM_PEERS; ++j) { + if (i != j) { + tox_node_bootstrap(nodes[i], nodes[j]); + } + } + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef NUM_PEERS diff --git a/auto_tests/scenarios/scenario_group_tcp_test.c b/auto_tests/scenarios/scenario_group_tcp_test.c new file mode 100644 index 00000000..4b838408 --- /dev/null +++ b/auto_tests/scenarios/scenario_group_tcp_test.c @@ -0,0 +1,234 @@ +#include "framework/framework.h" +#include +#include +#include + +#define CODEWORD "RONALD MCDONALD" +#define CODEWORD_LEN (sizeof(CODEWORD) - 1) +#define RELAY_TCP_PORT 33811 + +typedef struct { + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + uint32_t group_number; + uint32_t peer_id; + bool joined; + bool got_private_message; + bool got_group_message; + bool got_invite; +} State; + +static void on_group_invite(const Tox_Event_Group_Invite *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + + const uint32_t friend_number = tox_event_group_invite_get_friend_number(event); + const uint8_t *invite_data = tox_event_group_invite_get_invite_data(event); + const size_t length = tox_event_group_invite_get_invite_data_length(event); + + Tox_Err_Group_Invite_Accept err_accept; + state->group_number = tox_group_invite_accept(tox_node_get_tox(self), friend_number, invite_data, length, (const uint8_t *)"test", 4, + nullptr, 0, &err_accept); + if (err_accept == TOX_ERR_GROUP_INVITE_ACCEPT_OK) { + state->got_invite = true; + } +} + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + state->peer_id = tox_event_group_peer_join_get_peer_id(event); + state->joined = true; + tox_node_log(self, "Peer %u joined group", state->peer_id); +} + +static void on_group_private_message(const Tox_Event_Group_Private_Message *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + const uint8_t *message = tox_event_group_private_message_get_message(event); + const size_t length = tox_event_group_private_message_get_message_length(event); + + if (length == CODEWORD_LEN && memcmp(CODEWORD, message, length) == 0) { + state->got_private_message = true; + } +} + +static void on_group_message(const Tox_Event_Group_Message *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + const uint8_t *message = tox_event_group_message_get_message(event); + const size_t length = tox_event_group_message_get_message_length(event); + + if (length == CODEWORD_LEN && memcmp(CODEWORD, message, length) == 0) { + state->got_group_message = true; + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + Tox *tox = tox_node_get_tox(self); + + tox_events_callback_group_peer_join(tox_node_get_dispatch(self), on_group_peer_join); + + Tox_Err_Group_New new_err; + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"test", 4, + (const uint8_t *)"test", 4, &new_err); + ck_assert(new_err == TOX_ERR_GROUP_NEW_OK); + + Tox_Err_Group_State_Query id_err; + tox_group_get_chat_id(tox, state->group_number, state->chat_id, &id_err); + ck_assert(id_err == TOX_ERR_GROUP_STATE_QUERY_OK); + + char chat_id_str[TOX_GROUP_CHAT_ID_SIZE * 2 + 1]; + for (int i = 0; i < TOX_GROUP_CHAT_ID_SIZE; ++i) { + sprintf(chat_id_str + i * 2, "%02X", state->chat_id[i]); + } + tox_node_log(self, "Created group with chat_id: %s", chat_id_str); + + tox_scenario_barrier_wait(self); // Barrier 1: chat_id is ready for Bob + + tox_node_log(self, "Waiting for Bob to join group..."); + WAIT_UNTIL(state->joined); + if (!state->joined) { + return; + } + + Tox_Err_Group_Send_Private_Message perr; + tox_group_send_private_message(tox, state->group_number, state->peer_id, + TOX_MESSAGE_TYPE_NORMAL, + (const uint8_t *)CODEWORD, CODEWORD_LEN, &perr); + ck_assert(perr == TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK); + + tox_scenario_barrier_wait(self); // Barrier 2: PM sent, Bob leaving + tox_scenario_barrier_wait(self); // Barrier 3: Bob left + state->joined = false; + + tox_node_log(self, "Inviting Bob back via friend invite..."); + Tox_Err_Group_Invite_Friend err_invite; + tox_group_invite_friend(tox, state->group_number, 0, &err_invite); + ck_assert(err_invite == TOX_ERR_GROUP_INVITE_FRIEND_OK); + + WAIT_UNTIL(state->joined); + if (!state->joined) { + return; + } + + Tox_Err_Group_Send_Message merr; + tox_group_send_message(tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, + (const uint8_t *)CODEWORD, CODEWORD_LEN, &merr); + ck_assert(merr == TOX_ERR_GROUP_SEND_MESSAGE_OK); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + Tox *tox = tox_node_get_tox(self); + + tox_events_callback_group_private_message(tox_node_get_dispatch(self), on_group_private_message); + tox_events_callback_group_message(tox_node_get_dispatch(self), on_group_message); + tox_events_callback_group_invite(tox_node_get_dispatch(self), on_group_invite); + + tox_node_log(self, "Waiting for TCP connection..."); + WAIT_UNTIL(tox_node_get_connection_status(self) == TOX_CONNECTION_TCP); + tox_node_log(self, "Connected. Waiting for Alice..."); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + if (!tox_node_is_friend_connected(self, 0)) { + tox_node_log(self, "TIMEOUT waiting for Alice."); + return; + } + tox_node_log(self, "Alice connected."); + + tox_scenario_barrier_wait(self); // Barrier 1: Alice has chat_id + + ToxScenario *s = tox_node_get_scenario(self); + ToxNode *alice_node = tox_scenario_get_node(s, 0); + State *alice_state = (State *)tox_node_get_script_ctx(alice_node); + + tox_node_log(self, "Joining group via chat_id..."); + Tox_Err_Group_Join jerr; + state->group_number = tox_group_join(tox, alice_state->chat_id, (const uint8_t *)"test", 4, nullptr, 0, &jerr); + ck_assert(jerr == TOX_ERR_GROUP_JOIN_OK); + + WAIT_UNTIL(state->got_private_message); + if (!state->got_private_message) { + return; + } + tox_scenario_barrier_wait(self); // Barrier 2: PM received, now leaving + + Tox_Err_Group_Leave err_exit; + tox_group_leave(tox, state->group_number, nullptr, 0, &err_exit); + ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); + + state->got_invite = false; + tox_scenario_barrier_wait(self); // Barrier 3: Left, waiting for invite + WAIT_UNTIL(state->got_invite); + WAIT_UNTIL(state->got_group_message); +} + +static void relay_script(ToxNode *self, void *ctx) +{ + (void)ctx; + tox_scenario_barrier_wait(self); // Barrier 1 + tox_scenario_barrier_wait(self); // Barrier 2 + tox_scenario_barrier_wait(self); // Barrier 3 +} + +int main(int argc, char **argv) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 120000); + + struct Tox_Options *options_alice = tox_options_new(nullptr); + tox_options_set_udp_enabled(options_alice, false); + + struct Tox_Options *options_bob = tox_options_new(nullptr); + tox_options_set_udp_enabled(options_bob, false); + + struct Tox_Options *options_relay = tox_options_new(nullptr); + tox_options_set_tcp_port(options_relay, RELAY_TCP_PORT); + + State *alice_state = (State *)calloc(1, sizeof(State)); + State *bob_state = (State *)calloc(1, sizeof(State)); + + ToxNode *alice = tox_scenario_add_node_ex(s, "Alice", alice_script, alice_state, sizeof(State), options_alice); + ToxNode *bob = tox_scenario_add_node_ex(s, "Bob", bob_script, bob_state, sizeof(State), options_bob); + ToxNode *relay = tox_scenario_add_node_ex(s, "Relay", relay_script, nullptr, 0, options_relay); + + tox_options_free(options_alice); + tox_options_free(options_bob); + tox_options_free(options_relay); + + if (!alice || !bob || !relay) { + fprintf(stderr, "Failed to create nodes\n"); + return 1; + } + + uint8_t relay_dht_id[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox_node_get_tox(relay), relay_dht_id); + + uint16_t relay_tcp_port = tox_self_get_tcp_port(tox_node_get_tox(relay), nullptr); + + Tox_Err_Bootstrap berr; + + // Both Alice and Bob use the Relay for TCP and DHT bootstrapping + tox_add_tcp_relay(tox_node_get_tox(alice), "127.0.0.1", relay_tcp_port, relay_dht_id, &berr); + tox_add_tcp_relay(tox_node_get_tox(bob), "127.0.0.1", relay_tcp_port, relay_dht_id, &berr); + + tox_node_bootstrap(alice, relay); + tox_node_bootstrap(bob, relay); + + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + tox_scenario_free(s); + free(alice_state); + free(bob_state); + + return (res == TOX_SCENARIO_DONE) ? 0 : 1; +} + +#undef RELAY_TCP_PORT diff --git a/auto_tests/scenarios/scenario_group_topic_revert_test.c b/auto_tests/scenarios/scenario_group_topic_revert_test.c new file mode 100644 index 00000000..91602b65 --- /dev/null +++ b/auto_tests/scenarios/scenario_group_topic_revert_test.c @@ -0,0 +1,241 @@ +#include +#include +#include + +#include "framework/framework.h" + +#define NUM_PEERS 2 +#define GROUP_NAME "Bug Repro Group" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) +#define TOPIC1 "Topic A" +#define TOPIC2 "Topic B" + +typedef struct { + uint32_t group_number; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + bool chat_id_ready; + bool connected; + uint32_t peer_ids[NUM_PEERS]; + uint8_t last_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + size_t last_topic_len; + Tox_Group_Topic_Lock topic_lock; +} TopicState; + +static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + state->connected = true; + state->group_number = tox_event_group_self_join_get_group_number(event); +} + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); + uint32_t group_number = tox_event_group_peer_join_get_group_number(event); + + Tox_Err_Group_Peer_Query q_err; + size_t length + = tox_group_peer_get_name_size(tox_node_get_tox(self), group_number, peer_id, &q_err); + if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK && length > 0) { + uint8_t name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_peer_get_name(tox_node_get_tox(self), group_number, peer_id, name, &q_err); + if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK) { + name[length] = 0; + if (length >= 4 && memcmp(name, "Peer", 4) == 0) { + uint32_t idx = (uint32_t)atoi((const char *)name + 4); + if (idx < NUM_PEERS) { + state->peer_ids[idx] = peer_id; + } + } else if (length == 7 && memcmp(name, "Founder", 7) == 0) { + state->peer_ids[0] = peer_id; + } + } + } +} + +static void on_group_topic(const Tox_Event_Group_Topic *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + state->last_topic_len = tox_event_group_topic_get_topic_length(event); + memcpy(state->last_topic, tox_event_group_topic_get_topic(event), state->last_topic_len); +} + +static void on_group_topic_lock(const Tox_Event_Group_Topic_Lock *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + state->topic_lock = tox_event_group_topic_lock_get_topic_lock(event); +} + +static void common_init(ToxNode *self, TopicState *state) +{ + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + tox_events_callback_group_self_join(dispatch, on_group_self_join); + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_topic(dispatch, on_group_topic); + tox_events_callback_group_topic_lock(dispatch, on_group_topic_lock); + + for (uint32_t i = 0; i < NUM_PEERS; ++i) { + state->peer_ids[i] = UINT32_MAX; + } + state->topic_lock = TOX_GROUP_TOPIC_LOCK_ENABLED; + + tox_node_wait_for_self_connected(self); +} + +static bool topic_is(ToxNode *self, const char *topic) +{ + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + size_t len = strlen(topic); + if (state->last_topic_len == len && memcmp(state->last_topic, topic, len) == 0) { + return true; + } + Tox_Err_Group_State_Query err; + size_t current_len + = tox_group_get_topic_size(tox_node_get_tox(self), state->group_number, &err); + if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_len == len) { + uint8_t current_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + tox_group_get_topic(tox_node_get_tox(self), state->group_number, current_topic, &err); + if (err == TOX_ERR_GROUP_STATE_QUERY_OK && memcmp(current_topic, topic, len) == 0) { + state->last_topic_len = current_len; + memcpy(state->last_topic, current_topic, current_len); + return true; + } + } + return false; +} + +static bool topic_lock_is(ToxNode *self, Tox_Group_Topic_Lock lock) +{ + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + if (state->topic_lock == lock) { + return true; + } + Tox_Err_Group_State_Query err; + Tox_Group_Topic_Lock current_lock + = tox_group_get_topic_lock(tox_node_get_tox(self), state->group_number, &err); + if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_lock == lock) { + state->topic_lock = current_lock; + return true; + } + return false; +} + +static void founder_script(ToxNode *self, void *ctx) +{ + TopicState *state = (TopicState *)ctx; + Tox *tox = tox_node_get_tox(self); + common_init(self, state); + + Tox_Err_Group_New err_new; + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, + (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Founder", 7, &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + state->peer_ids[0] = tox_group_self_get_peer_id(tox, state->group_number, nullptr); + tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr); + state->chat_id_ready = true; + + tox_scenario_barrier_wait(self); // 1: Joined + WAIT_UNTIL(state->peer_ids[1] != UINT32_MAX); + tox_scenario_barrier_wait(self); // 2: Sync IDs + + // Disable topic lock + tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr); + WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED)); + tox_scenario_barrier_wait(self); // 3: Lock disabled + + // Set Topic A + tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC1, strlen(TOPIC1), nullptr); + WAIT_UNTIL(topic_is(self, TOPIC1)); + tox_scenario_barrier_wait(self); // 4: Topic A set + + // Set Topic B + tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC2, strlen(TOPIC2), nullptr); + WAIT_UNTIL(topic_is(self, TOPIC2)); + tox_scenario_barrier_wait(self); // 5: Topic B set + + // Peer 1 will now set Topic A. + // We expect to REJECT it (stay on Topic B). + // But if bug exists, we might Accept it (revert to Topic A). + + tox_scenario_barrier_wait(self); // 6: Peer 1 sets Topic A + + // Verify we have Topic B. + // Wait a bit to ensure potential network packets arrived. + uint64_t start = tox_scenario_get_time(tox_node_get_scenario(self)); + while (tox_scenario_get_time(tox_node_get_scenario(self)) - start < 1000) { + tox_scenario_yield(self); + if (topic_is(self, TOPIC1)) { + ck_assert_msg(false, "BUG REPRODUCED: Founder reverted to Topic A!"); + } + } + + ck_assert_msg(topic_is(self, TOPIC2), "Founder should be on Topic B"); +} + +static void peer_script(ToxNode *self, void *ctx) +{ + TopicState *state = (TopicState *)ctx; + Tox *tox = tox_node_get_tox(self); + common_init(self, state); + + ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const TopicState *founder_view = (const TopicState *)tox_node_get_peer_ctx(founder); + + WAIT_UNTIL(founder_view->chat_id_ready); + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)"Peer1", 5, nullptr, 0, nullptr); + + WAIT_UNTIL(state->connected); + state->peer_ids[1] = tox_group_self_get_peer_id(tox, state->group_number, nullptr); + + tox_scenario_barrier_wait(self); // 1: Joined + WAIT_UNTIL(state->peer_ids[0] != UINT32_MAX); + tox_scenario_barrier_wait(self); // 2: Sync IDs + + WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED)); + tox_scenario_barrier_wait(self); // 3: Lock disabled + + WAIT_UNTIL(topic_is(self, TOPIC1)); + tox_scenario_barrier_wait(self); // 4: Topic A set + + WAIT_UNTIL(topic_is(self, TOPIC2)); + tox_scenario_barrier_wait(self); // 5: Topic B set + + // Now we set Topic A. + // This simulates an old topic being re-broadcast or a malicious/accidental revert. + tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC1, strlen(TOPIC1), nullptr); + + tox_scenario_barrier_wait(self); // 6: Peer 1 sets Topic A +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + TopicState states[NUM_PEERS] = {0}; + ToxNode *nodes[NUM_PEERS]; + + nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(TopicState)); + nodes[1] = tox_scenario_add_node(s, "Peer1", peer_script, &states[1], sizeof(TopicState)); + + tox_node_bootstrap(nodes[0], nodes[1]); + tox_node_friend_add(nodes[0], nodes[1]); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef TOPIC2 +#undef TOPIC1 +#undef GROUP_NAME_LEN +#undef GROUP_NAME +#undef NUM_PEERS diff --git a/auto_tests/scenarios/scenario_group_topic_test.c b/auto_tests/scenarios/scenario_group_topic_test.c new file mode 100644 index 00000000..c6f3b5a6 --- /dev/null +++ b/auto_tests/scenarios/scenario_group_topic_test.c @@ -0,0 +1,310 @@ +#include "framework/framework.h" +#include +#include +#include + +#define NUM_PEERS 3 +#define GROUP_NAME "The Test Chamber" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) +#define TOPIC1 "Topic One" +#define TOPIC2 "Topic Two" + +typedef struct { + uint32_t group_number; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + bool chat_id_ready; + bool connected; + uint32_t peer_ids[NUM_PEERS]; + uint8_t last_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + size_t last_topic_len; + Tox_Group_Topic_Lock topic_lock; + Tox_Group_Role self_role; +} TopicState; + +static void on_group_self_join(const Tox_Event_Group_Self_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + state->connected = true; + state->group_number = tox_event_group_self_join_get_group_number(event); + state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, nullptr); +} + +static void on_group_peer_join(const Tox_Event_Group_Peer_Join *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + uint32_t peer_id = tox_event_group_peer_join_get_peer_id(event); + uint32_t group_number = tox_event_group_peer_join_get_group_number(event); + + Tox_Err_Group_Peer_Query q_err; + size_t length = tox_group_peer_get_name_size(tox_node_get_tox(self), group_number, peer_id, &q_err); + if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK && length > 0) { + uint8_t name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_peer_get_name(tox_node_get_tox(self), group_number, peer_id, name, &q_err); + if (q_err == TOX_ERR_GROUP_PEER_QUERY_OK) { + name[length] = 0; + if (length >= 4 && memcmp(name, "Peer", 4) == 0) { + uint32_t idx = (uint32_t)atoi((const char *)name + 4); + if (idx < NUM_PEERS) { + state->peer_ids[idx] = peer_id; + } + } + } + } +} + +static void on_group_topic(const Tox_Event_Group_Topic *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + state->last_topic_len = tox_event_group_topic_get_topic_length(event); + memcpy(state->last_topic, tox_event_group_topic_get_topic(event), state->last_topic_len); +} + +static void on_group_topic_lock(const Tox_Event_Group_Topic_Lock *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + state->topic_lock = tox_event_group_topic_lock_get_topic_lock(event); +} + +static void on_group_moderation(const Tox_Event_Group_Moderation *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + Tox_Err_Group_Self_Query err; + state->self_role = tox_group_self_get_role(tox_node_get_tox(self), state->group_number, &err); +} + +static void common_init(ToxNode *self, TopicState *state) +{ + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + tox_events_callback_group_self_join(dispatch, on_group_self_join); + tox_events_callback_group_peer_join(dispatch, on_group_peer_join); + tox_events_callback_group_topic(dispatch, on_group_topic); + tox_events_callback_group_topic_lock(dispatch, on_group_topic_lock); + tox_events_callback_group_moderation(dispatch, on_group_moderation); + + for (uint32_t i = 0; i < NUM_PEERS; ++i) { + state->peer_ids[i] = UINT32_MAX; + } + state->topic_lock = TOX_GROUP_TOPIC_LOCK_ENABLED; + + tox_node_wait_for_self_connected(self); +} + +static bool topic_is(ToxNode *self, const char *topic) +{ + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + size_t len = strlen(topic); + if (state->last_topic_len == len && memcmp(state->last_topic, topic, len) == 0) { + return true; + } + + Tox_Err_Group_State_Query err; + size_t current_len = tox_group_get_topic_size(tox_node_get_tox(self), state->group_number, &err); + if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_len == len) { + uint8_t current_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + tox_group_get_topic(tox_node_get_tox(self), state->group_number, current_topic, &err); + if (err == TOX_ERR_GROUP_STATE_QUERY_OK && memcmp(current_topic, topic, len) == 0) { + state->last_topic_len = current_len; + memcpy(state->last_topic, current_topic, current_len); + return true; + } + } + return false; +} + +static bool topic_lock_is(ToxNode *self, Tox_Group_Topic_Lock lock) +{ + TopicState *state = (TopicState *)tox_node_get_script_ctx(self); + if (state->topic_lock == lock) { + return true; + } + + Tox_Err_Group_State_Query err; + Tox_Group_Topic_Lock current_lock = tox_group_get_topic_lock(tox_node_get_tox(self), state->group_number, &err); + if (err == TOX_ERR_GROUP_STATE_QUERY_OK && current_lock == lock) { + state->topic_lock = current_lock; + return true; + } + return false; +} + +static void founder_script(ToxNode *self, void *ctx) +{ + TopicState *state = (TopicState *)ctx; + Tox *tox = tox_node_get_tox(self); + common_init(self, state); + + Tox_Err_Group_New err_new; + state->group_number = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, (const uint8_t *)"Peer0", 5, &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + state->peer_ids[0] = tox_group_self_get_peer_id(tox, state->group_number, nullptr); + + tox_group_get_chat_id(tox, state->group_number, state->chat_id, nullptr); + state->chat_id_ready = true; + + tox_scenario_barrier_wait(self); // 1: Peers joined + for (uint32_t i = 1; i < NUM_PEERS; ++i) { + WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX); + } + tox_scenario_barrier_wait(self); // 2: Sync IDs + + // 3: Initial Topic + tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC1, strlen(TOPIC1), nullptr); + WAIT_UNTIL(topic_is(self, TOPIC1)); + tox_scenario_barrier_wait(self); + + // 4: Disable topic lock + tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, nullptr); + WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED)); + tox_scenario_barrier_wait(self); + + // 5: Peers changing topic + tox_scenario_barrier_wait(self); // Peer 1 turn + WAIT_UNTIL(topic_is(self, "Topic from Peer1")); + tox_scenario_barrier_wait(self); // Peer 2 turn + WAIT_UNTIL(topic_is(self, "Topic from Peer2")); + + // 6: Set Peer 2 to Observer + tox_group_set_role(tox, state->group_number, state->peer_ids[2], TOX_GROUP_ROLE_OBSERVER, nullptr); + tox_scenario_barrier_wait(self); + + // 7: Peer 1 changes topic, Peer 2 should fail + tox_scenario_barrier_wait(self); // Peer 1 turn + WAIT_UNTIL(topic_is(self, "Topic again from Peer1")); + tox_scenario_barrier_wait(self); // Peer 2 turn (fails) + + // 8: Enable topic lock + tox_group_set_topic_lock(tox, state->group_number, TOX_GROUP_TOPIC_LOCK_ENABLED, nullptr); + WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_ENABLED)); + tox_scenario_barrier_wait(self); + + // 9: Peer 1 attempts to change topic (fails) + tox_scenario_barrier_wait(self); + + // 10: Founder changes topic + tox_group_set_topic(tox, state->group_number, (const uint8_t *)TOPIC2, strlen(TOPIC2), nullptr); + WAIT_UNTIL(topic_is(self, TOPIC2)); + tox_scenario_barrier_wait(self); +} + +static void peer_script(ToxNode *self, void *ctx) +{ + TopicState *state = (TopicState *)ctx; + Tox *tox = tox_node_get_tox(self); + common_init(self, state); + + ToxNode *founder = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const TopicState *founder_view = (const TopicState *)tox_node_get_peer_ctx(founder); + + WAIT_UNTIL(founder_view->chat_id_ready); + + char name[16]; + snprintf(name, sizeof(name), "Peer%u", tox_node_get_index(self)); + tox_group_join(tox, founder_view->chat_id, (const uint8_t *)name, strlen(name), nullptr, 0, nullptr); + + WAIT_UNTIL(state->connected); + state->peer_ids[tox_node_get_index(self)] = tox_group_self_get_peer_id(tox, state->group_number, nullptr); + + tox_scenario_barrier_wait(self); // 1: Joined + for (uint32_t i = 0; i < NUM_PEERS; ++i) if (i != tox_node_get_index(self)) { + WAIT_UNTIL(state->peer_ids[i] != UINT32_MAX); + } + tox_scenario_barrier_wait(self); // 2: Sync IDs + + // 3: Topic check + WAIT_UNTIL(topic_is(self, TOPIC1)); + tox_scenario_barrier_wait(self); + + // 4: Disable topic lock + WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_DISABLED)); + tox_scenario_barrier_wait(self); + + // 5: Peers changing topic + if (tox_node_get_index(self) == 1) { + tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic from Peer1", strlen("Topic from Peer1"), nullptr); + } + tox_scenario_barrier_wait(self); // Peer 1 turn + WAIT_UNTIL(topic_is(self, "Topic from Peer1")); + + if (tox_node_get_index(self) == 2) { + tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic from Peer2", strlen("Topic from Peer2"), nullptr); + } + tox_scenario_barrier_wait(self); // Peer 2 turn + WAIT_UNTIL(topic_is(self, "Topic from Peer2")); + + // 6: Set Peer 2 to Observer + tox_scenario_barrier_wait(self); + if (tox_node_get_index(self) == 2) { + WAIT_UNTIL(state->self_role == TOX_GROUP_ROLE_OBSERVER); + } + + // 7: Peer 1 changes topic, Peer 2 should fail + if (tox_node_get_index(self) == 1) { + tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Topic again from Peer1", strlen("Topic again from Peer1"), nullptr); + } + tox_scenario_barrier_wait(self); // Peer 1 turn + WAIT_UNTIL(topic_is(self, "Topic again from Peer1")); + + if (tox_node_get_index(self) == 2) { + Tox_Err_Group_Topic_Set err; + tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Fail topic", 10, &err); + ck_assert(err == TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS); + } + tox_scenario_barrier_wait(self); // Peer 2 turn (fails) + + // 8: Enable topic lock + WAIT_UNTIL(topic_lock_is(self, TOX_GROUP_TOPIC_LOCK_ENABLED)); + tox_scenario_barrier_wait(self); + + // 9: Peer 1 attempts to change topic (fails) + if (tox_node_get_index(self) == 1) { + Tox_Err_Group_Topic_Set err; + tox_group_set_topic(tox, state->group_number, (const uint8_t *)"Fail topic 2", 12, &err); + ck_assert(err == TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS); + } + tox_scenario_barrier_wait(self); + + // 10: Founder changes topic + tox_scenario_barrier_wait(self); + WAIT_UNTIL(topic_is(self, TOPIC2)); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + TopicState states[NUM_PEERS] = {0}; + ToxNode *nodes[NUM_PEERS]; + + nodes[0] = tox_scenario_add_node(s, "Founder", founder_script, &states[0], sizeof(TopicState)); + for (uint32_t i = 1; i < NUM_PEERS; ++i) { + char alias[16]; + snprintf(alias, sizeof(alias), "Peer%u", i); + nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(TopicState)); + } + + for (uint32_t i = 0; i < NUM_PEERS; ++i) { + for (uint32_t j = 0; j < NUM_PEERS; ++j) { + if (i != j) { + tox_node_bootstrap(nodes[i], nodes[j]); + tox_node_friend_add(nodes[i], nodes[j]); + } + } + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef NUM_PEERS diff --git a/auto_tests/scenarios/scenario_lan_discovery_test.c b/auto_tests/scenarios/scenario_lan_discovery_test.c new file mode 100644 index 00000000..b0679832 --- /dev/null +++ b/auto_tests/scenarios/scenario_lan_discovery_test.c @@ -0,0 +1,51 @@ +#include "framework/framework.h" +#include + +static void alice_script(ToxNode *self, void *ctx) +{ + tox_node_log(self, "Waiting for LAN discovery..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + if (tox_node_is_self_connected(self)) { + tox_node_log(self, "Discovered network via LAN!"); + } else { + tox_node_log(self, "Failed to discover network via LAN."); + } +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_log(self, "Waiting for LAN discovery..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + if (tox_node_is_self_connected(self)) { + tox_node_log(self, "Discovered network via LAN!"); + } else { + tox_node_log(self, "Failed to discover network via LAN."); + } +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + Tox_Options *opts = tox_options_new(nullptr); + tox_options_set_ipv6_enabled(opts, false); + tox_options_set_local_discovery_enabled(opts, true); + + // Both nodes have local discovery enabled and NO bootstrap + tox_scenario_add_node_ex(s, "Alice", alice_script, nullptr, 0, opts); + tox_scenario_add_node_ex(s, "Bob", bob_script, nullptr, 0, opts); + + tox_options_free(opts); + + tox_scenario_log(s, "Starting scenario (LAN Discovery)."); + ToxScenarioStatus res = tox_scenario_run(s); + + if (res == TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Scenario completed successfully!"); + } else { + tox_scenario_log(s, "Scenario failed with status: %u", res); + } + + tox_scenario_free(s); + return (res == TOX_SCENARIO_DONE) ? 0 : 1; +} diff --git a/auto_tests/scenarios/scenario_lossless_packet_test.c b/auto_tests/scenarios/scenario_lossless_packet_test.c new file mode 100644 index 00000000..e8935a29 --- /dev/null +++ b/auto_tests/scenarios/scenario_lossless_packet_test.c @@ -0,0 +1,68 @@ +#include "framework/framework.h" +#include +#include +#include + +#define LOSSLESS_PACKET_FILLER 160 + +static void alice_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + tox_node_log(self, "Waiting for connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Self connected, waiting for friend Bob..."); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to Bob. Connection type: %u", tox_friend_get_connection_status(tox, 0, nullptr)); + + const size_t packet_size = tox_max_custom_packet_size(); + uint8_t *packet = (uint8_t *)malloc(packet_size); + memset(packet, LOSSLESS_PACKET_FILLER, packet_size); + + tox_node_log(self, "Sending lossless packet to Bob (size %zu)...", packet_size); + Tox_Err_Friend_Custom_Packet err; + if (tox_friend_send_lossless_packet(tox, 0, packet, packet_size, &err)) { + tox_node_log(self, "Lossless packet sent successfully!"); + } else { + tox_node_log(self, "Failed to send lossless packet: %u", err); + } + free(packet); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + const Tox *tox = tox_node_get_tox(self); + tox_node_log(self, "Waiting for connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Self connected, waiting for friend Alice..."); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to Alice. Connection type: %u", tox_friend_get_connection_status(tox, 0, nullptr)); + + tox_node_log(self, "Waiting for lossless packet from Alice..."); + WAIT_FOR_EVENT(TOX_EVENT_FRIEND_LOSSLESS_PACKET); + if (tox_scenario_is_running(self)) { + tox_node_log(self, "Received lossless packet from Alice!"); + } else { + tox_node_log(self, "Timed out waiting for lossless packet."); + } +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_lossy_packet_test.c b/auto_tests/scenarios/scenario_lossy_packet_test.c new file mode 100644 index 00000000..b2d28272 --- /dev/null +++ b/auto_tests/scenarios/scenario_lossy_packet_test.c @@ -0,0 +1,52 @@ +#include "framework/framework.h" +#include +#include +#include + +#define LOSSY_PACKET_FILLER 200 + +static void alice_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + const size_t packet_size = tox_max_custom_packet_size(); + uint8_t *packet = (uint8_t *)malloc(packet_size); + memset(packet, LOSSY_PACKET_FILLER, packet_size); + + tox_node_log(self, "Sending lossy packet to Bob..."); + tox_friend_send_lossy_packet(tox, 0, packet, packet_size, nullptr); + free(packet); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Waiting for lossy packet from Alice..."); + WAIT_FOR_EVENT(TOX_EVENT_FRIEND_LOSSY_PACKET); + tox_node_log(self, "Received lossy packet from Alice!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_message_test.c b/auto_tests/scenarios/scenario_message_test.c new file mode 100644 index 00000000..b50a7142 --- /dev/null +++ b/auto_tests/scenarios/scenario_message_test.c @@ -0,0 +1,65 @@ +#include "framework/framework.h" +#include +#include + +static void alice_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + tox_node_log(self, "Waiting for DHT connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Connected to DHT. Waiting for friend connection..."); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Sending message to Bob..."); + uint8_t msg[] = "Hello Bob!"; + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err); + if (err != TOX_ERR_FRIEND_SEND_MESSAGE_OK) { + return; + } + + tox_node_log(self, "Waiting for response from Bob..."); + WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE); + tox_node_log(self, "Received response from Bob!"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + tox_node_log(self, "Waiting for DHT connection..."); + WAIT_UNTIL(tox_node_is_self_connected(self)); + tox_node_log(self, "Connected to DHT. Waiting for friend connection..."); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Waiting for message from Alice..."); + WAIT_FOR_EVENT(TOX_EVENT_FRIEND_MESSAGE); + tox_node_log(self, "Received message from Alice! Sending response..."); + + uint8_t msg[] = "Hello Alice!"; + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); // 60s virtual timeout + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + tox_scenario_log(s, "Starting scenario..."); + ToxScenarioStatus res = tox_scenario_run(s); + + if (res == TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Scenario completed successfully!"); + } else { + tox_scenario_log(s, "Scenario failed with status: %u", res); + } + + tox_scenario_free(s); + return (res == TOX_SCENARIO_DONE) ? 0 : 1; +} diff --git a/auto_tests/scenarios/scenario_netprof_test.c b/auto_tests/scenarios/scenario_netprof_test.c new file mode 100644 index 00000000..09a31921 --- /dev/null +++ b/auto_tests/scenarios/scenario_netprof_test.c @@ -0,0 +1,61 @@ +#include "framework/framework.h" +#include +#include +#include +#include +#include "../../toxcore/tox_private.h" + +static void alice_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + for (int i = 0; i < 256; i++) { + tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"test", 4, nullptr); + tox_scenario_yield(self); + } + + // Wait for Bob to also send his messages and for them to arrive. + for (int i = 0; i < 100; i++) { + tox_scenario_yield(self); + } + + const Tox *tox = tox_node_get_tox(self); + uint64_t udp_sent = tox_netprof_get_packet_total_count(tox, TOX_NETPROF_PACKET_TYPE_UDP, TOX_NETPROF_DIRECTION_SENT); + uint64_t udp_recv = tox_netprof_get_packet_total_count(tox, TOX_NETPROF_PACKET_TYPE_UDP, TOX_NETPROF_DIRECTION_RECV); + + tox_node_log(self, "UDP Sent: %" PRIu64 ", Received: %" PRIu64, udp_sent, udp_recv); + + ck_assert(udp_sent >= 256); + ck_assert(udp_recv >= 1); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + for (int i = 0; i < 256; i++) { + tox_friend_send_message(tox_node_get_tox(self), 0, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)"test", 4, nullptr); + tox_scenario_yield(self); + } +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_nospam_test.c b/auto_tests/scenarios/scenario_nospam_test.c new file mode 100644 index 00000000..6082d9c2 --- /dev/null +++ b/auto_tests/scenarios/scenario_nospam_test.c @@ -0,0 +1,105 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + bool nospam_changed; +} AliceState; + +static void set_checksum(uint8_t *address) +{ + uint8_t checksum[2] = {0}; + for (int i = 0; i < 36; ++i) { + checksum[i % 2] ^= address[i]; + } + address[36] = checksum[0]; + address[37] = checksum[1]; +} + +static void alice_script(ToxNode *self, void *ctx) +{ + AliceState *state = (AliceState *)ctx; + tox_node_wait_for_self_connected(self); + + Tox *tox = tox_node_get_tox(self); + uint32_t old_nospam = tox_self_get_nospam(tox); + uint32_t new_nospam = old_nospam + 1; + + tox_node_log(self, "Old nospam: %u, setting new nospam: %u", old_nospam, new_nospam); + tox_self_set_nospam(tox, new_nospam); + ck_assert(tox_self_get_nospam(tox) == new_nospam); + + // Signal to Bob that we changed nospam + state->nospam_changed = true; + + tox_node_log(self, "Waiting for friend request from Bob..."); + WAIT_FOR_EVENT(TOX_EVENT_FRIEND_REQUEST); + tox_node_log(self, "Received friend request from Bob!"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + + ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0); + const AliceState *alice_view = (const AliceState *)tox_node_get_peer_ctx(alice); + + tox_node_log(self, "Waiting for Alice to change nospam..."); + WAIT_UNTIL(alice_view->nospam_changed); + + uint8_t alice_addr[TOX_ADDRESS_SIZE]; + tox_node_get_address(alice, alice_addr); + + // Alice's address in alice_addr is already the NEW one. + // Manually construct an old address. + uint8_t old_alice_addr[TOX_ADDRESS_SIZE]; + memcpy(old_alice_addr, alice_addr, TOX_ADDRESS_SIZE); + + uint32_t nospam; + memcpy(&nospam, old_alice_addr + 32, 4); + nospam--; // Revert to old nospam + memcpy(old_alice_addr + 32, &nospam, 4); + set_checksum(old_alice_addr); + + tox_node_log(self, "Trying to add Alice with OLD nospam..."); + Tox_Err_Friend_Add err; + uint32_t friend_num = tox_friend_add(tox_node_get_tox(self), old_alice_addr, (const uint8_t *)"Hi", 2, &err); + ck_assert(err == TOX_ERR_FRIEND_ADD_OK); + + // Wait some time, but not too long. + for (int i = 0; i < 100; ++i) { + tox_scenario_yield(self); + } + + tox_node_log(self, "Deleting Alice and trying with NEW nospam..."); + tox_friend_delete(tox_node_get_tox(self), friend_num, nullptr); + + tox_friend_add(tox_node_get_tox(self), alice_addr, (const uint8_t *)"Hi", 2, &err); + ck_assert(err == TOX_ERR_FRIEND_ADD_OK); + tox_node_log(self, "Friend request sent!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + AliceState alice_state = {false}; + tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(AliceState)); + tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_bootstrap(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_overflow_recvq_test.c b/auto_tests/scenarios/scenario_overflow_recvq_test.c new file mode 100644 index 00000000..bad1519d --- /dev/null +++ b/auto_tests/scenarios/scenario_overflow_recvq_test.c @@ -0,0 +1,125 @@ +#include "framework/framework.h" +#include +#include + +#define NUM_MSGS 40000 + +typedef struct { + uint32_t recv_count; +} State; + +static void on_friend_message(const Tox_Event_Friend_Message *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + state->recv_count++; +} + +static void receiver_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + Tox_Dispatch *dispatch = tox_node_get_dispatch(self); + tox_events_callback_friend_message(dispatch, on_friend_message); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + tox_node_wait_for_friend_connected(self, 1); + + tox_node_log(self, "Ready to receive..."); + tox_scenario_barrier_wait(self); // Barrier 1: Connected + + // Wait until we get many messages or scenario timeout. + // We don't expect ALL 80k messages to arrive in a short time, + // but we want to see if it survives the flood. + uint32_t last_count = 0; + uint32_t stable_ticks = 0; + while (tox_scenario_is_running(self)) { + tox_scenario_yield(self); + if (state->recv_count == last_count && state->recv_count > 0) { + stable_ticks++; + if (stable_ticks > 100) { + break; // No new messages for 5 seconds + } + } else { + stable_ticks = 0; + } + last_count = state->recv_count; + + if (state->recv_count >= NUM_MSGS * 2) { + break; + } + } + tox_node_log(self, "Received %u messages.", state->recv_count); +} + +static void sender_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + tox_scenario_barrier_wait(self); // Barrier 1: Connected + + tox_node_log(self, "Sending messages..."); + Tox *tox = tox_node_get_tox(self); + for (uint32_t i = 0; i < NUM_MSGS; i++) { + uint8_t message[128] = {0}; + snprintf((char *)message, sizeof(message), "%u-%u", tox_node_get_index(self), i); + + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof(message), &err); + + if (err == TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ) { + // This is expected when flooding + if (i % 1000 == 0) { + tox_node_log(self, "Send queue full at message %u, yielding...", i); + } + tox_scenario_yield(self); + i--; // Retry this message + continue; + } + + if (err == TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED) { + if (i % 1000 == 0) { + tox_node_log(self, "Friend not connected at message %u, waiting...", i); + } + tox_node_wait_for_friend_connected(self, 0); + i--; // Retry this message + continue; + } + + ck_assert_msg(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK, "send_message failed with %s", + tox_err_friend_send_message_to_string(err)); + + if (i % 500 == 0) { + tox_scenario_yield(self); + } + } + tox_node_log(self, "Finished sending."); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 120000); + State receiver_state = {0}; + + ToxNode *receiver = tox_scenario_add_node(s, "Receiver", receiver_script, &receiver_state, sizeof(State)); + ToxNode *sender1 = tox_scenario_add_node(s, "Sender1", sender_script, nullptr, 0); + ToxNode *sender2 = tox_scenario_add_node(s, "Sender2", sender_script, nullptr, 0); + + tox_node_bootstrap(sender1, receiver); + tox_node_bootstrap(sender2, receiver); + tox_node_friend_add(receiver, sender1); + tox_node_friend_add(sender1, receiver); + tox_node_friend_add(receiver, sender2); + tox_node_friend_add(sender2, receiver); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE && res != TOX_SCENARIO_TIMEOUT) { + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef NUM_MSGS diff --git a/auto_tests/scenarios/scenario_overflow_sendq_test.c b/auto_tests/scenarios/scenario_overflow_sendq_test.c new file mode 100644 index 00000000..9a35cb5f --- /dev/null +++ b/auto_tests/scenarios/scenario_overflow_sendq_test.c @@ -0,0 +1,63 @@ +#include "framework/framework.h" +#include +#include + +#define NUM_MSGS 40000 + +static void alice_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + const uint8_t message[] = {0}; + bool errored = false; + Tox *tox = tox_node_get_tox(self); + + for (uint32_t i = 0; i < NUM_MSGS; i++) { + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof(message), &err); + + if (err != TOX_ERR_FRIEND_SEND_MESSAGE_OK) { + errored = true; + } + + if (errored) { + ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ); + } else { + ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); + } + } + + ck_assert(errored); + tox_node_log(self, "Success: Send queue overflowed as expected."); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0); + WAIT_UNTIL(tox_node_is_finished(alice)); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 30000); + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef NUM_MSGS diff --git a/auto_tests/scenarios/scenario_reconnect_test.c b/auto_tests/scenarios/scenario_reconnect_test.c new file mode 100644 index 00000000..1f0013ba --- /dev/null +++ b/auto_tests/scenarios/scenario_reconnect_test.c @@ -0,0 +1,61 @@ +#include "framework/framework.h" +#include + +static void alice_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to Bob."); + + // Bob will go offline now. + tox_node_log(self, "Waiting for Bob to time out..."); + WAIT_UNTIL(!tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Bob timed out as expected."); + + tox_node_log(self, "Waiting for Bob to reconnect..."); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Bob reconnected!"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to Alice. Going offline for 40 seconds..."); + + tox_node_set_offline(self, true); + + // In our virtual clock, 40 seconds is 40000 / TOX_SCENARIO_TICK_MS ticks. + for (int i = 0; i < 40000 / TOX_SCENARIO_TICK_MS; ++i) { + tox_scenario_yield(self); + } + + tox_node_log(self, "Coming back online."); + tox_node_set_offline(self, false); + + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Reconnected to Alice!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + tox_node_bootstrap(alice, bob); + tox_node_bootstrap(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_save_friend_test.c b/auto_tests/scenarios/scenario_save_friend_test.c new file mode 100644 index 00000000..4cb230a5 --- /dev/null +++ b/auto_tests/scenarios/scenario_save_friend_test.c @@ -0,0 +1,93 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + uint8_t *name; + uint8_t *status_message; + size_t name_len; + size_t status_len; +} ReferenceData; + +static void alice_script(ToxNode *self, void *ctx) +{ + const ReferenceData *ref = (const ReferenceData *)ctx; + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + // Wait for name and status to propagate + WAIT_UNTIL(tox_node_friend_name_is(self, 0, ref->name, ref->name_len)); + WAIT_UNTIL(tox_node_friend_status_message_is(self, 0, ref->status_message, ref->status_len)); + tox_node_log(self, "Received reference name and status from Bob"); + + // Save and Reload + tox_node_log(self, "Reloading..."); + tox_node_reload(self); + tox_node_log(self, "Reloaded"); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + // Check if Bob's info is still correct after reload + ck_assert(tox_node_friend_name_is(self, 0, ref->name, ref->name_len)); + ck_assert(tox_node_friend_status_message_is(self, 0, ref->status_message, ref->status_len)); + tox_node_log(self, "Bob's name and status are still correct after reload"); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + ReferenceData *ref = (ReferenceData *)ctx; + Tox *tox = tox_node_get_tox(self); + + tox_self_set_name(tox, ref->name, ref->name_len, nullptr); + tox_self_set_status_message(tox, ref->status_message, ref->status_len, nullptr); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + // Stay online for Alice + tox_node_log(self, "Waiting for Alice to finish..."); + while (!tox_node_is_finished(tox_scenario_get_node(tox_node_get_scenario(self), 0))) { + tox_scenario_yield(self); + } + tox_node_log(self, "Alice finished, Bob is done"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + + ReferenceData ref; + ref.name_len = tox_max_name_length(); + ref.status_len = tox_max_status_message_length(); + ref.name = (uint8_t *)malloc(ref.name_len); + ref.status_message = (uint8_t *)malloc(ref.status_len); + + for (size_t i = 0; i < ref.name_len; ++i) { + ref.name[i] = (uint8_t)(rand() % 256); + } + for (size_t i = 0; i < ref.status_len; ++i) { + ref.status_message[i] = (uint8_t)(rand() % 256); + } + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, &ref, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, &ref, 0); + + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + tox_node_bootstrap(alice, bob); + tox_node_bootstrap(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + free(ref.name); + free(ref.status_message); + return 0; +} diff --git a/auto_tests/scenarios/scenario_save_load_test.c b/auto_tests/scenarios/scenario_save_load_test.c new file mode 100644 index 00000000..c092ef1a --- /dev/null +++ b/auto_tests/scenarios/scenario_save_load_test.c @@ -0,0 +1,90 @@ +#include "framework/framework.h" +#include +#include +#include + +#define FRIEND_REQUEST_MSG "Gentoo" + +static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + const uint8_t *msg = tox_event_friend_request_get_message(event); + size_t len = tox_event_friend_request_get_message_length(event); + + if (len == 7 && memcmp(msg, FRIEND_REQUEST_MSG, 7) == 0) { + tox_friend_add_norequest(tox_node_get_tox(self), tox_event_friend_request_get_public_key(event), nullptr); + } +} + +static void relay_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + + ToxScenario *s = tox_node_get_scenario(self); + bool peers_done = false; + while (!peers_done && tox_scenario_is_running(self)) { + peers_done = true; + for (uint32_t i = 1; i < 3; ++i) { + if (!tox_node_is_finished(tox_scenario_get_node(s, i))) { + peers_done = false; + break; + } + } + if (!peers_done) { + tox_scenario_yield(self); + } + } +} + +static void peer_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + uint32_t my_index = tox_node_get_index(self); + + if (my_index == 1) { + tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request); + } + + WAIT_UNTIL(tox_node_is_self_connected(self)); + + if (my_index == 2) { + ToxNode *peer1 = tox_scenario_get_node(tox_node_get_scenario(self), 1); + uint8_t addr[TOX_ADDRESS_SIZE]; + tox_node_get_address(peer1, addr); + tox_friend_add(tox, addr, (const uint8_t *)FRIEND_REQUEST_MSG, 7, nullptr); + } + + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to friend"); + + // Reload test + tox_node_log(self, "Reloading..."); + tox_node_reload(self); + tox_node_log(self, "Reloaded"); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + tox_node_log(self, "Connected to friend again after reload"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + + ToxNode *relay = tox_scenario_add_node(s, "Relay", relay_script, nullptr, 0); + ToxNode *peer1 = tox_scenario_add_node(s, "Peer1", peer_script, nullptr, 0); + ToxNode *peer2 = tox_scenario_add_node(s, "Peer2", peer_script, nullptr, 0); + + // All peers bootstrap from relay + tox_node_bootstrap(peer1, relay); + tox_node_bootstrap(peer2, relay); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_self_query_test.c b/auto_tests/scenarios/scenario_self_query_test.c new file mode 100644 index 00000000..e0910b22 --- /dev/null +++ b/auto_tests/scenarios/scenario_self_query_test.c @@ -0,0 +1,92 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + bool connection_status_called; + Tox_Connection last_status; +} State; + +static void on_self_connection_status(const Tox_Event_Self_Connection_Status *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + State *state = (State *)tox_node_get_script_ctx(self); + state->connection_status_called = true; + state->last_status = tox_event_self_connection_status_get_connection_status(event); + tox_node_log(self, "Self connection status: %s", tox_connection_to_string(state->last_status)); +} + +static void alice_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + + // Register dispatch callback + tox_events_callback_self_connection_status(tox_node_get_dispatch(self), on_self_connection_status); + + tox_node_wait_for_self_connected(self); + ck_assert(state->connection_status_called); + ck_assert(state->last_status != TOX_CONNECTION_NONE); + + Tox *tox = tox_node_get_tox(self); + // Test ports + Tox_Err_Get_Port err_port; + uint16_t udp_port = tox_self_get_udp_port(tox, &err_port); + ck_assert(err_port == TOX_ERR_GET_PORT_OK); + ck_assert(udp_port > 0); + tox_node_log(self, "UDP Port: %u", udp_port); + + // Friend list test + tox_node_wait_for_friend_connected(self, 0); + tox_node_wait_for_friend_connected(self, 1); + + size_t friend_count = tox_self_get_friend_list_size(tox); + ck_assert(friend_count == 2); + + uint32_t friends[2]; + tox_self_get_friend_list(tox, friends); + ck_assert(friends[0] == 0); + ck_assert(friends[1] == 1); + + tox_node_log(self, "Self query tests passed!"); +} + +static void peer_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + ToxNode *alice = tox_scenario_get_node(tox_node_get_scenario(self), 0); + WAIT_UNTIL(tox_node_is_finished(alice)); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + State alice_state = {false, TOX_CONNECTION_NONE}; + + tox_scenario_add_node(s, "Alice", alice_script, &alice_state, sizeof(State)); + tox_scenario_add_node(s, "Bob", peer_script, nullptr, 0); + tox_scenario_add_node(s, "Charlie", peer_script, nullptr, 0); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + ToxNode *charlie = tox_scenario_get_node(s, 2); + + tox_node_bootstrap(alice, bob); + tox_node_bootstrap(charlie, bob); + + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + tox_node_friend_add(alice, charlie); + tox_node_friend_add(charlie, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + diff --git a/auto_tests/scenarios/scenario_send_message_test.c b/auto_tests/scenarios/scenario_send_message_test.c new file mode 100644 index 00000000..5bf3b816 --- /dev/null +++ b/auto_tests/scenarios/scenario_send_message_test.c @@ -0,0 +1,98 @@ +#include "framework/framework.h" +#include +#include +#include + +#define MESSAGE_FILLER 'G' + +typedef struct { + bool message_received; + uint32_t received_len; +} MessageState; + +static void on_friend_message(const Tox_Event_Friend_Message *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + MessageState *state = (MessageState *)tox_node_get_script_ctx(self); + + state->received_len = tox_event_friend_message_get_message_length(event); + const uint8_t *msg = tox_event_friend_message_get_message(event); + + size_t max_len = tox_max_message_length(); + bool correct = (state->received_len == max_len); + if (correct) { + for (size_t i = 0; i < max_len; ++i) { + if (msg[i] != MESSAGE_FILLER) { + correct = false; + break; + } + } + } + + if (correct) { + state->message_received = true; + tox_node_log(self, "Received correct long message"); + } else { + tox_node_log(self, "Received INCORRECT message, len %u", state->received_len); + } +} + +static void sender_script(ToxNode *self, void *ctx) +{ + Tox *tox = tox_node_get_tox(self); + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + size_t max_len = tox_max_message_length(); + uint8_t *msg = (uint8_t *)malloc(max_len + 1); + memset(msg, MESSAGE_FILLER, max_len + 1); + + Tox_Err_Friend_Send_Message err; + + // Test too long + tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, max_len + 1, &err); + ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG); + tox_node_log(self, "Correctly failed to send too long message"); + + // Test max length + tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, max_len, &err); + ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); + tox_node_log(self, "Sent max length message"); + + free(msg); +} + +static void receiver_script(ToxNode *self, void *ctx) +{ + const MessageState *state = (const MessageState *)ctx; + tox_events_callback_friend_message(tox_node_get_dispatch(self), on_friend_message); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + WAIT_UNTIL(state->message_received); + tox_node_log(self, "Done"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + MessageState state_receiver = {0}; + + ToxNode *sender = tox_scenario_add_node(s, "Sender", sender_script, nullptr, 0); + ToxNode *receiver = tox_scenario_add_node(s, "Receiver", receiver_script, &state_receiver, sizeof(MessageState)); + + tox_node_friend_add(sender, receiver); + tox_node_friend_add(receiver, sender); + tox_node_bootstrap(sender, receiver); + tox_node_bootstrap(receiver, sender); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_set_name_test.c b/auto_tests/scenarios/scenario_set_name_test.c new file mode 100644 index 00000000..6a41851f --- /dev/null +++ b/auto_tests/scenarios/scenario_set_name_test.c @@ -0,0 +1,45 @@ +#include "framework/framework.h" +#include +#include + +#define NICKNAME "Gentoo" + +static void alice_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Setting name to %s", NICKNAME); + tox_self_set_name(tox_node_get_tox(self), (const uint8_t *)NICKNAME, sizeof(NICKNAME), nullptr); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Waiting for Alice to change name..."); + WAIT_UNTIL(tox_node_friend_name_is(self, 0, (const uint8_t *)NICKNAME, sizeof(NICKNAME))); + tox_node_log(self, "Alice\'s name is now %s", NICKNAME); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_set_status_message_test.c b/auto_tests/scenarios/scenario_set_status_message_test.c new file mode 100644 index 00000000..39e7da44 --- /dev/null +++ b/auto_tests/scenarios/scenario_set_status_message_test.c @@ -0,0 +1,45 @@ +#include "framework/framework.h" +#include +#include + +#define STATUS_MESSAGE "Installing Gentoo" + +static void alice_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Setting status message to %s", STATUS_MESSAGE); + tox_self_set_status_message(tox_node_get_tox(self), (const uint8_t *)STATUS_MESSAGE, sizeof(STATUS_MESSAGE), nullptr); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Waiting for Alice to change status message..."); + WAIT_UNTIL(tox_node_friend_status_message_is(self, 0, (const uint8_t *)STATUS_MESSAGE, sizeof(STATUS_MESSAGE))); + tox_node_log(self, "Alice\'s status message is now %s", STATUS_MESSAGE); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_tox_many_tcp_test.c b/auto_tests/scenarios/scenario_tox_many_tcp_test.c new file mode 100644 index 00000000..4ff12212 --- /dev/null +++ b/auto_tests/scenarios/scenario_tox_many_tcp_test.c @@ -0,0 +1,160 @@ +#include "framework/framework.h" +#include +#include +#include + +#define NUM_TOXES 40 +#define NUM_FRIEND_PAIRS 50 +#define FR_MESSAGE "Gentoo" +#define RELAY_TCP_PORT 33448 + +typedef struct { + uint32_t expected_friends; +} State; + +static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + const uint8_t *msg = tox_event_friend_request_get_message(event); + size_t len = tox_event_friend_request_get_message_length(event); + + if (len == 6 && memcmp(msg, FR_MESSAGE, 6) == 0) { + tox_friend_add_norequest(tox_node_get_tox(self), tox_event_friend_request_get_public_key(event), nullptr); + } +} + +static void peer_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request); + + tox_node_log(self, "Waiting for connection to relay..."); + tox_node_wait_for_self_connected(self); + tox_node_log(self, "Connected. Expected friends: %u", state->expected_friends); + + // Wait until we have the expected number of friends connected via TCP + uint32_t last_connected = 0; + while (1) { + uint32_t connected_friends = 0; + uint32_t total_friends = tox_self_get_friend_list_size(tox_node_get_tox(self)); + for (uint32_t i = 0; i < total_friends; ++i) { + if (tox_node_get_friend_connection_status(self, i) == TOX_CONNECTION_TCP) { + connected_friends++; + } + } + + if (connected_friends != last_connected) { + tox_node_log(self, "Connected friends: %u/%u", connected_friends, state->expected_friends); + last_connected = connected_friends; + } + + if (connected_friends >= state->expected_friends) { + break; + } + tox_scenario_yield(self); + } + + tox_node_log(self, "All %u friends connected via TCP.", state->expected_friends); +} + +static void relay_script(ToxNode *self, void *ctx) +{ + (void)ctx; + ToxScenario *s = tox_node_get_scenario(self); + + while (tox_scenario_is_running(self)) { + bool all_finished = true; + for (uint32_t i = 0; i < NUM_TOXES; ++i) { + ToxNode *peer = tox_scenario_get_node(s, i + 1); // Peers start at index 1 + if (!tox_node_is_finished(peer)) { + all_finished = false; + break; + } + } + if (all_finished) { + break; + } + tox_scenario_yield(self); + } +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + + struct Tox_Options *opts_relay = tox_options_new(nullptr); + tox_options_set_tcp_port(opts_relay, RELAY_TCP_PORT); + ToxNode *relay = tox_scenario_add_node_ex(s, "Relay", relay_script, nullptr, 0, opts_relay); + tox_options_free(opts_relay); + + uint8_t relay_dht_id[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox_node_get_tox(relay), relay_dht_id); + + uint16_t relay_tcp_port = tox_self_get_tcp_port(tox_node_get_tox(relay), nullptr); + + State states[NUM_TOXES] = {0}; + ToxNode *nodes[NUM_TOXES]; + + struct Tox_Options *opts_peer = tox_options_new(nullptr); + tox_options_set_udp_enabled(opts_peer, false); + tox_options_set_local_discovery_enabled(opts_peer, false); + + for (int i = 0; i < NUM_TOXES; ++i) { + char alias[16]; + snprintf(alias, sizeof(alias), "Tox%d", i); + nodes[i] = tox_scenario_add_node_ex(s, alias, peer_script, &states[i], sizeof(State), opts_peer); + + // All peers use the Relay for TCP and DHT bootstrapping + tox_add_tcp_relay(tox_node_get_tox(nodes[i]), "127.0.0.1", relay_tcp_port, relay_dht_id, nullptr); + tox_node_bootstrap(nodes[i], relay); + } + tox_options_free(opts_peer); + + struct { + uint16_t t1; + uint16_t t2; + } pairs[NUM_FRIEND_PAIRS]; + + // Generate friend pairs + for (int i = 0; i < NUM_FRIEND_PAIRS; ++i) { + bool unique; + do { + unique = true; + pairs[i].t1 = rand() % NUM_TOXES; + pairs[i].t2 = (pairs[i].t1 + rand() % (NUM_TOXES - 1) + 1) % NUM_TOXES; + + // Avoid reciprocal or duplicate pairs + for (int j = 0; j < i; ++j) { + if ((pairs[j].t1 == pairs[i].t1 && pairs[j].t2 == pairs[i].t2) || + (pairs[j].t1 == pairs[i].t2 && pairs[j].t2 == pairs[i].t1)) { + unique = false; + break; + } + } + } while (!unique); + + uint8_t addr[TOX_ADDRESS_SIZE]; + tox_self_get_address(tox_node_get_tox(nodes[pairs[i].t1]), addr); + + Tox_Err_Friend_Add err; + tox_friend_add(tox_node_get_tox(nodes[pairs[i].t2]), addr, (const uint8_t *)FR_MESSAGE, 6, &err); + ck_assert(err == TOX_ERR_FRIEND_ADD_OK); + + states[pairs[i].t1].expected_friends++; + states[pairs[i].t2].expected_friends++; + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef FR_MESSAGE +#undef NUM_TOXES +#undef NUM_FRIEND_PAIRS +#undef RELAY_TCP_PORT diff --git a/auto_tests/scenarios/scenario_tox_many_test.c b/auto_tests/scenarios/scenario_tox_many_test.c new file mode 100644 index 00000000..94276fc8 --- /dev/null +++ b/auto_tests/scenarios/scenario_tox_many_test.c @@ -0,0 +1,102 @@ +#include "framework/framework.h" +#include +#include +#include + +#define NUM_TOXES 90 +#define NUM_FRIEND_PAIRS 50 +#define FR_MESSAGE "Gentoo" + +typedef struct { + uint32_t friend_count; +} State; + +static void on_friend_request(const Tox_Event_Friend_Request *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + const uint8_t *msg = tox_event_friend_request_get_message(event); + size_t len = tox_event_friend_request_get_message_length(event); + + if (len == 6 && memcmp(msg, FR_MESSAGE, 6) == 0) { + tox_friend_add_norequest(tox_node_get_tox(self), tox_event_friend_request_get_public_key(event), nullptr); + } +} + +static void peer_script(ToxNode *self, void *ctx) +{ + State *state = (State *)ctx; + tox_events_callback_friend_request(tox_node_get_dispatch(self), on_friend_request); + + tox_node_wait_for_self_connected(self); + + // Wait until we have the expected number of friends connected + while (1) { + uint32_t connected_friends = 0; + uint32_t total_friends = tox_self_get_friend_list_size(tox_node_get_tox(self)); + for (uint32_t i = 0; i < total_friends; ++i) { + if (tox_node_is_friend_connected(self, i)) { + connected_friends++; + } + } + if (connected_friends >= state->friend_count) { + break; + } + tox_scenario_yield(self); + } + + tox_node_log(self, "All %u friends connected.", state->friend_count); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 300000); + State states[NUM_TOXES] = {0}; + ToxNode *nodes[NUM_TOXES]; + + for (int i = 0; i < NUM_TOXES; ++i) { + char alias[16]; + snprintf(alias, sizeof(alias), "Tox%d", i); + nodes[i] = tox_scenario_add_node(s, alias, peer_script, &states[i], sizeof(State)); + } + + for (int i = 0; i < NUM_TOXES; ++i) { + tox_node_bootstrap(nodes[i], nodes[(i + 1) % NUM_TOXES]); + tox_node_bootstrap(nodes[(i + 1) % NUM_TOXES], nodes[i]); + } + + // Generate friend pairs + for (int i = 0; i < NUM_FRIEND_PAIRS; ++i) { + int t1, t2; + do { + t1 = rand() % NUM_TOXES; + t2 = rand() % NUM_TOXES; + } while (t1 == t2); + + uint8_t addr[TOX_ADDRESS_SIZE]; + tox_self_get_address(tox_node_get_tox(nodes[t1]), addr); + + Tox_Err_Friend_Add err; + tox_friend_add(tox_node_get_tox(nodes[t2]), addr, (const uint8_t *)FR_MESSAGE, 6, &err); + if (err == TOX_ERR_FRIEND_ADD_OK) { + states[t1].friend_count++; + states[t2].friend_count++; + + // Bootstrap off each other + tox_node_bootstrap(nodes[t2], nodes[t1]); + tox_node_bootstrap(nodes[t1], nodes[t2]); + } + } + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + +#undef FR_MESSAGE +#undef NUM_TOXES +#undef NUM_FRIEND_PAIRS diff --git a/auto_tests/scenarios/scenario_toxav_basic_test.c b/auto_tests/scenarios/scenario_toxav_basic_test.c new file mode 100644 index 00000000..655ff8fb --- /dev/null +++ b/auto_tests/scenarios/scenario_toxav_basic_test.c @@ -0,0 +1,205 @@ +#include "framework/framework.h" +#include "../../toxav/toxav.h" +#include +#include +#include + +typedef struct { + bool incoming; + uint32_t state; +} CallState; + +#define WAIT_UNTIL_AV(av, cond) do { \ + while(!(cond) && tox_scenario_is_running(self)) { \ + toxav_iterate(av); \ + tox_scenario_yield(self); \ + } \ +} while(0) + +static void on_call(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + CallState *state = (CallState *)tox_node_get_script_ctx(self); + tox_node_log(self, "Received call from friend %u", friend_number); + state->incoming = true; +} + +static void on_call_state(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + CallState *cs = (CallState *)tox_node_get_script_ctx(self); + tox_node_log(self, "Call state changed to %u", state); + cs->state = state; +} + +static void on_audio_receive(ToxAV *av, uint32_t friend_number, int16_t const *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, void *user_data) +{ + (void)av; + (void)friend_number; + (void)pcm; + (void)sample_count; + (void)channels; + (void)sampling_rate; + (void)user_data; +} + +static void on_video_receive(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, void *user_data) +{ + (void)av; + (void)friend_number; + (void)width; + (void)height; + (void)y; + (void)u; + (void)v; + (void)ystride; + (void)ustride; + (void)vstride; + (void)user_data; +} + +static void alice_script(ToxNode *self, void *ctx) +{ + CallState *state = (CallState *)ctx; + Tox *tox = tox_node_get_tox(self); + Toxav_Err_New av_err; + ToxAV *av = toxav_new(tox, &av_err); + ck_assert(av_err == TOXAV_ERR_NEW_OK); + + toxav_callback_call(av, on_call, self); + toxav_callback_call_state(av, on_call_state, self); + toxav_callback_audio_receive_frame(av, on_audio_receive, self); + toxav_callback_video_receive_frame(av, on_video_receive, self); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + // 1. Regular AV call - Alice calls, Bob answers, Bob hangs up + tox_node_log(self, "--- Starting Regular AV Call ---"); + state->state = 0; + Toxav_Err_Call call_err; + toxav_call(av, 0, 48, 4000, &call_err); + ck_assert(call_err == TOXAV_ERR_CALL_OK); + + WAIT_UNTIL_AV(av, state->state & TOXAV_FRIEND_CALL_STATE_FINISHED); + tox_node_log(self, "Regular AV Call finished (state=%u)", state->state); + + tox_scenario_barrier_wait(self); + + // 2. Reject flow - Alice calls, Bob rejects + tox_node_log(self, "--- Starting Reject Flow ---"); + state->state = 0; + toxav_call(av, 0, 48, 0, &call_err); + ck_assert(call_err == TOXAV_ERR_CALL_OK); + + WAIT_UNTIL_AV(av, state->state & TOXAV_FRIEND_CALL_STATE_FINISHED); + tox_node_log(self, "Reject Flow finished"); + + tox_scenario_barrier_wait(self); + + // 3. Cancel flow - Alice calls, Alice cancels + tox_node_log(self, "--- Starting Cancel Flow ---"); + state->state = 0; + toxav_call(av, 0, 48, 0, &call_err); + ck_assert(call_err == TOXAV_ERR_CALL_OK); + + // Wait for Bob to see it ringing + tox_scenario_barrier_wait(self); + + tox_node_log(self, "Alice: Canceling call..."); + Toxav_Err_Call_Control cc_err; + toxav_call_control(av, 0, TOXAV_CALL_CONTROL_CANCEL, &cc_err); + ck_assert(cc_err == TOXAV_ERR_CALL_CONTROL_OK); + + // Alice doesn't receive FINISHED state when SHE cancels + tox_node_log(self, "Alice: Cancel Flow finished"); + + tox_scenario_barrier_wait(self); + + toxav_kill(av); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + CallState *state = (CallState *)ctx; + Tox *tox = tox_node_get_tox(self); + Toxav_Err_New av_err; + ToxAV *av = toxav_new(tox, &av_err); + ck_assert(av_err == TOXAV_ERR_NEW_OK); + + toxav_callback_call(av, on_call, self); + toxav_callback_call_state(av, on_call_state, self); + toxav_callback_audio_receive_frame(av, on_audio_receive, self); + toxav_callback_video_receive_frame(av, on_video_receive, self); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + // 1. Regular AV call - Bob answers, then hangs up + WAIT_UNTIL_AV(av, state->incoming); + state->incoming = false; + Toxav_Err_Answer answer_err; + toxav_answer(av, 0, 48, 4000, &answer_err); + ck_assert(answer_err == TOXAV_ERR_ANSWER_OK); + + // Wait a bit and hang up + for (int i = 0; i < 10; i++) { + toxav_iterate(av); + tox_scenario_yield(self); + } + tox_node_log(self, "Bob: Hanging up..."); + Toxav_Err_Call_Control cc_err; + toxav_call_control(av, 0, TOXAV_CALL_CONTROL_CANCEL, &cc_err); + ck_assert(cc_err == TOXAV_ERR_CALL_CONTROL_OK); + + tox_scenario_barrier_wait(self); + + // 2. Reject flow - Bob rejects + WAIT_UNTIL_AV(av, state->incoming); + state->incoming = false; + tox_node_log(self, "Bob: Rejecting call..."); + toxav_call_control(av, 0, TOXAV_CALL_CONTROL_CANCEL, &cc_err); + ck_assert(cc_err == TOXAV_ERR_CALL_CONTROL_OK); + + tox_scenario_barrier_wait(self); + + // 3. Cancel flow - Alice cancels + WAIT_UNTIL_AV(av, state->incoming); + state->incoming = false; + tox_scenario_barrier_wait(self); // Alice will now cancel + + WAIT_UNTIL_AV(av, state->state & TOXAV_FRIEND_CALL_STATE_FINISHED); + tox_node_log(self, "Bob: Cancel Flow finished (Alice canceled)"); + + tox_scenario_barrier_wait(self); + + toxav_kill(av); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + CallState alice_state = {0}; + CallState bob_state = {0}; + + Tox_Options *opts = tox_options_new(nullptr); + tox_options_set_ipv6_enabled(opts, false); + tox_options_set_local_discovery_enabled(opts, false); + + ToxNode *alice = tox_scenario_add_node_ex(s, "Alice", alice_script, &alice_state, sizeof(CallState), opts); + ToxNode *bob = tox_scenario_add_node_ex(s, "Bob", bob_script, &bob_state, sizeof(CallState), opts); + + tox_options_free(opts); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + tox_scenario_free(s); + return (res == TOX_SCENARIO_DONE) ? 0 : 1; +} diff --git a/auto_tests/scenarios/scenario_toxav_many_test.c b/auto_tests/scenarios/scenario_toxav_many_test.c new file mode 100644 index 00000000..1d3d5a26 --- /dev/null +++ b/auto_tests/scenarios/scenario_toxav_many_test.c @@ -0,0 +1,184 @@ +#include "framework/framework.h" +#include "../../toxav/toxav.h" +#include +#include +#include + +#define NUM_BOBS 3 + +typedef struct { + bool incoming; + uint32_t state; +} CallState; + +static void on_call(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + CallState *states = (CallState *)tox_node_get_script_ctx(self); + tox_node_log(self, "Received call from friend %u", friend_number); + states[friend_number].incoming = true; +} + +static void on_call_state(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + CallState *states = (CallState *)tox_node_get_script_ctx(self); + tox_node_log(self, "Call state for friend %u changed to %u", friend_number, state); + states[friend_number].state = state; +} + +static void on_audio_receive(ToxAV *av, uint32_t friend_number, int16_t const *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, void *user_data) +{ +} + +static void on_video_receive(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, void *user_data) +{ +} + +static void alice_script(ToxNode *self, void *ctx) +{ + CallState *states = (CallState *)ctx; + Tox *tox = tox_node_get_tox(self); + Toxav_Err_New av_err; + ToxAV *av = toxav_new(tox, &av_err); + ck_assert(av_err == TOXAV_ERR_NEW_OK); + + toxav_callback_call(av, on_call, self); + toxav_callback_call_state(av, on_call_state, self); + toxav_callback_audio_receive_frame(av, on_audio_receive, self); + toxav_callback_video_receive_frame(av, on_video_receive, self); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + for (uint32_t i = 0; i < NUM_BOBS; i++) { + WAIT_UNTIL(tox_node_is_friend_connected(self, i)); + } + + tox_node_log(self, "All Bobs connected. Calling them..."); + + for (uint32_t i = 0; i < NUM_BOBS; i++) { + Toxav_Err_Call call_err; + toxav_call(av, i, 48, 3000, &call_err); + ck_assert(call_err == TOXAV_ERR_CALL_OK); + } + + int16_t pcm[960] = {0}; + uint8_t *video_y = (uint8_t *)calloc(800 * 600, sizeof(uint8_t)); + uint8_t *video_u = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t)); + uint8_t *video_v = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t)); + + // Send a few frames to verify flow + for (int i = 0; i < 20; i++) { + toxav_iterate(av); + for (uint32_t j = 0; j < NUM_BOBS; j++) { + if (states[j].state & TOXAV_FRIEND_CALL_STATE_SENDING_A) { + toxav_audio_send_frame(av, j, pcm, 960, 1, 48000, nullptr); + } + if (states[j].state & TOXAV_FRIEND_CALL_STATE_SENDING_V) { + toxav_video_send_frame(av, j, 800, 600, video_y, video_u, video_v, nullptr); + } + } + tox_scenario_yield(self); + } + + tox_node_log(self, "Hanging up all calls..."); + for (uint32_t i = 0; i < NUM_BOBS; i++) { + toxav_call_control(av, i, TOXAV_CALL_CONTROL_CANCEL, nullptr); + } + + // Give it a few ticks to send hangup packets + for (int i = 0; i < 5; i++) { + toxav_iterate(av); + tox_scenario_yield(self); + } + + free(video_y); + free(video_u); + free(video_v); + toxav_kill(av); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + CallState *states = (CallState *)ctx; + Tox *tox = tox_node_get_tox(self); + Toxav_Err_New av_err; + ToxAV *av = toxav_new(tox, &av_err); + ck_assert(av_err == TOXAV_ERR_NEW_OK); + + toxav_callback_call(av, on_call, self); + toxav_callback_call_state(av, on_call_state, self); + toxav_callback_audio_receive_frame(av, on_audio_receive, self); + toxav_callback_video_receive_frame(av, on_video_receive, self); + + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + while (!states[0].incoming && tox_scenario_is_running(self)) { + toxav_iterate(av); + tox_scenario_yield(self); + } + + tox_node_log(self, "Answering call..."); + Toxav_Err_Answer answer_err; + toxav_answer(av, 0, 8, 500, &answer_err); + ck_assert(answer_err == TOXAV_ERR_ANSWER_OK); + + int16_t pcm[960] = {0}; + uint8_t *video_y = (uint8_t *)calloc(800 * 600, sizeof(uint8_t)); + uint8_t *video_u = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t)); + uint8_t *video_v = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t)); + + while (!(states[0].state & TOXAV_FRIEND_CALL_STATE_FINISHED) && tox_scenario_is_running(self)) { + toxav_iterate(av); + if (states[0].state & TOXAV_FRIEND_CALL_STATE_SENDING_A) { + toxav_audio_send_frame(av, 0, pcm, 960, 1, 48000, nullptr); + } + if (states[0].state & TOXAV_FRIEND_CALL_STATE_SENDING_V) { + toxav_video_send_frame(av, 0, 800, 600, video_y, video_u, video_v, nullptr); + } + tox_scenario_yield(self); + } + + tox_node_log(self, "Call finished."); + + free(video_y); + free(video_u); + free(video_v); + toxav_kill(av); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + CallState alice_states[NUM_BOBS] = {0}; + Tox_Options *opts = tox_options_new(nullptr); + tox_options_set_ipv6_enabled(opts, false); + tox_options_set_local_discovery_enabled(opts, false); + + ToxNode *alice = tox_scenario_add_node_ex(s, "Alice", alice_script, alice_states, sizeof(alice_states), opts); + + ToxNode *bobs[NUM_BOBS]; + CallState bob_states[NUM_BOBS]; + for (int i = 0; i < NUM_BOBS; i++) { + char name[32]; + snprintf(name, sizeof(name), "Bob-%d", i); + bob_states[i] = (CallState) { + 0 + }; + bobs[i] = tox_scenario_add_node_ex(s, name, bob_script, &bob_states[i], sizeof(CallState), opts); + + tox_node_bootstrap(bobs[i], alice); + tox_node_friend_add(alice, bobs[i]); + tox_node_friend_add(bobs[i], alice); + } + + tox_options_free(opts); + + ToxScenarioStatus res = tox_scenario_run(s); + tox_scenario_free(s); + return (res == TOX_SCENARIO_DONE) ? 0 : 1; +} diff --git a/auto_tests/scenarios/scenario_typing_test.c b/auto_tests/scenarios/scenario_typing_test.c new file mode 100644 index 00000000..77fcfef5 --- /dev/null +++ b/auto_tests/scenarios/scenario_typing_test.c @@ -0,0 +1,54 @@ +#include "framework/framework.h" +#include + +static void alice_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Setting typing to true"); + tox_self_set_typing(tox_node_get_tox(self), 0, true, nullptr); + + // Wait some time + for (int i = 0; i < 10; ++i) { + tox_scenario_yield(self); + } + + tox_node_log(self, "Setting typing to false"); + tox_self_set_typing(tox_node_get_tox(self), 0, false, nullptr); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + WAIT_UNTIL(tox_node_is_self_connected(self)); + WAIT_UNTIL(tox_node_is_friend_connected(self, 0)); + + tox_node_log(self, "Waiting for Alice to start typing..."); + WAIT_UNTIL(tox_node_friend_typing_is(self, 0, true)); + tox_node_log(self, "Alice is typing!"); + + tox_node_log(self, "Waiting for Alice to stop typing..."); + WAIT_UNTIL(tox_node_friend_typing_is(self, 0, false)); + tox_node_log(self, "Alice stopped typing."); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + ToxNode *alice = tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + ToxNode *bob = tox_scenario_add_node(s, "Bob", bob_script, nullptr, 0); + + tox_node_bootstrap(bob, alice); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + tox_scenario_log(s, "Test failed with status %u", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} diff --git a/auto_tests/scenarios/scenario_user_status_test.c b/auto_tests/scenarios/scenario_user_status_test.c new file mode 100644 index 00000000..1881ea62 --- /dev/null +++ b/auto_tests/scenarios/scenario_user_status_test.c @@ -0,0 +1,102 @@ +#include "framework/framework.h" +#include +#include +#include + +typedef struct { + Tox_User_Status last_status; + bool status_changed; +} BobState; + +static void on_friend_status(const Tox_Event_Friend_Status *event, void *user_data) +{ + ToxNode *self = (ToxNode *)user_data; + BobState *state = (BobState *)tox_node_get_script_ctx(self); + + if (tox_event_friend_status_get_friend_number(event) == 0) { + state->last_status = tox_event_friend_status_get_status(event); + state->status_changed = true; + tox_node_log(self, "Alice changed status to %s", tox_user_status_to_string(state->last_status)); + } +} + +static void alice_script(ToxNode *self, void *ctx) +{ + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + Tox *tox = tox_node_get_tox(self); + + tox_node_log(self, "Setting status to AWAY"); + tox_self_set_status(tox, TOX_USER_STATUS_AWAY); + ck_assert(tox_self_get_status(tox) == TOX_USER_STATUS_AWAY); + + // Yield to let the status propagate + for (int i = 0; i < 20; ++i) { + tox_scenario_yield(self); + } + + tox_node_log(self, "Setting status to BUSY"); + tox_self_set_status(tox, TOX_USER_STATUS_BUSY); + ck_assert(tox_self_get_status(tox) == TOX_USER_STATUS_BUSY); + + for (int i = 0; i < 20; ++i) { + tox_scenario_yield(self); + } + + tox_node_log(self, "Setting status back to NONE"); + tox_self_set_status(tox, TOX_USER_STATUS_NONE); + ck_assert(tox_self_get_status(tox) == TOX_USER_STATUS_NONE); + + ToxNode *bob = tox_scenario_get_node(tox_node_get_scenario(self), 1); + WAIT_UNTIL(tox_node_is_finished(bob)); +} + +static void bob_script(ToxNode *self, void *ctx) +{ + BobState *state = (BobState *)ctx; + tox_events_callback_friend_status(tox_node_get_dispatch(self), on_friend_status); + + tox_node_wait_for_self_connected(self); + tox_node_wait_for_friend_connected(self, 0); + + tox_node_log(self, "Waiting for Alice to become AWAY..."); + WAIT_UNTIL(state->status_changed && state->last_status == TOX_USER_STATUS_AWAY); + state->status_changed = false; + + tox_node_log(self, "Waiting for Alice to become BUSY..."); + WAIT_UNTIL(state->status_changed && state->last_status == TOX_USER_STATUS_BUSY); + state->status_changed = false; + + tox_node_log(self, "Waiting for Alice to become NONE..."); + WAIT_UNTIL(state->status_changed && state->last_status == TOX_USER_STATUS_NONE); + + tox_node_log(self, "Status propagation verified!"); +} + +int main(int argc, char *argv[]) +{ + ToxScenario *s = tox_scenario_new(argc, argv, 60000); + + BobState bob_state = {TOX_USER_STATUS_NONE, false}; + + tox_scenario_add_node(s, "Alice", alice_script, nullptr, 0); + tox_scenario_add_node(s, "Bob", bob_script, &bob_state, sizeof(BobState)); + + ToxNode *alice = tox_scenario_get_node(s, 0); + ToxNode *bob = tox_scenario_get_node(s, 1); + + tox_node_bootstrap(alice, bob); + tox_node_friend_add(alice, bob); + tox_node_friend_add(bob, alice); + + ToxScenarioStatus res = tox_scenario_run(s); + if (res != TOX_SCENARIO_DONE) { + fprintf(stderr, "Scenario failed with status %u\n", res); + return 1; + } + + tox_scenario_free(s); + return 0; +} + diff --git a/auto_tests/send_message_test.c b/auto_tests/send_message_test.c deleted file mode 100644 index bba885c7..00000000 --- a/auto_tests/send_message_test.c +++ /dev/null @@ -1,83 +0,0 @@ -/* Tests that we can send messages to friends. - */ - -#include -#include -#include - -typedef struct State { - bool message_received; -} State; - -#include "auto_test_support.h" - -#define MESSAGE_FILLER 'G' - -static void message_callback( - const Tox_Event_Friend_Message *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - const Tox_Message_Type type = tox_event_friend_message_get_type(event); - const uint8_t *string = tox_event_friend_message_get_message(event); - const uint32_t length = tox_event_friend_message_get_message_length(event); - - if (type != TOX_MESSAGE_TYPE_NORMAL) { - ck_abort_msg("Bad type"); - } - - const size_t cmp_msg_len = tox_max_message_length(); - uint8_t *cmp_msg = (uint8_t *)malloc(cmp_msg_len); - ck_assert(cmp_msg != nullptr); - memset(cmp_msg, MESSAGE_FILLER, cmp_msg_len); - - if (length == tox_max_message_length() && memcmp(string, cmp_msg, cmp_msg_len) == 0) { - state->message_received = true; - } - - free(cmp_msg); -} - -static void send_message_test(AutoTox *autotoxes) -{ - tox_events_callback_friend_message(autotoxes[1].dispatch, &message_callback); - - const size_t msgs_len = tox_max_message_length() + 1; - uint8_t *msgs = (uint8_t *)malloc(msgs_len); - ck_assert(msgs != nullptr); - memset(msgs, MESSAGE_FILLER, msgs_len); - - Tox_Err_Friend_Send_Message errm; - tox_friend_send_message(autotoxes[0].tox, 0, TOX_MESSAGE_TYPE_NORMAL, msgs, msgs_len, &errm); - ck_assert_msg(errm == TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG, "tox_max_message_length() is too small? error=%u", errm); - - tox_friend_send_message(autotoxes[0].tox, 0, TOX_MESSAGE_TYPE_NORMAL, msgs, tox_max_message_length(), &errm); - ck_assert_msg(errm == TOX_ERR_FRIEND_SEND_MESSAGE_OK, "tox_max_message_length() is too big? error=%u", errm); - - free(msgs); - - do { - iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); - } while (!((State *)autotoxes[1].state)->message_received); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - struct Tox_Options *tox_options = tox_options_new(nullptr); - ck_assert(tox_options != nullptr); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - tox_options_set_ipv6_enabled(tox_options, true); - run_auto_test(tox_options, 2, send_message_test, sizeof(State), &options); - - tox_options_set_ipv6_enabled(tox_options, false); - run_auto_test(tox_options, 2, send_message_test, sizeof(State), &options); - - tox_options_free(tox_options); - - return 0; -} diff --git a/auto_tests/set_name_test.c b/auto_tests/set_name_test.c deleted file mode 100644 index 71c39035..00000000 --- a/auto_tests/set_name_test.c +++ /dev/null @@ -1,130 +0,0 @@ -/* Tests that we can set our name. - */ - -#include -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/ccompat.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#define NICKNAME "Gentoo" - -static void nickchange_callback(const Tox_Event_Friend_Name *event, void *user_data) -{ - //const uint32_t friend_number = tox_event_friend_name_get_friend_number(event); - const uint8_t *name = tox_event_friend_name_get_name(event); - const uint32_t name_length = tox_event_friend_name_get_name_length(event); - - ck_assert_msg(name_length == sizeof(NICKNAME), "Name length not correct: %d != %d", (uint16_t)name_length, - (uint16_t)sizeof(NICKNAME)); - ck_assert_msg(memcmp(name, NICKNAME, sizeof(NICKNAME)) == 0, "Name not correct: %s", (const char *)name); - bool *nickname_updated = (bool *)user_data; - *nickname_updated = true; -} - -static void test_set_name(void) -{ - printf("initialising 2 toxes\n"); - uint32_t index[] = { 1, 2 }; - const time_t cur_time = time(nullptr); - Tox *const tox1 = tox_new_log(nullptr, nullptr, &index[0]); - Tox *const tox2 = tox_new_log(nullptr, nullptr, &index[1]); - - ck_assert_msg(tox1 && tox2, "failed to create 2 tox instances"); - - // we only run events on tox2 in this test case - tox_events_init(tox2); - - Tox_Dispatch *dispatch2 = tox_dispatch_new(nullptr); - ck_assert(dispatch2 != nullptr); - - printf("tox1 adds tox2 as friend, tox2 adds tox1\n"); - uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_public_key(tox2, public_key); - tox_friend_add_norequest(tox1, public_key, nullptr); - tox_self_get_public_key(tox1, public_key); - tox_friend_add_norequest(tox2, public_key, nullptr); - - printf("bootstrapping tox2 off tox1\n"); - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(tox1, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); - - tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); - - do { - tox_iterate(tox1, nullptr); - - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - //tox_dispatch_invoke(dispatch2, events, nullptr); - tox_events_free(events); - - c_sleep(ITERATION_INTERVAL); - } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE); - - printf("toxes are online, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); - const time_t con_time = time(nullptr); - - do { - tox_iterate(tox1, nullptr); - - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - //tox_dispatch_invoke(dispatch2, events, nullptr); - tox_events_free(events); - - c_sleep(ITERATION_INTERVAL); - } while (tox_friend_get_connection_status(tox1, 0, nullptr) != TOX_CONNECTION_UDP || - tox_friend_get_connection_status(tox2, 0, nullptr) != TOX_CONNECTION_UDP); - - printf("tox clients connected took %lu seconds\n", (unsigned long)(time(nullptr) - con_time)); - - tox_events_callback_friend_name(dispatch2, nickchange_callback); - Tox_Err_Set_Info err_n; - bool ret = tox_self_set_name(tox1, (const uint8_t *)NICKNAME, sizeof(NICKNAME), &err_n); - ck_assert_msg(ret && err_n == TOX_ERR_SET_INFO_OK, "tox_self_set_name failed because %u\n", err_n); - - bool nickname_updated = false; - - do { - tox_iterate(tox1, nullptr); - - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch2, events, &nickname_updated); - tox_events_free(events); - - c_sleep(ITERATION_INTERVAL); - } while (!nickname_updated); - - ck_assert_msg(tox_friend_get_name_size(tox2, 0, nullptr) == sizeof(NICKNAME), "Name length not correct"); - uint8_t temp_name[sizeof(NICKNAME)]; - tox_friend_get_name(tox2, 0, temp_name, nullptr); - ck_assert_msg(memcmp(temp_name, NICKNAME, sizeof(NICKNAME)) == 0, "Name not correct"); - - printf("test_set_name succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); - - tox_dispatch_free(dispatch2); - - tox_kill(tox1); - tox_kill(tox2); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - test_set_name(); - return 0; -} diff --git a/auto_tests/set_status_message_test.c b/auto_tests/set_status_message_test.c deleted file mode 100644 index ded46e53..00000000 --- a/auto_tests/set_status_message_test.c +++ /dev/null @@ -1,133 +0,0 @@ -/* Tests that we can set our status message - */ - -#include -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/ccompat.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#define STATUS_MESSAGE "Installing Gentoo" - -static void status_callback(const Tox_Event_Friend_Status_Message *event, void *user_data) -{ - //uint32_t friend_number = tox_event_friend_status_message_get_friend_number(event); - const uint8_t *message = tox_event_friend_status_message_get_message(event); - uint32_t message_length = tox_event_friend_status_message_get_message_length(event); - - ck_assert_msg(message_length == sizeof(STATUS_MESSAGE) && - memcmp(message, STATUS_MESSAGE, sizeof(STATUS_MESSAGE)) == 0, - "incorrect data in status callback"); - bool *status_updated = (bool *)user_data; - *status_updated = true; -} - -static void test_set_status_message(void) -{ - printf("initialising 2 toxes\n"); - uint32_t index[] = { 1, 2 }; - const time_t cur_time = time(nullptr); - Tox *const tox1 = tox_new_log(nullptr, nullptr, &index[0]); - Tox *const tox2 = tox_new_log(nullptr, nullptr, &index[1]); - - ck_assert_msg(tox1 && tox2, "failed to create 2 tox instances"); - - // we only run events on tox2 in this test case - tox_events_init(tox2); - - Tox_Dispatch *dispatch2 = tox_dispatch_new(nullptr); - ck_assert(dispatch2 != nullptr); - - printf("tox1 adds tox2 as friend, tox2 adds tox1\n"); - uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_public_key(tox2, public_key); - tox_friend_add_norequest(tox1, public_key, nullptr); - tox_self_get_public_key(tox1, public_key); - tox_friend_add_norequest(tox2, public_key, nullptr); - - printf("bootstrapping tox2 off tox1\n"); - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(tox1, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); - - tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); - - do { - tox_iterate(tox1, nullptr); - - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - //tox_dispatch_invoke(dispatch2, events, nullptr); - tox_events_free(events); - - c_sleep(ITERATION_INTERVAL); - } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE); - - printf("toxes are online, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); - const time_t con_time = time(nullptr); - - do { - tox_iterate(tox1, nullptr); - - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - //tox_dispatch_invoke(dispatch2, events, nullptr); - tox_events_free(events); - - c_sleep(ITERATION_INTERVAL); - } while (tox_friend_get_connection_status(tox1, 0, nullptr) != TOX_CONNECTION_UDP || - tox_friend_get_connection_status(tox2, 0, nullptr) != TOX_CONNECTION_UDP); - - printf("tox clients connected took %lu seconds\n", (unsigned long)(time(nullptr) - con_time)); - - tox_events_callback_friend_status_message(dispatch2, status_callback); - Tox_Err_Set_Info err_n; - bool ret = tox_self_set_status_message(tox1, (const uint8_t *)STATUS_MESSAGE, sizeof(STATUS_MESSAGE), - &err_n); - ck_assert_msg(ret && err_n == TOX_ERR_SET_INFO_OK, "tox_self_set_status_message failed because %u\n", err_n); - - bool status_updated = false; - - do { - tox_iterate(tox1, nullptr); - - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(tox2, true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch2, events, &status_updated); - tox_events_free(events); - - c_sleep(ITERATION_INTERVAL); - } while (!status_updated); - - ck_assert_msg(tox_friend_get_status_message_size(tox2, 0, nullptr) == sizeof(STATUS_MESSAGE), - "status message length not correct"); - uint8_t cmp_status[sizeof(STATUS_MESSAGE)]; - tox_friend_get_status_message(tox2, 0, cmp_status, nullptr); - ck_assert_msg(memcmp(cmp_status, STATUS_MESSAGE, sizeof(STATUS_MESSAGE)) == 0, - "status message not correct"); - - printf("test_set_status_message succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); - - tox_dispatch_free(dispatch2); - - tox_kill(tox1); - tox_kill(tox2); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - test_set_status_message(); - return 0; -} diff --git a/auto_tests/tox_events_test.c b/auto_tests/tox_events_test.c deleted file mode 100644 index 1b7cb729..00000000 --- a/auto_tests/tox_events_test.c +++ /dev/null @@ -1,134 +0,0 @@ -/* Auto Tests: Many clients. - */ - -#include -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/tox.h" -#include "../toxcore/tox_events.h" -#include "../toxcore/tox_struct.h" -#include "auto_test_support.h" -#include "check_compat.h" - -static bool await_message(Tox **toxes) -{ - for (uint32_t i = 0; i < 100; ++i) { - // Ignore events on tox 1. - tox_events_free(tox_events_iterate(toxes[0], false, nullptr)); - // Check if tox 2 got the message from tox 1. - Tox_Events *events = tox_events_iterate(toxes[1], false, nullptr); - - if (events != nullptr) { - uint32_t events_size = tox_events_get_size(events); - ck_assert(events_size == 1); - - const Tox_Event_Friend_Message *msg_event = nullptr; - for (uint32_t j = 0; j < events_size; ++j) { - const Tox_Event *ev = tox_events_get(events, j); - if (tox_event_get_type(ev) == TOX_EVENT_FRIEND_MESSAGE) { - msg_event = tox_event_get_friend_message(ev); - } - } - - ck_assert(msg_event != nullptr); - ck_assert(tox_event_friend_message_get_message_length(msg_event) == sizeof("hello")); - const uint8_t *msg = tox_event_friend_message_get_message(msg_event); - ck_assert_msg(memcmp(msg, "hello", sizeof("hello")) == 0, - "message was not expected 'hello' but '%s'", (const char *)msg); - - tox_events_free(events); - return true; - } - - c_sleep(tox_iteration_interval(toxes[0])); - } - - return false; -} - -static uint64_t get_state_clock_callback(void *user_data) -{ - const uint64_t *clock = (const uint64_t *)user_data; - return *clock; -} - -static void test_tox_events(void) -{ - uint8_t message[sizeof("hello")]; - memcpy(message, "hello", sizeof(message)); - - Tox *toxes[2]; - uint32_t index[2]; - - for (uint32_t i = 0; i < 2; ++i) { - index[i] = i + 1; - toxes[i] = tox_new_log(nullptr, nullptr, &index[i]); - tox_events_init(toxes[i]); - ck_assert_msg(toxes[i] != nullptr, "failed to create tox instances %u", i); - } - - uint64_t clock = current_time_monotonic(toxes[0]->mono_time); - Mono_Time *mono_time; - - mono_time = toxes[0]->mono_time; - mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &clock); - mono_time = toxes[1]->mono_time; - mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &clock); - - uint8_t pk[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(toxes[0], pk); - tox_bootstrap(toxes[1], "localhost", tox_self_get_udp_port(toxes[0], nullptr), pk, nullptr); - - tox_self_get_public_key(toxes[0], pk); - tox_friend_add_norequest(toxes[1], pk, nullptr); - - tox_self_get_public_key(toxes[1], pk); - tox_friend_add_norequest(toxes[0], pk, nullptr); - - printf("bootstrapping and connecting 2 toxes\n"); - - while (tox_self_get_connection_status(toxes[0]) == TOX_CONNECTION_NONE || - tox_self_get_connection_status(toxes[1]) == TOX_CONNECTION_NONE) { - // Ignore connection events for now. - tox_events_free(tox_events_iterate(toxes[0], false, nullptr)); - tox_events_free(tox_events_iterate(toxes[1], false, nullptr)); - - clock += 100; - c_sleep(5); - } - - printf("toxes online, waiting for friend connection\n"); - - while (tox_friend_get_connection_status(toxes[0], 0, nullptr) == TOX_CONNECTION_NONE || - tox_friend_get_connection_status(toxes[1], 0, nullptr) == TOX_CONNECTION_NONE) { - // Ignore connection events for now. - tox_events_free(tox_events_iterate(toxes[0], false, nullptr)); - tox_events_free(tox_events_iterate(toxes[1], false, nullptr)); - - clock += 100; - c_sleep(5); - } - - printf("friends are connected via %s, now sending message\n", - tox_friend_get_connection_status(toxes[0], 0, nullptr) == TOX_CONNECTION_TCP ? "TCP" : "UDP"); - - Tox_Err_Friend_Send_Message err; - tox_friend_send_message(toxes[0], 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof(message), &err); - ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); - - ck_assert(await_message(toxes)); - - for (uint32_t i = 0; i < 2; ++i) { - tox_kill(toxes[i]); - } -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - test_tox_events(); - return 0; -} diff --git a/auto_tests/tox_many_tcp_test.c b/auto_tests/tox_many_tcp_test.c deleted file mode 100644 index 217beb37..00000000 --- a/auto_tests/tox_many_tcp_test.c +++ /dev/null @@ -1,297 +0,0 @@ -/* Auto Tests: Many TCP. - */ - -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/crypto_core.h" -#include "../toxcore/os_random.h" -#include "../toxcore/tox.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#ifndef USE_IPV6 -#define USE_IPV6 1 -#endif - -#ifdef TOX_LOCALHOST -#undef TOX_LOCALHOST -#endif -#if USE_IPV6 -#define TOX_LOCALHOST "::1" -#else -#define TOX_LOCALHOST "127.0.0.1" -#endif - -typedef struct State { - uint32_t to_comp; - Tox *tox; -} State; - -static bool enable_broken_tests = false; - -static void accept_friend_request(const Tox_Event_Friend_Request *event, void *userdata) -{ - State *state = (State *)userdata; - - const uint8_t *public_key = tox_event_friend_request_get_public_key(event); - const uint8_t *message = tox_event_friend_request_get_message(event); - const uint32_t message_length = tox_event_friend_request_get_message_length(event); - - if (state->to_comp != 974536) { - return; - } - - if (message_length == 7 && memcmp("Gentoo", message, 7) == 0) { - tox_friend_add_norequest(state->tox, public_key, nullptr); - } -} - -#define NUM_FRIENDS 50 -#define NUM_TOXES_TCP 40 - -static uint16_t tcp_relay_port = 33448; - -static void test_many_clients_tcp(void) -{ - const Random *rng = os_random(); - ck_assert(rng != nullptr); - long long unsigned int cur_time = time(nullptr); - Tox *toxes[NUM_TOXES_TCP]; - uint32_t index[NUM_TOXES_TCP]; - uint32_t to_comp = 974536; - - for (uint32_t i = 0; i < NUM_TOXES_TCP; ++i) { - struct Tox_Options *opts = tox_options_new(nullptr); - - if (i == 0) { - tox_options_set_tcp_port(opts, tcp_relay_port); - } else { - tox_options_set_udp_enabled(opts, false); - } - - index[i] = i + 1; - Tox_Err_New err; - toxes[i] = tox_new_log(opts, &err, &index[i]); - if (i == 0 && err == TOX_ERR_NEW_PORT_ALLOC) { - ck_assert(toxes[i] == nullptr); - --i; - ++tcp_relay_port; - tox_options_free(opts); - continue; - } - ck_assert_msg(toxes[i] != nullptr, "Failed to create tox instances %u", i); - tox_events_init(toxes[i]); - uint8_t dpk[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(toxes[0], dpk); - Tox_Err_Bootstrap error; - ck_assert_msg(tox_add_tcp_relay(toxes[i], TOX_LOCALHOST, tcp_relay_port, dpk, &error), "add relay error, %u, %u", i, - error); - uint16_t first_port = tox_self_get_udp_port(toxes[0], nullptr); - ck_assert_msg(tox_bootstrap(toxes[i], TOX_LOCALHOST, first_port, dpk, nullptr), "Bootstrap error"); - - tox_options_free(opts); - } - - Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); - ck_assert(dispatch != nullptr); - - tox_events_callback_friend_request(dispatch, accept_friend_request); - - struct { - uint16_t tox1; - uint16_t tox2; - } pairs[NUM_FRIENDS]; - - uint8_t address[TOX_ADDRESS_SIZE]; - - for (uint32_t i = 0; i < NUM_FRIENDS; ++i) { -loop_top: - pairs[i].tox1 = random_u32(rng) % NUM_TOXES_TCP; - pairs[i].tox2 = (pairs[i].tox1 + random_u32(rng) % (NUM_TOXES_TCP - 1) + 1) % NUM_TOXES_TCP; - - for (uint32_t j = 0; j < i; ++j) { - if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2) { - goto loop_top; - } - } - - tox_self_get_address(toxes[pairs[i].tox1], address); - - Tox_Err_Friend_Add test; - uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (const uint8_t *)"Gentoo", 7, &test); - - if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) { - goto loop_top; - } - - ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend error code: %u", test); - } - - while (true) { - uint16_t counter = 0; - - for (uint32_t i = 0; i < NUM_TOXES_TCP; ++i) { - for (uint32_t j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j) { - if (tox_friend_get_connection_status(toxes[i], j, nullptr) == TOX_CONNECTION_TCP) { - ++counter; - } - } - } - - if (counter == NUM_FRIENDS * 2) { - break; - } - - for (uint32_t i = 0; i < NUM_TOXES_TCP; ++i) { - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(toxes[i], true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - State state = {to_comp, toxes[i]}; - tox_dispatch_invoke(dispatch, events, &state); - tox_events_free(events); - } - - c_sleep(50); - } - - tox_dispatch_free(dispatch); - for (uint32_t i = 0; i < NUM_TOXES_TCP; ++i) { - tox_kill(toxes[i]); - } - - printf("test_many_clients_tcp succeeded, took %llu seconds\n", time(nullptr) - cur_time); -} - -#define NUM_TCP_RELAYS 3 - -static void test_many_clients_tcp_b(void) -{ - const Random *rng = os_random(); - ck_assert(rng != nullptr); - long long unsigned int cur_time = time(nullptr); - Tox *toxes[NUM_TOXES_TCP]; - uint32_t index[NUM_TOXES_TCP]; - uint32_t to_comp = 974536; - - for (uint32_t i = 0; i < NUM_TOXES_TCP; ++i) { - struct Tox_Options *opts = tox_options_new(nullptr); - - if (i < NUM_TCP_RELAYS) { - tox_options_set_tcp_port(opts, tcp_relay_port + i); - } else { - tox_options_set_udp_enabled(opts, 0); - } - - index[i] = i + 1; - toxes[i] = tox_new_log(opts, nullptr, &index[i]); - ck_assert_msg(toxes[i] != nullptr, "Failed to create tox instances %u", i); - tox_events_init(toxes[i]); - uint8_t dpk[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(toxes[(i % NUM_TCP_RELAYS)], dpk); - ck_assert_msg(tox_add_tcp_relay(toxes[i], TOX_LOCALHOST, tcp_relay_port + (i % NUM_TCP_RELAYS), dpk, nullptr), - "add relay error"); - tox_self_get_dht_id(toxes[0], dpk); - uint16_t first_port = tox_self_get_udp_port(toxes[0], nullptr); - ck_assert_msg(tox_bootstrap(toxes[i], TOX_LOCALHOST, first_port, dpk, nullptr), "Bootstrap error"); - - tox_options_free(opts); - } - - Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); - ck_assert(dispatch != nullptr); - - tox_events_callback_friend_request(dispatch, accept_friend_request); - - struct { - uint16_t tox1; - uint16_t tox2; - } pairs[NUM_FRIENDS]; - - uint8_t address[TOX_ADDRESS_SIZE]; - - for (uint32_t i = 0; i < NUM_FRIENDS; ++i) { -loop_top: - pairs[i].tox1 = random_u32(rng) % NUM_TOXES_TCP; - pairs[i].tox2 = (pairs[i].tox1 + random_u32(rng) % (NUM_TOXES_TCP - 1) + 1) % NUM_TOXES_TCP; - - for (uint32_t j = 0; j < i; ++j) { - if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2) { - goto loop_top; - } - } - - tox_self_get_address(toxes[pairs[i].tox1], address); - - Tox_Err_Friend_Add test; - uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (const uint8_t *)"Gentoo", 7, &test); - - if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) { - goto loop_top; - } - - ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend error code: %u", test); - } - - uint16_t last_count = 0; - - while (true) { - uint16_t counter = 0; - - for (uint32_t i = 0; i < NUM_TOXES_TCP; ++i) { - for (uint32_t j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j) { - if (tox_friend_get_connection_status(toxes[i], j, nullptr) == TOX_CONNECTION_TCP) { - ++counter; - } - } - } - - if (counter != last_count) { - printf("many_clients_tcp_b got to %u\n", counter); - last_count = counter; - } - - if (counter == NUM_FRIENDS * 2) { - break; - } - - for (uint32_t i = 0; i < NUM_TOXES_TCP; ++i) { - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(toxes[i], true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - State state = {to_comp, toxes[i]}; - tox_dispatch_invoke(dispatch, events, &state); - tox_events_free(events); - } - - c_sleep(30); - } - - tox_dispatch_free(dispatch); - for (uint32_t i = 0; i < NUM_TOXES_TCP; ++i) { - tox_kill(toxes[i]); - } - - printf("test_many_clients_tcp_b succeeded, took %llu seconds\n", time(nullptr) - cur_time); -} - -static void tox_suite(void) -{ - /* Each tox connects to a single tox TCP */ - test_many_clients_tcp(); - - if (enable_broken_tests) { - /* Try to make a connection to each "older sibling" tox instance via TCP */ - /* Currently this test intermittently fails for unknown reasons. */ - test_many_clients_tcp_b(); - } -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - tox_suite(); - return 0; -} diff --git a/auto_tests/tox_many_test.c b/auto_tests/tox_many_test.c deleted file mode 100644 index 63e2ccac..00000000 --- a/auto_tests/tox_many_test.c +++ /dev/null @@ -1,148 +0,0 @@ -/* Auto Tests: Many clients. - */ - -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/crypto_core.h" -#include "../toxcore/os_random.h" -#include "../toxcore/tox.h" -#include "auto_test_support.h" -#include "check_compat.h" - -static void accept_friend_request(const Tox_Event_Friend_Request *event, void *userdata) -{ - Tox *tox = (Tox *)userdata; - - const uint8_t *public_key = tox_event_friend_request_get_public_key(event); - const uint8_t *message = tox_event_friend_request_get_message(event); - const uint32_t message_length = tox_event_friend_request_get_message_length(event); - - if (message_length == 7 && memcmp("Gentoo", message, 7) == 0) { - tox_friend_add_norequest(tox, public_key, nullptr); - } -} - -#define TCP_TEST_NUM_TOXES 90 -#define TCP_TEST_NUM_FRIENDS 50 - -static void test_many_clients(void) -{ - const Random *rng = os_random(); - ck_assert(rng != nullptr); - time_t cur_time = time(nullptr); - Tox *toxes[TCP_TEST_NUM_TOXES]; - uint32_t index[TCP_TEST_NUM_TOXES]; - - for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { - index[i] = i + 1; - toxes[i] = tox_new_log(nullptr, nullptr, &index[i]); - ck_assert_msg(toxes[i] != nullptr, "failed to create tox instances %u", i); - tox_events_init(toxes[i]); - } - - Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); - ck_assert(dispatch != nullptr); - - tox_events_callback_friend_request(dispatch, accept_friend_request); - - struct { - uint16_t tox1; - uint16_t tox2; - } pairs[TCP_TEST_NUM_FRIENDS]; - - uint8_t address[TOX_ADDRESS_SIZE]; - - uint32_t num_f = 0; - - for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { - num_f += tox_self_get_friend_list_size(toxes[i]); - } - - ck_assert_msg(num_f == 0, "bad num friends: %u", num_f); - - for (uint32_t i = 0; i < TCP_TEST_NUM_FRIENDS; ++i) { -loop_top: - pairs[i].tox1 = random_u32(rng) % TCP_TEST_NUM_TOXES; - pairs[i].tox2 = (pairs[i].tox1 + random_u32(rng) % (TCP_TEST_NUM_TOXES - 1) + 1) % TCP_TEST_NUM_TOXES; - - for (uint32_t j = 0; j < i; ++j) { - if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2) { - goto loop_top; - } - } - - tox_self_get_address(toxes[pairs[i].tox1], address); - - Tox_Err_Friend_Add test; - uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (const uint8_t *)"Gentoo", 7, &test); - - if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) { - goto loop_top; - } - - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(toxes[pairs[i].tox1], dht_key); - const uint16_t dht_port = tox_self_get_udp_port(toxes[pairs[i].tox1], nullptr); - - tox_bootstrap(toxes[pairs[i].tox2], "localhost", dht_port, dht_key, nullptr); - - ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "failed to add friend error code: %u", test); - } - - for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { - num_f += tox_self_get_friend_list_size(toxes[i]); - } - - ck_assert_msg(num_f == TCP_TEST_NUM_FRIENDS, "bad num friends: %u", num_f); - - uint16_t last_count = 0; - - while (true) { - uint16_t counter = 0; - - for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { - for (uint32_t j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j) { - if (tox_friend_get_connection_status(toxes[i], j, nullptr) == TOX_CONNECTION_UDP) { - ++counter; - } - } - } - - if (counter != last_count) { - printf("many_clients got to %u\n", counter); - last_count = counter; - } - - if (counter == TCP_TEST_NUM_FRIENDS * 2) { - break; - } - - for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { - Tox_Err_Events_Iterate err = TOX_ERR_EVENTS_ITERATE_OK; - Tox_Events *events = tox_events_iterate(toxes[i], true, &err); - ck_assert(err == TOX_ERR_EVENTS_ITERATE_OK); - tox_dispatch_invoke(dispatch, events, toxes[i]); - tox_events_free(events); - } - - c_sleep(50); - } - - tox_dispatch_free(dispatch); - for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { - tox_kill(toxes[i]); - } - - printf("test_many_clients succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - test_many_clients(); - return 0; -} diff --git a/auto_tests/toxav_basic_test.c b/auto_tests/toxav_basic_test.c deleted file mode 100644 index f81c6538..00000000 --- a/auto_tests/toxav_basic_test.c +++ /dev/null @@ -1,566 +0,0 @@ -#include -#include -#include -#include - -#if !defined(_WIN32) && !defined(__WIN32__) && !defined(WIN32) -#include -#endif - -#include - -#include "../testing/misc_tools.h" -#include "../toxav/toxav.h" -#include "../toxcore/crypto_core.h" -#include "../toxcore/logger.h" -#include "../toxcore/tox.h" -#include "../toxcore/tox_struct.h" -#include "../toxcore/util.h" -#include "auto_test_support.h" -#include "check_compat.h" - -#define TEST_REGULAR_AV 1 -#define TEST_REGULAR_A 1 -#define TEST_REGULAR_V 1 -#define TEST_REJECT 1 -#define TEST_CANCEL 1 -#define TEST_MUTE_UNMUTE 1 -#define TEST_STOP_RESUME_PAYLOAD 1 -#define TEST_PAUSE_RESUME_SEND 1 - -#define ck_assert_call_control(a, b, c) do { \ - Toxav_Err_Call_Control cc_err; \ - bool ok = toxav_call_control(a, b, c, &cc_err); \ - if (!ok) { \ - printf("toxav_call_control returned error %u\n", cc_err); \ - } \ - ck_assert(ok); \ - ck_assert(cc_err == TOXAV_ERR_CALL_CONTROL_OK); \ -} while (0) - -typedef struct { - bool incoming; - uint32_t state; -} CallControl; - -typedef struct Time_Data { - pthread_mutex_t lock; - uint64_t clock; -} Time_Data; - -static uint64_t get_state_clock_callback_basic(void *user_data) -{ - Time_Data *time_data = (Time_Data *)user_data; - pthread_mutex_lock(&time_data->lock); - uint64_t clock = time_data->clock; - pthread_mutex_unlock(&time_data->lock); - return clock; -} - -static void increment_clock(Time_Data *time_data, uint64_t count) -{ - pthread_mutex_lock(&time_data->lock); - time_data->clock += count; - pthread_mutex_unlock(&time_data->lock); -} - -static void set_current_time_callback(Tox *tox, Time_Data *time_data) -{ - Mono_Time *mono_time = tox->mono_time; - mono_time_set_current_time_callback(mono_time, get_state_clock_callback_basic, time_data); -} - -static void clear_call_control(CallControl *cc) -{ - const CallControl empty = {0}; - *cc = empty; -} - -/** - * Callbacks - */ -static void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) -{ - printf("Handling CALL callback\n"); - ((CallControl *)user_data)->incoming = true; -} - -static void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) -{ - printf("Handling CALL STATE callback: %u\n", state); - ((CallControl *)user_data)->state = state; -} - -static void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number, - uint16_t width, uint16_t height, - uint8_t const *y, uint8_t const *u, uint8_t const *v, - int32_t ystride, int32_t ustride, int32_t vstride, - void *user_data) -{ - printf("Received video payload\n"); -} - -static void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, - int16_t const *pcm, - size_t sample_count, - uint8_t channels, - uint32_t sampling_rate, - void *user_data) -{ - printf("Received audio payload\n"); -} - -static void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, - void *userdata) -{ - if (length == 7 && memcmp("gentoo", data, 7) == 0) { - ck_assert(tox_friend_add_norequest(m, public_key, nullptr) != (uint32_t) -1); - } -} - -/** - * Iterate helper - */ -static void iterate_tox(Tox *bootstrap, Tox *alice, Tox *bob, Time_Data *time_data) -{ - tox_iterate(bootstrap, nullptr); - tox_iterate(alice, nullptr); - tox_iterate(bob, nullptr); - - if (time_data) { - increment_clock(time_data, 50); - } - - c_sleep(5); -} - -static bool toxav_audio_send_frame_helper(ToxAV *av, uint32_t friend_number, Toxav_Err_Send_Frame *error) -{ - static const int16_t pcm[960] = {0}; - return toxav_audio_send_frame(av, 0, pcm, 960, 1, 48000, nullptr); -} - -static void regular_call_flow( - Tox *alice, Tox *bob, Tox *bootstrap, - ToxAV *alice_av, ToxAV *bob_av, - CallControl *alice_cc, CallControl *bob_cc, - int a_br, int v_br, Time_Data *time_data) -{ - clear_call_control(alice_cc); - clear_call_control(bob_cc); - - Toxav_Err_Call call_err; - toxav_call(alice_av, 0, a_br, v_br, &call_err); - - ck_assert_msg(call_err == TOXAV_ERR_CALL_OK, "toxav_call failed: %u\n", call_err); - - const uint64_t start_time = get_state_clock_callback_basic(time_data); - - do { - if (bob_cc->incoming) { - Toxav_Err_Answer answer_err; - toxav_answer(bob_av, 0, a_br, v_br, &answer_err); - - ck_assert_msg(answer_err == TOXAV_ERR_ANSWER_OK, "toxav_answer failed: %u\n", answer_err); - - bob_cc->incoming = false; - } else { /* TODO(mannol): rtp */ - if (get_state_clock_callback_basic(time_data) - start_time >= 1000) { - - Toxav_Err_Call_Control cc_err; - toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_CANCEL, &cc_err); - - ck_assert_msg(cc_err == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %u\n", cc_err); - } - } - - iterate_tox(bootstrap, alice, bob, time_data); - } while (bob_cc->state != TOXAV_FRIEND_CALL_STATE_FINISHED); - - printf("Success!\n"); -} - -static void test_av_flows(void) -{ - Tox *alice, *bob, *bootstrap; - ToxAV *alice_av, *bob_av; - uint32_t index[] = { 1, 2, 3 }; - - CallControl alice_cc, bob_cc; - - Time_Data time_data; - pthread_mutex_init(&time_data.lock, nullptr); - - { - Tox_Options *opts = tox_options_new(nullptr); - ck_assert(opts != nullptr); - tox_options_set_experimental_thread_safety(opts, true); - Tox_Err_New error; - - bootstrap = tox_new_log(opts, &error, &index[0]); - ck_assert(error == TOX_ERR_NEW_OK); - time_data.clock = current_time_monotonic(bootstrap->mono_time); - set_current_time_callback(bootstrap, &time_data); - - alice = tox_new_log(opts, &error, &index[1]); - ck_assert(error == TOX_ERR_NEW_OK); - set_current_time_callback(alice, &time_data); - - bob = tox_new_log(opts, &error, &index[2]); - ck_assert(error == TOX_ERR_NEW_OK); - set_current_time_callback(bob, &time_data); - - tox_options_free(opts); - } - - printf("Created 3 instances of Tox\n"); - printf("Preparing network...\n"); - uint64_t cur_time = get_state_clock_callback_basic(&time_data); - - uint8_t address[TOX_ADDRESS_SIZE]; - - tox_callback_friend_request(alice, t_accept_friend_request_cb); - tox_self_get_address(alice, address); - - printf("bootstrapping Alice and Bob off a third bootstrap node\n"); - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(bootstrap, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(bootstrap, nullptr); - - tox_bootstrap(alice, "localhost", dht_port, dht_key, nullptr); - tox_bootstrap(bob, "localhost", dht_port, dht_key, nullptr); - - ck_assert(tox_friend_add(bob, address, (const uint8_t *)"gentoo", 7, nullptr) != (uint32_t) -1); - - uint8_t off = 1; - - while (true) { - iterate_tox(bootstrap, alice, bob, &time_data); - - if (tox_self_get_connection_status(bootstrap) && - tox_self_get_connection_status(alice) && - tox_self_get_connection_status(bob) && off) { - printf("Toxes are online, took %llu seconds\n", (unsigned long long)(get_state_clock_callback_basic(&time_data) - cur_time) / 1000); - off = 0; - } - - if (tox_friend_get_connection_status(alice, 0, nullptr) == TOX_CONNECTION_UDP && - tox_friend_get_connection_status(bob, 0, nullptr) == TOX_CONNECTION_UDP) { - break; - } - - increment_clock(&time_data, 100); - } - - { - Toxav_Err_New error; - alice_av = toxav_new(alice, &error); - ck_assert(error == TOXAV_ERR_NEW_OK); - - bob_av = toxav_new(bob, &error); - ck_assert(error == TOXAV_ERR_NEW_OK); - } - - toxav_callback_call(alice_av, t_toxav_call_cb, &alice_cc); - toxav_callback_call_state(alice_av, t_toxav_call_state_cb, &alice_cc); - toxav_callback_video_receive_frame(alice_av, t_toxav_receive_video_frame_cb, &alice_cc); - toxav_callback_audio_receive_frame(alice_av, t_toxav_receive_audio_frame_cb, &alice_cc); - - toxav_callback_call(bob_av, t_toxav_call_cb, &bob_cc); - toxav_callback_call_state(bob_av, t_toxav_call_state_cb, &bob_cc); - toxav_callback_video_receive_frame(bob_av, t_toxav_receive_video_frame_cb, &bob_cc); - toxav_callback_audio_receive_frame(bob_av, t_toxav_receive_audio_frame_cb, &bob_cc); - - printf("Created 2 instances of ToxAV\n"); - printf("All set after %llu seconds!\n", (unsigned long long)(get_state_clock_callback_basic(&time_data) - cur_time) / 1000); - - if (TEST_REGULAR_AV) { - printf("\nTrying regular call (Audio and Video)...\n"); - regular_call_flow(alice, bob, bootstrap, alice_av, bob_av, &alice_cc, &bob_cc, - 48, 4000, &time_data); - } - - if (TEST_REGULAR_A) { - printf("\nTrying regular call (Audio only)...\n"); - regular_call_flow(alice, bob, bootstrap, alice_av, bob_av, &alice_cc, &bob_cc, - 48, 0, &time_data); - } - - if (TEST_REGULAR_V) { - printf("\nTrying regular call (Video only)...\n"); - regular_call_flow(alice, bob, bootstrap, alice_av, bob_av, &alice_cc, &bob_cc, - 0, 4000, &time_data); - } - - if (TEST_REJECT) { /* Alice calls; Bob rejects */ - printf("\nTrying reject flow...\n"); - - clear_call_control(&alice_cc); - clear_call_control(&bob_cc); - - { - Toxav_Err_Call rc; - toxav_call(alice_av, 0, 48, 0, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %u\n", rc); - } - - do { - iterate_tox(bootstrap, alice, bob, &time_data); - } while (!bob_cc.incoming); - - /* Reject */ - { - Toxav_Err_Call_Control rc; - toxav_call_control(bob_av, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %u\n", rc); - } - - do { - iterate_tox(bootstrap, alice, bob, &time_data); - } while (alice_cc.state != TOXAV_FRIEND_CALL_STATE_FINISHED); - - printf("Success!\n"); - } - - if (TEST_CANCEL) { /* Alice calls; Alice cancels while ringing */ - printf("\nTrying cancel (while ringing) flow...\n"); - - clear_call_control(&alice_cc); - clear_call_control(&bob_cc); - - { - Toxav_Err_Call rc; - toxav_call(alice_av, 0, 48, 0, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %u\n", rc); - } - - do { - iterate_tox(bootstrap, alice, bob, &time_data); - } while (!bob_cc.incoming); - - /* Cancel */ - { - Toxav_Err_Call_Control rc; - toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %u\n", rc); - } - - /* Alice will not receive end state */ - do { - iterate_tox(bootstrap, alice, bob, &time_data); - } while (bob_cc.state != TOXAV_FRIEND_CALL_STATE_FINISHED); - - printf("Success!\n"); - } - - if (TEST_MUTE_UNMUTE) { /* Check Mute-Unmute etc */ - printf("\nTrying mute functionality...\n"); - - clear_call_control(&alice_cc); - clear_call_control(&bob_cc); - - /* Assume sending audio and video */ - { - Toxav_Err_Call rc; - toxav_call(alice_av, 0, 48, 1000, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %u\n", rc); - } - - do { - iterate_tox(bootstrap, alice, bob, &time_data); - } while (!bob_cc.incoming); - - /* At first try all stuff while in invalid state */ - ck_assert(!toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_PAUSE, nullptr)); - ck_assert(!toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_RESUME, nullptr)); - ck_assert(!toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, nullptr)); - ck_assert(!toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, nullptr)); - ck_assert(!toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO, nullptr)); - ck_assert(!toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO, nullptr)); - - { - Toxav_Err_Answer rc; - toxav_answer(bob_av, 0, 48, 4000, &rc); - - ck_assert_msg(rc == TOXAV_ERR_ANSWER_OK, "toxav_answer failed: %u\n", rc); - } - - iterate_tox(bootstrap, alice, bob, &time_data); - - /* Pause and Resume */ - printf("Pause and Resume\n"); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_PAUSE); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state == 0); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_RESUME); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state & (TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_SENDING_V)); - - /* Mute/Unmute single */ - printf("Mute/Unmute single\n"); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); - - /* Mute/Unmute both */ - printf("Mute/Unmute both\n"); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_V); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V); - - { - Toxav_Err_Call_Control rc; - toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %u\n", rc); - } - - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state == TOXAV_FRIEND_CALL_STATE_FINISHED); - - printf("Success!\n"); - } - - if (TEST_STOP_RESUME_PAYLOAD) { /* Stop and resume audio/video payload */ - printf("\nTrying stop/resume functionality...\n"); - - clear_call_control(&alice_cc); - clear_call_control(&bob_cc); - - /* Assume sending audio and video */ - { - Toxav_Err_Call rc; - toxav_call(alice_av, 0, 48, 0, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %u\n", rc); - } - - do { - iterate_tox(bootstrap, alice, bob, &time_data); - } while (!bob_cc.incoming); - - { - Toxav_Err_Answer rc; - toxav_answer(bob_av, 0, 48, 0, &rc); - - ck_assert_msg(rc == TOXAV_ERR_ANSWER_OK, "toxav_answer failed: %u\n", rc); - } - - iterate_tox(bootstrap, alice, bob, &time_data); - - printf("Call started as audio only\n"); - printf("Turning on video for Alice...\n"); - ck_assert(toxav_video_set_bit_rate(alice_av, 0, 1000, nullptr)); - - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state & TOXAV_FRIEND_CALL_STATE_SENDING_V); - - printf("Turning off video for Alice...\n"); - ck_assert(toxav_video_set_bit_rate(alice_av, 0, 0, nullptr)); - - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(!(bob_cc.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)); - - printf("Turning off audio for Alice...\n"); - ck_assert(toxav_audio_set_bit_rate(alice_av, 0, 0, nullptr)); - - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(!(bob_cc.state & TOXAV_FRIEND_CALL_STATE_SENDING_A)); - - { - Toxav_Err_Call_Control rc; - toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %u\n", rc); - } - - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state == TOXAV_FRIEND_CALL_STATE_FINISHED); - - printf("Success!\n"); - } - - if (TEST_PAUSE_RESUME_SEND) { /* Stop and resume audio/video payload and test send options */ - printf("\nTrying stop/resume functionality...\n"); - - clear_call_control(&alice_cc); - clear_call_control(&bob_cc); - - /* Assume sending audio and video */ - { - Toxav_Err_Call rc; - toxav_call(alice_av, 0, 48, 0, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %u\n", rc); - } - - do { - iterate_tox(bootstrap, alice, bob, &time_data); - } while (!bob_cc.incoming); - - { - Toxav_Err_Answer rc; - toxav_answer(bob_av, 0, 48, 0, &rc); - - ck_assert_msg(rc == TOXAV_ERR_ANSWER_OK, "toxav_answer failed: %u\n", rc); - } - - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_PAUSE); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(!toxav_audio_send_frame_helper(alice_av, 0, nullptr)); - ck_assert(!toxav_audio_send_frame_helper(bob_av, 0, nullptr)); - ck_assert_call_control(alice_av, 0, TOXAV_CALL_CONTROL_RESUME); - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(toxav_audio_send_frame_helper(alice_av, 0, nullptr)); - ck_assert(toxav_audio_send_frame_helper(bob_av, 0, nullptr)); - iterate_tox(bootstrap, alice, bob, &time_data); - - { - Toxav_Err_Call_Control rc; - toxav_call_control(alice_av, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); - - ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %u\n", rc); - } - - iterate_tox(bootstrap, alice, bob, &time_data); - ck_assert(bob_cc.state == TOXAV_FRIEND_CALL_STATE_FINISHED); - - printf("Success!\n"); - } - - toxav_kill(bob_av); - toxav_kill(alice_av); - tox_kill(bob); - tox_kill(alice); - tox_kill(bootstrap); - - pthread_mutex_destroy(&time_data.lock); - - printf("\nTest successful!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - test_av_flows(); - return 0; -} diff --git a/auto_tests/toxav_many_test.c b/auto_tests/toxav_many_test.c deleted file mode 100644 index 3de09fd9..00000000 --- a/auto_tests/toxav_many_test.c +++ /dev/null @@ -1,383 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#if !defined(_WIN32) && !defined(__WIN32__) && !defined(WIN32) -#include -#endif - -#include - -#include "../testing/misc_tools.h" -#include "../toxav/toxav.h" -#include "../toxcore/crypto_core.h" -#include "../toxcore/logger.h" -#include "../toxcore/tox.h" -#include "../toxcore/tox_struct.h" -#include "../toxcore/util.h" -#include "auto_test_support.h" -#include "check_compat.h" - -typedef struct CallControl { - bool incoming; - uint32_t state; -} CallControl; - -typedef struct Thread_Data { - ToxAV *alice_av; - ToxAV *bob_av; - CallControl *alice_cc; - CallControl *bob_cc; - uint32_t friend_number; -} Thread_Data; - -/** - * Callbacks - */ -static void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) -{ - printf("Handling CALL callback\n"); - ((CallControl *)user_data)[friend_number].incoming = true; -} - -static void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) -{ - printf("Handling CALL STATE callback: %u %p\n", state, (void *)av); - ((CallControl *)user_data)[friend_number].state = state; -} - -static void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number, - uint16_t width, uint16_t height, - uint8_t const *y, uint8_t const *u, uint8_t const *v, - int32_t ystride, int32_t ustride, int32_t vstride, - void *user_data) -{ -} - -static void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, - int16_t const *pcm, - size_t sample_count, - uint8_t channels, - uint32_t sampling_rate, - void *user_data) -{ -} - -static void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, - void *userdata) -{ - if (length == 7 && memcmp("gentoo", data, 7) == 0) { - ck_assert(tox_friend_add_norequest(m, public_key, nullptr) != (uint32_t) -1); - } -} - -/** - * Iterate helper - */ -static ToxAV *setup_av_instance(Tox *tox, CallControl *cc) -{ - Toxav_Err_New error; - - ToxAV *av = toxav_new(tox, &error); - ck_assert(error == TOXAV_ERR_NEW_OK); - - toxav_callback_call(av, t_toxav_call_cb, cc); - toxav_callback_call_state(av, t_toxav_call_state_cb, cc); - toxav_callback_video_receive_frame(av, t_toxav_receive_video_frame_cb, cc); - toxav_callback_audio_receive_frame(av, t_toxav_receive_audio_frame_cb, cc); - - return av; -} - -static void *call_thread(void *pd) -{ - ToxAV *alice_av = ((Thread_Data *) pd)->alice_av; - ToxAV *bob_av = ((Thread_Data *) pd)->bob_av; - uint32_t friend_number = ((Thread_Data *) pd)->friend_number; - - int16_t *pcm = (int16_t *)calloc(960, sizeof(int16_t)); - uint8_t *video_y = (uint8_t *)calloc(800 * 600, sizeof(uint8_t)); - uint8_t *video_u = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t)); - uint8_t *video_v = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t)); - - time_t start_time = time(nullptr); - - do { - toxav_iterate(alice_av); - toxav_iterate(bob_av); - - toxav_audio_send_frame(alice_av, friend_number, pcm, 960, 1, 48000, nullptr); - toxav_audio_send_frame(bob_av, 0, pcm, 960, 1, 48000, nullptr); - - toxav_video_send_frame(alice_av, friend_number, 800, 600, video_y, video_u, video_v, nullptr); - toxav_video_send_frame(bob_av, 0, 800, 600, video_y, video_u, video_v, nullptr); - - c_sleep(10); - } while (time(nullptr) - start_time < 4); - - free(pcm); - free(video_y); - free(video_u); - free(video_v); - - printf("Closing thread\n"); - pthread_exit(nullptr); - - return nullptr; -} - -typedef struct Time_Data { - pthread_mutex_t lock; - uint64_t clock; -} Time_Data; - -static uint64_t get_state_clock_callback(void *user_data) -{ - Time_Data *time_data = (Time_Data *)user_data; - pthread_mutex_lock(&time_data->lock); - uint64_t clock = time_data->clock; - pthread_mutex_unlock(&time_data->lock); - return clock; -} - -static void increment_clock(Time_Data *time_data, uint64_t count) -{ - pthread_mutex_lock(&time_data->lock); - time_data->clock += count; - pthread_mutex_unlock(&time_data->lock); -} - -static void set_current_time_callback(Tox *tox, Time_Data *time_data) -{ - Mono_Time *mono_time = tox->mono_time; - mono_time_set_current_time_callback(mono_time, get_state_clock_callback, time_data); -} - -static void test_av_three_calls(void) -{ - uint32_t index[] = { 1, 2, 3, 4, 5 }; - Tox *alice, *bootstrap, *bobs[3]; - ToxAV *alice_av, *bobs_av[3]; - void *retval; - - CallControl alice_cc[3], bobs_cc[3]; - - Time_Data time_data; - pthread_mutex_init(&time_data.lock, nullptr); - { - Tox_Options *opts = tox_options_new(nullptr); - ck_assert(opts != nullptr); - tox_options_set_experimental_thread_safety(opts, true); - Tox_Err_New error; - - bootstrap = tox_new_log(opts, &error, &index[0]); - ck_assert(error == TOX_ERR_NEW_OK); - - time_data.clock = current_time_monotonic(bootstrap->mono_time); - set_current_time_callback(bootstrap, &time_data); - - alice = tox_new_log(opts, &error, &index[1]); - ck_assert(error == TOX_ERR_NEW_OK); - set_current_time_callback(alice, &time_data); - - bobs[0] = tox_new_log(opts, &error, &index[2]); - ck_assert(error == TOX_ERR_NEW_OK); - set_current_time_callback(bobs[0], &time_data); - - bobs[1] = tox_new_log(opts, &error, &index[3]); - ck_assert(error == TOX_ERR_NEW_OK); - set_current_time_callback(bobs[1], &time_data); - - bobs[2] = tox_new_log(opts, &error, &index[4]); - ck_assert(error == TOX_ERR_NEW_OK); - set_current_time_callback(bobs[2], &time_data); - tox_options_free(opts); - } - - printf("Created 5 instances of Tox\n"); - printf("Preparing network...\n"); - time_t cur_time = time(nullptr); - - uint8_t address[TOX_ADDRESS_SIZE]; - - tox_callback_friend_request(alice, t_accept_friend_request_cb); - tox_self_get_address(alice, address); - - printf("bootstrapping Alice and the %u Bobs off a third bootstrap node\n", - (unsigned)(sizeof(bobs) / sizeof(bobs[0]))); - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - tox_self_get_dht_id(bootstrap, dht_key); - const uint16_t dht_port = tox_self_get_udp_port(bootstrap, nullptr); - - tox_bootstrap(alice, "localhost", dht_port, dht_key, nullptr); - tox_bootstrap(bobs[0], "localhost", dht_port, dht_key, nullptr); - tox_bootstrap(bobs[1], "localhost", dht_port, dht_key, nullptr); - tox_bootstrap(bobs[2], "localhost", dht_port, dht_key, nullptr); - - ck_assert(tox_friend_add(bobs[0], address, (const uint8_t *)"gentoo", 7, nullptr) != (uint32_t) -1); - ck_assert(tox_friend_add(bobs[1], address, (const uint8_t *)"gentoo", 7, nullptr) != (uint32_t) -1); - ck_assert(tox_friend_add(bobs[2], address, (const uint8_t *)"gentoo", 7, nullptr) != (uint32_t) -1); - - uint8_t off = 1; - - while (true) { - tox_iterate(bootstrap, nullptr); - tox_iterate(alice, nullptr); - tox_iterate(bobs[0], nullptr); - tox_iterate(bobs[1], nullptr); - tox_iterate(bobs[2], nullptr); - - if (tox_self_get_connection_status(bootstrap) && - tox_self_get_connection_status(alice) && - tox_self_get_connection_status(bobs[0]) && - tox_self_get_connection_status(bobs[1]) && - tox_self_get_connection_status(bobs[2]) && off) { - printf("Toxes are online, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); - off = 0; - } - - if (tox_friend_get_connection_status(alice, 0, nullptr) == TOX_CONNECTION_UDP && - tox_friend_get_connection_status(alice, 1, nullptr) == TOX_CONNECTION_UDP && - tox_friend_get_connection_status(alice, 2, nullptr) == TOX_CONNECTION_UDP && - tox_friend_get_connection_status(bobs[0], 0, nullptr) == TOX_CONNECTION_UDP && - tox_friend_get_connection_status(bobs[1], 0, nullptr) == TOX_CONNECTION_UDP && - tox_friend_get_connection_status(bobs[2], 0, nullptr) == TOX_CONNECTION_UDP) { - break; - } - - increment_clock(&time_data, 200); - c_sleep(5); - } - - alice_av = setup_av_instance(alice, alice_cc); - bobs_av[0] = setup_av_instance(bobs[0], &bobs_cc[0]); - bobs_av[1] = setup_av_instance(bobs[1], &bobs_cc[1]); - bobs_av[2] = setup_av_instance(bobs[2], &bobs_cc[2]); - - printf("Created 4 instances of ToxAV\n"); - printf("All set after %lu seconds!\n", (unsigned long)(time(nullptr) - cur_time)); - - Thread_Data tds[3]; - - for (size_t i = 0; i < 3; i++) { - tds[i].alice_av = alice_av; - tds[i].bob_av = bobs_av[i]; - tds[i].alice_cc = &alice_cc[i]; - tds[i].bob_cc = &bobs_cc[i]; - tds[i].friend_number = i; - memset(tds[i].alice_cc, 0, sizeof(CallControl)); - memset(tds[i].bob_cc, 0, sizeof(CallControl)); - } - - pthread_t tids[3]; - - for (size_t i = 0; i < 3; i++) { - (void) pthread_create(&tids[i], nullptr, call_thread, &tds[i]); - } - - time_t start_time = time(nullptr); - - do { - tox_iterate(bootstrap, nullptr); - tox_iterate(alice, nullptr); - tox_iterate(bobs[0], nullptr); - tox_iterate(bobs[1], nullptr); - tox_iterate(bobs[2], nullptr); - - increment_clock(&time_data, 100); - c_sleep(5); - } while (time(nullptr) - start_time < 1); - - /* Call */ - for (size_t i = 0; i < 3; i++) { - Toxav_Err_Call rc; - toxav_call(alice_av, tds[i].friend_number, 48, 3000, &rc); - - if (rc != TOXAV_ERR_CALL_OK) { - printf("toxav_call failed: %u\n", rc); - ck_assert(0); - } - } - - do { - tox_iterate(bootstrap, nullptr); - tox_iterate(alice, nullptr); - tox_iterate(bobs[0], nullptr); - tox_iterate(bobs[1], nullptr); - tox_iterate(bobs[2], nullptr); - - for (size_t i = 0; i < 3; i++) { - if (bobs_cc[i].incoming) { - /* Answer */ - Toxav_Err_Answer rc; - toxav_answer(bobs_av[i], 0, 8, 500, &rc); - - if (rc != TOXAV_ERR_ANSWER_OK) { - printf("toxav_answer failed: %u\n", rc); - ck_assert(0); - } - - bobs_cc[i].incoming = false; - } - } - - increment_clock(&time_data, 100); - c_sleep(5); - } while (time(nullptr) - start_time < 3); - - /* Hangup */ - for (size_t i = 0; i < 3; i++) { - Toxav_Err_Call_Control rc; - toxav_call_control(alice_av, i, TOXAV_CALL_CONTROL_CANCEL, &rc); - - if (rc != TOXAV_ERR_CALL_CONTROL_OK) { - printf("toxav_call_control failed: %u %p %p\n", rc, (void *)alice_av, (void *)&bobs_av[i]); - } - } - - do { - tox_iterate(bootstrap, nullptr); - tox_iterate(alice, nullptr); - tox_iterate(bobs[0], nullptr); - tox_iterate(bobs[1], nullptr); - tox_iterate(bobs[2], nullptr); - - increment_clock(&time_data, 100); - c_sleep(5); - } while (time(nullptr) - start_time < 5); - - ck_assert(pthread_join(tids[0], &retval) == 0); - ck_assert(retval == nullptr); - - ck_assert(pthread_join(tids[1], &retval) == 0); - ck_assert(retval == nullptr); - - ck_assert(pthread_join(tids[2], &retval) == 0); - ck_assert(retval == nullptr); - - printf("Killing all instances\n"); - toxav_kill(bobs_av[2]); - toxav_kill(bobs_av[1]); - toxav_kill(bobs_av[0]); - toxav_kill(alice_av); - tox_kill(bobs[2]); - tox_kill(bobs[1]); - tox_kill(bobs[0]); - tox_kill(alice); - tox_kill(bootstrap); - - pthread_mutex_destroy(&time_data.lock); - - printf("\nTest successful!\n"); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - test_av_three_calls(); - return 0; -} diff --git a/auto_tests/typing_test.c b/auto_tests/typing_test.c deleted file mode 100644 index 4a03023f..00000000 --- a/auto_tests/typing_test.c +++ /dev/null @@ -1,67 +0,0 @@ -/* Tests that our typing notifications work. - */ - -#include -#include -#include - -#include "../testing/misc_tools.h" -#include "../toxcore/ccompat.h" -#include "../toxcore/tox.h" -#include "../toxcore/util.h" -#include "check_compat.h" - -typedef struct State { - bool friend_is_typing; -} State; - -#include "auto_test_support.h" - -static void typing_callback(const Tox_Event_Friend_Typing *event, void *user_data) -{ - const AutoTox *autotox = (AutoTox *)user_data; - State *state = (State *)autotox->state; - - //const uint32_t friend_number = tox_event_friend_typing_get_friend_number(event); - const bool typing = tox_event_friend_typing_get_typing(event); - - state->friend_is_typing = typing; -} - -static void test_typing(AutoTox *autotoxes) -{ - time_t cur_time = time(nullptr); - - tox_events_callback_friend_typing(autotoxes[1].dispatch, &typing_callback); - tox_self_set_typing(autotoxes[0].tox, 0, true, nullptr); - - do { - iterate_all_wait(autotoxes, 2, 200); - } while (!((State *)autotoxes[1].state)->friend_is_typing); - - ck_assert_msg(tox_friend_get_typing(autotoxes[1].tox, 0, nullptr) == 1, - "tox_friend_get_typing should have returned true, but it didn't"); - tox_self_set_typing(autotoxes[0].tox, 0, false, nullptr); - - do { - iterate_all_wait(autotoxes, 2, 200); - } while (((State *)autotoxes[1].state)->friend_is_typing); - - Tox_Err_Friend_Query err_t; - ck_assert_msg(tox_friend_get_typing(autotoxes[1].tox, 0, &err_t) == 0, - "tox_friend_get_typing should have returned false, but it didn't"); - ck_assert_msg(err_t == TOX_ERR_FRIEND_QUERY_OK, "tox_friend_get_typing call did not return correct error"); - - printf("test_typing succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); -} - -int main(void) -{ - setvbuf(stdout, nullptr, _IONBF, 0); - - Run_Auto_Options options = default_run_auto_options(); - options.graph = GRAPH_LINEAR; - run_auto_test(nullptr, 2, test_typing, sizeof(State), &options); - - return 0; -} diff --git a/other/analysis/gen-file.sh b/other/analysis/gen-file.sh index 6af978c8..36e81193 100644 --- a/other/analysis/gen-file.sh +++ b/other/analysis/gen-file.sh @@ -4,11 +4,16 @@ CPPFLAGS="-DMIN_LOGGER_LEVEL=LOGGER_LEVEL_TRACE" CPPFLAGS+=("-DCMP_NO_FLOAT=1") CPPFLAGS+=("-isystem" "/usr/include/opus") CPPFLAGS+=("-Iauto_tests") +CPPFLAGS+=("-Iauto_tests/scenarios") +CPPFLAGS+=("-Iauto_tests/scenarios/framework") CPPFLAGS+=("-Iother") CPPFLAGS+=("-Iother/bootstrap_daemon/src") CPPFLAGS+=("-Iother/fun") CPPFLAGS+=("-Itesting") CPPFLAGS+=("-Itesting/fuzzing") +CPPFLAGS+=("-Itesting/support") +CPPFLAGS+=("-Itesting/support/doubles") +CPPFLAGS+=("-Itesting/support/public") CPPFLAGS+=("-Itoxcore") CPPFLAGS+=("-Itoxcore/events") CPPFLAGS+=("-Itoxav") @@ -49,29 +54,44 @@ callmain() { # Include all C and C++ code FIND_QUERY="find . '-(' -name '*.c' -or -name '*.cc' '-)'" -# Excludes -FIND_QUERY="$FIND_QUERY -and -not -wholename './_build/*'" -FIND_QUERY="$FIND_QUERY -and -not -wholename './other/docker/*'" -FIND_QUERY="$FIND_QUERY -and -not -wholename './super_donators/*'" -FIND_QUERY="$FIND_QUERY -and -not -name amalgamation.cc" -FIND_QUERY="$FIND_QUERY -and -not -name av_test.c" -FIND_QUERY="$FIND_QUERY -and -not -name cracker.c" -FIND_QUERY="$FIND_QUERY -and -not -name version_test.c" -FIND_QUERY="$FIND_QUERY -and -not -name '*_fuzz_test.cc'" -FIND_QUERY="$FIND_QUERY -and -not -wholename './testing/fuzzing/*'" -FIND_QUERY="$FIND_QUERY -and -not -wholename './third_party/cmp/examples/*'" -FIND_QUERY="$FIND_QUERY -and -not -wholename './third_party/cmp/test/*'" +HEADER_QUERY="find . '-(' -name '*.h' -or -name '*.hh' '-)'" -if [ "$SKIP_GTEST" == 1 ]; then - FIND_QUERY="$FIND_QUERY -and -not -name '*_test.cc'" +COMMON_EXCLUDES="" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -wholename './_build/*'" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -wholename './other/docker/*'" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -wholename './super_donators/*'" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -wholename './testing/fuzzing/*'" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -wholename './third_party/cmp/examples/*'" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -wholename './third_party/cmp/test/*'" + +# File name excludes +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -name amalgamation.cc" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -name av_test.c" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -name cracker.c" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -name version_test.c" +COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -name '*_fuzz_test.cc'" + +if [ "$SKIP_BENCHMARK" == 1 ]; then + COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -name '*_bench.cc'" fi -readarray -t FILES <<<"$(eval "$FIND_QUERY")" +if [ "$SKIP_GTEST" == 1 ]; then + COMMON_EXCLUDES="$COMMON_EXCLUDES -and -not -name '*_test.cc'" +fi -(for i in "${FILES[@]}"; do +FIND_QUERY="$FIND_QUERY $COMMON_EXCLUDES" +HEADER_QUERY="$HEADER_QUERY $COMMON_EXCLUDES" + +readarray -t FILES <<<"$(eval "$FIND_QUERY")" +readarray -t HEADERS <<<"$(eval "$HEADER_QUERY")" + +INCLUDES=$(for i in "${FILES[@]}" "${HEADERS[@]}"; do grep -o '#include <[^>]*>' "$i" | grep -E -v '||>amalgamation.cc +done | sort -u) + +echo "$INCLUDES" | grep "" >>amalgamation.cc +echo "$INCLUDES" | grep -v "" >>amalgamation.cc put auto_tests/check_compat.h diff --git a/other/analysis/run-clang b/other/analysis/run-clang index 157ddbee..bafcc6f2 100755 --- a/other/analysis/run-clang +++ b/other/analysis/run-clang @@ -36,7 +36,13 @@ run() { -Wno-unreachable-code-return \ -Wno-unsafe-buffer-usage \ -Wno-unused-parameter \ - -Wno-used-but-marked-unused + -Wno-used-but-marked-unused \ + -Wno-unneeded-member-function \ + -Wno-unused-function \ + -Wno-unused-member-function \ + -Wno-unused-parameter \ + -Wno-unused-template + # TODO(iphydf): Remove these last 5 when the test framework matures. } . other/analysis/variants.sh diff --git a/other/analysis/run-clang-analyze b/other/analysis/run-clang-analyze index 55cd0d91..6ef7c092 100755 --- a/other/analysis/run-clang-analyze +++ b/other/analysis/run-clang-analyze @@ -9,7 +9,30 @@ run() { clang++ --analyze amalgamation.cc \ "${CPPFLAGS[@]}" \ "$@" \ - -std=c++20 + -std=c++20 & + local pid=$! + local start_time="$(date +%s)" + local last_update="$start_time" + local timeout=$((30 * 60)) + local interval=$((5 * 60)) + + while kill -0 "$pid" 2>/dev/null; do + local current_time="$(date +%s)" + if ((current_time - start_time > timeout)); then + echo "Timeout reached. Killing process." + kill -9 "$pid" + wait "$pid" + return 1 + fi + + if ((current_time - last_update > interval)); then + echo "Still running..." + last_update=$current_time + fi + sleep 1 + done + + wait "$pid" } . other/analysis/variants.sh diff --git a/other/analysis/run-clang-tidy b/other/analysis/run-clang-tidy index b48419f7..83c0222f 100755 --- a/other/analysis/run-clang-tidy +++ b/other/analysis/run-clang-tidy @@ -16,8 +16,7 @@ CHECKS="$CHECKS,-clang-analyzer-nullability.NullPassedToNonnull" # ========================================================= # TODO(iphydf): Fix these. -ERRORS="$ERRORS,-cert-err34-c" -ERRORS="$ERRORS,-readability-suspicious-call-argument" +CHECKS="$CHECKS,-readability-suspicious-call-argument" CHECKS="$CHECKS,-cppcoreguidelines-avoid-goto,-hicpp-avoid-goto" CHECKS="$CHECKS,-bugprone-incorrect-roundings" @@ -193,12 +192,15 @@ CHECKS="$CHECKS,-cppcoreguidelines-macro-usage" # These are all very C++ and/or LLVM specific. CHECKS="$CHECKS,-llvmlibc-*" +# We use unions in C, and can't use std::variant or boost::variant. +CHECKS="$CHECKS,-cppcoreguidelines-pro-type-union-access" + set -eux -# TODO(iphydf): Add toxav. DIRS=( other/bootstrap_daemon/src other + toxav toxcore toxcore/events toxencryptsave diff --git a/other/analysis/run-cppcheck b/other/analysis/run-cppcheck index e861851d..cda5a442 100755 --- a/other/analysis/run-cppcheck +++ b/other/analysis/run-cppcheck @@ -1,6 +1,7 @@ #!/bin/bash SKIP_GTEST=1 +SKIP_BENCHMARK=1 . other/analysis/gen-file.sh @@ -14,8 +15,6 @@ CPPCHECK+=("--library=other/docker/cppcheck/toxcore.cfg") CPPCHECK+=("--error-exitcode=1") # Some files don't match all our suppressions below. CPPCHECK+=("--suppress=unmatchedSuppression") -# We don't cast function pointers, which cppcheck suggests here. -CPPCHECK+=("--suppress=constParameterCallback") # This disagrees with clang's warnings. CPPCHECK+=("--suppress=invalidPrintfArgType_uint") # False positives in switch statements. @@ -24,8 +23,11 @@ CPPCHECK+=("--suppress=knownConditionTrueFalse") CPPCHECK+=("--suppress=missingIncludeSystem") # TODO(iphydf): Maybe fix? CPPCHECK+=("--suppress=signConversion") -# We have suppressions in the code for the misra extension. -CPPCHECK+=("--suppress=unmatchedSuppression") +# These const correctness checks are very broken for C. +CPPCHECK+=("--suppress=constParameter") +CPPCHECK+=("--suppress=constParameterCallback") +CPPCHECK+=("--suppress=constParameterPointer") +CPPCHECK+=("--suppress=constVariablePointer") # We use this for VLAs. CPPCHECK_CXX+=("--suppress=allocaCalled") diff --git a/other/analysis/run-gcc b/other/analysis/run-gcc index 8a49b325..c3252982 100755 --- a/other/analysis/run-gcc +++ b/other/analysis/run-gcc @@ -65,7 +65,9 @@ run() { -Wunused-value \ -Wunused-but-set-parameter \ -Wunused-but-set-variable \ - -fopenmp + -fopenmp \ + -Wno-unused-function + # TODO(iphydf): Remove this last 1 when the test framework matures. } . other/analysis/variants.sh diff --git a/other/bootstrap_daemon/src/config.c b/other/bootstrap_daemon/src/config.c index 1ba697d4..21300d58 100644 --- a/other/bootstrap_daemon/src/config.c +++ b/other/bootstrap_daemon/src/config.c @@ -312,6 +312,20 @@ bool get_general_config(const char *cfg_file_path, char **pid_file_path, char ** return true; } +static int hex_digit_to_int(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + /** * * Converts a hex string with even number of characters into binary. @@ -337,9 +351,15 @@ static uint8_t *bootstrap_hex_string_to_bin(const char *hex_string) const char *pos = hex_string; for (size_t i = 0; i < len; ++i, pos += 2) { - unsigned int val; - sscanf(pos, "%02x", &val); - ret[i] = val; + const int hi = hex_digit_to_int(pos[0]); + const int lo = hex_digit_to_int(pos[1]); + + if (hi == -1 || lo == -1) { + free(ret); + return nullptr; + } + + ret[i] = (uint8_t)((hi << 4) | lo); } return ret; diff --git a/other/deploy/apple/Info.plist b/other/deploy/apple/Info.plist index fcee5dc5..f637ac34 100644 --- a/other/deploy/apple/Info.plist +++ b/other/deploy/apple/Info.plist @@ -19,9 +19,9 @@ CFBundleSignature ???? CFBundleVersion - 0.2.20 + 0.2.21 CFBundleShortVersionString - 0.2.20 + 0.2.21 CFBundleGetInfoString Tox Framework diff --git a/other/deploy/apple/toxcore.podspec b/other/deploy/apple/toxcore.podspec index 764b1cb0..a75e88e8 100644 --- a/other/deploy/apple/toxcore.podspec +++ b/other/deploy/apple/toxcore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "toxcore" - s.version = "0.2.20" + s.version = "0.2.21" s.summary = "Cocoapods wrapper for toxcore" s.homepage = "https://github.com/TokTok/c-toxcore" s.license = 'GPLv3' diff --git a/other/deploy/single-file/make_single_file b/other/deploy/single-file/make_single_file index 7143da4f..a86ae25d 100755 --- a/other/deploy/single-file/make_single_file +++ b/other/deploy/single-file/make_single_file @@ -6,10 +6,10 @@ # # Example: # -# other/deploy/single-file/make_single_file auto_tests/toxav_basic_test.c auto_tests/auto_test_support.c testing/misc_tools.c | \ +# other/deploy/single-file/make_single_file auto_tests/scenarios/scenario_toxav_basic_test.c auto_tests/scenarios/framework/framework.c testing/misc_tools.c | \ # tcc -o toxav_basic_test - $(pkg-config --cflags --libs libsodium opus vpx) # -# other/deploy/single-file/make_single_file -core auto_tests/send_message_test.c auto_tests/auto_test_support.c testing/misc_tools.c | \ +# other/deploy/single-file/make_single_file -core auto_tests/scenarios/scenario_send_message_test.c auto_tests/scenarios/framework/framework.c testing/misc_tools.c | \ # tcc -o send_message_test - $(pkg-config --cflags --libs libsodium) use strict; diff --git a/other/docker/autotools/autotools.Dockerfile.dockerignore b/other/docker/autotools/autotools.Dockerfile.dockerignore index ddd22a5f..f1bb8137 100644 --- a/other/docker/autotools/autotools.Dockerfile.dockerignore +++ b/other/docker/autotools/autotools.Dockerfile.dockerignore @@ -6,6 +6,7 @@ !*.spec.in !**/Makefile.am !**/Makefile.inc +!tools/* !docs/updates/* !other/DHTnodes !other/astyle/* diff --git a/other/docker/compcert/Makefile b/other/docker/compcert/Makefile index 9aa84f18..e953175c 100644 --- a/other/docker/compcert/Makefile +++ b/other/docker/compcert/Makefile @@ -19,8 +19,8 @@ $(libsodium_OBJECTS): CFLAGS += \ -Ilibsodium/src/libsodium/include/sodium toxcore_SOURCES := $(wildcard \ - auto_tests/auto_test_support.c \ - auto_tests/send_message_test.c \ + auto_tests/scenarios/framework/framework.c \ + auto_tests/scenarios/scenario_send_message_test.c \ testing/misc_tools.c \ toxav/*.c \ toxcore/*.c \ diff --git a/other/docker/compcert/compcert.Dockerfile b/other/docker/compcert/compcert.Dockerfile index 90f16edf..d42db3c3 100644 --- a/other/docker/compcert/compcert.Dockerfile +++ b/other/docker/compcert/compcert.Dockerfile @@ -16,4 +16,4 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN git clone --depth=1 --branch=stable https://github.com/jedisct1/libsodium /work/libsodium COPY other/docker/compcert/Makefile /work/ RUN make "-j$(nproc)" -RUN ./send_message_test | grep 'tox clients connected' +RUN ./send_message_test 2>&1 | grep 'Correctly failed to send too long message' diff --git a/other/docker/misra/Makefile b/other/docker/misra/Makefile index 9f6e32a3..01c73b6c 100644 --- a/other/docker/misra/Makefile +++ b/other/docker/misra/Makefile @@ -42,6 +42,10 @@ SUPPRESSIONS += 10.8 # # Reason: this is needed for generic callbacks to make any sense. SUPPRESSIONS += 11.5 +# A conversion shall not remove any const, volatile or _Atomic qualification from the type pointed to by a pointer. +# +# Reason: we need to cast from _Nullable to _Nonnull. +SUPPRESSIONS += 11.8 # The precedence of operators within expressions should be made explicit. # # Reason: this asks us to add a lot of extra parentheses that don't really help diff --git a/other/docker/modules/check b/other/docker/modules/check index 703ecb59..8cbaa87a 100755 --- a/other/docker/modules/check +++ b/other/docker/modules/check @@ -4,29 +4,43 @@ import os import subprocess import sys from dataclasses import dataclass -from typing import Iterable -from typing import Optional +from dataclasses import field +from typing import Any +from typing import Dict +from typing import List -LIBS = {} -MODS = {} STD_MODULE = """module std [system] { textual header "/usr/include/alloca.h" textual header "/usr/include/assert.h" textual header "/usr/include/c++/14.2.0/algorithm" textual header "/usr/include/c++/14.2.0/array" + textual header "/usr/include/c++/14.2.0/atomic" + textual header "/usr/include/c++/14.2.0/cassert" + textual header "/usr/include/c++/14.2.0/cerrno" textual header "/usr/include/c++/14.2.0/chrono" + textual header "/usr/include/c++/14.2.0/climits" + textual header "/usr/include/c++/14.2.0/compare" textual header "/usr/include/c++/14.2.0/cstddef" textual header "/usr/include/c++/14.2.0/cstdint" textual header "/usr/include/c++/14.2.0/cstdio" textual header "/usr/include/c++/14.2.0/cstdlib" textual header "/usr/include/c++/14.2.0/cstring" + textual header "/usr/include/c++/14.2.0/deque" + textual header "/usr/include/c++/14.2.0/functional" textual header "/usr/include/c++/14.2.0/iomanip" textual header "/usr/include/c++/14.2.0/iosfwd" + textual header "/usr/include/c++/14.2.0/iostream" textual header "/usr/include/c++/14.2.0/limits" + textual header "/usr/include/c++/14.2.0/map" textual header "/usr/include/c++/14.2.0/memory" + textual header "/usr/include/c++/14.2.0/mutex" + textual header "/usr/include/c++/14.2.0/new" + textual header "/usr/include/c++/14.2.0/optional" textual header "/usr/include/c++/14.2.0/ostream" + textual header "/usr/include/c++/14.2.0/queue" textual header "/usr/include/c++/14.2.0/random" textual header "/usr/include/c++/14.2.0/stdlib.h" + textual header "/usr/include/c++/14.2.0/string" textual header "/usr/include/c++/14.2.0/thread" textual header "/usr/include/c++/14.2.0/type_traits" textual header "/usr/include/c++/14.2.0/vector" @@ -43,29 +57,33 @@ STD_MODULE = """module std [system] { textual header "/usr/include/sys/types.h" textual header "/usr/include/time.h" } -module "//c-toxcore/third_party:cmp" { +module "c_toxcore_third_party_cmp" { header "third_party/cmp/cmp.h" use std } -module "//c-toxcore/toxencryptsave:defines" { +module "c_toxcore_toxencryptsave_defines" { header "toxencryptsave/defines.h" } -module "@benchmark" { +module "_benchmark" { textual header "/usr/include/benchmark/benchmark.h" use std } -module "@com_google_googletest//:gtest" { +module "_com_google_googletest___gtest" { textual header "/usr/include/gmock/gmock.h" textual header "/usr/include/gtest/gtest.h" use std } -module "@libsodium" { +module "_com_google_googletest___gtest_main" { + // Dummy module for gtest_main, assuming it links but headers are in gtest + use "_com_google_googletest___gtest" +} +module "_libsodium" { textual header "/usr/include/sodium.h" } -module "@pthread" { +module "_pthread" { textual header "/usr/include/pthread.h" } -module "@psocket" { +module "_psocket" { textual header "/usr/include/arpa/inet.h" textual header "/usr/include/fcntl.h" textual header "/usr/include/fortify/sys/socket.h" @@ -79,94 +97,132 @@ module "@psocket" { @dataclass -class Context: - pkg: str - pkg_prefix: str +class Target: + name: str + package: str + srcs: List[str] = field(default_factory=list) + hdrs: List[str] = field(default_factory=list) + deps: List[str] = field(default_factory=list) - def bzl_load(self, bzl: str, *syms: str) -> None: + @property + def label(self) -> str: + # Sanitize label for module name + sanitized = (f"//c-toxcore/{self.package}:{self.name}".replace( + "/", "_").replace(":", + "_").replace(".", + "_").replace("-", + "_").replace("@", "_")) + if sanitized.startswith("__"): + sanitized = sanitized[2:] + return sanitized + + +TARGETS: List[Target] = [] + + +class BuildContext: + + def __init__(self, package: str): + self.package = package + + def bzl_load(self, *args: Any, **kwargs: Any) -> None: pass - def bzl_exports_files( - self, - srcs: list[str], - visibility: Optional[list[str]] = None, - ) -> None: + def bzl_exports_files(self, *args: Any, **kwargs: Any) -> None: pass - def bzl_cc_library( - self, - name: str, - srcs: Iterable[str] = tuple(), - hdrs: Iterable[str] = tuple(), - deps: Iterable[str] = tuple(), - visibility: Iterable[str] = tuple(), - testonly: bool = False, - copts: Iterable[str] = tuple(), - ) -> None: - LIBS[name] = { - "srcs": - srcs, - "deps": [ - f"{self.pkg_prefix}{dep}" if dep[0] == ":" else dep - for dep in deps - ], - "hdrs": - hdrs, - } - - def bzl_cc_binary( - self, - name: str, - srcs: Iterable[str] = tuple(), - hdrs: Iterable[str] = tuple(), - deps: Iterable[str] = tuple(), - **kwargs: list[str], - ) -> None: - LIBS[name] = { - "srcs": - srcs, - "deps": [ - f"{self.pkg_prefix}{dep}" if dep[0] == ":" else dep - for dep in deps - ], - "hdrs": - hdrs, - } - - def bzl_cc_fuzz_test(self, name: str, **kwargs: list[str]) -> None: + def bzl_alias(self, *args: Any, **kwargs: Any) -> None: pass - def bzl_select(self, selector: dict[str, list[str]]) -> list[str]: - return selector["//tools/config:linux"] - - def bzl_glob(self, include: list[str]) -> list[str]: - return [ - f[len(self.pkg) + 1:] for p in include - for f in glob.glob(os.path.join(self.pkg, p)) - ] - - def bzl_alias(self, name: str, actual: str, visibility: list[str]) -> None: + def bzl_sh_library(self, *args: Any, **kwargs: Any) -> None: pass - def bzl_sh_library(self, name: str, **kwargs: list[str]) -> None: + def bzl_cc_fuzz_test(self, *args: Any, **kwargs: Any) -> None: pass + def bzl_select(self, selector: Dict[str, List[str]]) -> List[str]: + return selector.get("//tools/config:linux", + selector.get("//conditions:default", [])) + + def bzl_glob(self, include: List[str]) -> List[str]: + results = [] + for pattern in include: + full_pattern = os.path.join(self.package, pattern) + files = glob.glob(full_pattern) + results.extend([os.path.relpath(f, self.package) for f in files]) + return results + + def _add_target(self, name: str, srcs: Any, hdrs: Any, deps: Any) -> None: + srcs = list(srcs) if srcs else [] + hdrs = list(hdrs) if hdrs else [] + deps = list(deps) if deps else [] + TARGETS.append(Target(name, self.package, srcs, hdrs, deps)) + + def bzl_cc_library(self, + name: str, + srcs: Any = (), + hdrs: Any = (), + deps: Any = (), + **kwargs: Any) -> None: + self._add_target(name, srcs, hdrs, deps) + + def bzl_cc_binary(self, + name: str, + srcs: Any = (), + hdrs: Any = (), + deps: Any = (), + **kwargs: Any) -> None: + self._add_target(name, srcs, hdrs, deps) + + def bzl_cc_test(self, + name: str, + srcs: Any = (), + hdrs: Any = (), + deps: Any = (), + **kwargs: Any) -> None: + self._add_target(name, srcs, hdrs, deps) + + +def resolve_module_name(dep: str, current_pkg: str) -> str: + # Resolve to canonical label first + label = dep + if dep.startswith("@"): + label = dep + elif dep.startswith("//"): + if ":" in dep: + label = dep + else: + pkg_name = os.path.basename(dep) + label = f"{dep}:{pkg_name}" + elif dep.startswith(":"): + label = f"//c-toxcore/{current_pkg}{dep}" + + # Sanitize + sanitized = (label.replace("/", "_").replace(":", "_").replace( + ".", "_").replace("-", "_").replace("@", "_")) + if sanitized.startswith("__"): + sanitized = sanitized[2:] + return sanitized + def main() -> None: - srcs: list[str] = [] - for pkg in ("toxcore", ): - # TODO(iphydf): Why does this break everything? - # ctx = Context(pkg, "//c-toxcore/{pkg}") - ctx = Context(pkg, "") - with open(os.path.join(pkg, "BUILD.bazel"), "r") as fh: + packages = ["toxcore", "testing/support"] + + for pkg in packages: + ctx = BuildContext(pkg) + build_file = os.path.join(pkg, "BUILD.bazel") + if not os.path.exists(build_file): + continue + + with open(build_file, "r") as f: exec( - fh.read(), + f.read(), { "load": ctx.bzl_load, "exports_files": ctx.bzl_exports_files, "cc_library": ctx.bzl_cc_library, "cc_binary": ctx.bzl_cc_binary, - "cc_test": ctx.bzl_cc_binary, + "cc_test": ctx.bzl_cc_test, "cc_fuzz_test": ctx.bzl_cc_fuzz_test, "select": ctx.bzl_select, "glob": ctx.bzl_glob, @@ -175,36 +231,36 @@ def main() -> None: }, ) - with open("module.modulemap", "w") as fh: - fh.write(STD_MODULE) - for name, lib in LIBS.items(): - fh.write(f'module "{ctx.pkg_prefix}:{name}"' + " {\n") - for hdr in lib["hdrs"]: - fh.write(f' header "{pkg}/{hdr}"\n') - fh.write(f" use std\n") - for dep in lib.get("deps", []): - fh.write(f' use "{dep}"\n') - fh.write("}\n") + with open("module.modulemap", "w") as f: + f.write(STD_MODULE) + for t in TARGETS: + f.write(f'module "{t.label}" {{\n') + for hdr in t.hdrs: + f.write(f' header "{os.path.join(t.package, hdr)}"\n') + f.write(" use std\n") + for dep in t.deps: + mod_name = resolve_module_name(dep, t.package) + # Export all dependencies to ensure visibility of types used in headers + f.write(f" use {mod_name}\n") + f.write(f" export {mod_name}\n") + f.write("}\n") - for name, lib in LIBS.items(): - for src in lib.get("srcs", []): - MODS[os.path.join(pkg, src)] = name - srcs.extend( - os.path.join(pkg, src) # just within a package for now - for lib in LIBS.values() for src in lib.get("srcs", [])) - # subprocess.run(["cat", "module.modulemap"], check=True) - for src in sorted( - set(srcs) - set([ - # TODO(iphydf): Figure out what's wrong here. - "toxcore/crypto_core_test.cc", - "toxcore/group_announce_test.cc", - "toxcore/group_moderation_test.cc", - "toxcore/mono_time_test.cc", - "toxcore/network_test.cc", - "toxcore/ping_array_test.cc", - "toxcore/util_test.cc", - ])): + # with open("module.modulemap", "r") as f: + # print(f.read(), file=sys.stderr) + + src_to_module = {} + for t in TARGETS: + for src in t.srcs: + full_src = os.path.join(t.package, src) + src_to_module[full_src] = t.label + + # Sort for deterministic output + all_srcs = sorted(src_to_module.keys()) + + for src in all_srcs: print(f"Validating {src}", file=sys.stderr) + module_name = src_to_module[src] + subprocess.run( [ "clang", @@ -219,7 +275,8 @@ def main() -> None: "-fmodules", "-fmodules-strict-decluse", "-fmodule-map-file=module.modulemap", - f"-fmodule-name={ctx.pkg_prefix}:{MODS[src]}", + f"-fmodule-name={module_name}", + "-I.", src, ], check=True, diff --git a/other/docker/slimcc/Makefile b/other/docker/slimcc/Makefile index f97d0896..bfa66a64 100644 --- a/other/docker/slimcc/Makefile +++ b/other/docker/slimcc/Makefile @@ -1,5 +1,6 @@ -SOURCES := auto_tests/send_message_test.c \ - auto_tests/auto_test_support.c \ +SOURCES := \ + auto_tests/scenarios/framework/framework.c \ + auto_tests/scenarios/scenario_send_message_test.c \ testing/misc_tools.c \ $(wildcard tox*/*.c tox*/*/*.c) \ third_party/cmp/cmp.c diff --git a/other/docker/slimcc/slimcc.Dockerfile b/other/docker/slimcc/slimcc.Dockerfile index ed205acf..5a4def67 100644 --- a/other/docker/slimcc/slimcc.Dockerfile +++ b/other/docker/slimcc/slimcc.Dockerfile @@ -41,4 +41,4 @@ COPY other/docker/slimcc/Makefile /work/c-toxcore/ RUN ["make"] SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN ./send_message_test | grep "tox clients connected" +RUN ./send_message_test 2>&1 | grep "Correctly failed to send too long message" diff --git a/other/docker/tcc/tcc.Dockerfile b/other/docker/tcc/tcc.Dockerfile index 68ed2b5e..c00de969 100644 --- a/other/docker/tcc/tcc.Dockerfile +++ b/other/docker/tcc/tcc.Dockerfile @@ -22,8 +22,8 @@ RUN tcc \ -o send_message_test \ -Wall -Werror \ -bench -g \ - auto_tests/auto_test_support.c \ - auto_tests/send_message_test.c \ + auto_tests/scenarios/framework/framework.c \ + auto_tests/scenarios/scenario_send_message_test.c \ testing/misc_tools.c \ toxav/*.c \ toxcore/*.c \ @@ -31,29 +31,29 @@ RUN tcc \ toxencryptsave/*.c \ third_party/cmp/*.c \ $(pkg-config --cflags --libs libsodium opus vpx) \ - && ./send_message_test | grep 'tox clients connected' + && ./send_message_test 2>&1 | grep 'Correctly failed to send too long message' COPY other/deploy/single-file/make_single_file /work/ RUN \ ./make_single_file -core \ - auto_tests/auto_test_support.c \ - auto_tests/send_message_test.c \ + auto_tests/scenarios/framework/framework.c \ + auto_tests/scenarios/scenario_send_message_test.c \ testing/misc_tools.c | \ tcc - \ -o send_message_test \ -Wall -Werror \ -bench -g \ $(pkg-config --cflags --libs libsodium) \ - && ./send_message_test | grep 'tox clients connected' + && ./send_message_test 2>&1 | grep 'Correctly failed to send too long message' RUN \ ./make_single_file \ - auto_tests/auto_test_support.c \ - auto_tests/toxav_basic_test.c \ + auto_tests/scenarios/framework/framework.c \ + auto_tests/scenarios/scenario_toxav_basic_test.c \ testing/misc_tools.c | \ tcc - \ -o toxav_basic_test \ -Wall -Werror \ -bench -g \ $(pkg-config --cflags --libs libsodium opus vpx) \ - && ./toxav_basic_test | grep 'Test successful' + && ./toxav_basic_test 2>&1 | grep 'Cancel Flow finished' diff --git a/other/docker/windows/Dockerfile b/other/docker/windows/Dockerfile index ac7ba11c..64a5b209 100644 --- a/other/docker/windows/Dockerfile +++ b/other/docker/windows/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim +FROM debian:trixie-slim # When editing, make sure to update /other/windows_build_script_toxcore.sh and # INSTALL.md to match. diff --git a/other/docker/windows/build_toxcore.sh b/other/docker/windows/build_toxcore.sh index d67b68c5..39306ac5 100755 --- a/other/docker/windows/build_toxcore.sh +++ b/other/docker/windows/build_toxcore.sh @@ -56,7 +56,7 @@ build() { " >windows_toolchain.cmake if [ "$ENABLE_TEST" = "true" ]; then - echo "SET(CROSSCOMPILING_EMULATOR /usr/bin/wine)" >>windows_toolchain.cmake + echo "SET(CMAKE_CROSSCOMPILING_EMULATOR /usr/bin/wine)" >>windows_toolchain.cmake fi if [ "$ARCH" = "i686" ]; then diff --git a/other/docker/windows/get_packages.sh b/other/docker/windows/get_packages.sh index 54fc1558..589048d6 100755 --- a/other/docker/windows/get_packages.sh +++ b/other/docker/windows/get_packages.sh @@ -43,19 +43,19 @@ if [ "$SUPPORT_TEST" = "true" ]; then CURL_OPTIONS=(-L --connect-timeout 10) # While we would prefer to use Debian's Wine packages, use WineHQ's packages - # instead as Debian Bookworm's Wine crashes when creating a 64-bit prefix. + # instead as Debian Trixie's Wine crashes when creating a 64-bit prefix. # see https://github.com/TokTok/c-toxcore/pull/2713#issuecomment-1967319113 # for the crash details curl "${CURL_OPTIONS[@]}" -o /etc/apt/keyrings/winehq-archive.key \ https://dl.winehq.org/wine-builds/winehq.key curl "${CURL_OPTIONS[@]}" -O --output-dir /etc/apt/sources.list.d/ \ - https://dl.winehq.org/wine-builds/debian/dists/bookworm/winehq-bookworm.sources + https://dl.winehq.org/wine-builds/debian/dists/trixie/winehq-trixie.sources . ./check_sha256.sh check_sha256 "d965d646defe94b3dfba6d5b4406900ac6c81065428bf9d9303ad7a72ee8d1b8" \ "/etc/apt/keyrings/winehq-archive.key" - check_sha256 "8dd8ef66c749d56e798646674c1c185a99b3ed6727ca0fbb5e493951e66c0f9e" \ - "/etc/apt/sources.list.d/winehq-bookworm.sources" + check_sha256 "d10fb49718b5ceda9cc9c2f8f52b076772396b4b3563a1aad2ab6503414dcee7" \ + "/etc/apt/sources.list.d/winehq-trixie.sources" dpkg --add-architecture i386 apt-get update diff --git a/other/event_tooling/generate_event_c.cpp b/other/event_tooling/generate_event_c.cpp index 3583b4f6..476b4e6c 100644 --- a/other/event_tooling/generate_event_c.cpp +++ b/other/event_tooling/generate_event_c.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// Copyright © 2023-2025 The TokTok team. +// Copyright © 2023-2026 The TokTok team. // this file can be used to generate event.c files // requires c++17 @@ -27,6 +27,7 @@ std::string output_folder = "out"; struct EventTypeTrivial { std::string type; // eg uint32_t std::string name; // eg friend_number + std::string cb_type = ""; }; struct EventTypeByteRange { @@ -34,11 +35,17 @@ struct EventTypeByteRange { std::string name_data; // eg data std::string name_length; // eg data_length std::string name_length_cb; // eg length // TODO: merge with normal? + std::string type_c_arg = "uint8_t"; + std::string type_length_cb = "size_t"; + bool null_terminated = false; }; -// TODO: EventTypeByteArray +struct EventTypeByteArray { + std::string name; // eg public_key + std::string length_constant; // eg TOX_PUBLIC_KEY_SIZE; +}; -using EventType = std::variant; +using EventType = std::variant; // helper type for the visitor #4 template struct overloaded : Ts... { using Ts::operator()...; }; @@ -125,6 +132,40 @@ std::string bin_unpack_name_from_type(const std::string& type) { } } +std::string zero_initializer_for_type(const std::string& type) { + if (type == "uint64_t" || type == "uint32_t" || type == "uint16_t" || type == "uint8_t") { + return "0"; + } else if (type == "bool") { + return "false"; + } else if (type == "Tox_User_Status") { + return "TOX_USER_STATUS_NONE"; + } else if (type == "Tox_Conference_Type") { + return "TOX_CONFERENCE_TYPE_TEXT"; + } else if (type == "Tox_Message_Type") { + return "TOX_MESSAGE_TYPE_NORMAL"; + } else if (type == "Tox_File_Control") { + return "TOX_FILE_CONTROL_RESUME"; + } else if (type == "Tox_Connection") { + return "TOX_CONNECTION_NONE"; + } else if (type == "Tox_Group_Privacy_State") { + return "TOX_GROUP_PRIVACY_STATE_PUBLIC"; + } else if (type == "Tox_Group_Voice_State") { + return "TOX_GROUP_VOICE_STATE_NON_MUTED"; + } else if (type == "Tox_Group_Topic_Lock") { + return "TOX_GROUP_TOPIC_LOCK_UNLOCKED"; + } else if (type == "Tox_Group_Join_Fail") { + return "TOX_GROUP_JOIN_FAIL_NULL"; + } else if (type == "Tox_Group_Mod_Event") { + return "TOX_GROUP_MOD_EVENT_MOD"; + } else if (type == "Tox_Group_Exit_Type") { + return "TOX_GROUP_EXIT_TYPE_KICK"; + } else { + std::cerr << "unknown type " << type << "\n"; + exit(1); + return "0"; + } +} + void generate_event_impl(const std::string& event_name, const std::vector& event_types) { const std::string event_name_l = str_tolower(event_name); std::string file_name = output_folder + "/" + event_name_l + ".c"; @@ -135,7 +176,6 @@ void generate_event_impl(const std::string& event_name, const std::vector)"; - if (need_stdlib_h) { - f << R"( -#include )"; - } if (need_string_h) { f << R"( #include )"; @@ -206,6 +244,9 @@ void generate_event_impl(const std::string& event_name, const std::vector" << t.name_data << " != nullptr) {\n"; - f << " free(" << event_name_l << "->" << t.name_data << ");\n"; + f << " mem_delete(mem, " << event_name_l << "->" << t.name_data << ");\n"; f << " " << event_name_l << "->" << t.name_data << " = nullptr;\n"; f << " " << event_name_l << "->" << t.name_length << " = 0;\n"; f << " }\n\n"; f << " if (" << t.name_data << " == nullptr) {\n"; f << " assert(" << t.name_length << " == 0);\n"; f << " return true;\n }\n\n"; - f << " uint8_t *" << t.name_data << "_copy = (uint8_t *)malloc(" << t.name_length << ");\n\n"; + if (t.null_terminated) { + f << " uint8_t *" << t.name_data << "_copy = (uint8_t *)mem_balloc(mem, " << t.name_length << " + 1);\n\n"; + } else { + f << " uint8_t *" << t.name_data << "_copy = (uint8_t *)mem_balloc(mem, " << t.name_length << ");\n\n"; + } f << " if (" << t.name_data << "_copy == nullptr) {\n"; f << " return false;\n }\n\n"; f << " memcpy(" << t.name_data << "_copy, " << t.name_data << ", " << t.name_length << ");\n"; + if (t.null_terminated) { + f << " " << t.name_data << "_copy[" << t.name_length << "] = 0;\n"; + } f << " " << event_name_l << "->" << t.name_data << " = " << t.name_data << "_copy;\n"; f << " " << event_name_l << "->" << t.name_length << " = " << t.name_length << ";\n"; f << " return true;\n"; + }, + [&](const EventTypeByteArray& t) { + f << " memcpy(" << event_name_l << "->" << t.name << ", " << t.name << ", " << t.length_constant << ");\n"; + f << " return true;\n"; } }, t @@ -288,6 +346,12 @@ void generate_event_impl(const std::string& event_name, const std::vector" << t.name_data << ";\n}\n\n"; + }, + [&](const EventTypeByteArray& t) { + f << "const uint8_t *tox_event_" << event_name_l << "_get_" << t.name; + f << "(const Tox_Event_" << event_name << " *" << event_name_l << ")\n"; + f << "{\n assert(" << event_name_l << " != nullptr);\n"; + f << " return " << event_name_l << "->" << t.name << ";\n}\n\n"; } }, t @@ -297,10 +361,28 @@ void generate_event_impl(const std::string& event_name, const std::vector" << t.name_data << ");\n"; + f << " mem_delete(mem, " << event_name_l << "->" << t.name_data << ");\n"; //f << " mem->funcs->free(mem->obj, " << event_name_l << "->" << t.name_data << ");\n"; data_count++; - } + }, + [&](const EventTypeByteArray&) {} }, t ); @@ -353,6 +436,9 @@ void generate_event_impl(const std::string& event_name, const std::vector" << t.name_data << ", event->" << t.name_length << ")"; + }, + [&](const EventTypeByteArray& t) { + f << "bin_pack_bin(bp, event->" << t.name << ", " << t.length_constant << ")"; } }, t @@ -386,6 +472,9 @@ void generate_event_impl(const std::string& event_name, const std::vector" << t.name_data << ", &event->" << t.name_length << ")"; + }, + [&](const EventTypeByteArray& t) { + f << "bin_unpack_bin_fixed(bu, event->" << t.name << ", " << t.length_constant << ")"; } }, t @@ -417,7 +506,7 @@ void generate_event_impl(const std::string& event_name, const std::vectorevents == nullptr) {\n return nullptr;\n }\n\n"; f << " Tox_Event_" << event_name << " *" << event_name_l << " = tox_events_add_" << event_name_l << "(state->events, state->mem);\n\n"; f << " if (" << event_name_l << " == nullptr) {\n"; @@ -469,10 +556,13 @@ void generate_event_impl(const std::string& event_name, const std::vectormem, "; + if (t.type_c_arg != "uint8_t") { + f << "(const uint8_t *)"; + } + f << t.name_data << ", " << t.name_length_cb << ");\n"; + }, + [&](const EventTypeByteArray& t) { + f << " tox_event_" << event_name_l << "_set_" << t.name << "(" << event_name_l << ", " << t.name << ");\n"; } }, t @@ -548,14 +646,21 @@ int main(int argc, char** argv) { EventTypeByteRange{"title", "title_length", "length"}, // the latter two are ideally the same } }, - + { + "Dht_Nodes_Response", + { + EventTypeByteArray{"public_key", "TOX_PUBLIC_KEY_SIZE"}, + EventTypeByteRange{"ip", "ip_length", "ip_length", "char", "uint32_t", true}, + EventTypeTrivial{"uint16_t", "port"}, + } + }, { "File_Chunk_Request", { EventTypeTrivial{"uint32_t", "friend_number"}, EventTypeTrivial{"uint32_t", "file_number"}, EventTypeTrivial{"uint64_t", "position"}, - EventTypeTrivial{"uint16_t", "length"}, + EventTypeTrivial{"uint16_t", "length", "size_t"}, } }, { @@ -629,15 +734,13 @@ int main(int argc, char** argv) { EventTypeTrivial{"uint32_t", "message_id"}, } }, -#if 0 { "Friend_Request", { - //EventTypeTrivial{"uint32_t", "friend_number"}, // public_key ByteArray + EventTypeByteArray{"public_key", "TOX_PUBLIC_KEY_SIZE"}, EventTypeByteRange{"message", "message_length", "length"}, // the latter two are ideally the same } }, -#endif { "Friend_Status", { diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index 5269d736..b247f45e 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel @@ -15,6 +15,7 @@ sh_test( "-Wno-boolean-return", "-Wno-callback-names", "-Wno-enum-from-int", + "-Wno-tagged-union", "+RTS", "-N4", "-RTS", diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 3c2bea1c..11aaea0c 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -38,3 +38,5 @@ if(BUILD_MISC_TESTS) target_link_libraries(Messenger_test PRIVATE Threads::Threads) endif() endif() + +add_subdirectory(support) diff --git a/testing/Messenger_test.c b/testing/Messenger_test.c index bbb2a417..d0ba5690 100644 --- a/testing/Messenger_test.c +++ b/testing/Messenger_test.c @@ -108,7 +108,7 @@ int main(int argc, char *argv[]) m = new_messenger(mono_time, mem, os_random(), os_network(), &options, &err); if (!m) { - fprintf(stderr, "Failed to allocate messenger datastructure: %d\n", err); + fprintf(stderr, "Failed to allocate messenger datastructure: %u\n", err); exit(0); } diff --git a/testing/fuzzing/BUILD.bazel b/testing/fuzzing/BUILD.bazel index 4c9c0a1e..ef7d4820 100644 --- a/testing/fuzzing/BUILD.bazel +++ b/testing/fuzzing/BUILD.bazel @@ -1,28 +1,6 @@ -load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") +load("@rules_cc//cc:defs.bzl", "cc_binary") load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test") -cc_library( - name = "fuzz_support", - srcs = [ - "func_conversion.hh", - "fuzz_support.cc", - ], - hdrs = ["fuzz_support.hh"], - visibility = ["//c-toxcore:__subpackages__"], - deps = [ - "//c-toxcore/toxcore:crypto_core", - "//c-toxcore/toxcore:network", - "//c-toxcore/toxcore:tox", - ], -) - -cc_library( - name = "fuzz_tox", - hdrs = ["fuzz_tox.hh"], - visibility = ["//c-toxcore:__subpackages__"], - deps = [":fuzz_support"], -) - cc_fuzz_test( name = "bootstrap_fuzz_test", size = "small", @@ -30,8 +8,7 @@ cc_fuzz_test( copts = ["-UNDEBUG"], corpus = ["//tools/toktok-fuzzer/corpus:bootstrap_fuzz_test"], deps = [ - ":fuzz_support", - ":fuzz_tox", + "//c-toxcore/testing/support", "//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox_dispatch", "//c-toxcore/toxcore:tox_events", @@ -46,8 +23,7 @@ cc_fuzz_test( corpus = ["//tools/toktok-fuzzer/corpus:e2e_fuzz_test"], data = ["//tools/toktok-fuzzer/init:e2e_fuzz_test.dat"], deps = [ - ":fuzz_support", - ":fuzz_tox", + "//c-toxcore/testing/support", "//c-toxcore/toxcore:crypto_core", "//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox_dispatch", @@ -62,7 +38,7 @@ cc_fuzz_test( copts = ["-UNDEBUG"], corpus = ["//tools/toktok-fuzzer/corpus:toxsave_fuzz_test"], deps = [ - ":fuzz_support", + "//c-toxcore/testing/support", "//c-toxcore/toxcore:tox", ], ) @@ -72,7 +48,7 @@ cc_binary( srcs = ["protodump.cc"], copts = ["-UNDEBUG"], deps = [ - ":fuzz_support", + "//c-toxcore/testing/support", "//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox_dispatch", "//c-toxcore/toxcore:tox_events", @@ -90,38 +66,3 @@ genrule( tags = ["manual"], tools = [":protodump"], ) - -# bazel test --config=asan-libfuzzer //c-toxcore/testing/fuzzing:protodump_reduce_test -cc_test( - name = "protodump_reduce_test", - size = "small", - srcs = ["protodump_reduce.cc"], - args = ["$(location :e2e_fuzz_test_init.dat)"], - copts = ["-UNDEBUG"], - data = [":e2e_fuzz_test_init.dat"], - tags = ["manual"], - deps = [ - ":fuzz_support", - ":fuzz_tox", - "//c-toxcore/toxcore:crypto_core", - "//c-toxcore/toxcore:tox", - "//c-toxcore/toxcore:tox_dispatch", - "//c-toxcore/toxcore:tox_events", - "@rules_fuzzing//fuzzing:cc_engine", - ], -) - -cc_fuzz_test( - name = "protodump_reduce", - size = "small", - srcs = ["protodump_reduce.cc"], - copts = ["-UNDEBUG"], - deps = [ - ":fuzz_support", - ":fuzz_tox", - "//c-toxcore/toxcore:crypto_core", - "//c-toxcore/toxcore:tox", - "//c-toxcore/toxcore:tox_dispatch", - "//c-toxcore/toxcore:tox_events", - ], -) diff --git a/testing/fuzzing/CMakeLists.txt b/testing/fuzzing/CMakeLists.txt index 32a31097..a95c6527 100644 --- a/testing/fuzzing/CMakeLists.txt +++ b/testing/fuzzing/CMakeLists.txt @@ -1,5 +1,4 @@ # Override network and random functions -add_library(fuzz_support func_conversion.hh fuzz_support.cc fuzz_support.hh) set(LIBFUZZER_LINKER_FLAGS) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") @@ -12,9 +11,9 @@ function(fuzz_test target source_dir) set(CORPUS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/toktok-fuzzer/corpus/${target}_fuzz_test) file(GLOB CORPUS "${CORPUS_DIR}/*") add_executable(${target}_fuzz_test ${source_dir}/${target}_fuzz_test.cc) - target_link_libraries(${target}_fuzz_test PRIVATE fuzz_support test_util toxcore_fuzz ${LIBFUZZER_LINKER_FLAGS}) + target_link_libraries(${target}_fuzz_test PRIVATE support test_util toxcore_fuzz ${LIBFUZZER_LINKER_FLAGS}) if(CORPUS) - add_test(NAME ${target}_fuzz COMMAND ${CROSSCOMPILING_EMULATOR} ${target}_fuzz_test -max_total_time=10 ${CORPUS}) + add_test(NAME ${target}_fuzz COMMAND ${target}_fuzz_test -max_total_time=10 ${CORPUS}) set_property(TEST ${target}_fuzz PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw;srcdir=${CMAKE_CURRENT_SOURCE_DIR}") endif() endfunction() diff --git a/testing/fuzzing/bootstrap_fuzz_test.cc b/testing/fuzzing/bootstrap_fuzz_test.cc index 1c73d130..2b1ae118 100644 --- a/testing/fuzzing/bootstrap_fuzz_test.cc +++ b/testing/fuzzing/bootstrap_fuzz_test.cc @@ -1,14 +1,26 @@ #include #include +#include +#include #include "../../toxcore/tox.h" #include "../../toxcore/tox_dispatch.h" #include "../../toxcore/tox_events.h" -#include "fuzz_support.hh" -#include "fuzz_tox.hh" +#include "../support/public/fuzz_data.hh" +#include "../support/public/fuzz_helpers.hh" +#include "../support/public/simulated_environment.hh" namespace { +using tox::test::configure_fuzz_memory_source; +using tox::test::configure_fuzz_packet_source; +using tox::test::FakeClock; +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + +template +using Ptr = std::unique_ptr; + void setup_callbacks(Tox_Dispatch *dispatch) { tox_events_callback_conference_connected( @@ -97,10 +109,15 @@ void setup_callbacks(Tox_Dispatch *dispatch) void TestBootstrap(Fuzz_Data &input) { - // Null system for regularly working memory allocations needed in - // tox_events_equal. - Null_System null_sys; - Fuzz_System sys(input); + SimulatedEnvironment env; + env.fake_clock().advance(1000000000); // Match legacy behavior + auto node = env.create_node(33445); + configure_fuzz_memory_source(env.fake_memory(), input); + configure_fuzz_packet_source(*node->endpoint, input); + + // Create a second null system for tox_events_equal check + SimulatedEnvironment null_env; + auto null_node = null_env.create_node(0); // Port 0 (unbound/irrelevant) Ptr opts(tox_options_new(nullptr), tox_options_free); assert(opts != nullptr); @@ -110,8 +127,26 @@ void TestBootstrap(Fuzz_Data &input) const char *message, void *user_data) { // Log to stdout. if (Fuzz_Data::FUZZ_DEBUG) { - std::printf("[tox1] %c %s:%u(%s): %s\n", tox_log_level_name(level), file, line, - func, message); + // Approximate level name mapping + char level_char = '?'; + switch (level) { + case TOX_LOG_LEVEL_TRACE: + level_char = 'T'; + break; + case TOX_LOG_LEVEL_DEBUG: + level_char = 'D'; + break; + case TOX_LOG_LEVEL_INFO: + level_char = 'I'; + break; + case TOX_LOG_LEVEL_WARNING: + level_char = 'W'; + break; + case TOX_LOG_LEVEL_ERROR: + level_char = 'E'; + break; + } + std::printf("[tox1] %c %s:%u(%s): %s\n", level_char, file, line, func, message); } }); @@ -134,7 +169,7 @@ void TestBootstrap(Fuzz_Data &input) } Tox_Options_Testing tox_options_testing; - tox_options_testing.operating_system = sys.sys.get(); + tox_options_testing.operating_system = &node->system; Tox_Err_New error_new; Tox_Err_New_Testing error_new_testing; @@ -165,15 +200,17 @@ void TestBootstrap(Fuzz_Data &input) while (!input.empty()) { Tox_Err_Events_Iterate error_iterate; Tox_Events *events = tox_events_iterate(tox, true, &error_iterate); - assert(tox_events_equal(null_sys.sys.get(), events, events)); + assert(tox_events_equal(&null_node->system, events, events)); + tox_dispatch_invoke(dispatch, events, tox); tox_events_free(events); - // Move the clock forward a decent amount so all the time-based checks - // trigger more quickly. - sys.clock += 200; - // If no input was consumed, something went wrong. - assert(input_size != input.size()); + env.advance_time(200); + + // If no input was consumed, stop. + if (input_size == input.size()) { + break; + } input_size = input.size(); } diff --git a/testing/fuzzing/e2e_fuzz_test.cc b/testing/fuzzing/e2e_fuzz_test.cc index 91f59ac9..8774ecda 100644 --- a/testing/fuzzing/e2e_fuzz_test.cc +++ b/testing/fuzzing/e2e_fuzz_test.cc @@ -5,17 +5,29 @@ #include #include +#include #include #include "../../toxcore/crypto_core.h" #include "../../toxcore/tox.h" #include "../../toxcore/tox_dispatch.h" #include "../../toxcore/tox_events.h" -#include "fuzz_support.hh" -#include "fuzz_tox.hh" +#include "../support/public/fuzz_data.hh" +#include "../support/public/fuzz_helpers.hh" +#include "../support/public/simulated_environment.hh" namespace { +using tox::test::configure_fuzz_memory_source; +using tox::test::configure_fuzz_packet_source; +using tox::test::configure_fuzz_random_source; +using tox::test::FakeClock; +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + +template +using Ptr = std::unique_ptr; + void setup_callbacks(Tox_Dispatch *dispatch) { tox_events_callback_conference_connected( @@ -131,9 +143,16 @@ void setup_callbacks(Tox_Dispatch *dispatch) void TestEndToEnd(Fuzz_Data &input) { - Fuzz_System sys(input); - // Used for places where we want all allocations to succeed. - Null_System null_sys; + SimulatedEnvironment env; + env.fake_clock().advance(1000000000); // Match legacy behavior + auto node = env.create_node(33445); + configure_fuzz_memory_source(env.fake_memory(), input); + configure_fuzz_packet_source(*node->endpoint, input); + configure_fuzz_random_source(env.fake_random(), input); + + // Null system replacement for event comparison + SimulatedEnvironment null_env; + auto null_node = null_env.create_node(0); Ptr opts(tox_options_new(nullptr), tox_options_free); assert(opts != nullptr); @@ -144,13 +163,31 @@ void TestEndToEnd(Fuzz_Data &input) const char *message, void *user_data) { // Log to stdout. if (Fuzz_Data::FUZZ_DEBUG) { - std::printf("[tox1] %c %s:%u(%s): %s\n", tox_log_level_name(level), file, line, - func, message); + // Approximate level name mapping + char level_char = '?'; + switch (level) { + case TOX_LOG_LEVEL_TRACE: + level_char = 'T'; + break; + case TOX_LOG_LEVEL_DEBUG: + level_char = 'D'; + break; + case TOX_LOG_LEVEL_INFO: + level_char = 'I'; + break; + case TOX_LOG_LEVEL_WARNING: + level_char = 'W'; + break; + case TOX_LOG_LEVEL_ERROR: + level_char = 'E'; + break; + } + std::printf("[tox1] %c %s:%u(%s): %s\n", level_char, file, line, func, message); } }); Tox_Options_Testing tox_options_testing; - tox_options_testing.operating_system = sys.sys.get(); + tox_options_testing.operating_system = &node->system; Tox_Err_New error_new; Tox_Err_New_Testing error_new_testing; @@ -171,15 +208,18 @@ void TestEndToEnd(Fuzz_Data &input) assert(dispatch != nullptr); setup_callbacks(dispatch); + // MIN_ITERATION_INTERVAL = 20 + const uint8_t MIN_ITERATION_INTERVAL = 20; + while (!input.empty()) { Tox_Err_Events_Iterate error_iterate; Tox_Events *events = tox_events_iterate(tox, true, &error_iterate); - assert(tox_events_equal(null_sys.sys.get(), events, events)); + assert(tox_events_equal(&null_node->system, events, events)); tox_dispatch_invoke(dispatch, events, tox); tox_events_free(events); - // Move the clock forward a decent amount so all the time-based checks - // trigger more quickly. - sys.clock += std::max(System::MIN_ITERATION_INTERVAL, random_u08(sys.rng.get())); + + uint32_t rand_val = env.fake_random().uniform(256); + env.fake_clock().advance(std::max(MIN_ITERATION_INTERVAL, rand_val)); } tox_dispatch_free(dispatch); diff --git a/testing/fuzzing/func_conversion.hh b/testing/fuzzing/func_conversion.hh deleted file mode 100644 index bfc08beb..00000000 --- a/testing/fuzzing/func_conversion.hh +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2022-2025 The TokTok team. - */ - -#ifndef C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H -#define C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H - -namespace detail { - -template -struct func_conversion { -private: - template - using func_pointer = R (*)(Args...); - - template - struct static_caster { - From obj; - - template - operator To() const - { - return static_cast(obj); - } - }; - -public: - template - constexpr operator func_pointer() - { - return [](Arg obj, auto... args) { return f(static_caster{obj}, args...); }; - } -}; - -template -struct make_funptr; - -template -struct make_funptr { - using type = R (*)(Args...); -}; - -/** @brief Turn a memfunptr type into a plain funptr type. - * - * Not needed in C++20, because we can pass the lambda itself as template - * argument, but in C++17, we need to do an early conversion. - */ -template -using make_funptr_t = typename make_funptr::type; - -} - -/** @brief Turn a C++ lambda into a C function pointer with `void*` param. - * - * Takes a lambda function with any pointer type as first parameter and turns it - * into a C function pointer with `void*` as the first parameter. Internally, it - * `static_cast`s that `void*` to the lambda's parameter type, avoiding a bunch - * of casts inside the lambdas. - * - * This works on any type `T` that can be `static_cast` to `U`, not just `void*` - * to `U*`, but the common case for C callbacks is `void*`. - */ -template -static constexpr auto operator!(F f) -{ - return detail::func_conversion, f>{}; -} - -#endif // C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H diff --git a/testing/fuzzing/fuzz_support.cc b/testing/fuzzing/fuzz_support.cc deleted file mode 100644 index 6e43f871..00000000 --- a/testing/fuzzing/fuzz_support.cc +++ /dev/null @@ -1,479 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2021-2025 The TokTok team. - */ - -#include "fuzz_support.hh" - -#ifdef _WIN32 -#include -// Comment line here to avoid reordering by source code formatters. -#include -#include -#else -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include "../../toxcore/crypto_core.h" -#include "../../toxcore/network.h" -#include "../../toxcore/tox_memory_impl.h" -#include "../../toxcore/tox_private.h" -#include "../../toxcore/tox_random_impl.h" -#include "func_conversion.hh" - -// TODO(iphydf): Put this somewhere shared. -struct Network_Addr { - struct sockaddr_storage addr; - size_t size; -}; - -System::System(std::unique_ptr in_sys, std::unique_ptr in_mem, - std::unique_ptr in_ns, std::unique_ptr in_rng) - : sys(std::move(in_sys)) - , mem(std::move(in_mem)) - , ns(std::move(in_ns)) - , rng(std::move(in_rng)) -{ -} -System::System(System &&) = default; - -System::~System() { } - -static int recv_common(Fuzz_Data &input, uint8_t *buf, size_t buf_len) -{ - if (input.size() < 2) { - errno = ENOMEM; - return -1; - } - - CONSUME_OR_ABORT(const uint8_t *fuzz_len_bytes, input, 2); - const std::size_t fuzz_len = (fuzz_len_bytes[0] << 8) | fuzz_len_bytes[1]; - - if (fuzz_len == 0xffff) { - errno = EWOULDBLOCK; - if (Fuzz_Data::FUZZ_DEBUG) { - std::printf("recvfrom: no data for tox1\n"); - } - return -1; - } - - if (Fuzz_Data::FUZZ_DEBUG) { - std::printf( - "recvfrom: %zu (%02x, %02x) for tox1\n", fuzz_len, input.data()[-2], input.data()[-1]); - } - const size_t res = std::min(buf_len, std::min(fuzz_len, input.size())); - - CONSUME_OR_ABORT(const uint8_t *data, input, res); - std::copy(data, data + res, buf); - - return res; -} - -static void *report_alloc(const char *name, const char *func, std::size_t size, void *ptr) -{ - if (Fuzz_Data::FUZZ_DEBUG) { - printf("%s: %s(%zu): %s\n", name, func, size, ptr == nullptr ? "false" : "true"); - } - return ptr; -} - -template -static void *alloc_common(const char *func, std::size_t size, Fuzz_Data &data, Args... args) -{ - CONSUME1_OR_RETURN_VAL( - const bool, want_alloc, data, report_alloc("tox1", func, size, Func(args...))); - if (!want_alloc) { - return nullptr; - } - return report_alloc("tox1", func, size, Func(args...)); -} - -static constexpr Tox_Memory_Funcs fuzz_memory_funcs = { - /* .malloc = */ - ![](Fuzz_System *self, uint32_t size) { - return alloc_common("malloc", size, self->data, size); - }, - /* .realloc = */ - ![](Fuzz_System *self, void *ptr, uint32_t size) { - return alloc_common( - "realloc", size, self->data, ptr, size); - }, - /* .dealloc = */ - ![](Fuzz_System *self, void *ptr) { std::free(ptr); }, -}; - -static constexpr Network_Funcs fuzz_network_funcs = { - /* .close = */ ![](Fuzz_System *self, Socket sock) { return 0; }, - /* .accept = */ ![](Fuzz_System *self, Socket sock) { return Socket{1337}; }, - /* .bind = */ ![](Fuzz_System *self, Socket sock, const Network_Addr *addr) { return 0; }, - /* .listen = */ ![](Fuzz_System *self, Socket sock, int backlog) { return 0; }, - /* .connect = */ ![](Fuzz_System *self, Socket sock, const Network_Addr *addr) { return 0; }, - /* .recvbuf = */ - ![](Fuzz_System *self, Socket sock) { - assert(sock.value == 42 || sock.value == 1337); - const size_t count = random_u16(self->rng.get()); - return static_cast(std::min(count, self->data.size())); - }, - /* .recv = */ - ![](Fuzz_System *self, Socket sock, uint8_t *buf, size_t len) { - assert(sock.value == 42 || sock.value == 1337); - // Receive data from the fuzzer. - return recv_common(self->data, buf, len); - }, - /* .recvfrom = */ - ![](Fuzz_System *self, Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) { - assert(sock.value == 42 || sock.value == 1337); - - addr->addr = sockaddr_storage{}; - // Dummy Addr - addr->addr.ss_family = AF_INET; - - // We want an AF_INET address with dummy values - sockaddr_in *addr_in = reinterpret_cast(&addr->addr); - addr_in->sin_port = htons(33446); - addr_in->sin_addr.s_addr = htonl(0x7f000002); // 127.0.0.2 - addr->size = sizeof(struct sockaddr); - - return recv_common(self->data, buf, len); - }, - /* .send = */ - ![](Fuzz_System *self, Socket sock, const uint8_t *buf, size_t len) { - assert(sock.value == 42 || sock.value == 1337); - // Always succeed. - return static_cast(len); - }, - /* .sendto = */ - ![](Fuzz_System *self, Socket sock, const uint8_t *buf, size_t len, const Network_Addr *addr) { - assert(sock.value == 42 || sock.value == 1337); - // Always succeed. - return static_cast(len); - }, - /* .socket = */ ![](Fuzz_System *self, int domain, int type, int proto) { return Socket{42}; }, - /* .socket_nonblock = */ ![](Fuzz_System *self, Socket sock, bool nonblock) { return 0; }, - /* .getsockopt = */ - ![](Fuzz_System *self, Socket sock, int level, int optname, void *optval, size_t *optlen) { - std::memset(optval, 0, *optlen); - return 0; - }, - /* .setsockopt = */ - ![](Fuzz_System *self, Socket sock, int level, int optname, const void *optval, size_t optlen) { - return 0; - }, -}; - -static constexpr Tox_Random_Funcs fuzz_random_funcs = { - /* .bytes_callback = */ - ![](Fuzz_System *self, uint8_t *bytes, size_t length) { - // Initialize the buffer with zeros in case there's no randomness left. - std::fill_n(bytes, length, 0); - - // For integers, we copy bytes directly, because we want to control the - // exact values. - if (length == sizeof(uint8_t) || length == sizeof(uint16_t) || length == sizeof(uint32_t) - || length == sizeof(uint64_t)) { - CONSUME_OR_RETURN(const uint8_t *data, self->data, length); - std::copy(data, data + length, bytes); - if (Fuzz_Data::FUZZ_DEBUG) { - if (length == 1) { - std::printf("rng: %d (0x%02x)\n", bytes[0], bytes[0]); - } else { - std::printf("rng: %02x..%02x[%zu]\n", bytes[0], bytes[length - 1], length); - } - } - return; - } - - // For nonces and keys, we fill the buffer with the same 1-2 bytes - // repeated. We only need these to be different enough to not often be - // the same. - assert(length == 24 || length == 32); - // We must cover the case of having only 1 byte left in the input. In - // that case, we will use the same byte for all the bytes in the output. - const size_t chunk_size = std::max(self->data.size(), static_cast(2)); - CONSUME_OR_RETURN(const uint8_t *chunk, self->data, chunk_size); - if (chunk_size == 2) { - std::fill_n(bytes, length / 2, chunk[0]); - std::fill_n(bytes + length / 2, length / 2, chunk[1]); - } else { - std::fill_n(bytes, length, chunk[0]); - } - if (Fuzz_Data::FUZZ_DEBUG) { - if (length == 1) { - std::printf("rng: %d (0x%02x)\n", bytes[0], bytes[0]); - } else { - std::printf("rng: %02x..%02x[%zu]\n", bytes[0], bytes[length - 1], length); - } - } - }, - /* .uniform_callback = */ - ![](Fuzz_System *self, uint32_t upper_bound) { - uint32_t randnum = 0; - if (upper_bound > 0) { - self->rng->funcs->bytes_callback( - self, reinterpret_cast(&randnum), sizeof(randnum)); - randnum %= upper_bound; - } - return randnum; - }, -}; - -Fuzz_System::Fuzz_System(Fuzz_Data &input) - : System{ - std::make_unique(), - std::make_unique(Tox_Memory{&fuzz_memory_funcs, this}), - std::make_unique(Network{&fuzz_network_funcs, this}), - std::make_unique(Tox_Random{&fuzz_random_funcs, this}), - } - , data(input) -{ - sys->mono_time_callback = [](void *self) { return static_cast(self)->clock; }; - sys->mono_time_user_data = this; - sys->mem = mem.get(); - sys->ns = ns.get(); - sys->rng = rng.get(); -} - -static constexpr Tox_Memory_Funcs null_memory_funcs = { - /* .malloc = */ - ![](Null_System *self, uint32_t size) { return std::malloc(size); }, - /* .realloc = */ - ![](Null_System *self, void *ptr, uint32_t size) { return std::realloc(ptr, size); }, - /* .dealloc = */ - ![](Null_System *self, void *ptr) { std::free(ptr); }, -}; - -static constexpr Network_Funcs null_network_funcs = { - /* .close = */ ![](Null_System *self, Socket sock) { return 0; }, - /* .accept = */ ![](Null_System *self, Socket sock) { return Socket{1337}; }, - /* .bind = */ ![](Null_System *self, Socket sock, const Network_Addr *addr) { return 0; }, - /* .listen = */ ![](Null_System *self, Socket sock, int backlog) { return 0; }, - /* .connect = */ ![](Null_System *self, Socket sock, const Network_Addr *addr) { return 0; }, - /* .recvbuf = */ ![](Null_System *self, Socket sock) { return 0; }, - /* .recv = */ - ![](Null_System *self, Socket sock, uint8_t *buf, size_t len) { - // Always fail. - errno = ENOMEM; - return -1; - }, - /* .recvfrom = */ - ![](Null_System *self, Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) { - // Always fail. - errno = ENOMEM; - return -1; - }, - /* .send = */ - ![](Null_System *self, Socket sock, const uint8_t *buf, size_t len) { - // Always succeed. - return static_cast(len); - }, - /* .sendto = */ - ![](Null_System *self, Socket sock, const uint8_t *buf, size_t len, const Network_Addr *addr) { - // Always succeed. - return static_cast(len); - }, - /* .socket = */ ![](Null_System *self, int domain, int type, int proto) { return Socket{42}; }, - /* .socket_nonblock = */ ![](Null_System *self, Socket sock, bool nonblock) { return 0; }, - /* .getsockopt = */ - ![](Null_System *self, Socket sock, int level, int optname, void *optval, size_t *optlen) { - std::memset(optval, 0, *optlen); - return 0; - }, - /* .setsockopt = */ - ![](Null_System *self, Socket sock, int level, int optname, const void *optval, size_t optlen) { - return 0; - }, -}; - -static uint64_t simple_rng(uint64_t &seed) -{ - // https://nuclear.llnl.gov/CNP/rng/rngman/node4.html - seed = 2862933555777941757LL * seed + 3037000493LL; - return seed; -} - -static constexpr Tox_Random_Funcs null_random_funcs = { - /* .bytes_callback = */ - ![](Null_System *self, uint8_t *bytes, size_t length) { - for (size_t i = 0; i < length; ++i) { - bytes[i] = simple_rng(self->seed) & 0xff; - } - }, - /* .uniform_callback = */ - ![](Null_System *self, uint32_t upper_bound) { - return static_cast(simple_rng(self->seed)) % upper_bound; - }, -}; - -Null_System::Null_System() - : System{ - std::make_unique(), - std::make_unique(Tox_Memory{&null_memory_funcs, this}), - std::make_unique(Network{&null_network_funcs, this}), - std::make_unique(Tox_Random{&null_random_funcs, this}), - } -{ - sys->mono_time_callback = [](void *self) { return static_cast(self)->clock; }; - sys->mono_time_user_data = this; - sys->mem = mem.get(); - sys->ns = ns.get(); - sys->rng = rng.get(); -} - -static uint16_t get_port(const Network_Addr *addr) -{ - if (addr->addr.ss_family == AF_INET6) { - return reinterpret_cast(&addr->addr)->sin6_port; - } else { - assert(addr->addr.ss_family == AF_INET); - return reinterpret_cast(&addr->addr)->sin_port; - } -} - -static constexpr Tox_Memory_Funcs record_memory_funcs = { - /* .malloc = */ - ![](Record_System *self, uint32_t size) { - self->push(true); - return report_alloc(self->name_, "malloc", size, std::malloc(size)); - }, - /* .realloc = */ - ![](Record_System *self, void *ptr, uint32_t size) { - self->push(true); - return report_alloc(self->name_, "realloc", size, std::realloc(ptr, size)); - }, - /* .dealloc = */ - ![](Record_System *self, void *ptr) { std::free(ptr); }, -}; - -static constexpr Network_Funcs record_network_funcs = { - /* .close = */ ![](Record_System *self, Socket sock) { return 0; }, - /* .accept = */ ![](Record_System *self, Socket sock) { return Socket{2}; }, - /* .bind = */ - ![](Record_System *self, Socket sock, const Network_Addr *addr) { - const uint16_t port = get_port(addr); - if (self->global_.bound.find(port) != self->global_.bound.end()) { - errno = EADDRINUSE; - return -1; - } - self->global_.bound.emplace(port, self); - self->port = port; - return 0; - }, - /* .listen = */ ![](Record_System *self, Socket sock, int backlog) { return 0; }, - /* .connect = */ ![](Record_System *self, Socket sock, const Network_Addr *addr) { return 0; }, - /* .recvbuf = */ ![](Record_System *self, Socket sock) { return 0; }, - /* .recv = */ - ![](Record_System *self, Socket sock, uint8_t *buf, size_t len) { - // Always fail. - errno = ENOMEM; - return -1; - }, - /* .recvfrom = */ - ![](Record_System *self, Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) { - assert(sock.value == 42); - if (self->recvq.empty()) { - self->push("\xff\xff"); - errno = EWOULDBLOCK; - if (Fuzz_Data::FUZZ_DEBUG) { - std::printf("%s: recvfrom: no data\n", self->name_); - } - return -1; - } - const auto [from, packet] = std::move(self->recvq.front()); - self->recvq.pop_front(); - const size_t recvlen = std::min(len, packet.size()); - std::copy(packet.begin(), packet.end(), buf); - - addr->addr = sockaddr_storage{}; - // Dummy Addr - addr->addr.ss_family = AF_INET; - - // We want an AF_INET address with dummy values - sockaddr_in *addr_in = reinterpret_cast(&addr->addr); - addr_in->sin_port = from; - addr_in->sin_addr.s_addr = htonl(0x7f000002); // 127.0.0.2 - addr->size = sizeof(struct sockaddr); - - assert(recvlen > 0 && recvlen <= INT_MAX); - self->push(uint8_t(recvlen >> 8)); - self->push(uint8_t(recvlen & 0xff)); - if (Fuzz_Data::FUZZ_DEBUG) { - std::printf("%s: recvfrom: %zu (%02x, %02x)\n", self->name_, recvlen, - self->recording().end()[-2], self->recording().end()[-1]); - } - self->push(buf, recvlen); - return static_cast(recvlen); - }, - /* .send = */ - ![](Record_System *self, Socket sock, const uint8_t *buf, size_t len) { - // Always succeed. - return static_cast(len); - }, - /* .sendto = */ - ![](Record_System *self, Socket sock, const uint8_t *buf, size_t len, - const Network_Addr *addr) { - assert(sock.value == 42); - auto backend = self->global_.bound.find(get_port(addr)); - assert(backend != self->global_.bound.end()); - backend->second->receive(self->port, buf, len); - return static_cast(len); - }, - /* .socket = */ - ![](Record_System *self, int domain, int type, int proto) { return Socket{42}; }, - /* .socket_nonblock = */ ![](Record_System *self, Socket sock, bool nonblock) { return 0; }, - /* .getsockopt = */ - ![](Record_System *self, Socket sock, int level, int optname, void *optval, size_t *optlen) { - std::memset(optval, 0, *optlen); - return 0; - }, - /* .setsockopt = */ - ![](Record_System *self, Socket sock, int level, int optname, const void *optval, - size_t optlen) { return 0; }, -}; - -static constexpr Tox_Random_Funcs record_random_funcs = { - /* .bytes_callback = */ - ![](Record_System *self, uint8_t *bytes, size_t length) { - for (size_t i = 0; i < length; ++i) { - bytes[i] = simple_rng(self->seed_) & 0xff; - self->push(bytes[i]); - } - if (Fuzz_Data::FUZZ_DEBUG) { - std::printf( - "%s: rng: %02x..%02x[%zu]\n", self->name_, bytes[0], bytes[length - 1], length); - } - }, - /* .uniform_callback = */ - fuzz_random_funcs.uniform_callback, -}; - -Record_System::Record_System(Global &global, uint64_t seed, const char *name) - : System{ - std::make_unique(), - std::make_unique(Tox_Memory{&record_memory_funcs, this}), - std::make_unique(Network{&record_network_funcs, this}), - std::make_unique(Random{&record_random_funcs, this}), - } - , global_(global) - , seed_(seed) - , name_(name) -{ - sys->mono_time_callback = [](void *self) { return static_cast(self)->clock; }; - sys->mono_time_user_data = this; - sys->mem = mem.get(); - sys->ns = ns.get(); - sys->rng = rng.get(); -} - -void Record_System::receive(uint16_t send_port, const uint8_t *buf, size_t len) -{ - assert(port != 0); - recvq.emplace_back(send_port, std::vector{buf, buf + len}); -} diff --git a/testing/fuzzing/fuzz_support.hh b/testing/fuzzing/fuzz_support.hh deleted file mode 100644 index cff634b5..00000000 --- a/testing/fuzzing/fuzz_support.hh +++ /dev/null @@ -1,407 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2021-2025 The TokTok team. - */ - -#ifndef C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H -#define C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../toxcore/tox_private.h" - -struct Fuzz_Data { - static constexpr bool FUZZ_DEBUG = false; - static constexpr std::size_t TRACE_TRAP = -1; // 579; - -private: - const uint8_t *data_; - const uint8_t *base_; - std::size_t size_; - -public: - Fuzz_Data(const uint8_t *input_data, std::size_t input_size) - : data_(input_data) - , base_(input_data) - , size_(input_size) - { - } - - Fuzz_Data &operator=(const Fuzz_Data &rhs) = delete; - Fuzz_Data(const Fuzz_Data &rhs) = delete; - - struct Consumer { - const char *func; - Fuzz_Data &fd; - - operator bool() - { - // Special case because memcpy causes UB for bool (which can't be - // anything other than 0 or 1). - const bool val = fd.data_[0]; - if (FUZZ_DEBUG) { - std::printf("consume@%zu(%s): bool %s\n", fd.pos(), func, val ? "true" : "false"); - } - ++fd.data_; - --fd.size_; - return val; - } - - template - operator T() - { - const uint8_t *bytes = fd.consume(func, sizeof(T)); - T val; - std::memcpy(&val, bytes, sizeof(T)); - return val; - } - }; - - Consumer consume1(const char *func) { return Consumer{func, *this}; } - std::size_t size() const { return size_; } - std::size_t pos() const { return data_ - base_; } - const uint8_t *data() const { return data_; } - bool empty() const { return size_ == 0; } - - const uint8_t *consume(const char *func, std::size_t count) - { - const uint8_t *val = data_; - if (FUZZ_DEBUG) { - if (pos() == TRACE_TRAP) { - __asm__("int $3"); - } - if (count == 1) { - std::printf("consume@%zu(%s): %d (0x%02x)\n", pos(), func, val[0], val[0]); - } else if (count != 0) { - std::printf("consume@%zu(%s): %02x..%02x[%zu]\n", pos(), func, val[0], - val[count - 1], count); - } - } - data_ += count; - size_ -= count; - return val; - } -}; - -/** @brief Consumes 1 byte of the fuzzer input or returns if no data available. - * - * This advances the fuzzer input data by 1 byte and consumes that byte in the - * declaration. - * - * @example - * @code - * CONSUME1_OR_RETURN(const uint8_t, one_byte, input); - * @endcode - */ -#define CONSUME1_OR_RETURN(TYPE, NAME, INPUT) \ - if (INPUT.size() < sizeof(TYPE)) { \ - return; \ - } \ - TYPE NAME = INPUT.consume1(__func__) - -/** @brief Consumes 1 byte of the fuzzer input or returns a value if no data - * available. - * - * This advances the fuzzer input data by 1 byte and consumes that byte in the - * declaration. - * - * @example - * @code - * CONSUME1_OR_RETURN_VAL(const uint8_t one_byte, input, nullptr); - * @endcode - */ -#define CONSUME1_OR_RETURN_VAL(TYPE, NAME, INPUT, VAL) \ - if (INPUT.size() < sizeof(TYPE)) { \ - return VAL; \ - } \ - TYPE NAME = INPUT.consume1(__func__) - -/** @brief Consumes SIZE bytes of the fuzzer input or returns if not enough data available. - * - * This advances the fuzzer input data by SIZE byte and consumes those bytes in - * the declaration. If less than SIZE bytes are available in the fuzzer input, - * this macro returns from the enclosing function. - * - * @example - * @code - * CONSUME_OR_RETURN(const uint8_t *ten_bytes, input, 10); - * @endcode - */ -#define CONSUME_OR_RETURN(DECL, INPUT, SIZE) \ - if (INPUT.size() < SIZE) { \ - return; \ - } \ - DECL = INPUT.consume(__func__, SIZE) - -#define CONSUME_OR_RETURN_VAL(DECL, INPUT, SIZE, VAL) \ - if (INPUT.size() < SIZE) { \ - return VAL; \ - } \ - DECL = INPUT.consume(__func__, SIZE) - -#define CONSUME_OR_ABORT(DECL, INPUT, SIZE) \ - if (INPUT.size() < SIZE) { \ - abort(); \ - } \ - DECL = INPUT.consume(__func__, SIZE) - -using Fuzz_Target = void (*)(Fuzz_Data &input); - -template -struct Fuzz_Target_Selector; - -template -struct Fuzz_Target_Selector { - static void select(uint8_t selector, Fuzz_Data &input) - { - if (selector == sizeof...(Args)) { - return Arg(input); - } - return Fuzz_Target_Selector::select(selector, input); - } -}; - -template <> -struct Fuzz_Target_Selector<> { - static void select(uint8_t selector, Fuzz_Data &input) - { - // The selector selected no function, so we do nothing and rely on the - // fuzzer to come up with a better selector. - } -}; - -template -void fuzz_select_target(const uint8_t *data, std::size_t size) -{ - Fuzz_Data input{data, size}; - - CONSUME1_OR_RETURN(const uint8_t, selector, input); - return Fuzz_Target_Selector::select(selector, input); -} - -struct Tox_Memory; -struct Network; -struct Tox_Random; - -struct System { - /** @brief Deterministic system clock for this instance. - * - * Different instances can evolve independently. The time is initialised - * with a large number, because otherwise many zero-initialised "empty" - * friends inside toxcore will be "not timed out" for a long time, messing - * up some logic. Tox moderately depends on the clock being fairly high up - * (not close to 0). - * - * We make it a nice large round number so we can recognise it when debugging. - */ - uint64_t clock = 1000000000; - - std::unique_ptr sys; - std::unique_ptr mem; - std::unique_ptr ns; - std::unique_ptr rng; - - System(std::unique_ptr sys, std::unique_ptr mem, - std::unique_ptr ns, std::unique_ptr rng); - System(System &&); - - // Not inline because sizeof of the above 2 structs is not known everywhere. - ~System(); - - /** - * During bootstrap, move the time forward a decent amount, because friend - * finding and bootstrapping takes significant (around 10 seconds) wall - * clock time that should be advanced more quickly in the test. - */ - static constexpr uint8_t BOOTSTRAP_ITERATION_INTERVAL = 200; - /** - * Less than BOOTSTRAP_ITERATION_INTERVAL because otherwise we'll spam - * onion announce packets. - */ - static constexpr uint8_t MESSAGE_ITERATION_INTERVAL = 20; - /** - * Move the clock forward at least 20ms so at least some amount of - * time passes on each iteration. - */ - static constexpr uint8_t MIN_ITERATION_INTERVAL = 20; -}; - -/** - * A Tox_System implementation that consumes fuzzer input to produce network - * inputs and random numbers. Once it runs out of fuzzer input, network receive - * functions return no more data and the random numbers are always zero. - */ -struct Fuzz_System : System { - Fuzz_Data &data; - - explicit Fuzz_System(Fuzz_Data &input); -}; - -/** - * A Tox_System implementation that consumes no fuzzer input but still has a - * working and deterministic RNG. Network receive functions always fail, send - * always succeeds. - */ -struct Null_System : System { - uint64_t seed = 4; // chosen by fair dice roll. guaranteed to be random. - - Null_System(); -}; - -template -class int_map { -public: - struct iterator { - std::pair pair; - - bool operator==(const iterator &rhs) const { return pair.first == rhs.pair.first; } - bool operator!=(const iterator &rhs) const { return pair.first != rhs.pair.first; } - - std::pair operator*() const { return pair; } - const std::pair *operator->() const { return &pair; } - }; - - int_map() = default; - ~int_map() = default; - - iterator find(uint16_t key) const - { - if (!values[key]) { - return end(); - } - return {{key, values[key]}}; - } - - iterator end() const { return {{static_cast(values.size()), nullptr}}; } - - void emplace(uint16_t key, V value) { values[key] = value; } - -private: - std::array values; -}; - -/** - * A Tox_System implementation that records all I/O but does not actually - * perform any real I/O. Everything inside this system is hermetic in-process - * and fully deterministic. - * - * Note: take care not to initialise two systems with the same seed, since - * that's the only thing distinguishing the system's behaviour. Two toxes - * initialised with the same seed will be identical (same keys, etc.). - */ -struct Record_System : System { - static constexpr bool FUZZ_DEBUG = Fuzz_Data::FUZZ_DEBUG; - - /** @brief State shared between all tox instances. */ - struct Global { - /** @brief Bound UDP ports and their system instance. - * - * This implements an in-process network where instances can send - * packets to other instances by inserting them into the receiver's - * recvq using the receive function. - * - * We need to keep track of ports associated with recv queues because - * toxcore sends packets to itself sometimes when doing onion routing - * with only 2 nodes in the network. - */ - int_map bound; - }; - - Global &global_; - uint64_t seed_; //!< Current PRNG state. - const char *name_; //!< Tox system name ("tox1"/"tox2") for logging. - - std::deque>> recvq; - uint16_t port = 0; //!< Sending port for this system instance. - - Record_System(Global &global, uint64_t seed, const char *name); - Record_System(const Record_System &) = delete; - Record_System operator=(const Record_System &) = delete; - - /** @brief Deposit a network packet in this instance's recvq. - */ - void receive(uint16_t send_port, const uint8_t *buf, size_t len); - - void push(bool byte) - { - if (FUZZ_DEBUG) { - if (recording_.size() == Fuzz_Data::TRACE_TRAP) { - __asm__("int $3"); - } - std::printf( - "%s: produce@%zu(bool %s)\n", name_, recording_.size(), byte ? "true" : "false"); - } - recording_.push_back(byte); - } - - void push(uint8_t byte) - { - if (FUZZ_DEBUG) { - if (recording_.size() == Fuzz_Data::TRACE_TRAP) { - __asm__("int $3"); - } - std::printf("%s: produce@%zu(%u (0x%02x))\n", name_, recording_.size(), byte, byte); - } - recording_.push_back(byte); - } - - void push(const uint8_t *bytes, std::size_t size) - { - if (FUZZ_DEBUG) { - if (recording_.size() == Fuzz_Data::TRACE_TRAP) { - __asm__("int $3"); - } - std::printf("%s: produce@%zu(%02x..%02x[%zu])\n", name_, recording_.size(), bytes[0], - bytes[size - 1], size); - } - recording_.insert(recording_.end(), bytes, bytes + size); - } - - template - void push(const char (&bytes)[N]) - { - push(reinterpret_cast(bytes), N - 1); - } - - const std::vector &recording() const { return recording_; } - std::vector take_recording() const { return std::move(recording_); } - -private: - std::vector recording_; -}; - -/** @brief Enable debug logging. - * - * This should not be enabled in fuzzer code while fuzzing, as console I/O slows - * everything down drastically. It's useful while developing the fuzzer and the - * protodump program. - */ -extern const bool FUZZ_DEBUG; - -inline constexpr char tox_log_level_name(Tox_Log_Level level) -{ - switch (level) { - case TOX_LOG_LEVEL_TRACE: - return 'T'; - case TOX_LOG_LEVEL_DEBUG: - return 'D'; - case TOX_LOG_LEVEL_INFO: - return 'I'; - case TOX_LOG_LEVEL_WARNING: - return 'W'; - case TOX_LOG_LEVEL_ERROR: - return 'E'; - } - - return '?'; -} - -#endif // C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H diff --git a/testing/fuzzing/fuzz_tox.hh b/testing/fuzzing/fuzz_tox.hh deleted file mode 100644 index 2c23edb5..00000000 --- a/testing/fuzzing/fuzz_tox.hh +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2022-2025 The TokTok team. - */ - -#ifndef C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H -#define C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H - -#include - -#include "../../toxcore/network.h" - -constexpr uint16_t SIZE_IP_PORT = SIZE_IP6 + sizeof(uint16_t); - -template -using Ptr = std::unique_ptr; - -#endif // C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H diff --git a/testing/fuzzing/protodump.cc b/testing/fuzzing/protodump.cc index 3dd781c4..83195c9a 100644 --- a/testing/fuzzing/protodump.cc +++ b/testing/fuzzing/protodump.cc @@ -26,23 +26,41 @@ #include #include #include +#include #include "../../toxcore/tox.h" #include "../../toxcore/tox_dispatch.h" #include "../../toxcore/tox_events.h" #include "../../toxcore/tox_private.h" -#include "fuzz_support.hh" +#include "../support/public/simulated_environment.hh" namespace { -/** @brief Number of messages to exchange between tox1 and tox2. - * - * The higher this number, the more room we give the fuzzer to mutate the - * exchange into something more interesting. If it's too high, the fuzzer will - * be slow. - */ +using tox::test::FakeClock; +using tox::test::Packet; +using tox::test::ScopedToxSystem; +using tox::test::SimulatedEnvironment; + constexpr uint32_t MESSAGE_COUNT = 5; +class Recorder { +public: + std::vector data; + + void push(bool val) { data.push_back(val); } + void push(uint8_t val) { data.push_back(val); } + void push(const uint8_t *bytes, size_t size) { data.insert(data.end(), bytes, bytes + size); } + + // Format: 2 bytes length (big-endian), then data. + void push_packet(const uint8_t *bytes, size_t size) + { + assert(size <= 65535); + push(static_cast(size >> 8)); + push(static_cast(size & 0xFF)); + push(bytes, size); + } +}; + struct State { Tox *tox; uint32_t done; @@ -91,7 +109,6 @@ void setup_callbacks(Tox_Dispatch *dispatch) tox_events_callback_friend_connection_status( dispatch, [](const Tox_Event_Friend_Connection_Status *event, void *user_data) { State *state = static_cast(user_data); - // OK: friend came online. const uint32_t friend_number = tox_event_friend_connection_status_get_friend_number(event); assert(friend_number == 0); @@ -163,9 +180,7 @@ void setup_callbacks(Tox_Dispatch *dispatch) assert(!tox_event_friend_typing_get_typing(event)); }); tox_events_callback_self_connection_status( - dispatch, [](const Tox_Event_Self_Connection_Status *event, void *user_data) { - // OK: we got connected. - }); + dispatch, [](const Tox_Event_Self_Connection_Status *event, void *user_data) {}); } void dump(std::vector recording, const char *filename) @@ -177,69 +192,80 @@ void dump(std::vector recording, const char *filename) void RecordBootstrap(const char *init, const char *bootstrap) { - auto global = std::make_unique(); + SimulatedEnvironment env1; + SimulatedEnvironment env2; + + // Set deterministic seeds. + std::minstd_rand rng1(4); + env1.fake_random().set_entropy_source([&](uint8_t *out, size_t count) { + std::uniform_int_distribution dist(0, 255); + for (size_t i = 0; i < count; ++i) + out[i] = static_cast(dist(rng1)); + }); + + std::minstd_rand rng2(5); + env2.fake_random().set_entropy_source([&](uint8_t *out, size_t count) { + std::uniform_int_distribution dist(0, 255); + for (size_t i = 0; i < count; ++i) + out[i] = static_cast(dist(rng2)); + }); + + Recorder recorder1; + Recorder recorder2; + + env1.fake_memory().set_observer([&](bool success) { recorder1.push(success); }); + + env1.fake_random().set_observer( + [&](const uint8_t *data, size_t count) { recorder1.push(data, count); }); + + auto node1 = env1.create_node(33445); + auto node2 = env2.create_node(33446); + + // Record received packets. + node1->endpoint->set_recv_observer([&](const std::vector &data, const IP_Port &from) { + recorder1.push_packet(data.data(), data.size()); + }); + + // Bridge the two simulated networks. + env1.simulation().net().add_observer( + [&](const Packet &p) { env2.simulation().net().send_packet(p); }); + + env2.simulation().net().add_observer( + [&](const Packet &p) { env1.simulation().net().send_packet(p); }); Tox_Options *opts = tox_options_new(nullptr); - assert(opts != nullptr); - tox_options_set_local_discovery_enabled(opts, false); - tox_options_set_log_callback(opts, - [](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, - const char *message, void *user_data) { - // Log to stdout. - std::printf("[%s] %c %s:%d(%s): %s\n", static_cast(user_data)->name_, - tox_log_level_name(level), file, line, func, message); - }); + Tox_Options_Testing opts_test1; + opts_test1.operating_system = &node1->system; Tox_Err_New error_new; Tox_Err_New_Testing error_new_testing; - Tox_Options_Testing tox_options_testing; - auto sys1 = std::make_unique(*global, 4, "tox1"); // fair dice roll - tox_options_set_log_user_data(opts, sys1.get()); - tox_options_testing.operating_system = sys1->sys.get(); - Tox *tox1 = tox_new_testing(opts, &error_new, &tox_options_testing, &error_new_testing); + Tox *tox1 = tox_new_testing(opts, &error_new, &opts_test1, &error_new_testing); assert(tox1 != nullptr); - assert(error_new == TOX_ERR_NEW_OK); - assert(error_new_testing == TOX_ERR_NEW_TESTING_OK); - std::array address1; - tox_self_get_address(tox1, address1.data()); - std::array pk1; - tox_self_get_public_key(tox1, pk1.data()); - std::array dht_key1; - tox_self_get_dht_id(tox1, dht_key1.data()); - auto sys2 = std::make_unique(*global, 5, "tox2"); // unfair dice roll - tox_options_set_log_user_data(opts, sys2.get()); - tox_options_testing.operating_system = sys2->sys.get(); - Tox *tox2 = tox_new_testing(opts, &error_new, &tox_options_testing, &error_new_testing); + Tox_Options_Testing opts_test2; + opts_test2.operating_system = &node2->system; + + Tox *tox2 = tox_new_testing(opts, &error_new, &opts_test2, &error_new_testing); assert(tox2 != nullptr); - assert(error_new == TOX_ERR_NEW_OK); - assert(error_new_testing == TOX_ERR_NEW_TESTING_OK); - std::array address2; - tox_self_get_address(tox2, address2.data()); - std::array pk2; - tox_self_get_public_key(tox2, pk2.data()); - std::array dht_key2; - tox_self_get_dht_id(tox2, dht_key2.data()); - - assert(address1 != address2); - assert(pk1 != pk2); - assert(dht_key1 != dht_key2); tox_options_free(opts); - const uint16_t port = tox_self_get_udp_port(tox1, nullptr); + std::array address1; + tox_self_get_address(tox1, address1.data()); + std::array dht_key1; + tox_self_get_dht_id(tox1, dht_key1.data()); - const bool udp_success = tox_bootstrap(tox2, "127.0.0.2", port, dht_key1.data(), nullptr); + // Bootstrap tox2 to tox1. + const bool udp_success = tox_bootstrap(tox2, "127.0.0.1", 33445, dht_key1.data(), nullptr); assert(udp_success); tox_events_init(tox1); tox_events_init(tox2); Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); - assert(dispatch != nullptr); setup_callbacks(dispatch); State state1 = {tox1, 0}; @@ -250,35 +276,26 @@ void RecordBootstrap(const char *init, const char *bootstrap) Tox_Events *events; events = tox_events_iterate(tox1, true, &error_iterate); - assert(tox_events_equal(sys1->sys.get(), events, events)); tox_dispatch_invoke(dispatch, events, &state1); tox_events_free(events); events = tox_events_iterate(tox2, true, &error_iterate); - assert(tox_events_equal(sys2->sys.get(), events, events)); tox_dispatch_invoke(dispatch, events, &state2); tox_events_free(events); - // Move the clock forward a decent amount so all the time-based checks - // trigger more quickly. - sys1->clock += clock_increment; - sys2->clock += clock_increment; + // Record the clock increment. + env1.fake_clock().advance(clock_increment); + recorder1.push(clock_increment); - if (Fuzz_Data::FUZZ_DEBUG) { - printf("tox1: rng: %d (for clock)\n", clock_increment); - printf("tox2: rng: %d (for clock)\n", clock_increment); - } - sys1->push(clock_increment); - sys2->push(clock_increment); + env2.fake_clock().advance(clock_increment); + + env1.simulation().net().process_events(env1.fake_clock().current_time_ms()); + env2.simulation().net().process_events(env2.fake_clock().current_time_ms()); }; while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE) { - if (Fuzz_Data::FUZZ_DEBUG) { - std::printf("tox1: %d, tox2: %d\n", tox_self_get_connection_status(tox1), - tox_self_get_connection_status(tox2)); - } - iterate(System::BOOTSTRAP_ITERATION_INTERVAL); + iterate(200); } std::printf("toxes are online\n"); @@ -289,27 +306,18 @@ void RecordBootstrap(const char *init, const char *bootstrap) while (tox_friend_get_connection_status(tox2, friend_number, nullptr) == TOX_CONNECTION_NONE || tox_friend_get_connection_status(tox1, 0, nullptr) == TOX_CONNECTION_NONE) { - if (Fuzz_Data::FUZZ_DEBUG) { - std::printf("tox1: %d, tox2: %d, tox1 -> tox2: %d, tox2 -> tox1: %d\n", - tox_self_get_connection_status(tox1), tox_self_get_connection_status(tox2), - tox_friend_get_connection_status(tox1, 0, nullptr), - tox_friend_get_connection_status(tox2, 0, nullptr)); - } - iterate(System::BOOTSTRAP_ITERATION_INTERVAL); + iterate(200); } std::printf("tox clients connected\n"); - dump(sys1->take_recording(), init); + dump(recorder1.data, init); + + // Clear the recorder. + recorder1.data.clear(); while (state1.done < MESSAGE_COUNT && state2.done < MESSAGE_COUNT) { - if (Fuzz_Data::FUZZ_DEBUG) { - std::printf("tox1: %d, tox2: %d, tox1 -> tox2: %d, tox2 -> tox1: %d\n", - tox_self_get_connection_status(tox1), tox_self_get_connection_status(tox2), - tox_friend_get_connection_status(tox1, 0, nullptr), - tox_friend_get_connection_status(tox2, 0, nullptr)); - } - iterate(System::MESSAGE_ITERATION_INTERVAL); + iterate(20); } std::printf("test complete\n"); @@ -318,7 +326,7 @@ void RecordBootstrap(const char *init, const char *bootstrap) tox_kill(tox2); tox_kill(tox1); - dump(sys1->recording(), bootstrap); + dump(recorder1.data, bootstrap); } } diff --git a/testing/fuzzing/protodump_reduce.cc b/testing/fuzzing/protodump_reduce.cc deleted file mode 100644 index 53d39d0b..00000000 --- a/testing/fuzzing/protodump_reduce.cc +++ /dev/null @@ -1,213 +0,0 @@ -#include -#include - -#include "../../toxcore/crypto_core.h" -#include "../../toxcore/tox.h" -#include "../../toxcore/tox_dispatch.h" -#include "../../toxcore/tox_events.h" -#include "../../toxcore/tox_private.h" -#include "fuzz_support.hh" -#include "fuzz_tox.hh" - -namespace { - -constexpr bool PROTODUMP_DEBUG = Fuzz_Data::FUZZ_DEBUG; - -void setup_callbacks(Tox_Dispatch *dispatch) -{ - tox_events_callback_conference_connected( - dispatch, [](const Tox_Event_Conference_Connected *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_conference_connected( - dispatch, [](const Tox_Event_Conference_Connected *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_conference_invite( - dispatch, [](const Tox_Event_Conference_Invite *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_conference_message( - dispatch, [](const Tox_Event_Conference_Message *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_conference_peer_list_changed( - dispatch, [](const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_conference_peer_name( - dispatch, [](const Tox_Event_Conference_Peer_Name *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_conference_title(dispatch, - [](const Tox_Event_Conference_Title *event, void *user_data) { assert(event == nullptr); }); - tox_events_callback_file_chunk_request( - dispatch, [](const Tox_Event_File_Chunk_Request *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_file_recv(dispatch, - [](const Tox_Event_File_Recv *event, void *user_data) { assert(event == nullptr); }); - tox_events_callback_file_recv_chunk(dispatch, - [](const Tox_Event_File_Recv_Chunk *event, void *user_data) { assert(event == nullptr); }); - tox_events_callback_file_recv_control( - dispatch, [](const Tox_Event_File_Recv_Control *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_friend_connection_status( - dispatch, [](const Tox_Event_Friend_Connection_Status *event, void *user_data) { - Tox *tox = static_cast(user_data); - // OK: friend came online. - const uint32_t friend_number - = tox_event_friend_connection_status_get_friend_number(event); - assert(friend_number == 0); - const uint8_t message = 'A'; - Tox_Err_Friend_Send_Message err; - tox_friend_send_message(tox, friend_number, TOX_MESSAGE_TYPE_NORMAL, &message, 1, &err); - assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); - }); - tox_events_callback_friend_lossless_packet( - dispatch, [](const Tox_Event_Friend_Lossless_Packet *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_friend_lossy_packet( - dispatch, [](const Tox_Event_Friend_Lossy_Packet *event, void *user_data) { - assert(event == nullptr); - }); - tox_events_callback_friend_message( - dispatch, [](const Tox_Event_Friend_Message *event, void *user_data) { - Tox *tox = static_cast(user_data); - const uint32_t friend_number = tox_event_friend_message_get_friend_number(event); - assert(friend_number == 0); - const uint32_t message_length = tox_event_friend_message_get_message_length(event); - assert(message_length == 1); - const uint8_t *message = tox_event_friend_message_get_message(event); - const uint8_t reply = message[0] + 1; - Tox_Err_Friend_Send_Message err; - tox_friend_send_message(tox, friend_number, TOX_MESSAGE_TYPE_NORMAL, &reply, 1, &err); - assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); - }); - tox_events_callback_friend_name( - dispatch, [](const Tox_Event_Friend_Name *event, void *user_data) { - const uint32_t friend_number = tox_event_friend_name_get_friend_number(event); - assert(friend_number == 0); - }); - tox_events_callback_friend_read_receipt( - dispatch, [](const Tox_Event_Friend_Read_Receipt *event, void *user_data) { - const uint32_t friend_number = tox_event_friend_read_receipt_get_friend_number(event); - assert(friend_number == 0); - const uint32_t message_id = tox_event_friend_read_receipt_get_message_id(event); - uint32_t *done = static_cast(user_data); - *done = std::max(*done, message_id); - }); - tox_events_callback_friend_request( - dispatch, [](const Tox_Event_Friend_Request *event, void *user_data) { - Tox *tox = static_cast(user_data); - Tox_Err_Friend_Add err; - tox_friend_add_norequest(tox, tox_event_friend_request_get_public_key(event), &err); - }); - tox_events_callback_friend_status( - dispatch, [](const Tox_Event_Friend_Status *event, void *user_data) { - const uint32_t friend_number = tox_event_friend_status_get_friend_number(event); - assert(friend_number == 0); - }); - tox_events_callback_friend_status_message( - dispatch, [](const Tox_Event_Friend_Status_Message *event, void *user_data) { - const uint32_t friend_number = tox_event_friend_status_message_get_friend_number(event); - assert(friend_number == 0); - }); - tox_events_callback_friend_typing( - dispatch, [](const Tox_Event_Friend_Typing *event, void *user_data) { - const uint32_t friend_number = tox_event_friend_typing_get_friend_number(event); - assert(friend_number == 0); - assert(!tox_event_friend_typing_get_typing(event)); - }); - tox_events_callback_self_connection_status( - dispatch, [](const Tox_Event_Self_Connection_Status *event, void *user_data) { - // OK: we got connected. - }); -} - -void TestEndToEnd(Fuzz_Data &input) -{ - /** - * Whether to abort the program if a friend connection can be established. - * - * This is useful to make the fuzzer produce minimal startup data so the - * interesting part of the fuzzer (the part that comes after the friend - * connection is established) can run sooner and thus more frequently. - */ - const bool PROTODUMP_REDUCE = getenv("PROTODUMP_REDUCE") != nullptr; - - Fuzz_System sys(input); - - Ptr opts(tox_options_new(nullptr), tox_options_free); - assert(opts != nullptr); - tox_options_set_local_discovery_enabled(opts.get(), false); - - Tox_Options_Testing tox_options_testing; - tox_options_testing.operating_system = sys.sys.get(); - - tox_options_set_log_callback(opts.get(), - [](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, - const char *message, void *user_data) { - // Log to stdout. - if (PROTODUMP_DEBUG) { - std::printf("[tox1] %c %s:%d(%s): %s\n", tox_log_level_name(level), file, line, - func, message); - } - }); - - Tox_Err_New error_new; - Tox_Err_New_Testing error_new_testing; - Tox *tox = tox_new_testing(opts.get(), &error_new, &tox_options_testing, &error_new_testing); - - if (tox == nullptr) { - // It might fail, because some I/O happens in tox_new, and the fuzzer - // might do things that make that I/O fail. - return; - } - - assert(error_new == TOX_ERR_NEW_OK); - assert(error_new_testing == TOX_ERR_NEW_TESTING_OK); - - tox_events_init(tox); - - Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); - assert(dispatch != nullptr); - setup_callbacks(dispatch); - - while (!input.empty()) { - Tox_Err_Events_Iterate error_iterate; - Tox_Events *events = tox_events_iterate(tox, true, &error_iterate); - tox_events_equal(tox_get_system(tox), events, events); // TODO(iphydf): assert? - tox_dispatch_invoke(dispatch, events, tox); - tox_events_free(events); - const uint8_t clock_increment = random_u08(sys.rng.get()); - if (PROTODUMP_DEBUG) { - printf("clock increment: %d\n", clock_increment); - } - sys.clock += std::max(System::MIN_ITERATION_INTERVAL, clock_increment); - } - - if (PROTODUMP_REDUCE) { - assert(tox_friend_get_connection_status(tox, 0, nullptr) != 2); - } else { - printf("friend: %d\n", tox_friend_get_connection_status(tox, 0, nullptr)); - printf("self: %d\n", tox_self_get_connection_status(tox)); - assert(tox_friend_get_connection_status(tox, 0, nullptr) == 2); - assert(input.empty()); - } - - tox_dispatch_free(dispatch); - tox_kill(tox); -} - -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) -{ - Fuzz_Data input{data, size}; - TestEndToEnd(input); - return 0; // Non-zero return values are reserved for future use. -} diff --git a/testing/fuzzing/toxsave_fuzz_test.cc b/testing/fuzzing/toxsave_fuzz_test.cc index 0ba9737f..4c702b83 100644 --- a/testing/fuzzing/toxsave_fuzz_test.cc +++ b/testing/fuzzing/toxsave_fuzz_test.cc @@ -4,10 +4,14 @@ #include "../../toxcore/tox.h" #include "../../toxcore/tox_private.h" -#include "fuzz_support.hh" +#include "../support/public/fuzz_data.hh" +#include "../support/public/simulated_environment.hh" namespace { +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + void TestSaveDataLoading(Fuzz_Data &input) { Tox_Err_Options_New error_options; @@ -27,10 +31,12 @@ void TestSaveDataLoading(Fuzz_Data &input) tox_options_set_savedata_type(tox_options, TOX_SAVEDATA_TYPE_TOX_SAVE); Tox_Options_Testing tox_options_testing; - Null_System sys; - tox_options_testing.operating_system = sys.sys.get(); + SimulatedEnvironment env; + auto node = env.create_node(33445); + tox_options_testing.operating_system = &node->system; - Tox *tox = tox_new_testing(tox_options, nullptr, &tox_options_testing, nullptr); + Tox_Err_New_Testing err_testing; + Tox *tox = tox_new_testing(tox_options, nullptr, &tox_options_testing, &err_testing); tox_options_free(tox_options); if (tox == nullptr) { // Tox save was invalid, we're finished here diff --git a/testing/support/BUILD.bazel b/testing/support/BUILD.bazel new file mode 100644 index 00000000..bc32df56 --- /dev/null +++ b/testing/support/BUILD.bazel @@ -0,0 +1,111 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +cc_library( + name = "support", + srcs = [ + "doubles/fake_network_stack.cc", + "doubles/fake_sockets.cc", + "doubles/network_universe.cc", + "src/clock.cc", + "src/environment.cc", + "src/fake_clock.cc", + "src/fake_memory.cc", + "src/fake_random.cc", + "src/fuzz_helpers.cc", + "src/memory.cc", + "src/network.cc", + "src/random.cc", + "src/simulated_environment.cc", + "src/simulation.cc", + "src/tox_network.cc", + ], + hdrs = [ + "doubles/fake_clock.hh", + "doubles/fake_memory.hh", + "doubles/fake_network_stack.hh", + "doubles/fake_random.hh", + "doubles/fake_sockets.hh", + "doubles/network_universe.hh", + "public/clock.hh", + "public/environment.hh", + "public/fuzz_data.hh", + "public/fuzz_helpers.hh", + "public/memory.hh", + "public/network.hh", + "public/random.hh", + "public/simulated_environment.hh", + "public/simulation.hh", + "public/tox_network.hh", + ], + copts = select({ + "//tools/config:windows": ["/wd4200"], # Zero-sized array in struct/union + "//conditions:default": [], + }), + visibility = ["//visibility:public"], + deps = [ + "//c-toxcore/toxcore:mem", + "//c-toxcore/toxcore:network", + "//c-toxcore/toxcore:tox", + "//c-toxcore/toxcore:tox_memory", + "//c-toxcore/toxcore:tox_options", + "//c-toxcore/toxcore:tox_random", + "@psocket", + ], +) + +cc_test( + name = "fake_sockets_test", + srcs = ["doubles/fake_sockets_test.cc"], + deps = [ + ":support", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + "@psocket", + ], +) + +cc_test( + name = "fake_network_stack_test", + srcs = ["doubles/fake_network_stack_test.cc"], + deps = [ + ":support", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + "@psocket", + ], +) + +cc_test( + name = "network_universe_test", + srcs = ["doubles/network_universe_test.cc"], + deps = [ + ":support", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + "@psocket", + ], +) + +cc_test( + name = "bootstrap_scaling_test", + srcs = ["bootstrap_scaling_test.cc"], + deps = [ + ":support", + "//c-toxcore/toxcore:network", + "//c-toxcore/toxcore:tox", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "tox_network_test", + timeout = "long", + srcs = ["tox_network_test.cc"], + deps = [ + ":support", + "//c-toxcore/toxcore:tox", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/testing/support/CMakeLists.txt b/testing/support/CMakeLists.txt new file mode 100644 index 00000000..00da142e --- /dev/null +++ b/testing/support/CMakeLists.txt @@ -0,0 +1,72 @@ +if(NOT UNITTEST) + return() +endif() + +set(support_SOURCES + doubles/fake_network_stack.cc + doubles/fake_sockets.cc + doubles/network_universe.cc + src/clock.cc + src/environment.cc + src/fake_clock.cc + src/fake_memory.cc + src/fake_random.cc + src/fuzz_helpers.cc + src/memory.cc + src/network.cc + src/random.cc + src/simulated_environment.cc + src/simulation.cc + src/tox_network.cc + doubles/fake_clock.hh + doubles/fake_memory.hh + doubles/fake_network_stack.hh + doubles/fake_random.hh + doubles/fake_sockets.hh + doubles/network_universe.hh + public/clock.hh + public/environment.hh + public/fuzz_data.hh + public/fuzz_helpers.hh + public/memory.hh + public/network.hh + public/random.hh + public/simulated_environment.hh + public/simulation.hh + public/tox_network.hh +) + +add_library(support STATIC ${support_SOURCES}) + +if(TARGET toxcore_static) + target_link_libraries(support PRIVATE toxcore_static) +else() + target_link_libraries(support PRIVATE toxcore_shared) +endif() + +if(TARGET pthreads4w::pthreads4w) + target_link_libraries(support PUBLIC pthreads4w::pthreads4w) +elseif(TARGET PThreads4W::PThreads4W) + target_link_libraries(support PUBLIC PThreads4W::PThreads4W) +elseif(TARGET Threads::Threads) + target_link_libraries(support PUBLIC Threads::Threads) +endif() + +if(TARGET GTest::gtest_main) + function(support_test target source) + add_executable(${target} ${source}) + target_link_libraries(${target} PRIVATE support GTest::gtest_main) + if(TARGET toxcore_static) + target_link_libraries(${target} PRIVATE toxcore_static) + else() + target_link_libraries(${target} PRIVATE toxcore_shared) + endif() + add_test(NAME ${target} COMMAND ${target}) + endfunction() + + support_test(fake_sockets_test doubles/fake_sockets_test.cc) + support_test(fake_network_stack_test doubles/fake_network_stack_test.cc) + support_test(network_universe_test doubles/network_universe_test.cc) + support_test(bootstrap_scaling_test bootstrap_scaling_test.cc) + support_test(tox_network_test tox_network_test.cc) +endif() diff --git a/testing/support/bootstrap_scaling_test.cc b/testing/support/bootstrap_scaling_test.cc new file mode 100644 index 00000000..989a3689 --- /dev/null +++ b/testing/support/bootstrap_scaling_test.cc @@ -0,0 +1,109 @@ +// clang-format off +#include "public/simulation.hh" +// clang-format on + +#include + +#include +#include +#include + +#include "../../toxcore/network.h" +#include "../../toxcore/tox.h" + +namespace tox::test { +namespace { + + class BootstrapScalingTest : public ::testing::Test { + protected: + Simulation sim; + }; + + TEST_F(BootstrapScalingTest, TwentyNodes) + { + sim.net().set_verbose(false); + const int num_nodes = 20; + std::vector> nodes; + std::vector toxes; + + std::cerr << "[Test] Creating " << num_nodes << " nodes..." << std::endl; + + // Create all nodes and tox instances + for (int i = 0; i < num_nodes; ++i) { + auto node = sim.create_node(); + auto tox = node->create_tox(); + ASSERT_NE(tox, nullptr) << "Failed to create Tox instance " << i; + nodes.push_back(std::move(node)); + toxes.push_back(std::move(tox)); + } + + // Node 0 is the bootstrap target + uint8_t bootstrap_dht_id[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(toxes[0].get(), bootstrap_dht_id); + + FakeUdpSocket *main_sock = nodes[0]->get_primary_socket(); + ASSERT_NE(main_sock, nullptr) << "Node 0 has no primary socket"; + uint16_t bootstrap_port = main_sock->local_port(); + + char bootstrap_ip[TOX_INET_ADDRSTRLEN]; + ip_parse_addr(&nodes[0]->ip, bootstrap_ip, sizeof(bootstrap_ip)); + + std::cerr << "[Test] Bootstrapping to Node 0 at " << bootstrap_ip << ":" << bootstrap_port + << std::endl; + + size_t total_packets = 0; + sim.net().add_observer([&](const Packet &) { total_packets++; }); + + // Everyone else bootstraps to Node 0 + for (int i = 1; i < num_nodes; ++i) { + Tox_Err_Bootstrap err; + bool success = tox_bootstrap( + toxes[i].get(), bootstrap_ip, bootstrap_port, bootstrap_dht_id, &err); + ASSERT_TRUE(success) << "Bootstrap call failed for node " << i << ": " << err; + } + + // Run simulation until everyone is connected or timeout + const uint64_t virtual_timeout_ms = 300000; + bool all_connected = false; + + sim.run_until( + [&]() { + all_connected = true; + size_t connected_count = 0; + for (int i = 0; i < num_nodes; ++i) { + tox_iterate(toxes[i].get(), nullptr); + if (tox_self_get_connection_status(toxes[i].get()) != TOX_CONNECTION_NONE) { + connected_count++; + } else { + all_connected = false; + } + } + + static uint64_t last_print = 0; + if (sim.clock().current_time_ms() - last_print > 5000) { + std::cerr << "[Test] DHT connected: " << connected_count << "/" << num_nodes + << " at " << sim.clock().current_time_ms() + << "ms. Total packets: " << total_packets << std::endl; + last_print = sim.clock().current_time_ms(); + } + + return all_connected; + }, + virtual_timeout_ms); + + if (!all_connected) { + std::cerr << "[Test] Bootstrap failed. Status of nodes:" << std::endl; + for (int i = 0; i < num_nodes; ++i) { + Tox_Connection status = tox_self_get_connection_status(toxes[i].get()); + if (status == TOX_CONNECTION_NONE) { + std::cerr << " Node " << i << " is NOT connected." << std::endl; + } + } + } + + EXPECT_TRUE(all_connected) + << "Only " << (num_nodes - (all_connected ? 0 : 1)) << " nodes connected? Check logs."; + } + +} // namespace +} // namespace tox::test diff --git a/testing/support/doubles/fake_clock.hh b/testing/support/doubles/fake_clock.hh new file mode 100644 index 00000000..0bfadeb5 --- /dev/null +++ b/testing/support/doubles/fake_clock.hh @@ -0,0 +1,23 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H +#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H + +#include "../public/clock.hh" + +namespace tox::test { + +class FakeClock : public ClockSystem { +public: + explicit FakeClock(uint64_t start_time_ms = 1000); + + uint64_t current_time_ms() const override; + uint64_t current_time_s() const override; + + void advance(uint64_t ms); + +private: + uint64_t now_ms_; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H diff --git a/testing/support/doubles/fake_memory.hh b/testing/support/doubles/fake_memory.hh new file mode 100644 index 00000000..4754efb2 --- /dev/null +++ b/testing/support/doubles/fake_memory.hh @@ -0,0 +1,51 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H +#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H + +#include + +#include "../public/memory.hh" + +// Forward declaration +struct Tox_Memory; + +namespace tox::test { + +class FakeMemory : public MemorySystem { +public: + using FailureInjector = std::function; // Return true to fail + using Observer = std::function; + + FakeMemory(); + ~FakeMemory() override; + + void *malloc(size_t size) override; + void *realloc(void *ptr, size_t size) override; + void free(void *ptr) override; + + // Configure failure injection + void set_failure_injector(FailureInjector injector); + + // Configure observer + void set_observer(Observer observer); + + // Get the C-compatible struct + struct Tox_Memory get_c_memory(); + +private: + struct Header { + size_t size; + size_t magic; + }; + static constexpr size_t kMagic = 0xDEADC0DE; + static constexpr size_t kFreeMagic = 0xBAADF00D; + + size_t current_allocation_ = 0; + size_t max_allocation_ = 0; + + FailureInjector failure_injector_; + Observer observer_; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H diff --git a/testing/support/doubles/fake_network_stack.cc b/testing/support/doubles/fake_network_stack.cc new file mode 100644 index 00000000..02aa7ded --- /dev/null +++ b/testing/support/doubles/fake_network_stack.cc @@ -0,0 +1,299 @@ +#include "fake_network_stack.hh" + +#include +#include +#include + +#include "../../../toxcore/mem.h" + +namespace tox::test { + +static const Network_Funcs kVtable = { + .close + = [](void *obj, Socket sock) { return static_cast(obj)->close(sock); }, + .accept + = [](void *obj, Socket sock) { return static_cast(obj)->accept(sock); }, + .bind + = [](void *obj, Socket sock, + const IP_Port *addr) { return static_cast(obj)->bind(sock, addr); }, + .listen + = [](void *obj, Socket sock, + int backlog) { return static_cast(obj)->listen(sock, backlog); }, + .connect = + [](void *obj, Socket sock, const IP_Port *addr) { + return static_cast(obj)->connect(sock, addr); + }, + .recvbuf + = [](void *obj, Socket sock) { return static_cast(obj)->recvbuf(sock); }, + .recv = [](void *obj, Socket sock, uint8_t *buf, + size_t len) { return static_cast(obj)->recv(sock, buf, len); }, + .recvfrom = + [](void *obj, Socket sock, uint8_t *buf, size_t len, IP_Port *addr) { + return static_cast(obj)->recvfrom(sock, buf, len, addr); + }, + .send = [](void *obj, Socket sock, const uint8_t *buf, + size_t len) { return static_cast(obj)->send(sock, buf, len); }, + .sendto = + [](void *obj, Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) { + return static_cast(obj)->sendto(sock, buf, len, addr); + }, + .socket + = [](void *obj, int domain, int type, + int proto) { return static_cast(obj)->socket(domain, type, proto); }, + .socket_nonblock = + [](void *obj, Socket sock, bool nonblock) { + return static_cast(obj)->socket_nonblock(sock, nonblock); + }, + .getsockopt = + [](void *obj, Socket sock, int level, int optname, void *optval, size_t *optlen) { + return static_cast(obj)->getsockopt( + sock, level, optname, optval, optlen); + }, + .setsockopt = + [](void *obj, Socket sock, int level, int optname, const void *optval, size_t optlen) { + return static_cast(obj)->setsockopt( + sock, level, optname, optval, optlen); + }, + .getaddrinfo = + [](void *obj, const Memory *mem, const char *address, int family, int protocol, + IP_Port **addrs) { + FakeNetworkStack *self = static_cast(obj); + if (self->universe().is_verbose()) { + std::cerr << "[FakeNetworkStack] getaddrinfo for " << address << std::endl; + } + if (strcmp(address, "127.0.0.1") == 0 || strcmp(address, "localhost") == 0) { + *addrs = static_cast(mem_alloc(mem, sizeof(IP_Port))); + memset(&(*addrs)->ip, 0, sizeof(IP)); + ip_init(&(*addrs)->ip, false); + (*addrs)->ip.ip.v4.uint32 = net_htonl(0x7F000001); + (*addrs)->port = 0; + return 1; + } + + IP ip; + if (addr_parse_ip(address, &ip)) { + *addrs = static_cast(mem_alloc(mem, sizeof(IP_Port))); + (*addrs)->ip = ip; + (*addrs)->port = 0; + if (self->universe().is_verbose()) { + std::cerr << "[FakeNetworkStack] resolved " << address << std::endl; + } + return 1; + } + return 0; + }, + .freeaddrinfo = + [](void *obj, const Memory *mem, IP_Port *addrs) { + mem_delete(mem, addrs); + return 0; + }, +}; + +FakeNetworkStack::FakeNetworkStack(NetworkUniverse &universe, const IP &node_ip) + : universe_(universe) + , node_ip_(node_ip) +{ +} + +FakeNetworkStack::~FakeNetworkStack() = default; + +struct Network FakeNetworkStack::get_c_network() { return Network{&kVtable, this}; } + +Socket FakeNetworkStack::socket(int domain, int type, int protocol) +{ + std::lock_guard lock(mutex_); + int fd = next_fd_++; + + std::unique_ptr sock; + if (type == SOCK_DGRAM) { + if (universe_.is_verbose()) { + std::cerr << "[FakeNetworkStack] create UDP socket fd=" << fd << std::endl; + } + sock = std::make_unique(universe_); + } else if (type == SOCK_STREAM) { + if (universe_.is_verbose()) { + std::cerr << "[FakeNetworkStack] create TCP socket fd=" << fd << std::endl; + } + sock = std::make_unique(universe_); + } else { + // Unknown type + return net_socket_from_native(-1); + } + + sockets_[fd] = std::move(sock); + sockets_[fd]->set_ip(node_ip_); + return net_socket_from_native(fd); +} + +FakeSocket *FakeNetworkStack::get_sock(Socket sock) +{ + std::lock_guard lock(mutex_); + auto it = sockets_.find(net_socket_to_native(sock)); + if (it != sockets_.end()) { + return it->second.get(); + } + return nullptr; +} + +int FakeNetworkStack::close(Socket sock) +{ + std::lock_guard lock(mutex_); + int fd = net_socket_to_native(sock); + auto it = sockets_.find(fd); + if (it == sockets_.end()) { + errno = EBADF; + return -1; + } + it->second->close(); + sockets_.erase(it); + return 0; +} + +// Delegate all others +int FakeNetworkStack::bind(Socket sock, const IP_Port *addr) +{ + if (auto *s = get_sock(sock)) { + int ret = s->bind(addr); + if (universe_.is_verbose() && ret == 0) { + char ip_str[TOX_INET_ADDRSTRLEN]; + ip_parse_addr(&s->ip_address(), ip_str, sizeof(ip_str)); + std::cerr << "[FakeNetworkStack] bound socket to " << ip_str << ":" << s->local_port() + << std::endl; + } + return ret; + } + errno = EBADF; + return -1; +} + +int FakeNetworkStack::connect(Socket sock, const IP_Port *addr) +{ + if (auto *s = get_sock(sock)) + return s->connect(addr); + errno = EBADF; + return -1; +} + +int FakeNetworkStack::listen(Socket sock, int backlog) +{ + if (auto *s = get_sock(sock)) + return s->listen(backlog); + errno = EBADF; + return -1; +} + +Socket FakeNetworkStack::accept(Socket sock) +{ + // This requires creating a new FD + IP_Port addr; + std::unique_ptr new_sock_obj; + + { + auto *s = get_sock(sock); + if (!s) { + errno = EBADF; + return net_socket_from_native(-1); + } + new_sock_obj = s->accept(&addr); + } + + if (!new_sock_obj) { + // errno set by accept + return net_socket_from_native(-1); + } + + std::lock_guard lock(mutex_); + int fd = next_fd_++; + sockets_[fd] = std::move(new_sock_obj); + return net_socket_from_native(fd); +} + +int FakeNetworkStack::send(Socket sock, const uint8_t *buf, size_t len) +{ + if (auto *s = get_sock(sock)) + return s->send(buf, len); + errno = EBADF; + return -1; +} + +int FakeNetworkStack::recv(Socket sock, uint8_t *buf, size_t len) +{ + if (auto *s = get_sock(sock)) + return s->recv(buf, len); + errno = EBADF; + return -1; +} + +int FakeNetworkStack::recvbuf(Socket sock) +{ + if (auto *s = get_sock(sock)) + return s->recv_buffer_size(); + errno = EBADF; + return -1; +} + +int FakeNetworkStack::sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) +{ + if (auto *s = get_sock(sock)) + return s->sendto(buf, len, addr); + errno = EBADF; + return -1; +} + +int FakeNetworkStack::recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr) +{ + if (auto *s = get_sock(sock)) + return s->recvfrom(buf, len, addr); + errno = EBADF; + return -1; +} + +int FakeNetworkStack::socket_nonblock(Socket sock, bool nonblock) +{ + if (auto *s = get_sock(sock)) + return s->socket_nonblock(nonblock); + errno = EBADF; + return -1; +} + +int FakeNetworkStack::getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen) +{ + if (auto *s = get_sock(sock)) + return s->getsockopt(level, optname, optval, optlen); + errno = EBADF; + return -1; +} + +int FakeNetworkStack::setsockopt( + Socket sock, int level, int optname, const void *optval, size_t optlen) +{ + if (auto *s = get_sock(sock)) + return s->setsockopt(level, optname, optval, optlen); + errno = EBADF; + return -1; +} + +FakeUdpSocket *FakeNetworkStack::get_udp_socket(Socket sock) +{ + if (auto *s = get_sock(sock)) { + if (s->type() == SOCK_DGRAM) { + return static_cast(s); + } + } + return nullptr; +} + +std::vector FakeNetworkStack::get_bound_udp_sockets() +{ + std::lock_guard lock(mutex_); + std::vector result; + for (const auto &pair : sockets_) { + FakeSocket *s = pair.second.get(); + if (s->type() == SOCK_DGRAM && s->local_port() != 0) { + result.push_back(static_cast(s)); + } + } + return result; +} + +} // namespace tox::test diff --git a/testing/support/doubles/fake_network_stack.hh b/testing/support/doubles/fake_network_stack.hh new file mode 100644 index 00000000..a09dd282 --- /dev/null +++ b/testing/support/doubles/fake_network_stack.hh @@ -0,0 +1,56 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H +#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H + +#include +#include + +#include "../public/network.hh" +#include "fake_sockets.hh" +#include "network_universe.hh" + +namespace tox::test { + +class FakeNetworkStack : public NetworkSystem { +public: + explicit FakeNetworkStack(NetworkUniverse &universe, const IP &node_ip); + ~FakeNetworkStack() override; + + // NetworkSystem Implementation + Socket socket(int domain, int type, int protocol) override; + int bind(Socket sock, const IP_Port *addr) override; + int close(Socket sock) override; + int sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) override; + int recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr) override; + + int listen(Socket sock, int backlog) override; + Socket accept(Socket sock) override; + int connect(Socket sock, const IP_Port *addr) override; + int send(Socket sock, const uint8_t *buf, size_t len) override; + int recv(Socket sock, uint8_t *buf, size_t len) override; + int recvbuf(Socket sock) override; + + int socket_nonblock(Socket sock, bool nonblock) override; + int getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen) override; + int setsockopt(Socket sock, int level, int optname, const void *optval, size_t optlen) override; + + struct Network get_c_network(); + + // For testing/fuzzing introspection + FakeUdpSocket *get_udp_socket(Socket sock); + std::vector get_bound_udp_sockets(); + + NetworkUniverse &universe() { return universe_; } + +private: + FakeSocket *get_sock(Socket sock); + + NetworkUniverse &universe_; + std::map> sockets_; + int next_fd_ = 100; + IP node_ip_; + std::mutex mutex_; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H diff --git a/testing/support/doubles/fake_network_stack_test.cc b/testing/support/doubles/fake_network_stack_test.cc new file mode 100644 index 00000000..c171363c --- /dev/null +++ b/testing/support/doubles/fake_network_stack_test.cc @@ -0,0 +1,91 @@ +#include "fake_network_stack.hh" + +#include + +#include "network_universe.hh" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#else +#include +#include +#endif + +namespace tox::test { +namespace { + + class FakeNetworkStackTest : public ::testing::Test { + public: + FakeNetworkStackTest() + : stack{universe, make_ip(0x7F000001)} + { + } + ~FakeNetworkStackTest() override; + + protected: + NetworkUniverse universe; + FakeNetworkStack stack; + }; + + FakeNetworkStackTest::~FakeNetworkStackTest() = default; + + TEST_F(FakeNetworkStackTest, SocketCreationAndLifecycle) + { + Socket udp_sock = stack.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + ASSERT_NE(net_socket_to_native(udp_sock), -1); + + // Check introspection + ASSERT_NE(stack.get_udp_socket(udp_sock), nullptr); + + // Bind + IP_Port addr; + ip_init(&addr.ip, false); + addr.ip.ip.v4.uint32 = 0; + addr.port = net_htons(9002); + ASSERT_EQ(stack.bind(udp_sock, &addr), 0); + + // Check introspection again + auto sockets = stack.get_bound_udp_sockets(); + ASSERT_EQ(sockets.size(), 1); + EXPECT_EQ(sockets[0]->local_port(), 9002); + + ASSERT_EQ(stack.close(udp_sock), 0); + ASSERT_EQ(stack.get_bound_udp_sockets().size(), 0); + } + + TEST_F(FakeNetworkStackTest, TcpSocketThroughStack) + { + Socket tcp_sock = stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + ASSERT_NE(net_socket_to_native(tcp_sock), -1); + + IP_Port addr; + ip_init(&addr.ip, false); + addr.ip.ip.v4.uint32 = 0; + addr.port = net_htons(9003); + ASSERT_EQ(stack.bind(tcp_sock, &addr), 0); + ASSERT_EQ(stack.listen(tcp_sock, 5), 0); + + // Connect from another stack + FakeNetworkStack client_stack{universe, make_ip(0x7F000002)}; + Socket client_sock = client_stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + IP_Port server_addr; + ip_init(&server_addr.ip, false); + server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); // Localhost + server_addr.port = net_htons(9003); + + ASSERT_EQ(client_stack.connect(client_sock, &server_addr), -1); + ASSERT_EQ(errno, EINPROGRESS); + + universe.process_events(0); // SYN + universe.process_events(0); // SYN-ACK + universe.process_events(0); // ACK + + Socket accepted = stack.accept(tcp_sock); + ASSERT_NE(net_socket_to_native(accepted), -1); + } + +} // namespace +} // namespace tox::test diff --git a/testing/support/doubles/fake_random.hh b/testing/support/doubles/fake_random.hh new file mode 100644 index 00000000..df2de861 --- /dev/null +++ b/testing/support/doubles/fake_random.hh @@ -0,0 +1,45 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H +#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H + +#include +#include + +#include "../public/random.hh" + +// Forward declaration +struct Tox_Random; + +namespace tox::test { + +class FakeRandom : public RandomSystem { +public: + using EntropySource = std::function; + using Observer = std::function; + + explicit FakeRandom(uint64_t seed); + + uint32_t uniform(uint32_t upper_bound) override; + void bytes(uint8_t *out, size_t count) override; + + /** + * @brief Set a custom entropy source. + * If set, this function will be called to generate random bytes instead of the PRNG. + */ + void set_entropy_source(EntropySource source); + + /** + * @brief Set an observer to record generated bytes. + */ + void set_observer(Observer observer); + + struct Tox_Random get_c_random(); + +private: + std::minstd_rand rng_; + EntropySource entropy_source_; + Observer observer_; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H diff --git a/testing/support/doubles/fake_sockets.cc b/testing/support/doubles/fake_sockets.cc new file mode 100644 index 00000000..10dff41d --- /dev/null +++ b/testing/support/doubles/fake_sockets.cc @@ -0,0 +1,485 @@ +#include "fake_sockets.hh" + +#include +#include +#include +#include + +#include "network_universe.hh" + +namespace tox::test { + +// --- FakeSocket --- + +FakeSocket::FakeSocket(NetworkUniverse &universe, int type) + : universe_(universe) + , type_(type) +{ + ip_init(&ip_, false); + ip_.ip.v4.uint32 = net_htonl(0x7F000001); +} + +FakeSocket::~FakeSocket() = default; + +int FakeSocket::close() +{ + // Override in subclasses to unbind + return 0; +} + +int FakeSocket::getsockopt(int level, int optname, void *optval, size_t *optlen) { return 0; } +int FakeSocket::setsockopt(int level, int optname, const void *optval, size_t optlen) { return 0; } +int FakeSocket::socket_nonblock(bool nonblock) +{ + nonblocking_ = nonblock; + return 0; +} + +// --- FakeUdpSocket --- + +FakeUdpSocket::FakeUdpSocket(NetworkUniverse &universe) + : FakeSocket(universe, SOCK_DGRAM) +{ +} + +FakeUdpSocket::~FakeUdpSocket() { close_impl(); } + +int FakeUdpSocket::close() +{ + std::lock_guard lock(mutex_); + close_impl(); + return 0; +} + +void FakeUdpSocket::close_impl() +{ + if (local_port_ != 0) { + universe_.unbind_udp(ip_, local_port_); + local_port_ = 0; + } +} + +int FakeUdpSocket::bind(const IP_Port *addr) +{ + std::lock_guard lock(mutex_); + if (local_port_ != 0) + return -1; // Already bound + + uint16_t port = addr->port; + if (port == 0) { + port = universe_.find_free_port(ip_); + } else { + port = net_ntohs(port); + } + + if (universe_.bind_udp(ip_, port, this)) { + local_port_ = port; + return 0; + } + errno = EADDRINUSE; + return -1; +} + +int FakeUdpSocket::connect(const IP_Port *addr) +{ + // UDP connect just sets default dest. + // Not strictly needed for toxcore UDP but good for completeness. + return 0; +} + +int FakeUdpSocket::listen(int backlog) +{ + errno = EOPNOTSUPP; + return -1; +} +std::unique_ptr FakeUdpSocket::accept(IP_Port *addr) +{ + errno = EOPNOTSUPP; + return nullptr; +} +int FakeUdpSocket::send(const uint8_t *buf, size_t len) +{ + errno = EDESTADDRREQ; + return -1; +} +int FakeUdpSocket::recv(uint8_t *buf, size_t len) +{ + errno = EOPNOTSUPP; + return -1; +} + +int FakeUdpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr) +{ + std::lock_guard lock(mutex_); + if (local_port_ == 0) { + // Implicit bind + uint16_t p = universe_.find_free_port(ip_); + if (universe_.bind_udp(ip_, p, this)) { + local_port_ = p; + } else { + errno = EADDRINUSE; + return -1; + } + } + + Packet p{}; + // Source + p.from.ip = ip_; + p.from.port = net_htons(local_port_); + p.to = *addr; + p.data.assign(buf, buf + len); + p.is_tcp = false; + + universe_.send_packet(p); + if (universe_.is_verbose()) { + uint32_t tip4 = net_ntohl(addr->ip.ip.v4.uint32); + std::cerr << "[FakeUdpSocket] sent " << len << " bytes from port " << local_port_ << " to " + << ((tip4 >> 24) & 0xFF) << "." << ((tip4 >> 16) & 0xFF) << "." + << ((tip4 >> 8) & 0xFF) << "." << (tip4 & 0xFF) << ":" << net_ntohs(addr->port) + << std::endl; + } + return len; +} + +int FakeUdpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr) +{ + RecvObserver observer_copy; + std::vector data_copy; + IP_Port from_copy; + size_t copy_len = 0; + + { + std::lock_guard lock(mutex_); + + if (recv_queue_.empty() && packet_source_) { + // NOTE: We call packet_source_ with lock held. + // Be careful not to call back into socket methods from packet_source_. + std::vector data; + IP_Port from; + if (packet_source_(data, from)) { + recv_queue_.push_back({std::move(data), from}); + } + } + + if (recv_queue_.empty()) { + errno = EWOULDBLOCK; + return -1; + } + + auto &p = recv_queue_.front(); + copy_len = std::min(len, p.data.size()); + std::memcpy(buf, p.data.data(), copy_len); + *addr = p.from; + + if (recv_observer_) { + observer_copy = recv_observer_; + data_copy = p.data; + from_copy = p.from; + } + + recv_queue_.pop_front(); + } + + if (observer_copy) { + observer_copy(data_copy, from_copy); + } + + if (universe_.is_verbose()) { + std::cerr << "[FakeUdpSocket] recv " << copy_len << " bytes at port " << local_port_ + << " from port " << net_ntohs(addr->port) << std::endl; + } + + return copy_len; +} + +void FakeUdpSocket::push_packet(std::vector data, IP_Port from) +{ + std::lock_guard lock(mutex_); + if (universe_.is_verbose()) { + uint32_t fip4 = net_ntohl(from.ip.ip.v4.uint32); + std::cerr << "[FakeUdpSocket] push " << data.size() << " bytes into queue for " + << ((ip_.ip.v4.uint32 >> 24) & 0xFF) + << "." // ip_ is in network order from net_htonl + << ((ip_.ip.v4.uint32 >> 16) & 0xFF) << "." << ((ip_.ip.v4.uint32 >> 8) & 0xFF) + << "." << (ip_.ip.v4.uint32 & 0xFF) << ":" << local_port_ << " from " + << ((fip4 >> 24) & 0xFF) << "." << ((fip4 >> 16) & 0xFF) << "." + << ((fip4 >> 8) & 0xFF) << "." << (fip4 & 0xFF) << ":" << net_ntohs(from.port) + << std::endl; + } + recv_queue_.push_back({std::move(data), from}); +} + +void FakeUdpSocket::set_packet_source(PacketSource source) +{ + std::lock_guard lock(mutex_); + packet_source_ = std::move(source); +} + +void FakeUdpSocket::set_recv_observer(RecvObserver observer) +{ + std::lock_guard lock(mutex_); + recv_observer_ = std::move(observer); +} + +// --- FakeTcpSocket --- + +FakeTcpSocket::FakeTcpSocket(NetworkUniverse &universe) + : FakeSocket(universe, SOCK_STREAM) + , remote_addr_{} +{ +} + +FakeTcpSocket::~FakeTcpSocket() { close_impl(); } + +int FakeTcpSocket::close() +{ + std::lock_guard lock(mutex_); + close_impl(); + return 0; +} + +void FakeTcpSocket::close_impl() +{ + if (local_port_ != 0) { + universe_.unbind_tcp(ip_, local_port_, this); + local_port_ = 0; + } + state_ = CLOSED; +} + +int FakeTcpSocket::bind(const IP_Port *addr) +{ + std::lock_guard lock(mutex_); + if (local_port_ != 0) + return -1; + + uint16_t port = addr->port; + if (port == 0) { + port = universe_.find_free_port(ip_); + } else { + port = net_ntohs(port); + } + + if (universe_.bind_tcp(ip_, port, this)) { + local_port_ = port; + return 0; + } + errno = EADDRINUSE; + return -1; +} + +int FakeTcpSocket::listen(int backlog) +{ + std::lock_guard lock(mutex_); + state_ = LISTEN; + backlog_ = backlog; + return 0; +} + +int FakeTcpSocket::connect(const IP_Port *addr) +{ + std::lock_guard lock(mutex_); + if (local_port_ == 0) { + // Implicit bind + uint16_t p = universe_.find_free_port(ip_); + if (universe_.bind_tcp(ip_, p, this)) { + local_port_ = p; + } else { + errno = EADDRINUSE; + return -1; + } + } + + remote_addr_ = *addr; + state_ = SYN_SENT; + + Packet p{}; + p.from.ip = ip_; + p.from.port = net_htons(local_port_); + p.to = *addr; + p.is_tcp = true; + p.tcp_flags = 0x02; // SYN + p.seq = next_seq_; + + universe_.send_packet(p); + + // Non-blocking connect not fully simulated (we return 0 but state is SYN_SENT). + // Real connect() blocks or returns EINPROGRESS. + // For simplicity, we assume the test will pump events until connected. + errno = EINPROGRESS; + return -1; +} + +std::unique_ptr FakeTcpSocket::accept(IP_Port *addr) +{ + std::lock_guard lock(mutex_); + if (state_ != LISTEN) { + errno = EINVAL; + return nullptr; + } + + if (pending_connections_.empty()) { + errno = EWOULDBLOCK; + return nullptr; + } + + auto client = std::move(pending_connections_.front()); + pending_connections_.pop_front(); + + if (addr) { + *addr = client->remote_addr(); + } + return client; +} + +int FakeTcpSocket::send(const uint8_t *buf, size_t len) +{ + std::lock_guard lock(mutex_); + if (state_ != ESTABLISHED) { + errno = ENOTCONN; + return -1; + } + + // Wrap as TCP packet + Packet p{}; + // Source + p.from.ip = ip_; + p.from.port = net_htons(local_port_); + p.to = remote_addr_; + p.data.assign(buf, buf + len); + p.is_tcp = true; + p.tcp_flags = 0x10; // ACK (Data packets usually have ACK) + p.seq = next_seq_; + p.ack = last_ack_; + + next_seq_ += len; + universe_.send_packet(p); + return len; +} + +int FakeTcpSocket::recv(uint8_t *buf, size_t len) +{ + std::lock_guard lock(mutex_); + if (recv_buffer_.empty()) { + if (state_ == CLOSED || state_ == CLOSE_WAIT) + return 0; // EOF + errno = EWOULDBLOCK; + return -1; + } + + size_t actual = std::min(len, recv_buffer_.size()); + for (size_t i = 0; i < actual; ++i) { + buf[i] = recv_buffer_.front(); + recv_buffer_.pop_front(); + } + return actual; +} + +size_t FakeTcpSocket::recv_buffer_size() +{ + std::lock_guard lock(mutex_); + return recv_buffer_.size(); +} + +int FakeTcpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr) +{ + errno = EOPNOTSUPP; + return -1; +} +int FakeTcpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr) +{ + errno = EOPNOTSUPP; + return -1; +} + +void FakeTcpSocket::handle_packet(const Packet &p) +{ + std::lock_guard lock(mutex_); + if (universe_.is_verbose()) { + std::cerr << "Handle Packet: Port " << local_port_ << " Flags " + << static_cast(p.tcp_flags) << " State " << state_ << std::endl; + } + + if (state_ == LISTEN) { + if (p.tcp_flags & 0x02) { // SYN + // Create new socket for connection + auto new_sock = std::make_unique(universe_); + // Bind to ephemeral? No, it's accepted on the same port but distinct 4-tuple. + // In our simplified model, the new socket is not bound to the global map + // until accepted? Or effectively bound to the 4-tuple. + // For now, let's just create it and queue it. + + new_sock->state_ = SYN_RECEIVED; + new_sock->remote_addr_ = p.from; + new_sock->local_port_ = local_port_; + new_sock->last_ack_ = p.seq + 1; + new_sock->next_seq_ = 1000; // Random ISN + + universe_.bind_tcp(ip_, local_port_, new_sock.get()); + + // Send SYN-ACK + Packet resp{}; + resp.from = p.to; + resp.to = p.from; + resp.is_tcp = true; + resp.tcp_flags = 0x12; // SYN | ACK + resp.seq = new_sock->next_seq_++; + resp.ack = new_sock->last_ack_; + + universe_.send_packet(resp); + + // In real TCP, we wait for ACK to move to ESTABLISHED and accept queue. + // Here we cheat and move to ESTABLISHED immediately or wait for ACK? + // Let's wait for ACK. + // But where do we store this half-open socket? + // For simplicity: auto-transition to ESTABLISHED and queue it. + new_sock->state_ = ESTABLISHED; + pending_connections_.push_back(std::move(new_sock)); + } + } else if (state_ == SYN_SENT) { + if ((p.tcp_flags & 0x12) == 0x12) { // SYN | ACK + state_ = ESTABLISHED; + last_ack_ = p.seq + 1; + next_seq_++; // Consumer SYN + + // Send ACK + Packet ack{}; + ack.from = p.to; + ack.to = p.from; + ack.is_tcp = true; + ack.tcp_flags = 0x10; // ACK + ack.seq = next_seq_; + ack.ack = last_ack_; + universe_.send_packet(ack); + } + } else if (state_ == ESTABLISHED) { + if (p.tcp_flags & 0x01) { // FIN + state_ = CLOSE_WAIT; + // Send ACK + Packet ack{}; + ack.from = p.to; + ack.to = p.from; + ack.is_tcp = true; + ack.tcp_flags = 0x10; // ACK + ack.seq = next_seq_; + ack.ack = p.seq + 1; // Consume FIN + universe_.send_packet(ack); + } else if (!p.data.empty()) { + recv_buffer_.insert(recv_buffer_.end(), p.data.begin(), p.data.end()); + last_ack_ += p.data.size(); + // Should send ACK? + } + } +} + +std::unique_ptr FakeTcpSocket::create_connected( + NetworkUniverse &universe, const IP_Port &remote, uint16_t local_port) +{ + auto s = std::make_unique(universe); + s->state_ = ESTABLISHED; + s->remote_addr_ = remote; + s->local_port_ = local_port; + return s; +} + +} // namespace tox::test diff --git a/testing/support/doubles/fake_sockets.hh b/testing/support/doubles/fake_sockets.hh new file mode 100644 index 00000000..d42f0b2e --- /dev/null +++ b/testing/support/doubles/fake_sockets.hh @@ -0,0 +1,165 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H +#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#else +#include +#endif + +#include "../../../toxcore/network.h" + +namespace tox::test { + +class NetworkUniverse; +struct Packet; + +/** + * @brief Abstract base class for all fake sockets. + */ +class FakeSocket { +public: + FakeSocket(NetworkUniverse &universe, int type); + virtual ~FakeSocket(); + + virtual int bind(const IP_Port *addr) = 0; + virtual int connect(const IP_Port *addr) = 0; + virtual int listen(int backlog) = 0; + virtual std::unique_ptr accept(IP_Port *addr) = 0; + + virtual int send(const uint8_t *buf, size_t len) = 0; + virtual int recv(uint8_t *buf, size_t len) = 0; + + virtual size_t recv_buffer_size() { return 0; } + + virtual int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) = 0; + virtual int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) = 0; + + virtual int getsockopt(int level, int optname, void *optval, size_t *optlen); + virtual int setsockopt(int level, int optname, const void *optval, size_t optlen); + virtual int socket_nonblock(bool nonblock); + + virtual int close(); + + bool is_nonblocking() const { return nonblocking_; } + int type() const { return type_; } + uint16_t local_port() const { return local_port_; } + const IP &ip_address() const { return ip_; } + + void set_ip(const IP &ip) { ip_ = ip; } + +protected: + NetworkUniverse &universe_; + int type_; + bool nonblocking_ = false; + uint16_t local_port_ = 0; + IP ip_; + std::mutex mutex_; // Use regular mutex (recursive_mutex not fully supported in MinGW) +}; + +/** + * @brief Implements UDP logic. + */ +class FakeUdpSocket : public FakeSocket { +public: + explicit FakeUdpSocket(NetworkUniverse &universe); + ~FakeUdpSocket() override; + + int bind(const IP_Port *addr) override; + int connect(const IP_Port *addr) override; + int listen(int backlog) override; + std::unique_ptr accept(IP_Port *addr) override; + int close() override; + + int send(const uint8_t *buf, size_t len) override; + int recv(uint8_t *buf, size_t len) override; + + int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) override; + int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) override; + + // Called by Universe to deliver a packet + void push_packet(std::vector data, IP_Port from); + + using PacketSource = std::function &data, IP_Port &from)>; + void set_packet_source(PacketSource source); + + using RecvObserver = std::function &data, const IP_Port &from)>; + void set_recv_observer(RecvObserver observer); + +private: + void close_impl(); + + struct PendingPacket { + std::vector data; + IP_Port from; + }; + std::deque recv_queue_; + PacketSource packet_source_; + RecvObserver recv_observer_; +}; + +/** + * @brief Implements TCP logic (Simplified for simulation). + * + * Supports: + * - 3-way handshake simulation (instant or delayed) + * - Stream buffering + * - Connection states + */ +class FakeTcpSocket : public FakeSocket { +public: + enum State { CLOSED, LISTEN, SYN_SENT, SYN_RECEIVED, ESTABLISHED, CLOSE_WAIT }; + + explicit FakeTcpSocket(NetworkUniverse &universe); + ~FakeTcpSocket() override; + + int bind(const IP_Port *addr) override; + int connect(const IP_Port *addr) override; + int listen(int backlog) override; + std::unique_ptr accept(IP_Port *addr) override; + int close() override; + + int send(const uint8_t *buf, size_t len) override; + int recv(uint8_t *buf, size_t len) override; + size_t recv_buffer_size() override; + + int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) override; + int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) override; + + // Internal events + void handle_packet(const Packet &p); + + State state() const { return state_; } + const IP_Port &remote_addr() const { return remote_addr_; } + + // Factory for accepting connections + static std::unique_ptr create_connected( + NetworkUniverse &universe, const IP_Port &remote, uint16_t local_port); + +private: + void close_impl(); + + State state_ = CLOSED; + IP_Port remote_addr_; + std::deque recv_buffer_; + std::deque> pending_connections_; + int backlog_ = 0; + + uint32_t next_seq_ = 0; + uint32_t last_ack_ = 0; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H diff --git a/testing/support/doubles/fake_sockets_test.cc b/testing/support/doubles/fake_sockets_test.cc new file mode 100644 index 00000000..a5814968 --- /dev/null +++ b/testing/support/doubles/fake_sockets_test.cc @@ -0,0 +1,125 @@ +#include "fake_sockets.hh" + +#include + +#include "network_universe.hh" + +namespace tox::test { +namespace { + + class FakeTcpSocketTest : public ::testing::Test { + public: + ~FakeTcpSocketTest() override; + + protected: + NetworkUniverse universe; + FakeTcpSocket server{universe}; + FakeTcpSocket client{universe}; + }; + + FakeTcpSocketTest::~FakeTcpSocketTest() = default; + + TEST_F(FakeTcpSocketTest, ConnectAndAccept) + { + IP_Port server_addr; + ip_init(&server_addr.ip, false); + server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); + server_addr.port = net_htons(8080); + + ASSERT_EQ(server.bind(&server_addr), 0); + ASSERT_EQ(server.listen(5), 0); + + // Client connects + ASSERT_EQ(client.connect(&server_addr), -1); + ASSERT_EQ(errno, EINPROGRESS); + + // Process events (Client SYN -> Server) + universe.process_events(0); + + // Server accepts (SYN-ACK -> Client) + universe.process_events(0); + + // Client receives SYN-ACK, sends ACK (ACK -> Server) + universe.process_events(0); + + // Server receives ACK, connection established + IP_Port client_addr; + auto accepted = server.accept(&client_addr); + ASSERT_NE(accepted, nullptr); + auto *accepted_tcp = static_cast(accepted.get()); + EXPECT_EQ(accepted_tcp->state(), FakeTcpSocket::ESTABLISHED); + + EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED); + } + + TEST_F(FakeTcpSocketTest, DataTransfer) + { + IP_Port server_addr; + ip_init(&server_addr.ip, false); + server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); + server_addr.port = net_htons(8081); + + server.bind(&server_addr); + server.listen(5); + client.connect(&server_addr); + + // Handshake + universe.process_events(0); // SYN + universe.process_events(0); // SYN-ACK + universe.process_events(0); // ACK + + auto accepted = server.accept(nullptr); + ASSERT_NE(accepted, nullptr); + + // Send data Client -> Server + uint8_t send_buf[] = "Hello"; + ASSERT_EQ(client.send(send_buf, 5), 5); + + universe.process_events(0); // Data packet + + uint8_t recv_buf[10]; + ASSERT_EQ(accepted->recv(recv_buf, 10), 5); + EXPECT_EQ(std::string(reinterpret_cast(recv_buf), 5), "Hello"); + } + + class FakeUdpSocketTest : public ::testing::Test { + public: + ~FakeUdpSocketTest() override; + + protected: + NetworkUniverse universe; + FakeUdpSocket server{universe}; + FakeUdpSocket client{universe}; + }; + + FakeUdpSocketTest::~FakeUdpSocketTest() = default; + + TEST_F(FakeUdpSocketTest, BindAndSendTo) + { + IP_Port server_addr; + ip_init(&server_addr.ip, false); + server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); + server_addr.port = net_htons(9000); + + ASSERT_EQ(server.bind(&server_addr), 0); + + const char *message = "UDP Packet"; + ASSERT_EQ(client.sendto( + reinterpret_cast(message), strlen(message), &server_addr), + strlen(message)); + + universe.process_events(0); + + IP_Port sender_addr; + uint8_t recv_buf[100]; + int len = server.recvfrom(recv_buf, sizeof(recv_buf), &sender_addr); + + ASSERT_GT(len, 0); + EXPECT_EQ(std::string(reinterpret_cast(recv_buf), len), message); + EXPECT_EQ(sender_addr.port, net_htons(client.local_port())); + } + +} // namespace +} // namespace tox::test + +// end of file diff --git a/testing/support/doubles/network_universe.cc b/testing/support/doubles/network_universe.cc new file mode 100644 index 00000000..5750c804 --- /dev/null +++ b/testing/support/doubles/network_universe.cc @@ -0,0 +1,145 @@ +#include "network_universe.hh" + +#include +#include + +#include "fake_sockets.hh" + +namespace tox::test { + +bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const +{ + if (port != other.port) + return port < other.port; + if (ip.family.value != other.ip.family.value) + return ip.family.value < other.ip.family.value; + + if (net_family_is_ipv4(ip.family)) { + return ip.ip.v4.uint32 < other.ip.ip.v4.uint32; + } + + return std::memcmp(&ip.ip.v6, &other.ip.ip.v6, sizeof(ip.ip.v6)) < 0; +} + +NetworkUniverse::NetworkUniverse() { } +NetworkUniverse::~NetworkUniverse() { } + +bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket) +{ + std::lock_guard lock(mutex_); + IP_Port_Key key{ip, port}; + if (udp_bindings_.count(key)) + return false; + udp_bindings_[key] = socket; + return true; +} + +void NetworkUniverse::unbind_udp(IP ip, uint16_t port) +{ + std::lock_guard lock(mutex_); + udp_bindings_.erase({ip, port}); +} + +bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket) +{ + std::lock_guard lock(mutex_); + tcp_bindings_.insert({{ip, port}, socket}); + return true; +} + +void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket) +{ + std::lock_guard lock(mutex_); + auto range = tcp_bindings_.equal_range({ip, port}); + for (auto it = range.first; it != range.second; ++it) { + if (it->second == socket) { + tcp_bindings_.erase(it); + break; + } + } +} + +void NetworkUniverse::send_packet(Packet p) +{ + // Apply filters + for (const auto &filter : filters_) { + if (!filter(p)) + return; + } + + // Notify observers + for (const auto &observer : observers_) { + observer(p); + } + + p.delivery_time += global_latency_ms_; + + std::lock_guard lock(mutex_); + event_queue_.push(std::move(p)); +} + +void NetworkUniverse::process_events(uint64_t current_time_ms) +{ + while (true) { + Packet p; + std::vector tcp_targets; + FakeUdpSocket *udp_target = nullptr; + bool has_packet = false; + + { + std::lock_guard lock(mutex_); + if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) { + p = event_queue_.top(); + event_queue_.pop(); + has_packet = true; + + if (p.is_tcp) { + auto range = tcp_bindings_.equal_range({p.to.ip, net_ntohs(p.to.port)}); + for (auto it = range.first; it != range.second; ++it) { + tcp_targets.push_back(it->second); + } + } else { + if (udp_bindings_.count({p.to.ip, net_ntohs(p.to.port)})) { + udp_target = udp_bindings_[{p.to.ip, net_ntohs(p.to.port)}]; + } + } + } + } + + if (!has_packet) { + break; + } + + if (p.is_tcp) { + for (auto *it : tcp_targets) { + it->handle_packet(p); + } + } else { + if (udp_target) { + udp_target->push_packet(std::move(p.data), p.from); + } + } + } +} + +void NetworkUniverse::set_latency(uint64_t ms) { global_latency_ms_ = ms; } + +void NetworkUniverse::set_verbose(bool verbose) { verbose_ = verbose; } + +bool NetworkUniverse::is_verbose() const { return verbose_; } + +void NetworkUniverse::add_filter(PacketFilter filter) { filters_.push_back(std::move(filter)); } + +void NetworkUniverse::add_observer(PacketSink sink) { observers_.push_back(std::move(sink)); } + +uint16_t NetworkUniverse::find_free_port(IP ip, uint16_t start) +{ + std::lock_guard lock(mutex_); + for (uint16_t port = start; port < 65535; ++port) { + if (!udp_bindings_.count({ip, port})) + return port; + } + return 0; +} + +} // namespace tox::test diff --git a/testing/support/doubles/network_universe.hh b/testing/support/doubles/network_universe.hh new file mode 100644 index 00000000..cf1d6f3c --- /dev/null +++ b/testing/support/doubles/network_universe.hh @@ -0,0 +1,90 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H +#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "../../../toxcore/network.h" + +namespace tox::test { + +class FakeUdpSocket; +class FakeTcpSocket; + +struct Packet { + IP_Port from; + IP_Port to; + std::vector data; + uint64_t delivery_time; + bool is_tcp = false; + + // TCP Simulation Fields + uint8_t tcp_flags = 0; // 0x01=FIN, 0x02=SYN, 0x10=ACK + uint32_t seq = 0; + uint32_t ack = 0; + + bool operator>(const Packet &other) const { return delivery_time > other.delivery_time; } +}; + +/** + * @brief The God Object for the network simulation. + * Manages routing, latency, and connectivity. + */ +class NetworkUniverse { +public: + using PacketFilter = std::function; + using PacketSink = std::function; + + NetworkUniverse(); + ~NetworkUniverse(); + + // Registration + // Returns true if binding succeeded + bool bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket); + void unbind_udp(IP ip, uint16_t port); + + bool bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket); + void unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket); + + // Routing + void send_packet(Packet p); + + // Simulation + void process_events(uint64_t current_time_ms); + void set_latency(uint64_t ms); + void set_verbose(bool verbose); + bool is_verbose() const; + void add_filter(PacketFilter filter); + void add_observer(PacketSink sink); + + // Helpers + // Finds a free port starting from 'start' + uint16_t find_free_port(IP ip, uint16_t start = 33445); + + struct IP_Port_Key { + IP ip; + uint16_t port; + bool operator<(const IP_Port_Key &other) const; + }; + +private: + std::map udp_bindings_; + std::multimap tcp_bindings_; + + std::priority_queue, std::greater> event_queue_; + std::vector filters_; + std::vector observers_; + + uint64_t global_latency_ms_ = 0; + bool verbose_ = false; + std::recursive_mutex mutex_; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H diff --git a/testing/support/doubles/network_universe_test.cc b/testing/support/doubles/network_universe_test.cc new file mode 100644 index 00000000..494f938b --- /dev/null +++ b/testing/support/doubles/network_universe_test.cc @@ -0,0 +1,240 @@ +#include "network_universe.hh" + +#include + +#include "fake_sockets.hh" + +namespace tox::test { +namespace { + + class NetworkUniverseTest : public ::testing::Test { + public: + ~NetworkUniverseTest() override; + + protected: + NetworkUniverse universe; + FakeUdpSocket s1{universe}; + FakeUdpSocket s2{universe}; + }; + + NetworkUniverseTest::~NetworkUniverseTest() = default; + + TEST_F(NetworkUniverseTest, LatencySimulation) + { + universe.set_latency(100); + + IP_Port s2_addr; + ip_init(&s2_addr.ip, false); + s2_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); + s2_addr.port = net_htons(9004); + s2.bind(&s2_addr); + + uint8_t data[] = "Ping"; + s1.sendto(data, 4, &s2_addr); + + // Time 0: packet sent but delivery time is 100 + universe.process_events(0); + IP_Port from; + uint8_t buf[10]; + ASSERT_EQ(s2.recvfrom(buf, 10, &from), -1); + + // Time 50: still not delivered + universe.process_events(50); + ASSERT_EQ(s2.recvfrom(buf, 10, &from), -1); + + // Time 100: delivered + universe.process_events(100); + ASSERT_EQ(s2.recvfrom(buf, 10, &from), 4); + } + + TEST_F(NetworkUniverseTest, RoutesBasedOnIpAndPort) + { + IP ip1{}, ip2{}; + ip_init(&ip1, false); + ip1.ip.v4.uint32 = net_htonl(0x01010101); + ip_init(&ip2, false); + ip2.ip.v4.uint32 = net_htonl(0x02020202); + + uint16_t port = 33445; + + FakeUdpSocket sock1{universe}; + FakeUdpSocket sock2{universe}; + + sock1.set_ip(ip1); + sock2.set_ip(ip2); + + IP_Port addr1{ip1, net_htons(port)}; + IP_Port addr2{ip2, net_htons(port)}; + + ASSERT_EQ(sock1.bind(&addr1), 0); + ASSERT_EQ(sock2.bind(&addr2), 0); + + const char *msg1 = "To IP 1"; + const char *msg2 = "To IP 2"; + + FakeUdpSocket sender{universe}; + sender.sendto(reinterpret_cast(msg1), strlen(msg1), &addr1); + sender.sendto(reinterpret_cast(msg2), strlen(msg2), &addr2); + + universe.process_events(0); + + uint8_t buf[100]; + IP_Port from; + + int len1 = sock1.recvfrom(buf, sizeof(buf), &from); + ASSERT_GT(len1, 0); + EXPECT_EQ(std::string(reinterpret_cast(buf), static_cast(len1)), msg1); + + int len2 = sock2.recvfrom(buf, sizeof(buf), &from); + ASSERT_GT(len2, 0); + EXPECT_EQ(std::string(reinterpret_cast(buf), static_cast(len2)), msg2); + } + + TEST_F(NetworkUniverseTest, FindFreePortIsIpSpecific) + { + IP ip1{}, ip2{}; + ip_init(&ip1, false); + ip1.ip.v4.uint32 = net_htonl(0x01010101); + ip_init(&ip2, false); + ip2.ip.v4.uint32 = net_htonl(0x02020202); + + FakeUdpSocket sock1{universe}; + sock1.set_ip(ip1); + + uint16_t port = 33445; + IP_Port addr1{ip1, net_htons(port)}; + ASSERT_EQ(sock1.bind(&addr1), 0); + + // Port 33445 should be busy for ip1, but free for ip2 + EXPECT_EQ(universe.find_free_port(ip1, port), port + 1); + EXPECT_EQ(universe.find_free_port(ip2, port), port); + } + + TEST_F(NetworkUniverseTest, IpPortKeyEqualityRobustness) + { + IP ip1{}, ip2{}; + ip_init(&ip1, false); + ip1.ip.v4.uint32 = net_htonl(0x7F000001); + ip_init(&ip2, false); + ip2.ip.v4.uint32 = net_htonl(0x7F000001); + + uint16_t port = 12345; + + // Force different garbage in the union padding for IPv4 + // The union is 16 bytes. IP4 is 4 bytes. Trailing 12 bytes are unused. + memset(ip1.ip.v6.uint8 + 4, 0x11, 12); + memset(ip2.ip.v6.uint8 + 4, 0x22, 12); + + NetworkUniverse::IP_Port_Key key1{ip1, port}; + NetworkUniverse::IP_Port_Key key2{ip2, port}; + + // They should be considered equal (neither is less than the other) + EXPECT_FALSE(key1 < key2); + EXPECT_FALSE(key2 < key1); + + // Now try with different IPv4 but same garbage + ip2.ip.v4.uint32 = net_htonl(0x7F000002); + memset(ip2.ip.v6.uint8 + 4, 0x11, 12); // same garbage as ip1 + NetworkUniverse::IP_Port_Key key3{ip2, port}; + + EXPECT_TRUE(key1 < key3 || key3 < key1); + } + + TEST_F(NetworkUniverseTest, IPv4v6Distinction) + { + IP ip1{}, ip2{}; + ip_init(&ip1, false); // IPv4 + ip1.ip.v4.uint32 = net_htonl(0x01020304); + ip_init(&ip2, true); // IPv6 + // Set IPv6 bytes to match IPv4 bytes at the beginning + memset(ip2.ip.v6.uint8, 0, 16); + ip2.ip.v6.uint32[0] = net_htonl(0x01020304); + + uint16_t port = 12345; + + NetworkUniverse::IP_Port_Key key1{ip1, port}; + NetworkUniverse::IP_Port_Key key2{ip2, port}; + + // Different families must be different even if underlying bytes happen to match + EXPECT_TRUE(key1 < key2 || key2 < key1); + } + + TEST_F(NetworkUniverseTest, ManyNodes) + { + const int num_nodes = 5000; + struct NodeInfo { + std::unique_ptr sock; + IP_Port addr; + }; + std::vector nodes; + nodes.reserve(num_nodes); + + for (int i = 0; i < num_nodes; ++i) { + auto sock = std::make_unique(universe); + IP ip{}; + ip_init(&ip, false); + ip.ip.v4.uint32 = net_htonl(0x0A000000 + i); // 10.0.x.y + sock->set_ip(ip); + + IP_Port addr{ip, net_htons(33445)}; + ASSERT_EQ(sock->bind(&addr), 0); + nodes.push_back({std::move(sock), addr}); + } + + const int num_messages = 100; + // Send messages from first num_messages to last num_messages nodes + for (int i = 0; i < num_messages; ++i) { + const char *msg = "Stress test"; + nodes[i].sock->sendto(reinterpret_cast(msg), strlen(msg), + &nodes[num_nodes - 1 - i].addr); + } + + universe.process_events(0); + + for (int i = 0; i < num_messages; ++i) { + uint8_t buf[100]; + IP_Port from; + int len = nodes[num_nodes - 1 - i].sock->recvfrom(buf, sizeof(buf), &from); + ASSERT_GT(len, 0); + EXPECT_EQ(std::string(reinterpret_cast(buf), static_cast(len)), + "Stress test"); + EXPECT_TRUE(ip_equal(&from.ip, &nodes[i].addr.ip)); + } + } + + TEST_F(NetworkUniverseTest, IpPadding) + { + IP ip1{}; + ip_init(&ip1, false); + ip1.ip.v4.uint32 = net_htonl(0x7F000001); + + FakeUdpSocket sock{universe}; + sock.set_ip(ip1); + IP_Port bind_addr{ip1, net_htons(12345)}; + ASSERT_EQ(sock.bind(&bind_addr), 0); + + // Create an address with garbage in the padding + IP_Port target_addr; + memset(&target_addr, 0xAA, sizeof(target_addr)); + ip_init(&target_addr.ip, false); + target_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); + target_addr.port = net_htons(12345); + + FakeUdpSocket sender{universe}; + const char *msg = "Padding test"; + sender.sendto(reinterpret_cast(msg), strlen(msg), &target_addr); + + universe.process_events(0); + + uint8_t buf[100]; + IP_Port from; + int len = sock.recvfrom(buf, sizeof(buf), &from); + + // If this fails, it means NetworkUniverse is not robust against padding garbage + ASSERT_GT(len, 0); + EXPECT_EQ( + std::string(reinterpret_cast(buf), static_cast(len)), "Padding test"); + } + +} // namespace +} // namespace tox::test diff --git a/testing/support/public/clock.hh b/testing/support/public/clock.hh new file mode 100644 index 00000000..1b249db1 --- /dev/null +++ b/testing/support/public/clock.hh @@ -0,0 +1,28 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_CLOCK_H +#define C_TOXCORE_TESTING_SUPPORT_CLOCK_H + +#include + +namespace tox::test { + +/** + * @brief Abstraction over the system's monotonic clock. + */ +class ClockSystem { +public: + virtual ~ClockSystem(); + + /** + * @brief Returns current monotonic time in milliseconds. + */ + virtual uint64_t current_time_ms() const = 0; + + /** + * @brief Returns current monotonic time in seconds. + */ + virtual uint64_t current_time_s() const = 0; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_CLOCK_H diff --git a/testing/support/public/environment.hh b/testing/support/public/environment.hh new file mode 100644 index 00000000..52e879f5 --- /dev/null +++ b/testing/support/public/environment.hh @@ -0,0 +1,47 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_ENVIRONMENT_H +#define C_TOXCORE_TESTING_SUPPORT_ENVIRONMENT_H + +#include + +namespace tox::test { + +class NetworkSystem; +class ClockSystem; +class RandomSystem; +class MemorySystem; + +/** + * @brief Service locator for system resources in tests. + * + * This interface allows tests to access system resources (Network, Time, RNG, + * memory) in a way that can be swapped between real implementations (for + * integration tests) and simulated ones (for unit/fuzz tests). + */ +class Environment { +public: + virtual ~Environment(); + + /** + * @brief Access the network subsystem. + */ + virtual NetworkSystem &network() = 0; + + /** + * @brief Access the monotonic clock and timer subsystem. + */ + virtual ClockSystem &clock() = 0; + + /** + * @brief Access the random number generator. + */ + virtual RandomSystem &random() = 0; + + /** + * @brief Access the memory allocator. + */ + virtual MemorySystem &memory() = 0; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_ENVIRONMENT_H diff --git a/testing/support/public/fuzz_data.hh b/testing/support/public/fuzz_data.hh new file mode 100644 index 00000000..f3983fae --- /dev/null +++ b/testing/support/public/fuzz_data.hh @@ -0,0 +1,183 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_DATA_H +#define C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_DATA_H + +#include +#include +#include +#include +#include + +namespace tox::test { + +struct Fuzz_Data { + static constexpr bool FUZZ_DEBUG = false; + static constexpr std::size_t TRACE_TRAP = -1; + +private: + const uint8_t *data_; + const uint8_t *base_; + std::size_t size_; + +public: + Fuzz_Data(const uint8_t *input_data, std::size_t input_size) + : data_(input_data) + , base_(input_data) + , size_(input_size) + { + } + + Fuzz_Data &operator=(const Fuzz_Data &rhs) = delete; + Fuzz_Data(const Fuzz_Data &rhs) = delete; + + struct Consumer { + const char *func; + Fuzz_Data &fd; + + operator bool() + { + if (fd.empty()) + return false; + const bool val = fd.data_[0]; + if (FUZZ_DEBUG) { + std::printf("consume@%zu(%s): bool %s\n", fd.pos(), func, val ? "true" : "false"); + } + ++fd.data_; + --fd.size_; + return val; + } + + template + operator T() + { + if (sizeof(T) > fd.size()) + return T{}; + const uint8_t *bytes = fd.consume(func, sizeof(T)); + T val; + std::memcpy(&val, bytes, sizeof(T)); + return val; + } + }; + + Consumer consume1(const char *func) { return Consumer{func, *this}; } + + template + T consume_integral() + { + return consume1("consume_integral"); + } + + template + T consume_integral_in_range(T min, T max) + { + if (min >= max) + return min; + T val = consume_integral(); + return min + (val % (max - min + 1)); + } + + std::size_t remaining_bytes() const { return size(); } + + std::vector consume_bytes(std::size_t count) + { + if (count == 0 || count > size_) + return {}; + const uint8_t *start = consume("consume_bytes", count); + if (!start) + return {}; + return std::vector(start, start + count); + } + + std::vector consume_remaining_bytes() + { + if (empty()) + return {}; + std::size_t count = size(); + const uint8_t *start = consume("consume_remaining_bytes", count); + return std::vector(start, start + count); + } + + std::size_t size() const { return size_; } + std::size_t pos() const { return data_ - base_; } + const uint8_t *data() const { return data_; } + bool empty() const { return size_ == 0; } + + const uint8_t *consume(const char *func, std::size_t count) + { + if (count > size_) + return nullptr; + const uint8_t *val = data_; + if (FUZZ_DEBUG) { + if (count == 1) { + std::printf("consume@%zu(%s): %d (0x%02x)\n", pos(), func, val[0], val[0]); + } else if (count != 0) { + std::printf("consume@%zu(%s): %02x..%02x[%zu]\n", pos(), func, val[0], + val[count - 1], count); + } + } + data_ += count; + size_ -= count; + return val; + } +}; + +#define CONSUME1_OR_RETURN(TYPE, NAME, INPUT) \ + if ((INPUT).size() < sizeof(TYPE)) { \ + return; \ + } \ + TYPE NAME = (INPUT).consume1(__func__) + +#define CONSUME1_OR_RETURN_VAL(TYPE, NAME, INPUT, VAL) \ + if ((INPUT).size() < sizeof(TYPE)) { \ + return (VAL); \ + } \ + TYPE NAME = (INPUT).consume1(__func__) + +#define CONSUME_OR_RETURN(DECL, INPUT, SIZE) \ + if ((INPUT).size() < (SIZE)) { \ + return; \ + } \ + DECL = (INPUT).consume(__func__, (SIZE)) + +#define CONSUME_OR_RETURN_VAL(DECL, INPUT, SIZE, VAL) \ + if ((INPUT).size() < (SIZE)) { \ + return (VAL); \ + } \ + DECL = (INPUT).consume(__func__, (SIZE)) + +using Fuzz_Target = void (*)(Fuzz_Data &input); + +template +struct Fuzz_Target_Selector; + +template +struct Fuzz_Target_Selector { + static void select(uint8_t selector, Fuzz_Data &input) + { + if (selector == sizeof...(Args)) { + return Arg(input); + } + return Fuzz_Target_Selector::select(selector, input); + } +}; + +template <> +struct Fuzz_Target_Selector<> { + static void select(uint8_t selector, Fuzz_Data &input) + { + // The selector selected no function, so we do nothing and rely on the + // fuzzer to come up with a better selector. + } +}; + +template +void fuzz_select_target(const uint8_t *data, std::size_t size) +{ + Fuzz_Data input{data, size}; + + CONSUME1_OR_RETURN(const uint8_t, selector, input); + return Fuzz_Target_Selector::select(selector, input); +} + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_DATA_H diff --git a/testing/support/public/fuzz_helpers.hh b/testing/support/public/fuzz_helpers.hh new file mode 100644 index 00000000..ce8026ff --- /dev/null +++ b/testing/support/public/fuzz_helpers.hh @@ -0,0 +1,23 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_HELPERS_H +#define C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_HELPERS_H + +#include "../doubles/fake_memory.hh" +#include "../doubles/fake_random.hh" +#include "../doubles/fake_sockets.hh" +#include "fuzz_data.hh" + +namespace tox::test { + +// Configures the socket to pull packets from the Fuzz_Data stream +// mimicking the legacy Fuzz_System behavior (2-byte length prefix). +void configure_fuzz_packet_source(FakeUdpSocket &socket, Fuzz_Data &input); + +// Configures memory allocator to consume failure decisions from Fuzz_Data. +void configure_fuzz_memory_source(FakeMemory &memory, Fuzz_Data &input); + +// Configures random generator to consume bytes from Fuzz_Data. +void configure_fuzz_random_source(FakeRandom &random, Fuzz_Data &input); + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_PUBLIC_FUZZ_HELPERS_H diff --git a/testing/support/public/memory.hh b/testing/support/public/memory.hh new file mode 100644 index 00000000..4ffd0c46 --- /dev/null +++ b/testing/support/public/memory.hh @@ -0,0 +1,23 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_PUBLIC_MEMORY_H +#define C_TOXCORE_TESTING_SUPPORT_PUBLIC_MEMORY_H + +#include +#include + +namespace tox::test { + +/** + * @brief Abstraction over the memory allocator. + */ +class MemorySystem { +public: + virtual ~MemorySystem(); + + virtual void *malloc(size_t size) = 0; + virtual void *realloc(void *ptr, size_t size) = 0; + virtual void free(void *ptr) = 0; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_PUBLIC_MEMORY_H diff --git a/testing/support/public/network.hh b/testing/support/public/network.hh new file mode 100644 index 00000000..179b9341 --- /dev/null +++ b/testing/support/public/network.hh @@ -0,0 +1,51 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_NETWORK_H +#define C_TOXCORE_TESTING_SUPPORT_NETWORK_H + +#include +#include + +#include "../../../toxcore/network.h" + +namespace tox::test { + +/** + * @brief Abstraction over the network subsystem (sockets). + */ +class NetworkSystem { +public: + virtual ~NetworkSystem(); + + virtual Socket socket(int domain, int type, int protocol) = 0; + virtual int bind(Socket sock, const IP_Port *addr) = 0; + virtual int close(Socket sock) = 0; + virtual int sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) = 0; + virtual int recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr) = 0; + + // TCP Support + virtual int listen(Socket sock, int backlog) = 0; + virtual Socket accept(Socket sock) = 0; + virtual int connect(Socket sock, const IP_Port *addr) = 0; + virtual int send(Socket sock, const uint8_t *buf, size_t len) = 0; + virtual int recv(Socket sock, uint8_t *buf, size_t len) = 0; + virtual int recvbuf(Socket sock) = 0; + + // Auxiliary + virtual int socket_nonblock(Socket sock, bool nonblock) = 0; + virtual int getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen) = 0; + virtual int setsockopt(Socket sock, int level, int optname, const void *optval, size_t optlen) + = 0; +}; + +/** + * @brief Helper to create an IPv4 IP struct from a host-byte-order address. + */ +IP make_ip(uint32_t ipv4); + +/** + * @brief Helper to create a unique node IP in the 10.x.y.z range. + */ +IP make_node_ip(uint32_t node_id); + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_NETWORK_H diff --git a/testing/support/public/random.hh b/testing/support/public/random.hh new file mode 100644 index 00000000..72d0029c --- /dev/null +++ b/testing/support/public/random.hh @@ -0,0 +1,22 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_RANDOM_H +#define C_TOXCORE_TESTING_SUPPORT_RANDOM_H + +#include +#include + +namespace tox::test { + +/** + * @brief Abstraction over the random number generator. + */ +class RandomSystem { +public: + virtual ~RandomSystem(); + + virtual uint32_t uniform(uint32_t upper_bound) = 0; + virtual void bytes(uint8_t *out, size_t count) = 0; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_RANDOM_H diff --git a/testing/support/public/simulated_environment.hh b/testing/support/public/simulated_environment.hh new file mode 100644 index 00000000..e430fe34 --- /dev/null +++ b/testing/support/public/simulated_environment.hh @@ -0,0 +1,78 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_SIMULATED_ENVIRONMENT_H +#define C_TOXCORE_TESTING_SUPPORT_SIMULATED_ENVIRONMENT_H + +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#else +#include +#include +#endif + +#include "../../../toxcore/tox_memory_impl.h" +#include "../../../toxcore/tox_private.h" +#include "../../../toxcore/tox_random_impl.h" +#include "../doubles/fake_clock.hh" +#include "../doubles/fake_memory.hh" +#include "../doubles/fake_random.hh" +#include "environment.hh" +#include "simulation.hh" + +namespace tox::test { + +struct ScopedToxSystem { + // The underlying node in the simulation + std::unique_ptr node; + + // Direct access to primary socket (for fuzzer injection) + FakeUdpSocket *endpoint; + + // C structs + struct Network c_network; + struct Tox_Random c_random; + struct Tox_Memory c_memory; + + // The main struct passed to tox_new + Tox_System system; +}; + +class SimulatedEnvironment : public Environment { +public: + SimulatedEnvironment(); + ~SimulatedEnvironment() override; + + NetworkSystem &network() override; + ClockSystem &clock() override; + RandomSystem &random() override; + MemorySystem &memory() override; + + FakeClock &fake_clock(); + FakeRandom &fake_random(); + FakeMemory &fake_memory(); + + Simulation &simulation() { return *sim_; } + + /** + * @brief Creates a new virtual node in the simulation bound to the specified port. + */ + std::unique_ptr create_node(uint16_t port); + + void advance_time(uint64_t ms); + +private: + std::unique_ptr sim_; + + // Global instances for Environment interface compliance. + std::unique_ptr global_random_; + std::unique_ptr global_memory_; + + // For network(), we can't return a per-node stack. +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_SIMULATED_ENVIRONMENT_H diff --git a/testing/support/public/simulation.hh b/testing/support/public/simulation.hh new file mode 100644 index 00000000..63cbdab4 --- /dev/null +++ b/testing/support/public/simulation.hh @@ -0,0 +1,112 @@ +#ifndef C_TOXCORE_TESTING_SUPPORT_SIMULATION_H +#define C_TOXCORE_TESTING_SUPPORT_SIMULATION_H + +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#else +#include +#include +#endif + +#include "../../../toxcore/tox.h" +#include "../../../toxcore/tox_memory_impl.h" +#include "../../../toxcore/tox_private.h" +#include "../../../toxcore/tox_random_impl.h" +#include "../doubles/fake_clock.hh" +#include "../doubles/fake_memory.hh" +#include "../doubles/fake_network_stack.hh" +#include "../doubles/fake_random.hh" +#include "../doubles/network_universe.hh" +#include "environment.hh" + +namespace tox::test { + +class SimulatedNode; + +/** + * @brief The Simulation World. + * Holds the Clock and the Universe. + */ +class Simulation { +public: + Simulation(); + ~Simulation(); + + // Time Control + void advance_time(uint64_t ms); + void run_until(std::function condition, uint64_t timeout_ms = 5000); + + // Global Access + FakeClock &clock() { return *clock_; } + NetworkUniverse &net() { return *net_; } + + // Node Factory + std::unique_ptr create_node(); + +private: + std::unique_ptr clock_; + std::unique_ptr net_; + uint32_t node_count_ = 0; +}; + +/** + * @brief Represents a single node in the simulation. + * Implements the Environment interface for dependency injection. + */ +class SimulatedNode : public Environment { +public: + explicit SimulatedNode(Simulation &sim, uint32_t node_id); + ~SimulatedNode() override; + + // Environment Interface + NetworkSystem &network() override; + ClockSystem &clock() override; + RandomSystem &random() override; + MemorySystem &memory() override; + + // Direct Access to Fakes + FakeNetworkStack &fake_network() { return *network_; } + FakeRandom &fake_random() { return *random_; } + FakeMemory &fake_memory() { return *memory_; } + + // Tox Creation Helper + // Returns a configured Tox instance bound to this node's environment. + // The user owns the Tox instance. + struct ToxDeleter { + void operator()(Tox *t) const { tox_kill(t); } + }; + using ToxPtr = std::unique_ptr; + + ToxPtr create_tox(const Tox_Options *options = nullptr); + + // Helper to get C structs for manual injection + struct Network get_c_network() { return network_->get_c_network(); } + struct Tox_Random get_c_random() { return random_->get_c_random(); } + struct Tox_Memory get_c_memory() { return memory_->get_c_memory(); } + + // For fuzzing compatibility (exposes first bound UDP socket as "endpoint") + FakeUdpSocket *get_primary_socket(); + +private: + Simulation &sim_; + std::unique_ptr network_; + std::unique_ptr random_; + std::unique_ptr memory_; + + // C-compatible views (must stay valid for the lifetime of Tox) +public: + struct Network c_network; + struct Tox_Random c_random; + struct Tox_Memory c_memory; + struct IP ip; +}; + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_SIMULATION_H diff --git a/testing/support/public/tox_network.hh b/testing/support/public/tox_network.hh new file mode 100644 index 00000000..f799c09e --- /dev/null +++ b/testing/support/public/tox_network.hh @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2026 The TokTok team. + */ + +#ifndef C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H +#define C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H + +#include + +#include "simulation.hh" + +namespace tox::test { + +struct ConnectedFriend { + std::unique_ptr node; + SimulatedNode::ToxPtr tox; + uint32_t friend_number; + + ConnectedFriend(std::unique_ptr node_in, SimulatedNode::ToxPtr tox_in, + uint32_t friend_number_in) + : node(std::move(node_in)) + , tox(std::move(tox_in)) + , friend_number(friend_number_in) + { + } + + ConnectedFriend(ConnectedFriend &&) = default; + ConnectedFriend &operator=(ConnectedFriend &&) = default; + ~ConnectedFriend(); +}; + +/** + * @brief Sets up a network of connected Tox instances. + * + * This function creates num_friends Tox instances, adds them as friends to main_tox, + * and bootstraps them to main_node. It then runs the simulation until all friends + * are connected to main_tox. + * + * @param sim The simulation to run. + * @param main_tox The main Tox instance to which friends will connect. + * @param main_node The simulated node hosting the main Tox instance. + * @param num_friends The number of friends to create and connect. + * @param options Optional Tox_Options to use for the friend Tox instances. + * @return A vector of ConnectedFriend structures, each representing a friend. + */ +std::vector setup_connected_friends(Simulation &sim, Tox *main_tox, + SimulatedNode &main_node, int num_friends, const Tox_Options *options = nullptr); + +/** + * @brief Connects two existing Tox instances as friends. + * + * This function adds each Tox instance as a friend to the other, bootstraps + * them to each other, and runs the simulation until they are connected. + * + * @param sim The simulation to run. + * @param node1 The simulated node hosting the first Tox instance. + * @param tox1 The first Tox instance. + * @param node2 The simulated node hosting the second Tox instance. + * @param tox2 The second Tox instance. + * @return True if connected successfully, false otherwise. + */ +bool connect_friends( + Simulation &sim, SimulatedNode &node1, Tox *tox1, SimulatedNode &node2, Tox *tox2); + +/** + * @brief Sets up a group and has all friends join it. + * + * This function creates a new public group on main_tox, invites all friends in the + * provided vector, and runs the simulation until all friends have joined and + * main_tox sees all of them. + * + * @param sim The simulation to run. + * @param main_tox The main Tox instance that creates the group. + * @param friends The friends to invite to the group. + * @return The group number on the main Tox instance, or UINT32_MAX on failure. + */ +uint32_t setup_connected_group( + Simulation &sim, Tox *main_tox, const std::vector &friends); + +} // namespace tox::test + +#endif // C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H diff --git a/testing/support/src/clock.cc b/testing/support/src/clock.cc new file mode 100644 index 00000000..3657dfe3 --- /dev/null +++ b/testing/support/src/clock.cc @@ -0,0 +1,7 @@ +#include "../public/clock.hh" + +namespace tox::test { + +ClockSystem::~ClockSystem() = default; + +} // namespace tox::test diff --git a/testing/support/src/environment.cc b/testing/support/src/environment.cc new file mode 100644 index 00000000..4c8b4a6f --- /dev/null +++ b/testing/support/src/environment.cc @@ -0,0 +1,7 @@ +#include "../public/environment.hh" + +namespace tox::test { + +Environment::~Environment() = default; + +} // namespace tox::test diff --git a/testing/support/src/fake_clock.cc b/testing/support/src/fake_clock.cc new file mode 100644 index 00000000..b96e34bf --- /dev/null +++ b/testing/support/src/fake_clock.cc @@ -0,0 +1,16 @@ +#include "../doubles/fake_clock.hh" + +namespace tox::test { + +FakeClock::FakeClock(uint64_t start_time_ms) + : now_ms_(start_time_ms) +{ +} + +uint64_t FakeClock::current_time_ms() const { return now_ms_; } + +uint64_t FakeClock::current_time_s() const { return now_ms_ / 1000; } + +void FakeClock::advance(uint64_t ms) { now_ms_ += ms; } + +} // namespace tox::test diff --git a/testing/support/src/fake_memory.cc b/testing/support/src/fake_memory.cc new file mode 100644 index 00000000..4d67cf4c --- /dev/null +++ b/testing/support/src/fake_memory.cc @@ -0,0 +1,144 @@ +#include "../doubles/fake_memory.hh" + +#include +#include +#include + +#include "../../../toxcore/tox_memory_impl.h" + +namespace tox::test { + +// --- Trampolines --- + +static const Tox_Memory_Funcs kFakeMemoryVtable = { + .malloc_callback + = [](void *obj, uint32_t size) { return static_cast(obj)->malloc(size); }, + .realloc_callback + = [](void *obj, void *ptr, + uint32_t size) { return static_cast(obj)->realloc(ptr, size); }, + .dealloc_callback = [](void *obj, void *ptr) { static_cast(obj)->free(ptr); }, +}; + +// --- Implementation --- + +FakeMemory::FakeMemory() = default; +FakeMemory::~FakeMemory() = default; + +void *FakeMemory::malloc(size_t size) +{ + bool fail = failure_injector_ && failure_injector_(size); + + if (observer_) { + observer_(!fail); + } + + if (fail) { + return nullptr; + } + + void *ptr = std::malloc(size + sizeof(Header)); + if (!ptr) { + return nullptr; + } + + Header *header = static_cast
(ptr); + header->size = size; + header->magic = kMagic; + + current_allocation_ += size; + if (current_allocation_ > max_allocation_) { + max_allocation_ = current_allocation_; + } + + void *res = header + 1; + // std::cerr << "[FakeMemory] malloc(" << size << ") -> " << res << " (header=" << header << ")" + // << std::endl; + return res; +} + +void *FakeMemory::realloc(void *ptr, size_t size) +{ + if (!ptr) { + return malloc(size); + } + + Header *old_header = static_cast
(ptr) - 1; + if (old_header->magic != kMagic) { + if (old_header->magic == kFreeMagic) { + std::cerr << "[FakeMemory] realloc: Double realloc/free detected at " << ptr + << " (header=" << old_header << ")" << std::endl; + } else { + std::cerr << "[FakeMemory] realloc: Invalid pointer (wrong magic 0x" << std::hex + << old_header->magic << ") at " << ptr << " (header=" << old_header << ")" + << std::endl; + } + std::abort(); + } + + bool fail = failure_injector_ && failure_injector_(size); + + if (observer_) { + observer_(!fail); + } + + if (fail) { + // If realloc fails, original block is left untouched. + return nullptr; + } + + size_t old_size = old_header->size; + void *new_ptr = std::realloc(old_header, size + sizeof(Header)); + if (!new_ptr) { + return nullptr; + } + + Header *header = static_cast
(new_ptr); + current_allocation_ -= old_size; + current_allocation_ += size; + if (current_allocation_ > max_allocation_) { + max_allocation_ = current_allocation_; + } + + header->size = size; + header->magic = kMagic; + void *res = header + 1; + // std::cerr << "[FakeMemory] realloc(" << ptr << ", " << size << ") -> " << res << " (header=" + // << header << ")" << std::endl; + return res; +} + +void FakeMemory::free(void *ptr) +{ + if (!ptr) { + return; + } + + Header *header = static_cast
(ptr) - 1; + if (header->magic != kMagic) { + if (header->magic == kFreeMagic) { + std::cerr << "[FakeMemory] free: Double free detected at " << ptr + << " (header=" << header << ")" << std::endl; + } else { + std::cerr << "[FakeMemory] free: Invalid pointer (wrong magic 0x" << std::hex + << header->magic << ") at " << ptr << " (header=" << header << ")" + << std::endl; + } + std::abort(); + } + + size_t size = header->size; + current_allocation_ -= size; + header->magic = kFreeMagic; // Mark as free + std::free(header); +} + +void FakeMemory::set_failure_injector(FailureInjector injector) +{ + failure_injector_ = std::move(injector); +} + +void FakeMemory::set_observer(Observer observer) { observer_ = std::move(observer); } + +struct Tox_Memory FakeMemory::get_c_memory() { return Tox_Memory{&kFakeMemoryVtable, this}; } + +} // namespace tox::test diff --git a/testing/support/src/fake_random.cc b/testing/support/src/fake_random.cc new file mode 100644 index 00000000..01940a92 --- /dev/null +++ b/testing/support/src/fake_random.cc @@ -0,0 +1,63 @@ +#include "../doubles/fake_random.hh" + +#include + +#include "../../../toxcore/tox_random_impl.h" + +namespace tox::test { + +// --- Trampolines for Tox_Random_Funcs --- + +static const Tox_Random_Funcs kFakeRandomVtable = { + .bytes_callback + = [](void *obj, uint8_t *bytes, + uint32_t length) { static_cast(obj)->bytes(bytes, length); }, + .uniform_callback + = [](void *obj, + uint32_t upper_bound) { return static_cast(obj)->uniform(upper_bound); }, +}; + +FakeRandom::FakeRandom(uint64_t seed) + : rng_(seed) +{ +} + +void FakeRandom::set_entropy_source(EntropySource source) { entropy_source_ = std::move(source); } + +void FakeRandom::set_observer(Observer observer) { observer_ = std::move(observer); } + +uint32_t FakeRandom::uniform(uint32_t upper_bound) +{ + if (upper_bound == 0) { + return 0; + } + + // If we are recording (observer set) or replaying (entropy source set), + // we want consistency. Legacy behavior derived uniform from bytes. + if (entropy_source_ || observer_) { + uint32_t raw_val = 0; + bytes(reinterpret_cast(&raw_val), sizeof(raw_val)); + return raw_val % upper_bound; + } + + std::uniform_int_distribution dist(0, upper_bound - 1); + return dist(rng_); +} + +void FakeRandom::bytes(uint8_t *out, size_t count) +{ + if (entropy_source_) { + entropy_source_(out, count); + } else { + std::uniform_int_distribution dist(0, 255); + std::generate_n(out, count, [&]() { return static_cast(dist(rng_)); }); + } + + if (observer_) { + observer_(out, count); + } +} + +struct Tox_Random FakeRandom::get_c_random() { return Tox_Random{&kFakeRandomVtable, this}; } + +} // namespace tox::test diff --git a/testing/support/src/fuzz_helpers.cc b/testing/support/src/fuzz_helpers.cc new file mode 100644 index 00000000..f756655b --- /dev/null +++ b/testing/support/src/fuzz_helpers.cc @@ -0,0 +1,96 @@ +#include "../public/fuzz_helpers.hh" + +#include +#include +#include + +namespace tox::test { + +void configure_fuzz_packet_source(FakeUdpSocket &socket, Fuzz_Data &input) +{ + socket.set_packet_source([&input](std::vector &data, IP_Port &from) -> bool { + if (input.size() < 2) + return false; + + const uint8_t *len_bytes = input.consume("recv_len", 2); + if (!len_bytes) + return false; + + uint16_t len = (len_bytes[0] << 8) | len_bytes[1]; + + if (len == 0xffff) { + // EWOULDBLOCK simulation. Return false implies "no packet". + return false; + } + + size_t actual_len = std::min(static_cast(len), input.size()); + const uint8_t *payload = input.consume("recv_payload", actual_len); + + if (!payload && actual_len > 0) + return false; + + data.assign(payload, payload + actual_len); + + // Dummy address (legacy Fuzz_System used 127.0.0.2:33446) + ip_init(&from.ip, false); // IPv4 + from.ip.ip.v4.uint32 = net_htonl(0x7F000002); // 127.0.0.2 + from.port = net_htons(33446); + + return true; + }); +} + +void configure_fuzz_memory_source(FakeMemory &memory, Fuzz_Data &input) +{ + memory.set_failure_injector([&input](size_t size) -> bool { + if (input.size() < 1) { + // Legacy behavior: if input runs out, allocation succeeds. + return false; + } + + const uint8_t *b = input.consume("malloc_decision", 1); + bool succeed = (b && *b); + return !succeed; // Return true to fail + }); +} + +void configure_fuzz_random_source(FakeRandom &random, Fuzz_Data &input) +{ + random.set_entropy_source([&input](uint8_t *out, size_t count) { + // Initialize with zeros in case of underflow + std::memset(out, 0, count); + + // For small types (keys, nonces), behave like legacy fuzz support + if (count == 1 || count == 2 || count == 4 || count == 8) { + if (input.size() >= count) { + const uint8_t *bytes = input.consume("rnd_bytes", count); + std::memcpy(out, bytes, count); + } + return; + } + + // For large chunks, repeat available data + if (count == 24 || count == 32) { + size_t available = std::min(input.size(), static_cast(2)); + if (available > 0) { + const uint8_t *chunk = input.consume("rnd_chunk", available); + if (available == 2) { + std::memset(out, chunk[0], count / 2); + std::memset(out + count / 2, chunk[1], count / 2); + } else { + std::memset(out, chunk[0], count); + } + } + return; + } + + // Fallback for other sizes: consume as much as possible + size_t taken = std::min(input.size(), count); + if (taken > 0) { + const uint8_t *bytes = input.consume("rnd_generic", taken); + std::memcpy(out, bytes, taken); + } + }); +} + +} // namespace tox::test diff --git a/testing/support/src/memory.cc b/testing/support/src/memory.cc new file mode 100644 index 00000000..1bda02a2 --- /dev/null +++ b/testing/support/src/memory.cc @@ -0,0 +1,7 @@ +#include "../public/memory.hh" + +namespace tox::test { + +MemorySystem::~MemorySystem() = default; + +} // namespace tox::test diff --git a/testing/support/src/network.cc b/testing/support/src/network.cc new file mode 100644 index 00000000..d26b90e9 --- /dev/null +++ b/testing/support/src/network.cc @@ -0,0 +1,21 @@ +#include "../public/network.hh" + +namespace tox::test { + +NetworkSystem::~NetworkSystem() = default; + +IP make_ip(uint32_t ipv4) +{ + IP ip; + ip_init(&ip, false); + ip.ip.v4.uint32 = net_htonl(ipv4); + return ip; +} + +IP make_node_ip(uint32_t node_id) +{ + // Use 10.x.y.z range: 10. (id >> 16) . (id >> 8) . (id & 0xFF) + return make_ip(0x0A000000 | (node_id & 0x00FFFFFF)); +} + +} // namespace tox::test diff --git a/testing/support/src/random.cc b/testing/support/src/random.cc new file mode 100644 index 00000000..245666aa --- /dev/null +++ b/testing/support/src/random.cc @@ -0,0 +1,7 @@ +#include "../public/random.hh" + +namespace tox::test { + +RandomSystem::~RandomSystem() = default; + +} // namespace tox::test diff --git a/testing/support/src/simulated_environment.cc b/testing/support/src/simulated_environment.cc new file mode 100644 index 00000000..2d416eef --- /dev/null +++ b/testing/support/src/simulated_environment.cc @@ -0,0 +1,76 @@ +#include "../public/simulated_environment.hh" + +#include + +namespace tox::test { + +SimulatedEnvironment::SimulatedEnvironment() + : sim_(std::make_unique()) + , global_random_(std::make_unique(12345)) + , global_memory_(std::make_unique()) +{ +} + +SimulatedEnvironment::~SimulatedEnvironment() = default; + +NetworkSystem &SimulatedEnvironment::network() +{ + // Return a dummy stack for interface compliance; real networking is per-node. + static FakeNetworkStack *dummy = nullptr; + if (!dummy) { + IP dummy_ip; + ip_init(&dummy_ip, false); + dummy = new FakeNetworkStack(sim_->net(), dummy_ip); + } + return *dummy; +} + +ClockSystem &SimulatedEnvironment::clock() { return sim_->clock(); } +RandomSystem &SimulatedEnvironment::random() { return *global_random_; } +MemorySystem &SimulatedEnvironment::memory() { return *global_memory_; } + +FakeClock &SimulatedEnvironment::fake_clock() { return sim_->clock(); } +FakeRandom &SimulatedEnvironment::fake_random() { return *global_random_; } +FakeMemory &SimulatedEnvironment::fake_memory() { return *global_memory_; } + +std::unique_ptr SimulatedEnvironment::create_node(uint16_t port) +{ + auto scoped = std::make_unique(); + scoped->node = sim_->create_node(); + + // Bind port + if (port != 0) { + Socket s = scoped->node->fake_network().socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + IP_Port addr; + ip_init(&addr.ip, false); + addr.ip.ip.v4.uint32 = 0; + addr.port = net_htons(port); + scoped->node->fake_network().bind(s, &addr); + } + + // Get Primary Endpoint for Fuzzer + scoped->endpoint = scoped->node->get_primary_socket(); + + // Use global Random and Memory for legacy compatibility. + scoped->c_random = global_random_->get_c_random(); + scoped->c_memory = global_memory_->get_c_memory(); + + // Use Node's Network + scoped->c_network = scoped->node->get_c_network(); + + // Setup System + scoped->system.mem = &scoped->c_memory; + scoped->system.ns = &scoped->c_network; + scoped->system.rng = &scoped->c_random; + + scoped->system.mono_time_user_data = &sim_->clock(); + scoped->system.mono_time_callback = [](void *user_data) -> uint64_t { + return static_cast(user_data)->current_time_ms(); + }; + + return scoped; +} + +void SimulatedEnvironment::advance_time(uint64_t ms) { sim_->advance_time(ms); } + +} // namespace tox::test diff --git a/testing/support/src/simulation.cc b/testing/support/src/simulation.cc new file mode 100644 index 00000000..3a2d3364 --- /dev/null +++ b/testing/support/src/simulation.cc @@ -0,0 +1,113 @@ +#include "../public/simulation.hh" + +#include +#include + +namespace tox::test { + +// --- Simulation --- + +Simulation::Simulation() + : clock_(std::make_unique()) + , net_(std::make_unique()) +{ +} + +Simulation::~Simulation() = default; + +void Simulation::advance_time(uint64_t ms) +{ + clock_->advance(ms); + net_->process_events(clock_->current_time_ms()); +} + +void Simulation::run_until(std::function condition, uint64_t timeout_ms) +{ + uint64_t start_time = clock_->current_time_ms(); + while (!condition()) { + if (clock_->current_time_ms() - start_time > timeout_ms) { + break; + } + advance_time(10); // 10ms ticks + } +} + +std::unique_ptr Simulation::create_node() +{ + auto node = std::make_unique(*this, ++node_count_); + if (net_->is_verbose()) { + uint32_t ip4 = net_ntohl(node->ip.ip.v4.uint32); + std::cerr << "[Simulation] Created node " << node_count_ << " with IP " + << ((ip4 >> 24) & 0xFF) << "." << ((ip4 >> 16) & 0xFF) << "." + << ((ip4 >> 8) & 0xFF) << "." << (ip4 & 0xFF) << std::endl; + } + return node; +} + +// --- SimulatedNode --- + +SimulatedNode::SimulatedNode(Simulation &sim, uint32_t node_id) + : sim_(sim) + , network_(std::make_unique(sim.net(), make_node_ip(node_id))) + , random_(std::make_unique(12345 + node_id)) // Unique seed + , memory_(std::make_unique()) + , c_network(network_->get_c_network()) + , c_random(random_->get_c_random()) + , c_memory(memory_->get_c_memory()) + , ip(make_node_ip(node_id)) +{ +} + +SimulatedNode::~SimulatedNode() = default; + +NetworkSystem &SimulatedNode::network() { return *network_; } +ClockSystem &SimulatedNode::clock() { return sim_.clock(); } +RandomSystem &SimulatedNode::random() { return *random_; } +MemorySystem &SimulatedNode::memory() { return *memory_; } + +SimulatedNode::ToxPtr SimulatedNode::create_tox(const Tox_Options *options) +{ + std::unique_ptr default_options( + nullptr, tox_options_free); + + if (options == nullptr) { + default_options.reset(tox_options_new(nullptr)); + assert(default_options != nullptr); + tox_options_set_ipv6_enabled(default_options.get(), false); + tox_options_set_start_port(default_options.get(), 33445); + tox_options_set_end_port(default_options.get(), 55555); + options = default_options.get(); + } + + Tox_Options_Testing opts_testing; + Tox_System system; + system.ns = &c_network; + system.rng = &c_random; + system.mem = &c_memory; + system.mono_time_callback = [](void *user_data) -> uint64_t { + return static_cast(user_data)->current_time_ms(); + }; + system.mono_time_user_data = &sim_.clock(); + + opts_testing.operating_system = &system; + + Tox_Err_New err; + Tox_Err_New_Testing err_testing; + + Tox *t = tox_new_testing(options, &err, &opts_testing, &err_testing); + + if (!t) { + return nullptr; + } + return ToxPtr(t); +} + +FakeUdpSocket *SimulatedNode::get_primary_socket() +{ + auto sockets = network_->get_bound_udp_sockets(); + if (sockets.empty()) + return nullptr; + return sockets.front(); // Return the first one bound +} + +} // namespace tox::test diff --git a/testing/support/src/tox_network.cc b/testing/support/src/tox_network.cc new file mode 100644 index 00000000..d306e625 --- /dev/null +++ b/testing/support/src/tox_network.cc @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2026 The TokTok team. + */ + +#include "../public/tox_network.hh" + +#include +#include + +#include "../../../toxcore/network.h" +#include "../../../toxcore/tox.h" + +namespace tox::test { + +ConnectedFriend::~ConnectedFriend() = default; + +std::vector setup_connected_friends(Simulation &sim, Tox *main_tox, + SimulatedNode &main_node, int num_friends, const Tox_Options *options) +{ + std::vector friends; + friends.reserve(num_friends); + + uint8_t main_pk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(main_tox, main_pk); + uint8_t main_dht_id[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(main_tox, main_dht_id); + + FakeUdpSocket *main_socket = main_node.get_primary_socket(); + if (!main_socket) { + return {}; + } + uint16_t main_port = main_socket->local_port(); + + char main_ip_str[TOX_INET_ADDRSTRLEN]; + ip_parse_addr(&main_node.ip, main_ip_str, sizeof(main_ip_str)); + + uint8_t prev_dht_id[TOX_PUBLIC_KEY_SIZE]; + memcpy(prev_dht_id, main_dht_id, TOX_PUBLIC_KEY_SIZE); + char prev_ip_str[TOX_INET_ADDRSTRLEN]; + ip_parse_addr(&main_node.ip, prev_ip_str, sizeof(prev_ip_str)); + uint16_t prev_port = main_port; + + for (int i = 0; i < num_friends; ++i) { + auto node = sim.create_node(); + auto tox = node->create_tox(options); + if (!tox) { + return {}; + } + + uint8_t friend_pk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(tox.get(), friend_pk); + + Tox_Err_Friend_Add err; + uint32_t fn = tox_friend_add_norequest(main_tox, friend_pk, &err); + if (fn == UINT32_MAX || err != TOX_ERR_FRIEND_ADD_OK) { + return {}; + } + if (tox_friend_add_norequest(tox.get(), main_pk, &err) == UINT32_MAX + || err != TOX_ERR_FRIEND_ADD_OK) { + return {}; + } + + // Bootstrap to the main node AND the PREVIOUS node in the chain + tox_bootstrap(tox.get(), main_ip_str, main_port, main_dht_id, nullptr); + if (i > 0) { + tox_bootstrap(tox.get(), prev_ip_str, prev_port, prev_dht_id, nullptr); + } + + // Update prev for next node + tox_self_get_dht_id(tox.get(), prev_dht_id); + ip_parse_addr(&node->ip, prev_ip_str, sizeof(prev_ip_str)); + + FakeUdpSocket *node_socket = node->get_primary_socket(); + if (!node_socket) { + return {}; + } + prev_port = node_socket->local_port(); + + friends.push_back({std::move(node), std::move(tox), fn}); + + // Run simulation to let DHT stabilize + sim.run_until( + [&]() { + tox_iterate(main_tox, nullptr); + for (auto &f : friends) { + tox_iterate(f.tox.get(), nullptr); + } + return false; + }, + 200); + } + + // Optional: Bootstrap main_tox to the last node to complete the circle + if (!friends.empty()) { + tox_bootstrap(main_tox, prev_ip_str, prev_port, prev_dht_id, nullptr); + } + + // Run simulation until all are connected + sim.run_until( + [&]() { + bool all_connected = true; + int connected_count = 0; + tox_iterate(main_tox, nullptr); + for (auto &f : friends) { + tox_iterate(f.tox.get(), nullptr); + if (tox_friend_get_connection_status(main_tox, f.friend_number, nullptr) + != TOX_CONNECTION_NONE + && tox_friend_get_connection_status(f.tox.get(), 0, nullptr) + != TOX_CONNECTION_NONE) { + connected_count++; + } else { + all_connected = false; + } + } + static uint64_t last_print = 0; + if (sim.clock().current_time_ms() - last_print > 1000) { + std::cerr << "[setup_connected_friends] Friends connected: " << connected_count + << "/" << friends.size() << " (time: " << sim.clock().current_time_ms() + << "ms)" << std::endl; + + if (connected_count < static_cast(friends.size()) + && sim.clock().current_time_ms() > 10000) { + for (size_t i = 0; i < friends.size(); ++i) { + auto s1 = tox_friend_get_connection_status( + main_tox, friends[i].friend_number, nullptr); + auto s2 + = tox_friend_get_connection_status(friends[i].tox.get(), 0, nullptr); + if (s1 == TOX_CONNECTION_NONE || s2 == TOX_CONNECTION_NONE) { + std::cerr << " Friend " << i << " not connected (Main->F: " << s1 + << ", F->Main: " << s2 << ")" << std::endl; + } + } + } + + last_print = sim.clock().current_time_ms(); + } + return all_connected; + }, + 300000); // 5 minutes simulation time for 100 nodes to converge + + return friends; +} + +bool connect_friends( + Simulation &sim, SimulatedNode &node1, Tox *tox1, SimulatedNode &node2, Tox *tox2) +{ + uint8_t pk1[TOX_PUBLIC_KEY_SIZE]; + uint8_t pk2[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(tox1, pk1); + tox_self_get_public_key(tox2, pk2); + + Tox_Err_Friend_Add err_add; + uint32_t f1 = tox_friend_add_norequest(tox1, pk2, &err_add); + if (f1 == UINT32_MAX || err_add != TOX_ERR_FRIEND_ADD_OK) { + return false; + } + + uint32_t f2 = tox_friend_add_norequest(tox2, pk1, &err_add); + if (f2 == UINT32_MAX || err_add != TOX_ERR_FRIEND_ADD_OK) { + return false; + } + + uint8_t dht_id1[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dht_id1); + char ip1_str[TOX_INET_ADDRSTRLEN]; + ip_parse_addr(&node1.ip, ip1_str, sizeof(ip1_str)); + + FakeUdpSocket *s1 = node1.get_primary_socket(); + if (!s1) { + return false; + } + uint16_t port1 = s1->local_port(); + + uint8_t dht_id2[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox2, dht_id2); + char ip2_str[TOX_INET_ADDRSTRLEN]; + ip_parse_addr(&node2.ip, ip2_str, sizeof(ip2_str)); + + FakeUdpSocket *s2 = node2.get_primary_socket(); + if (!s2) { + return false; + } + uint16_t port2 = s2->local_port(); + + tox_bootstrap(tox1, ip2_str, port2, dht_id2, nullptr); + tox_bootstrap(tox2, ip1_str, port1, dht_id1, nullptr); + + sim.run_until( + [&]() { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + return tox_friend_get_connection_status(tox1, f1, nullptr) != TOX_CONNECTION_NONE + && tox_friend_get_connection_status(tox2, f2, nullptr) != TOX_CONNECTION_NONE; + }, + 60000); + + return tox_friend_get_connection_status(tox1, f1, nullptr) != TOX_CONNECTION_NONE + && tox_friend_get_connection_status(tox2, f2, nullptr) != TOX_CONNECTION_NONE; +} + +uint32_t setup_connected_group( + Simulation &sim, Tox *main_tox, const std::vector &friends) +{ + struct NodeGroupState { + uint32_t peer_count = 0; + uint32_t group_number = UINT32_MAX; + }; + + NodeGroupState main_state; + tox_callback_group_peer_join(main_tox, [](Tox *, uint32_t, uint32_t, void *user_data) { + static_cast(user_data)->peer_count++; + }); + + Tox_Err_Group_New err_new; + main_state.group_number = tox_group_new(main_tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, + reinterpret_cast("test"), 4, reinterpret_cast("main"), 4, + &err_new); + + if (main_state.group_number == UINT32_MAX || err_new != TOX_ERR_GROUP_NEW_OK) { + std::cerr << "tox_group_new failed with error: " << err_new << std::endl; + return UINT32_MAX; + } + + std::vector> friend_states; + friend_states.reserve(friends.size()); + + for (size_t i = 0; i < friends.size(); ++i) { + auto state = std::make_unique(); + tox_callback_group_peer_join( + friends[i].tox.get(), [](Tox *, uint32_t, uint32_t, void *user_data) { + static_cast(user_data)->peer_count++; + }); + + tox_callback_group_invite(friends[i].tox.get(), + [](Tox *tox, uint32_t friend_number, const uint8_t *invite_data, + size_t invite_data_length, const uint8_t *, size_t, void *user_data) { + NodeGroupState *ng_state = static_cast(user_data); + Tox_Err_Group_Invite_Accept err_accept; + ng_state->group_number + = tox_group_invite_accept(tox, friend_number, invite_data, invite_data_length, + reinterpret_cast("peer"), 4, nullptr, 0, &err_accept); + if (ng_state->group_number == UINT32_MAX + || err_accept != TOX_ERR_GROUP_INVITE_ACCEPT_OK) { + ng_state->group_number = UINT32_MAX; + } + }); + + friend_states.push_back(std::move(state)); + } + + // Run until all have joined and see everyone + bool success = false; + uint64_t last_print = 0; + size_t invites_sent = 0; + + sim.run_until( + [&]() { + tox_iterate(main_tox, &main_state); + + // Throttle invites: keep max 5 pending + size_t accepted_count = 0; + for (size_t k = 0; k < invites_sent; ++k) { + if (friend_states[k]->group_number != UINT32_MAX) { + accepted_count++; + } + } + + while (invites_sent < friends.size() && (invites_sent - accepted_count) < 5) { + Tox_Err_Group_Invite_Friend err_invite; + if (tox_group_invite_friend(main_tox, main_state.group_number, + friends[invites_sent].friend_number, &err_invite)) { + invites_sent++; + } else { + if (err_invite != TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND) { + std::cerr << "Invite failed for friend " << invites_sent << ": " + << err_invite << std::endl; + } + break; // Stop trying to send for this tick if we failed + } + } + + bool all_see_all = true; + + if (main_state.peer_count < friends.size()) { + all_see_all = false; + } + + for (size_t i = 0; i < friends.size(); ++i) { + tox_iterate(friends[i].tox.get(), friend_states[i].get()); + if (friend_states[i]->group_number == UINT32_MAX + || friend_states[i]->peer_count < friends.size()) { + all_see_all = false; + } + } + + if ((sim.clock().current_time_ms() - last_print) % 5000 == 0) { + int joined = 0; + int fully_connected = 0; + if (main_state.group_number != UINT32_MAX) + joined++; + if (main_state.peer_count >= friends.size()) + fully_connected++; + + for (const auto &fs : friend_states) { + if (fs->group_number != UINT32_MAX) { + joined++; + if (fs->peer_count >= friends.size()) + fully_connected++; + } + } + std::cerr << "[setup_connected_group] Main peer count: " << main_state.peer_count + << "/" << friends.size() << ", Nodes joined: " << joined << "/" + << (friends.size() + 1) << ", fully connected: " << fully_connected << "/" + << (friends.size() + 1) << " (time: " << sim.clock().current_time_ms() + << "ms)" << std::endl; + last_print = sim.clock().current_time_ms(); + } + + if (all_see_all) { + success = true; + return true; + } + return false; + }, + 300000); // 5 minutes + + return success ? main_state.group_number : UINT32_MAX; +} + +} // namespace tox::test diff --git a/testing/support/tox_network_test.cc b/testing/support/tox_network_test.cc new file mode 100644 index 00000000..1293fd5a --- /dev/null +++ b/testing/support/tox_network_test.cc @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2026 The TokTok team. + */ + +#include "public/tox_network.hh" + +#include + +namespace tox::test { +namespace { + + TEST(ToxNetworkTest, SetupConnectedFriends) + { + Simulation sim; + sim.net().set_latency(5); + auto main_node = sim.create_node(); + auto main_tox = main_node->create_tox(); + ASSERT_NE(main_tox, nullptr); + + const int num_friends = 3; + auto friends = setup_connected_friends(sim, main_tox.get(), *main_node, num_friends); + + ASSERT_EQ(friends.size(), num_friends); + + for (const auto &f : friends) { + EXPECT_NE(tox_friend_get_connection_status(main_tox.get(), f.friend_number, nullptr), + TOX_CONNECTION_NONE); + } + + // Verify they can actually communicate + struct Context { + int count = 0; + } ctx; + + tox_callback_friend_message(main_tox.get(), + [](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) { + static_cast(user_data)->count++; + }); + + for (const auto &f : friends) { + const uint8_t msg[] = "hello"; + tox_friend_send_message( + f.tox.get(), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr); + } + + sim.run_until([&]() { + tox_iterate(main_tox.get(), &ctx); + for (auto &f : friends) { + tox_iterate(f.tox.get(), nullptr); + } + return ctx.count == num_friends; + }); + + EXPECT_EQ(ctx.count, num_friends); + } + + TEST(ToxNetworkTest, Setup50ConnectedFriends) + { + Simulation sim; + sim.net().set_latency(5); + auto main_node = sim.create_node(); + auto main_tox = main_node->create_tox(); + ASSERT_NE(main_tox, nullptr); + + const int num_friends = 50; + auto friends = setup_connected_friends(sim, main_tox.get(), *main_node, num_friends); + + ASSERT_EQ(friends.size(), num_friends); + + struct Context { + int count = 0; + } ctx; + + tox_callback_friend_message(main_tox.get(), + [](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) { + static_cast(user_data)->count++; + }); + + for (const auto &f : friends) { + const uint8_t msg[] = "hello"; + tox_friend_send_message( + f.tox.get(), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr); + } + + sim.run_until( + [&]() { + tox_iterate(main_tox.get(), &ctx); + for (auto &f : friends) { + tox_iterate(f.tox.get(), nullptr); + } + return ctx.count == num_friends; + }, + 60000); + + EXPECT_EQ(ctx.count, num_friends); + } + + TEST(ToxNetworkTest, ConnectFriends) + { + Simulation sim; + sim.net().set_latency(5); + auto node1 = sim.create_node(); + auto tox1 = node1->create_tox(); + auto node2 = sim.create_node(); + auto tox2 = node2->create_tox(); + + ASSERT_NE(tox1, nullptr); + ASSERT_NE(tox2, nullptr); + + ASSERT_TRUE(connect_friends(sim, *node1, tox1.get(), *node2, tox2.get())); + + EXPECT_NE(tox_friend_get_connection_status(tox1.get(), 0, nullptr), TOX_CONNECTION_NONE); + EXPECT_NE(tox_friend_get_connection_status(tox2.get(), 0, nullptr), TOX_CONNECTION_NONE); + + // Verify communication + bool received = false; + tox_callback_friend_message(tox2.get(), + [](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) { + *static_cast(user_data) = true; + }); + + const uint8_t msg[] = "hello"; + tox_friend_send_message(tox1.get(), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr); + + sim.run_until([&]() { + tox_iterate(tox1.get(), nullptr); + tox_iterate(tox2.get(), &received); + return received; + }); + + EXPECT_TRUE(received); + } + + TEST(ToxNetworkTest, SetupConnectedGroup) + { + Simulation sim; + sim.net().set_latency(5); + auto main_node = sim.create_node(); + auto main_tox = main_node->create_tox(); + ASSERT_NE(main_tox, nullptr); + + const int num_friends = 15; + auto friends = setup_connected_friends(sim, main_tox.get(), *main_node, num_friends); + ASSERT_EQ(friends.size(), num_friends); + + uint32_t group_number = setup_connected_group(sim, main_tox.get(), friends); + EXPECT_NE(group_number, UINT32_MAX); + + // Verify we can send a group message + struct Context { + int count = 0; + } ctx; + + tox_callback_group_message(main_tox.get(), + [](Tox *, uint32_t, uint32_t, Tox_Message_Type, const uint8_t *, size_t, uint32_t, + void *user_data) { static_cast(user_data)->count++; }); + + for (const auto &f : friends) { + const uint8_t msg[] = "hello"; + uint32_t f_gn = 0; // It should be 0 since it's the first group. + Tox_Err_Group_Send_Message err_send; + tox_group_send_message( + f.tox.get(), f_gn, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err_send); + EXPECT_EQ(err_send, TOX_ERR_GROUP_SEND_MESSAGE_OK); + } + + sim.run_until( + [&]() { + tox_iterate(main_tox.get(), &ctx); + for (auto &f : friends) { + tox_iterate(f.tox.get(), nullptr); + } + return ctx.count == num_friends; + }, + 10000); + + EXPECT_EQ(ctx.count, num_friends); + } + + TEST(ToxNetworkTest, Setup50ConnectedGroup) + { + Simulation sim; + sim.net().set_latency(5); + auto main_node = sim.create_node(); + auto main_tox = main_node->create_tox(); + ASSERT_NE(main_tox, nullptr); + + const int num_friends = 50; + auto friends = setup_connected_friends(sim, main_tox.get(), *main_node, num_friends); + ASSERT_EQ(friends.size(), num_friends); + + uint32_t group_number = setup_connected_group(sim, main_tox.get(), friends); + EXPECT_NE(group_number, UINT32_MAX); + + struct Context { + int count = 0; + } ctx; + + tox_callback_group_message(main_tox.get(), + [](Tox *, uint32_t, uint32_t, Tox_Message_Type, const uint8_t *, size_t, uint32_t, + void *user_data) { static_cast(user_data)->count++; }); + + for (const auto &f : friends) { + const uint8_t msg[] = "hello"; + uint32_t f_gn = 0; + Tox_Err_Group_Send_Message err_send; + tox_group_send_message( + f.tox.get(), f_gn, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err_send); + EXPECT_EQ(err_send, TOX_ERR_GROUP_SEND_MESSAGE_OK); + } + + sim.run_until( + [&]() { + tox_iterate(main_tox.get(), &ctx); + for (auto &f : friends) { + tox_iterate(f.tox.get(), nullptr); + } + return ctx.count == num_friends; + }, + 120000); + + EXPECT_EQ(ctx.count, num_friends); + } + +} // namespace +} // namespace tox::test diff --git a/tools/test-wrapper.sh b/tools/test-wrapper.sh new file mode 100755 index 00000000..0870bfae --- /dev/null +++ b/tools/test-wrapper.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +# Default values +TIMEOUT=300 +RETRIES=3 + +# Automake calls LOG_COMPILER with any flags from AM_LOG_FLAGS and LOG_FLAGS, +# then the test program and its arguments. +# We want to be able to override these via environment variables too. + +if [ -n "$TEST_TIMEOUT" ]; then + TIMEOUT="$TEST_TIMEOUT" +fi + +if [ -n "$TEST_RETRIES" ]; then + RETRIES="$TEST_RETRIES" +fi + +# In case we want to pass them as flags in AM_LOG_FLAGS +while true; do + case "$1" in + --timeout) + TIMEOUT="$2" + shift 2 + ;; + --retries) + RETRIES="$2" + shift 2 + ;; + *) + break + ;; + esac +done + +if [ $# -eq 0 ]; then + echo "Usage: $0 [--timeout seconds] [--retries count] test-program [args...]" + exit 1 +fi + +attempt=0 +while [ "$attempt" -le "$RETRIES" ]; do + if [ "$attempt" -gt 0 ]; then + echo "Retry #$attempt for: $*" + fi + + # Use timeout command if available + if command -v timeout >/dev/null 2>&1; then + # Use --foreground to avoid issues with signals in some environments + # Use -s KILL to ensure it dies if it hangs + timeout --foreground -s KILL "${TIMEOUT}s" "$@" + status=$? + else + # Fallback if timeout is not available + "$@" + status=$? + fi + + # status 0: success + # status 77: skipped + # status 99: hard error + if [ "$status" -eq 0 ] || [ "$status" -eq 77 ] || [ "$status" -eq 99 ]; then + exit "$status" + fi + + # If it was timed out by GNU timeout, exit code is 124 (or 137 with -s KILL) + if [ "$status" -eq 124 ] || [ "$status" -eq 137 ]; then + echo "Test timed out after ${TIMEOUT}s: $*" + else + echo "Test failed with status $status: $*" + fi + + attempt=$((attempt + 1)) +done + +exit "$status" diff --git a/toxav/BUILD.bazel b/toxav/BUILD.bazel index c6ce2018..0460aa17 100644 --- a/toxav/BUILD.bazel +++ b/toxav/BUILD.bazel @@ -1,6 +1,5 @@ -load("@rules_cc//cc:defs.bzl", "cc_test") +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test") -load("//tools:no_undefined.bzl", "cc_library") exports_files( srcs = ["toxav.h"], @@ -80,7 +79,7 @@ cc_fuzz_test( copts = ["-UNDEBUG"], deps = [ ":rtp", - "//c-toxcore/testing/fuzzing:fuzz_support", + "//c-toxcore/testing/support", "//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:os_memory", @@ -131,12 +130,29 @@ cc_library( ], ) +cc_library( + name = "av_test_support", + testonly = True, + srcs = ["av_test_support.cc"], + hdrs = ["av_test_support.hh"], + deps = [ + ":audio", + ":rtp", + ":video", + "//c-toxcore/toxcore:logger", + "//c-toxcore/toxcore:mono_time", + "//c-toxcore/toxcore:os_memory", + "@com_google_googletest//:gtest", + ], +) + cc_test( name = "audio_test", timeout = "moderate", srcs = ["audio_test.cc"], deps = [ ":audio", + ":av_test_support", ":rtp", "//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:mono_time", @@ -166,6 +182,7 @@ cc_test( timeout = "moderate", srcs = ["video_test.cc"], deps = [ + ":av_test_support", ":rtp", ":video", "//c-toxcore/toxcore:logger", @@ -176,6 +193,50 @@ cc_test( ], ) +cc_binary( + name = "video_bench", + testonly = True, + srcs = ["video_bench.cc"], + deps = [ + ":av_test_support", + ":rtp", + ":video", + "//c-toxcore/toxcore:logger", + "//c-toxcore/toxcore:mono_time", + "//c-toxcore/toxcore:os_memory", + "@benchmark", + ], +) + +cc_binary( + name = "audio_bench", + testonly = True, + srcs = ["audio_bench.cc"], + deps = [ + ":audio", + ":av_test_support", + ":rtp", + "//c-toxcore/toxcore:logger", + "//c-toxcore/toxcore:mono_time", + "//c-toxcore/toxcore:os_memory", + "@benchmark", + ], +) + +cc_binary( + name = "rtp_bench", + testonly = True, + srcs = ["rtp_bench.cc"], + deps = [ + ":av_test_support", + ":rtp", + "//c-toxcore/toxcore:logger", + "//c-toxcore/toxcore:mono_time", + "//c-toxcore/toxcore:os_memory", + "@benchmark", + ], +) + cc_library( name = "msi", srcs = ["msi.c"], diff --git a/toxav/audio.c b/toxav/audio.c index e1710c68..b4bf42e4 100644 --- a/toxav/audio.c +++ b/toxav/audio.c @@ -48,16 +48,16 @@ struct ACSession { }; -static struct JitterBuffer *jbuf_new(uint32_t capacity); -static void jbuf_clear(struct JitterBuffer *q); -static void jbuf_free(struct JitterBuffer *q); -static int jbuf_write(const Logger *log, struct JitterBuffer *q, struct RTPMessage *m); -static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success); -static OpusEncoder *create_audio_encoder(const Logger *log, uint32_t bit_rate, uint32_t sampling_rate, +static struct JitterBuffer *_Nullable jbuf_new(uint32_t capacity); +static void jbuf_clear(struct JitterBuffer *_Nonnull q); +static void jbuf_free(struct JitterBuffer *_Nullable q); +static int jbuf_write(const Logger *_Nonnull log, struct JitterBuffer *_Nonnull q, struct RTPMessage *_Nonnull m); +static struct RTPMessage *_Nullable jbuf_read(struct JitterBuffer *_Nonnull q, int32_t *_Nonnull success); +static OpusEncoder *_Nullable create_audio_encoder(const Logger *_Nonnull log, uint32_t bit_rate, uint32_t sampling_rate, uint8_t channel_count); -static bool reconfigure_audio_encoder(const Logger *log, OpusEncoder **e, uint32_t new_br, uint32_t new_sr, - uint8_t new_ch, uint32_t *old_br, uint32_t *old_sr, uint8_t *old_ch); -static bool reconfigure_audio_decoder(ACSession *ac, uint32_t sampling_rate, uint8_t channels); +static bool reconfigure_audio_encoder(const Logger *_Nonnull log, OpusEncoder *_Nonnull *_Nonnull e, uint32_t new_br, uint32_t new_sr, + uint8_t new_ch, uint32_t *_Nonnull old_br, uint32_t *_Nonnull old_sr, uint8_t *_Nonnull old_ch); +static bool reconfigure_audio_decoder(ACSession *_Nonnull ac, uint32_t sampling_rate, uint8_t channels); @@ -246,7 +246,7 @@ void ac_iterate(ACSession *ac) if (rc < 0) { LOGGER_WARNING(ac->log, "Decoding error: %s", opus_strerror(rc)); - } else if (ac->acb != nullptr) { + } else if (ac->acb != nullptr && ac->lp_sampling_rate != 0) { ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate; ac->acb(ac->friend_number, temp_audio_buffer, (size_t)rc, ac->lp_channel_count, diff --git a/toxav/audio.h b/toxav/audio.h index 6cce3d2a..7e7bb0a3 100644 --- a/toxav/audio.h +++ b/toxav/audio.h @@ -5,8 +5,8 @@ #ifndef C_TOXCORE_TOXAV_AUDIO_H #define C_TOXCORE_TOXAV_AUDIO_H -#include #include +#include #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" @@ -35,23 +35,23 @@ extern "C" { #define AUDIO_MAX_BUFFER_SIZE_PCM16 ((AUDIO_MAX_SAMPLE_RATE * AUDIO_MAX_FRAME_DURATION_MS) / 1000) #define AUDIO_MAX_BUFFER_SIZE_BYTES (AUDIO_MAX_BUFFER_SIZE_PCM16 * 2) -typedef void ac_audio_receive_frame_cb(uint32_t friend_number, const int16_t *pcm, size_t sample_count, - uint8_t channels, uint32_t sampling_rate, void *user_data); +typedef void ac_audio_receive_frame_cb(uint32_t friend_number, const int16_t *_Nonnull pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, void *_Nullable user_data); typedef struct ACSession ACSession; struct RTPMessage; -ACSession *ac_new(Mono_Time *mono_time, const Logger *log, uint32_t friend_number, - ac_audio_receive_frame_cb *cb, void *user_data); -void ac_kill(ACSession *ac); -void ac_iterate(ACSession *ac); -int ac_queue_message(const Mono_Time *mono_time, void *cs, struct RTPMessage *msg); -int ac_reconfigure_encoder(ACSession *ac, uint32_t bit_rate, uint32_t sampling_rate, uint8_t channels); +ACSession *_Nullable ac_new(Mono_Time *_Nonnull mono_time, const Logger *_Nonnull log, uint32_t friend_number, + ac_audio_receive_frame_cb *_Nullable cb, void *_Nullable user_data); +void ac_kill(ACSession *_Nullable ac); +void ac_iterate(ACSession *_Nullable ac); +int ac_queue_message(const Mono_Time *_Nonnull mono_time, void *_Nullable cs, struct RTPMessage *_Nullable msg); +int ac_reconfigure_encoder(ACSession *_Nullable ac, uint32_t bit_rate, uint32_t sampling_rate, uint8_t channels); -uint32_t ac_get_lp_frame_duration(const ACSession *ac); +uint32_t ac_get_lp_frame_duration(const ACSession *_Nonnull ac); -int ac_encode(ACSession *ac, const int16_t *pcm, size_t sample_count, uint8_t *dest, size_t dest_max); +int ac_encode(ACSession *_Nonnull ac, const int16_t *_Nonnull pcm, size_t sample_count, uint8_t *_Nonnull dest, size_t dest_max); #ifdef __cplusplus } /* extern "C" */ diff --git a/toxav/audio_bench.cc b/toxav/audio_bench.cc new file mode 100644 index 00000000..c218ca58 --- /dev/null +++ b/toxav/audio_bench.cc @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2025 The TokTok team. + */ + +#include + +#include +#include +#include + +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/network.h" +#include "../toxcore/os_memory.h" +#include "audio.h" +#include "av_test_support.hh" +#include "rtp.h" + +namespace { + +class AudioBench : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State &state) override + { + const Memory *mem = os_memory(); + log = logger_new(mem); + tm.t = 1000; + mono_time = mono_time_new(mem, mock_time_cb, &tm); + ac = ac_new(mono_time, log, 123, nullptr, nullptr); + + sampling_rate = static_cast(state.range(0)); + channels = static_cast(state.range(1)); + uint32_t bitrate = (channels == 1) ? 32000 : 64000; + + ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels); + + sample_count = sampling_rate / 50; // 20ms frames + pcm.resize(sample_count * channels); + + rtp_mock.capture_packets = false; // Disable capturing for benchmarks + rtp_mock.auto_forward = true; + + rtp_mock.recv_session = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + } + + void TearDown(const ::benchmark::State &state) override + { + const Memory *mem = os_memory(); + if (rtp_mock.recv_session) { + rtp_kill(log, rtp_mock.recv_session); + } + if (ac) { + ac_kill(ac); + } + if (mono_time) { + mono_time_free(mem, mono_time); + } + if (log) { + logger_kill(log); + } + } + + Logger *log = nullptr; + Mono_Time *mono_time = nullptr; + MockTime tm; + ACSession *ac = nullptr; + RtpMock rtp_mock; + uint32_t sampling_rate = 0; + uint8_t channels = 0; + size_t sample_count = 0; + std::vector pcm; +}; + +// Benchmark encoding a sequence of silent audio frames. +BENCHMARK_DEFINE_F(AudioBench, EncodeSilentSequence)(benchmark::State &state) +{ + std::vector silent_pcm(sample_count * channels); + fill_silent_frame(channels, sample_count, silent_pcm); + + std::vector encoded(2000); + + for (auto _ : state) { + int encoded_size + = ac_encode(ac, silent_pcm.data(), sample_count, encoded.data(), encoded.size()); + benchmark::DoNotOptimize(encoded_size); + } +} + +BENCHMARK_REGISTER_F(AudioBench, EncodeSilentSequence) + ->Args({8000, 1}) + ->Args({48000, 1}) + ->Args({48000, 2}); + +// Benchmark encoding a sequence of audio frames. +BENCHMARK_DEFINE_F(AudioBench, EncodeSequence)(benchmark::State &state) +{ + int frame_index = 0; + const int num_prefilled = 50; + std::vector> pcms( + num_prefilled, std::vector(sample_count * channels)); + for (int i = 0; i < num_prefilled; ++i) { + fill_audio_frame(sampling_rate, channels, i, sample_count, pcms[i]); + } + + std::vector encoded(2000); + + for (auto _ : state) { + int idx = frame_index % num_prefilled; + int encoded_size + = ac_encode(ac, pcms[idx].data(), sample_count, encoded.data(), encoded.size()); + benchmark::DoNotOptimize(encoded_size); + frame_index++; + } +} + +BENCHMARK_REGISTER_F(AudioBench, EncodeSequence) + ->Args({8000, 1}) + ->Args({16000, 1}) + ->Args({24000, 1}) + ->Args({48000, 1}) + ->Args({48000, 2}); + +// Benchmark decoding a sequence of audio frames. +BENCHMARK_DEFINE_F(AudioBench, DecodeSequence)(benchmark::State &state) +{ + const int num_frames = 50; + std::vector> encoded_frames(num_frames); + + // Pre-encode + std::vector encoded_tmp(2000); + for (int i = 0; i < num_frames; ++i) { + fill_audio_frame(sampling_rate, channels, i, sample_count, pcm); + int size = ac_encode(ac, pcm.data(), sample_count, encoded_tmp.data(), encoded_tmp.size()); + + encoded_frames[i].resize(4 + size); + uint32_t net_sr = net_htonl(sampling_rate); + std::memcpy(encoded_frames[i].data(), &net_sr, 4); + std::memcpy(encoded_frames[i].data() + 4, encoded_tmp.data(), size); + } + + int frame_index = 0; + for (auto _ : state) { + int idx = frame_index % num_frames; + rtp_send_data(log, rtp_mock.recv_session, encoded_frames[idx].data(), + static_cast(encoded_frames[idx].size()), false); + ac_iterate(ac); + frame_index++; + } +} + +BENCHMARK_REGISTER_F(AudioBench, DecodeSequence) + ->Args({8000, 1}) + ->Args({16000, 1}) + ->Args({24000, 1}) + ->Args({48000, 1}) + ->Args({48000, 2}); + +// Full end-to-end sequence benchmark (Encode -> RTP -> Decode) +BENCHMARK_DEFINE_F(AudioBench, FullSequence)(benchmark::State &state) +{ + int frame_index = 0; + const int num_prefilled = 50; + std::vector> pcms( + num_prefilled, std::vector(sample_count * channels)); + for (int i = 0; i < num_prefilled; ++i) { + fill_audio_frame(sampling_rate, channels, i, sample_count, pcms[i]); + } + + std::vector encoded_tmp(2000); + + for (auto _ : state) { + int idx = frame_index % num_prefilled; + int size + = ac_encode(ac, pcms[idx].data(), sample_count, encoded_tmp.data(), encoded_tmp.size()); + + std::vector payload(4 + size); + uint32_t net_sr = net_htonl(sampling_rate); + std::memcpy(payload.data(), &net_sr, 4); + std::memcpy(payload.data() + 4, encoded_tmp.data(), size); + + rtp_send_data(log, rtp_mock.recv_session, payload.data(), + static_cast(payload.size()), false); + ac_iterate(ac); + + frame_index++; + } +} + +BENCHMARK_REGISTER_F(AudioBench, FullSequence) + ->Args({8000, 1}) + ->Args({16000, 1}) + ->Args({24000, 1}) + ->Args({48000, 1}) + ->Args({48000, 2}); + +} + +BENCHMARK_MAIN(); diff --git a/toxav/audio_test.cc b/toxav/audio_test.cc index fb6367e2..7bb65d6b 100644 --- a/toxav/audio_test.cc +++ b/toxav/audio_test.cc @@ -3,84 +3,19 @@ #include #include +#include #include #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/network.h" #include "../toxcore/os_memory.h" +#include "av_test_support.hh" #include "rtp.h" namespace { -struct AudioTimeMock { - uint64_t t; -}; - -uint64_t audio_mock_time_cb(void *ud) { return static_cast(ud)->t; } - -struct AudioTestData { - uint32_t friend_number = 0; - std::vector last_pcm; - size_t sample_count = 0; - uint8_t channels = 0; - uint32_t sampling_rate = 0; - - static void receive_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, - uint8_t channels, uint32_t sampling_rate, void *user_data) - { - auto *self = static_cast(user_data); - self->friend_number = friend_number; - self->last_pcm.assign(pcm, pcm + sample_count * channels); - self->sample_count = sample_count; - self->channels = channels; - self->sampling_rate = sampling_rate; - } -}; - -struct AudioRtpMock { - RTPSession *recv_session = nullptr; - std::vector> captured_packets; - bool auto_forward = true; - - static int send_packet(void *user_data, const uint8_t *data, uint16_t length) - { - auto *self = static_cast(user_data); - self->captured_packets.push_back(std::vector(data, data + length)); - if (self->auto_forward && self->recv_session) { - rtp_receive_packet(self->recv_session, data, length); - } - return 0; - } - - static int audio_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg) - { - return ac_queue_message(mono_time, cs, msg); - } -}; - -class AudioTest : public ::testing::Test { -protected: - void SetUp() override - { - const Memory *mem = os_memory(); - log = logger_new(mem); - tm.t = 1000; - mono_time = mono_time_new(mem, audio_mock_time_cb, &tm); - mono_time_update(mono_time); - } - - void TearDown() override - { - const Memory *mem = os_memory(); - mono_time_free(mem, mono_time); - logger_kill(log); - } - - Logger *log; - Mono_Time *mono_time; - AudioTimeMock tm; -}; +using AudioTest = AvTest; TEST_F(AudioTest, BasicNewKill) { @@ -96,11 +31,11 @@ TEST_F(AudioTest, EncodeDecodeLoop) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RtpMock rtp_mock; + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; uint32_t sampling_rate = 48000; @@ -144,6 +79,203 @@ TEST_F(AudioTest, EncodeDecodeLoop) ac_kill(ac); } +TEST_F(AudioTest, EncodeDecodeRealistic) +{ + AudioTestData data; + ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); + ASSERT_NE(ac, nullptr); + + RtpMock rtp_mock; + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + rtp_mock.recv_session = recv_rtp; + + uint32_t sampling_rate = 48000; + uint8_t channels = 1; + size_t sample_count = 960; // 20ms at 48kHz + uint32_t bitrate = 48000; + + ASSERT_EQ(ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels), 0); + + double frequency = 440.0; + double amplitude = 10000.0; + const double pi = std::acos(-1.0); + + std::vector all_sent; + std::vector all_recv; + + for (int frame = 0; frame < 50; ++frame) { + std::vector pcm(sample_count * channels); + for (size_t i = 0; i < sample_count; ++i) { + double t = static_cast(frame * sample_count + i) / sampling_rate; + pcm[i] = static_cast(std::sin(2.0 * pi * frequency * t) * amplitude); + } + all_sent.insert(all_sent.end(), pcm.begin(), pcm.end()); + + std::vector encoded(2000); + int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size()); + ASSERT_GT(encoded_size, 0); + + std::vector payload(4 + static_cast(encoded_size)); + uint32_t net_sr = net_htonl(sampling_rate); + memcpy(payload.data(), &net_sr, 4); + memcpy(payload.data() + 4, encoded.data(), static_cast(encoded_size)); + + rtp_send_data(log, send_rtp, payload.data(), static_cast(payload.size()), false); + + ac_iterate(ac); + + if (data.sample_count > 0) { + all_recv.insert(all_recv.end(), data.last_pcm.begin(), data.last_pcm.end()); + } + } + + ASSERT_FALSE(all_recv.empty()); + + // Find the best match by trying different delays. + // Jitter buffer delay (3 frames = 2880 samples) + Opus lookahead (~312 samples) = ~3192. + double min_mse = 1e18; + int best_delay = 0; + + // Search around the expected delay + for (int delay = 3000; delay < 3500; ++delay) { + double mse = 0; + int count = 0; + for (size_t i = 0; i < 2000; ++i) { // Compare a decent chunk + if (i + delay < all_sent.size() && i < all_recv.size()) { + int diff = all_sent[i + delay] - all_recv[i]; + mse += static_cast(diff) * diff; + count++; + } + } + if (count > 1000) { + mse /= count; + if (mse < min_mse) { + min_mse = mse; + best_delay = delay; + } + } + } + + printf("Best audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse); + + // For 48kbps Opus, the MSE for a sine wave should be quite low once aligned. + // 10M is about 20% of the signal power (50M), which is a safe threshold for verification. + EXPECT_LT(min_mse, 10000000.0); + + rtp_kill(log, send_rtp); + rtp_kill(log, recv_rtp); + ac_kill(ac); +} + +TEST_F(AudioTest, EncodeDecodeSiren) +{ + AudioTestData data; + ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); + ASSERT_NE(ac, nullptr); + + RtpMock rtp_mock; + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + rtp_mock.recv_session = recv_rtp; + + uint32_t sampling_rate = 48000; + uint8_t channels = 1; + size_t sample_count = 960; // 20ms at 48kHz + uint32_t bitrate = 64000; + + ASSERT_EQ(ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels), 0); + + double amplitude = 10000.0; + const double pi = std::acos(-1.0); + + std::vector all_sent; + std::vector all_recv; + + // 1 second of audio (50 frames) is enough for a siren test + for (int frame = 0; frame < 50; ++frame) { + std::vector pcm(sample_count * channels); + for (size_t i = 0; i < sample_count; ++i) { + double t = static_cast(frame * sample_count + i) / sampling_rate; + // Linear frequency sweep from 50Hz to 440Hz over 1 second + // f(t) = 50 + (440-50)/1 * t = 50 + 390t + // phi(t) = 2*pi * integral(f(t)) = 2*pi * (50t + 195t^2) + double phi = 2.0 * pi * (50.0 * t + 195.0 * t * t); + pcm[i] = static_cast(std::sin(phi) * amplitude); + } + all_sent.insert(all_sent.end(), pcm.begin(), pcm.end()); + + std::vector encoded(2000); + int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size()); + ASSERT_GT(encoded_size, 0); + + std::vector payload(4 + static_cast(encoded_size)); + uint32_t net_sr = net_htonl(sampling_rate); + memcpy(payload.data(), &net_sr, 4); + memcpy(payload.data() + 4, encoded.data(), static_cast(encoded_size)); + + rtp_send_data(log, send_rtp, payload.data(), static_cast(payload.size()), false); + + ac_iterate(ac); + + if (data.sample_count > 0) { + all_recv.insert(all_recv.end(), data.last_pcm.begin(), data.last_pcm.end()); + } + } + + ASSERT_FALSE(all_recv.empty()); + + auto calculate_mse_at = [&](int delay, size_t window) { + double mse = 0; + int count = 0; + for (size_t i = 0; i < window; ++i) { + int sent_idx = static_cast(i) + delay; + if (sent_idx >= 0 && static_cast(sent_idx) < all_sent.size() + && i < all_recv.size()) { + int diff = all_sent[static_cast(sent_idx)] - all_recv[i]; + mse += static_cast(diff) * diff; + count++; + } + } + return count > 0 ? mse / count : 1e18; + }; + + // Two-stage search for speed + double min_mse = 1e18; + int coarse_best = 0; + + // 1. Coarse search + for (int delay = -5000; delay < 5000; delay += 100) { + double mse = calculate_mse_at(delay, 5000); + if (mse < min_mse) { + min_mse = mse; + coarse_best = delay; + } + } + + // 2. Fine search around coarse best + int best_delay = coarse_best; + for (int delay = coarse_best - 100; delay <= coarse_best + 100; ++delay) { + double mse = calculate_mse_at(delay, 10000); + if (mse < min_mse) { + min_mse = mse; + best_delay = delay; + } + } + + printf("Best siren audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse); + + // For 64kbps Opus, the MSE for a siren wave should be reasonably low once aligned. + EXPECT_LT(min_mse, 20000000.0); + + rtp_kill(log, send_rtp); + rtp_kill(log, recv_rtp); + ac_kill(ac); +} TEST_F(AudioTest, ReconfigureEncoder) { AudioTestData data; @@ -184,12 +316,12 @@ TEST_F(AudioTest, QueueInvalidMessage) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; // Create a video RTP session but try to queue to audio session - RTPSession *video_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *audio_recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *video_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *audio_recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = audio_recv_rtp; std::vector dummy_video(100, 0); @@ -212,12 +344,12 @@ TEST_F(AudioTest, JitterBufferDuplicate) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; uint8_t dummy_data[100] = {0}; @@ -253,12 +385,12 @@ TEST_F(AudioTest, JitterBufferOutOfOrder) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; uint8_t dummy_data[100] = {0}; @@ -300,12 +432,12 @@ TEST_F(AudioTest, PacketLossConcealment) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; uint8_t dummy_data[100] = {0}; @@ -346,12 +478,12 @@ TEST_F(AudioTest, JitterBufferReset) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; uint8_t dummy_data[100] = {0}; @@ -391,12 +523,12 @@ TEST_F(AudioTest, DecoderReconfigureCooldown) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; uint8_t dummy_data[100] = {0}; @@ -452,12 +584,12 @@ TEST_F(AudioTest, QueueDummyMessage) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; // RTP_TYPE_AUDIO + 2 is the dummy type - RTPSession *dummy_rtp = rtp_new(log, RTP_TYPE_AUDIO + 2, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *audio_recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *dummy_rtp = rtp_new(log, RTP_TYPE_AUDIO + 2, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *audio_recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = audio_recv_rtp; std::vector dummy_payload(100, 0); @@ -480,12 +612,12 @@ TEST_F(AudioTest, LatePacketReset) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; uint8_t dummy_data[100] = {0}; @@ -531,12 +663,12 @@ TEST_F(AudioTest, InvalidSamplingRate) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; // 1. Send a packet with an absurdly large sampling rate. @@ -578,12 +710,12 @@ TEST_F(AudioTest, ShortPacket) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; // 1. Send a packet that is too short (only sampling rate, no Opus data). @@ -610,12 +742,12 @@ TEST_F(AudioTest, JitterBufferWrapAround) ACSession *ac = ac_new(mono_time, log, 123, AudioTestData::receive_frame, &data); ASSERT_NE(ac, nullptr); - AudioRtpMock rtp_mock; + RtpMock rtp_mock; rtp_mock.auto_forward = false; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, AudioRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, ac, AudioRtpMock::audio_cb); + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); rtp_mock.recv_session = recv_rtp; uint8_t dummy_data[100] = {0}; diff --git a/toxav/av_test_support.cc b/toxav/av_test_support.cc new file mode 100644 index 00000000..d77ae2e6 --- /dev/null +++ b/toxav/av_test_support.cc @@ -0,0 +1,171 @@ +#include "av_test_support.hh" + +#include +#include +#include + +#include "../toxcore/os_memory.h" + +// Mock Time +uint64_t mock_time_cb(void *ud) { return static_cast(ud)->t; } + +// RTP Mock +int RtpMock::send_packet(void *user_data, const uint8_t *data, uint16_t length) +{ + auto *self = static_cast(user_data); + if (self->capture_packets) { + if (self->store_last_packet_only) { + if (self->captured_packets.empty()) { + self->captured_packets.emplace_back(data, data + length); + } else { + self->captured_packets[0].assign(data, data + length); + } + } else { + self->captured_packets.push_back(std::vector(data, data + length)); + } + } + if (self->auto_forward && self->recv_session) { + rtp_receive_packet(self->recv_session, data, length); + } + return 0; +} + +int RtpMock::audio_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg) +{ + return ac_queue_message(mono_time, cs, msg); +} + +int RtpMock::video_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg) +{ + return vc_queue_message(mono_time, cs, msg); +} + +int RtpMock::noop_cb(const Mono_Time * /*mono_time*/, void * /*cs*/, RTPMessage *msg) +{ + std::free(msg); + return 0; +} + +// Audio Helpers +void fill_audio_frame(uint32_t sampling_rate, uint8_t channels, int frame_index, + size_t sample_count, std::vector &pcm) +{ + const double pi = std::acos(-1.0); + double amplitude = 10000.0; + + for (size_t i = 0; i < sample_count; ++i) { + double t = static_cast(frame_index * sample_count + i) / sampling_rate; + // Linear frequency sweep from 50Hz to 440Hz over 1 second (50 frames) + // f(t) = 50 + 390t + // phi(t) = 2*pi * (50t + 195t^2) + double phi = 2.0 * pi * (50.0 * t + 195.0 * t * t); + int16_t val = static_cast(std::sin(phi) * amplitude); + for (uint8_t c = 0; c < channels; ++c) { + pcm[i * channels + c] = val; + } + } +} + +void fill_silent_frame(uint8_t channels, size_t sample_count, std::vector &pcm) +{ + for (size_t i = 0; i < sample_count * channels; ++i) { + // Very low amplitude white noise (simulating silence with background hiss) + pcm[i] = (std::rand() % 21) - 10; + } +} + +AudioTestData::AudioTestData() = default; +AudioTestData::~AudioTestData() = default; + +void AudioTestData::receive_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, void *user_data) +{ + auto *self = static_cast(user_data); + self->friend_number = friend_number; + self->last_pcm.assign(pcm, pcm + sample_count * channels); + self->sample_count = sample_count; + self->channels = channels; + self->sampling_rate = sampling_rate; +} + +// Video Helpers +void fill_video_frame(uint16_t width, uint16_t height, int frame_index, std::vector &y, + std::vector &u, std::vector &v) +{ + // Background (dark gray) + std::fill(y.begin(), y.end(), 32); + std::fill(u.begin(), u.end(), 128); + std::fill(v.begin(), v.end(), 128); + + // Moving square (light gray) + int sq_size = height / 4; + if (sq_size < 16) + sq_size = 16; + int x0 = (frame_index * 8) % (width - sq_size); + int y0 = (frame_index * 4) % (height - sq_size); + for (int r = 0; r < sq_size; ++r) { + for (int c = 0; c < sq_size; ++c) { + y[(y0 + r) * width + (x0 + c)] = 200; + } + } +} + +double calculate_video_mse(uint16_t width, uint16_t height, int32_t ystride, + const std::vector &y_recv, const std::vector &y_orig) +{ + if (y_recv.empty() || y_orig.size() != static_cast(width) * height) { + return 1e10; + } + + double mse = 0; + for (int r = 0; r < height; ++r) { + for (int c = 0; c < width; ++c) { + int diff = static_cast(y_orig[r * width + c]) - y_recv[r * std::abs(ystride) + c]; + mse += diff * diff; + } + } + return mse / (static_cast(width) * height); +} + +// Video Test Data Helper +VideoTestData::VideoTestData() = default; +VideoTestData::~VideoTestData() = default; + +void VideoTestData::receive_frame(uint32_t friend_number, uint16_t width, uint16_t height, + const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, + int32_t vstride, void *user_data) +{ + auto *self = static_cast(user_data); + self->friend_number = friend_number; + self->width = width; + self->height = height; + self->ystride = ystride; + self->ustride = ustride; + self->vstride = vstride; + + self->y.assign(y, y + static_cast(std::abs(ystride)) * height); + self->u.assign(u, u + static_cast(std::abs(ustride)) * (height / 2)); + self->v.assign(v, v + static_cast(std::abs(vstride)) * (height / 2)); +} + +double VideoTestData::calculate_mse(const std::vector &y_orig) const +{ + return calculate_video_mse(width, height, ystride, y, y_orig); +} + +// Common Test Fixture +void AvTest::SetUp() +{ + const Memory *mem = os_memory(); + log = logger_new(mem); + tm.t = 1000; + mono_time = mono_time_new(mem, mock_time_cb, &tm); + mono_time_update(mono_time); +} + +void AvTest::TearDown() +{ + const Memory *mem = os_memory(); + mono_time_free(mem, mono_time); + logger_kill(log); +} diff --git a/toxav/av_test_support.hh b/toxav/av_test_support.hh new file mode 100644 index 00000000..4c0eb4ad --- /dev/null +++ b/toxav/av_test_support.hh @@ -0,0 +1,89 @@ +#ifndef TOXAV_AV_TEST_SUPPORT_H +#define TOXAV_AV_TEST_SUPPORT_H + +#include + +#include +#include + +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "audio.h" +#include "rtp.h" +#include "video.h" + +// Mock Time +struct MockTime { + uint64_t t = 1000; +}; +uint64_t mock_time_cb(void *ud); + +// RTP Mock +struct RtpMock { + RTPSession *recv_session = nullptr; + std::vector> captured_packets; + bool auto_forward = true; + bool capture_packets = true; + bool store_last_packet_only = false; + + static int send_packet(void *user_data, const uint8_t *data, uint16_t length); + static int audio_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg); + static int video_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg); + static int noop_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg); +}; + +// Audio Helpers +void fill_audio_frame(uint32_t sampling_rate, uint8_t channels, int frame_index, + size_t sample_count, std::vector &pcm); +void fill_silent_frame(uint8_t channels, size_t sample_count, std::vector &pcm); + +struct AudioTestData { + uint32_t friend_number = 0; + std::vector last_pcm; + size_t sample_count = 0; + uint8_t channels = 0; + uint32_t sampling_rate = 0; + + AudioTestData(); + ~AudioTestData(); + + static void receive_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, + uint8_t channels, uint32_t sampling_rate, void *user_data); +}; + +// Video Helpers +void fill_video_frame(uint16_t width, uint16_t height, int frame_index, std::vector &y, + std::vector &u, std::vector &v); +double calculate_video_mse(uint16_t width, uint16_t height, int32_t ystride, + const std::vector &y_recv, const std::vector &y_orig); + +// Video Test Data Helper +struct VideoTestData { + uint32_t friend_number = 0; + uint16_t width = 0; + uint16_t height = 0; + std::vector y, u, v; + int32_t ystride = 0, ustride = 0, vstride = 0; + + VideoTestData(); + ~VideoTestData(); + + static void receive_frame(uint32_t friend_number, uint16_t width, uint16_t height, + const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, + int32_t vstride, void *user_data); + + double calculate_mse(const std::vector &y_orig) const; +}; + +// Common Test Fixture +class AvTest : public ::testing::Test { +protected: + void SetUp() override; + void TearDown() override; + + Logger *log = nullptr; + Mono_Time *mono_time = nullptr; + MockTime tm; +}; + +#endif // TOXAV_AV_TEST_SUPPORT_H diff --git a/toxav/bwcontroller.c b/toxav/bwcontroller.c index 2de03a05..0f58c077 100644 --- a/toxav/bwcontroller.c +++ b/toxav/bwcontroller.c @@ -58,7 +58,7 @@ struct BWCMessage { uint32_t recv; }; -static void send_update(BWController *bwc); +static void send_update(BWController *_Nonnull bwc); BWController *bwc_new(const Logger *log, uint32_t friendnumber, @@ -163,7 +163,7 @@ static void send_update(BWController *bwc) } } -static int on_update(BWController *bwc, const struct BWCMessage *msg) +static int on_update(BWController *_Nonnull bwc, const struct BWCMessage *_Nonnull msg) { LOGGER_DEBUG(bwc->log, "%p Got update from peer", (void *)bwc); diff --git a/toxav/bwcontroller.h b/toxav/bwcontroller.h index b2e09dcd..a4b80cae 100644 --- a/toxav/bwcontroller.h +++ b/toxav/bwcontroller.h @@ -5,8 +5,8 @@ #ifndef C_TOXCORE_TOXAV_BWCONTROLLER_H #define C_TOXCORE_TOXAV_BWCONTROLLER_H -#include #include +#include #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" @@ -19,21 +19,21 @@ extern "C" { typedef struct BWController BWController; -typedef void bwc_loss_report_cb(BWController *bwc, uint32_t friend_number, float loss, void *user_data); +typedef void bwc_loss_report_cb(BWController *_Nonnull bwc, uint32_t friend_number, float loss, void *_Nullable user_data); -typedef int bwc_send_packet_cb(void *user_data, const uint8_t *data, uint16_t length); +typedef int bwc_send_packet_cb(void *_Nullable user_data, const uint8_t *_Nonnull data, uint16_t length); -BWController *bwc_new(const Logger *log, uint32_t friendnumber, - bwc_loss_report_cb *mcb, void *mcb_user_data, - bwc_send_packet_cb *send_packet, void *send_packet_user_data, - Mono_Time *bwc_mono_time); +BWController *_Nullable bwc_new(const Logger *_Nonnull log, uint32_t friendnumber, + bwc_loss_report_cb *_Nullable mcb, void *_Nullable mcb_user_data, + bwc_send_packet_cb *_Nullable send_packet, void *_Nullable send_packet_user_data, + Mono_Time *_Nonnull bwc_mono_time); -void bwc_kill(BWController *bwc); +void bwc_kill(BWController *_Nullable bwc); -void bwc_add_lost(BWController *bwc, uint32_t bytes_lost); -void bwc_add_recv(BWController *bwc, uint32_t recv_bytes); +void bwc_add_lost(BWController *_Nullable bwc, uint32_t bytes_lost); +void bwc_add_recv(BWController *_Nullable bwc, uint32_t recv_bytes); -void bwc_handle_packet(BWController *bwc, const uint8_t *data, size_t length); +void bwc_handle_packet(BWController *_Nullable bwc, const uint8_t *_Nonnull data, size_t length); #ifdef __cplusplus } /* extern "C" */ diff --git a/toxav/bwcontroller_test.cc b/toxav/bwcontroller_test.cc index de6e1859..9aaef226 100644 --- a/toxav/bwcontroller_test.cc +++ b/toxav/bwcontroller_test.cc @@ -150,22 +150,6 @@ TEST_F(BwcTest, HandlePacket) bwc_kill(bwc); } -TEST_F(BwcTest, NullArgs) -{ - // These should just return without crashing - bwc_kill(nullptr); - bwc_add_lost(nullptr, 100); - bwc_add_recv(nullptr, 100); - bwc_handle_packet(nullptr, nullptr, 0); - - MockBwcData sd; - BWController *bwc = bwc_new( - log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time); - bwc_add_recv(bwc, 0); // Should return early - bwc_add_lost(bwc, 0); // Should return early - bwc_kill(bwc); -} - TEST_F(BwcTest, InvalidPacketSize) { MockBwcData sd; diff --git a/toxav/groupav.c b/toxav/groupav.c index a27bb0aa..f785c9e1 100644 --- a/toxav/groupav.c +++ b/toxav/groupav.c @@ -31,7 +31,7 @@ typedef struct Group_JitterBuffer { uint64_t last_queued_time; } Group_JitterBuffer; -static void free_audio_packet(Group_Audio_Packet *pk) +static void free_audio_packet(Group_Audio_Packet *_Nullable pk) { if (pk == nullptr) { return; @@ -41,7 +41,7 @@ static void free_audio_packet(Group_Audio_Packet *pk) free(pk); } -static Group_JitterBuffer *create_queue(unsigned int capacity) +static Group_JitterBuffer *_Nullable create_queue(unsigned int capacity) { unsigned int size = 1; @@ -67,7 +67,7 @@ static Group_JitterBuffer *create_queue(unsigned int capacity) return q; } -static void clear_queue(Group_JitterBuffer *q) +static void clear_queue(Group_JitterBuffer *_Nonnull q) { while (q->bottom != q->top) { const size_t idx = q->bottom % q->size; @@ -77,7 +77,7 @@ static void clear_queue(Group_JitterBuffer *q) } } -static void terminate_queue(Group_JitterBuffer *q) +static void terminate_queue(Group_JitterBuffer *_Nullable q) { if (q == nullptr) { return; @@ -91,7 +91,7 @@ static void terminate_queue(Group_JitterBuffer *q) /** @retval 0 if packet was queued * @retval -1 if it wasn't. */ -static int queue(Group_JitterBuffer *q, const Mono_Time *mono_time, Group_Audio_Packet *pk) +static int queue(Group_JitterBuffer *_Nonnull q, const Mono_Time *_Nonnull mono_time, Group_Audio_Packet *_Nonnull pk) { const uint16_t sequnum = pk->sequnum; @@ -133,7 +133,7 @@ static int queue(Group_JitterBuffer *q, const Mono_Time *mono_time, Group_Audio_ * - 1 when there's a good packet * - 2 when there's a lost packet */ -static Group_Audio_Packet *dequeue(Group_JitterBuffer *q, int *success) +static Group_Audio_Packet *_Nullable dequeue(Group_JitterBuffer *_Nonnull q, int *_Nonnull success) { if (q->top == q->bottom) { *success = 0; @@ -185,7 +185,7 @@ typedef struct Group_Peer_AV { unsigned int last_packet_samples; } Group_Peer_AV; -static void kill_group_av(Group_AV *group_av) +static void kill_group_av(Group_AV *_Nonnull group_av) { if (group_av->audio_encoder != nullptr) { opus_encoder_destroy(group_av->audio_encoder); @@ -194,7 +194,7 @@ static void kill_group_av(Group_AV *group_av) free(group_av); } -static int recreate_encoder(Group_AV *group_av) +static int recreate_encoder(Group_AV *_Nonnull group_av) { if (group_av->audio_encoder != nullptr) { opus_encoder_destroy(group_av->audio_encoder); @@ -232,8 +232,8 @@ static int recreate_encoder(Group_AV *group_av) return 0; } -static Group_AV *new_group_av(const Logger *log, Tox *tox, Group_Chats *g_c, audio_data_cb *audio_callback, - void *userdata) +static Group_AV *_Nullable new_group_av(const Logger *_Nonnull log, Tox *_Nonnull tox, Group_Chats *_Nonnull g_c, audio_data_cb *_Nullable audio_callback, + void *_Nullable userdata) { if (g_c == nullptr) { return nullptr; @@ -255,7 +255,7 @@ static Group_AV *new_group_av(const Logger *log, Tox *tox, Group_Chats *g_c, aud return group_av; } -static void group_av_peer_new(void *object, Tox_Conference_Number conference_number, Tox_Conference_Peer_Number peer_number) +static void group_av_peer_new(void *_Nonnull object, Tox_Conference_Number conference_number, Tox_Conference_Peer_Number peer_number) { const Group_AV *group_av = (const Group_AV *)object; Group_Peer_AV *peer_av = (Group_Peer_AV *)calloc(1, sizeof(Group_Peer_AV)); @@ -272,7 +272,7 @@ static void group_av_peer_new(void *object, Tox_Conference_Number conference_num } } -static void group_av_peer_delete(void *object, Tox_Conference_Number conference_number, void *peer_object) +static void group_av_peer_delete(void *_Nullable object, Tox_Conference_Number conference_number, void *_Nullable peer_object) { Group_Peer_AV *peer_av = (Group_Peer_AV *)peer_object; @@ -288,7 +288,7 @@ static void group_av_peer_delete(void *object, Tox_Conference_Number conference_ free(peer_object); } -static void group_av_groupchat_delete(void *object, Tox_Conference_Number conference_number) +static void group_av_groupchat_delete(void *_Nullable object, Tox_Conference_Number conference_number) { Group_AV *group_av = (Group_AV *)object; if (group_av != nullptr) { @@ -296,7 +296,7 @@ static void group_av_groupchat_delete(void *object, Tox_Conference_Number confer } } -static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, Tox_Conference_Number conference_number, +static int decode_audio_packet(Group_AV *_Nonnull group_av, Group_Peer_AV *_Nonnull peer_av, Tox_Conference_Number conference_number, Tox_Conference_Peer_Number peer_number) { if (group_av == nullptr || peer_av == nullptr) { @@ -402,8 +402,8 @@ static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, Tox_C return -1; } -static int handle_group_audio_packet(void *object, Tox_Conference_Number conference_number, Tox_Conference_Peer_Number peer_number, void *peer_object, - const uint8_t *packet, uint16_t length) +static int handle_group_audio_packet(void *_Nonnull object, Tox_Conference_Number conference_number, Tox_Conference_Peer_Number peer_number, void *_Nonnull peer_object, + const uint8_t *_Nonnull packet, uint16_t length) { Group_AV *group_av = (Group_AV *)object; Group_Peer_AV *peer_av = (Group_Peer_AV *)peer_object; @@ -579,7 +579,7 @@ int join_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, Tox_Friend_ * @retval 0 on success. * @retval -1 on failure. */ -static int send_audio_packet(const Group_Chats *g_c, Tox_Conference_Number conference_number, const uint8_t *packet, uint16_t length) +static int send_audio_packet(const Group_Chats *_Nonnull g_c, Tox_Conference_Number conference_number, const uint8_t *_Nonnull packet, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_DATA_SIZE - 1 - sizeof(uint16_t)) { return -1; diff --git a/toxav/groupav.h b/toxav/groupav.h index 5d90b491..5ac95b30 100644 --- a/toxav/groupav.h +++ b/toxav/groupav.h @@ -16,30 +16,30 @@ // TODO(iphydf): Use this better typed one instead of the void-pointer one below. // typedef void audio_data_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, const int16_t *pcm, // uint32_t samples, uint8_t channels, uint32_t sample_rate, void *userdata); -typedef void audio_data_cb(void *tox, Tox_Conference_Number conference_number, Tox_Conference_Peer_Number peer_number, const int16_t pcm[], - uint32_t samples, uint8_t channels, uint32_t sample_rate, void *userdata); +typedef void audio_data_cb(void *_Nullable tox, Tox_Conference_Number conference_number, Tox_Conference_Peer_Number peer_number, const int16_t pcm[_Nullable], + uint32_t samples, uint8_t channels, uint32_t sample_rate, void *_Nullable userdata); /** @brief Create and connect to a new toxav group. * * @return conference number on success. * @retval -1 on failure. */ -int add_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, audio_data_cb *audio_callback, void *userdata); +int add_av_groupchat(const Logger *_Nonnull log, Tox *_Nonnull tox, Group_Chats *_Nonnull g_c, audio_data_cb *_Nullable audio_callback, void *_Nullable userdata); /** @brief Join a AV group (you need to have been invited first). * * @return conference number on success * @retval -1 on failure. */ -int join_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, Tox_Friend_Number friend_number, const uint8_t *data, - uint16_t length, audio_data_cb *audio_callback, void *userdata); +int join_av_groupchat(const Logger *_Nonnull log, Tox *_Nonnull tox, Group_Chats *_Nonnull g_c, Tox_Friend_Number friend_number, const uint8_t *_Nonnull data, + uint16_t length, audio_data_cb *_Nullable audio_callback, void *_Nullable userdata); /** @brief Send audio to the conference. * * @retval 0 on success. * @retval -1 on failure. */ -int group_send_audio(const Group_Chats *g_c, Tox_Conference_Number conference_number, const int16_t pcm[], uint32_t samples, +int group_send_audio(const Group_Chats *_Nonnull g_c, Tox_Conference_Number conference_number, const int16_t pcm[_Nonnull], uint32_t samples, uint8_t channels, uint32_t sample_rate); @@ -48,17 +48,17 @@ int group_send_audio(const Group_Chats *g_c, Tox_Conference_Number conference_nu * @retval 0 on success. * @retval -1 on failure. */ -int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, Tox_Conference_Number conference_number, - audio_data_cb *audio_callback, void *userdata); +int groupchat_enable_av(const Logger *_Nonnull log, Tox *_Nonnull tox, Group_Chats *_Nonnull g_c, Tox_Conference_Number conference_number, + audio_data_cb *_Nullable audio_callback, void *_Nullable userdata); /** @brief Disable A/V in a conference. * * @retval 0 on success. * @retval -1 on failure. */ -int groupchat_disable_av(const Group_Chats *g_c, Tox_Conference_Number conference_number); +int groupchat_disable_av(const Group_Chats *_Nonnull g_c, Tox_Conference_Number conference_number); /** Return whether A/V is enabled in the conference. */ -bool groupchat_av_enabled(const Group_Chats *g_c, Tox_Conference_Number conference_number); +bool groupchat_av_enabled(const Group_Chats *_Nonnull g_c, Tox_Conference_Number conference_number); #endif /* C_TOXCORE_TOXAV_GROUPAV_H */ diff --git a/toxav/msi.c b/toxav/msi.c index fb53edb7..25a64fb2 100644 --- a/toxav/msi.c +++ b/toxav/msi.c @@ -54,19 +54,19 @@ typedef struct MSIMessage { MSIHeaderCapabilities capabilities; } MSIMessage; -static void msg_init(MSIMessage *dest, MSIRequest request); -static void kill_call(const Logger *log, MSICall *call); -static int msg_parse_in(const Logger *log, MSIMessage *dest, const uint8_t *data, uint16_t length); -static uint8_t *msg_parse_header_out(MSIHeaderID id, uint8_t *dest, const uint8_t *value, uint8_t value_len, - uint16_t *length); -static int send_message(const Logger *log, MSISession *session, uint32_t friend_number, const MSIMessage *msg); -static int send_error(const Logger *log, MSISession *session, uint32_t friend_number, MSIError error); -static MSICall *get_call(MSISession *session, uint32_t friend_number); -static MSICall *new_call(MSISession *session, uint32_t friend_number); -static bool invoke_callback(const Logger *log, MSICall *call, MSICallbackID cb); -static void handle_init(const Logger *log, MSICall *call, const MSIMessage *msg); -static void handle_push(const Logger *log, MSICall *call, const MSIMessage *msg); -static void handle_pop(const Logger *log, MSICall *call, const MSIMessage *msg); +static void msg_init(MSIMessage *_Nonnull dest, MSIRequest request); +static void kill_call(const Logger *_Nonnull log, MSICall *_Nonnull call); +static int msg_parse_in(const Logger *_Nonnull log, MSIMessage *_Nonnull dest, const uint8_t *_Nonnull data, uint16_t length); +static uint8_t *_Nonnull msg_parse_header_out(MSIHeaderID id, uint8_t *_Nonnull dest, const uint8_t *_Nonnull value, uint8_t value_len, + uint16_t *_Nonnull length); +static int send_message(const Logger *_Nonnull log, MSISession *_Nonnull session, uint32_t friend_number, const MSIMessage *_Nonnull msg); +static int send_error(const Logger *_Nonnull log, MSISession *_Nonnull session, uint32_t friend_number, MSIError error); +static MSICall *_Nullable get_call(MSISession *_Nonnull session, uint32_t friend_number); +static MSICall *_Nullable new_call(MSISession *_Nonnull session, uint32_t friend_number); +static bool invoke_callback(const Logger *_Nonnull log, MSICall *_Nonnull call, MSICallbackID cb); +static void handle_init(const Logger *_Nonnull log, MSICall *_Nonnull call, const MSIMessage *_Nonnull msg); +static void handle_push(const Logger *_Nonnull log, MSICall *_Nonnull call, const MSIMessage *_Nonnull msg); +static void handle_pop(const Logger *_Nonnull log, MSICall *_Nonnull call, const MSIMessage *_Nonnull msg); /* * Public functions @@ -321,7 +321,7 @@ static void msg_init(MSIMessage *dest, MSIRequest request) dest->request.value = request; } -static bool check_size(const Logger *log, const uint8_t *bytes, int *constraint, uint8_t size) +static bool check_size(const Logger *_Nonnull log, const uint8_t *_Nonnull bytes, int *_Nonnull constraint, uint8_t size) { *constraint -= 2 + size; @@ -339,7 +339,7 @@ static bool check_size(const Logger *log, const uint8_t *bytes, int *constraint, } /** Assumes size == 1 */ -static bool check_enum_high(const Logger *log, const uint8_t *bytes, uint8_t enum_high) +static bool check_enum_high(const Logger *_Nonnull log, const uint8_t *_Nonnull bytes, uint8_t enum_high) { if (bytes[2] > enum_high) { LOGGER_ERROR(log, "Failed enum high limit!"); @@ -349,7 +349,7 @@ static bool check_enum_high(const Logger *log, const uint8_t *bytes, uint8_t enu return true; } -static const uint8_t *msg_parse_one(const Logger *log, MSIMessage *dest, const uint8_t *it, int *size_constraint) +static const uint8_t *_Nullable msg_parse_one(const Logger *_Nonnull log, MSIMessage *_Nonnull dest, const uint8_t *_Nonnull it, int *_Nonnull size_constraint) { switch (*it) { case ID_REQUEST: { @@ -451,7 +451,7 @@ static int send_message(const Logger *log, MSISession *session, uint32_t friend_ uint16_t size = 0; if (msg->request.exists) { - uint8_t cast = msg->request.value; + const uint8_t cast = msg->request.value; it = msg_parse_header_out(ID_REQUEST, it, &cast, sizeof(cast), &size); } else { @@ -460,7 +460,7 @@ static int send_message(const Logger *log, MSISession *session, uint32_t friend_ } if (msg->error.exists) { - uint8_t cast = msg->error.value; + const uint8_t cast = msg->error.value; it = msg_parse_header_out(ID_ERROR, it, &cast, sizeof(cast), &size); } @@ -501,7 +501,7 @@ static int send_error(const Logger *log, MSISession *session, uint32_t friend_nu return 0; } -static int invoke_callback_inner(const Logger *log, MSICall *call, MSICallbackID id) +static int invoke_callback_inner(const Logger *_Nonnull log, MSICall *_Nonnull call, MSICallbackID id) { MSISession *session = call->session; LOGGER_DEBUG(log, "invoking callback function: %u", id); @@ -671,7 +671,7 @@ CLEAR_CONTAINER: } -static bool try_handle_init(const Logger *log, MSICall *call, const MSIMessage *msg) +static bool try_handle_init(const Logger *_Nonnull log, MSICall *_Nonnull call, const MSIMessage *_Nonnull msg) { if (!msg->capabilities.exists) { LOGGER_WARNING(log, "Session: %p Invalid capabilities on 'init'", (void *)call->session); diff --git a/toxav/msi.h b/toxav/msi.h index c14376bf..69e4400f 100644 --- a/toxav/msi.h +++ b/toxav/msi.h @@ -6,6 +6,7 @@ #define C_TOXCORE_TOXAV_MSI_H #include +#include #include #include "../toxcore/logger.h" @@ -64,19 +65,19 @@ typedef enum MSICallbackID { * The call struct. Please do not modify outside msi.c */ typedef struct MSICall { - struct MSISession *session; /* Session pointer */ + struct MSISession *_Nonnull session; /* Session pointer */ - MSICallState state; - uint8_t peer_capabilities; /* Peer capabilities */ - uint8_t self_capabilities; /* Self capabilities */ - uint16_t peer_vfpsz; /* Video frame piece size */ - uint32_t friend_number; /* Index of this call in MSISession */ - MSIError error; /* Last error */ + MSICallState state; + uint8_t peer_capabilities; /* Peer capabilities */ + uint8_t self_capabilities; /* Self capabilities */ + uint16_t peer_vfpsz; /* Video frame piece size */ + uint32_t friend_number; /* Index of this call in MSISession */ + MSIError error; /* Last error */ - void *user_data; /* Pointer to av call handler */ + void *_Nullable user_data; /* Pointer to av call handler */ - struct MSICall *next; - struct MSICall *prev; + struct MSICall *_Nullable next; + struct MSICall *_Nullable prev; } MSICall; /** @@ -84,14 +85,14 @@ typedef struct MSICall { * returned the call is considered errored and will be handled * as such which means it will be terminated without any notice. */ -typedef int msi_action_cb(void *object, MSICall *call); +typedef int msi_action_cb(void *_Nullable object, MSICall *_Nonnull call); /** * Send packet callback. * * @return 0 on success, -1 on failure. */ -typedef int msi_send_packet_cb(void *user_data, uint32_t friend_number, const uint8_t *data, size_t length); +typedef int msi_send_packet_cb(void *_Nullable user_data, uint32_t friend_number, const uint8_t *_Nonnull data, size_t length); /** * MSI callbacks. @@ -110,14 +111,14 @@ typedef struct MSICallbacks { */ typedef struct MSISession { /* Call handlers */ - MSICall **calls; - uint32_t calls_tail; - uint32_t calls_head; + MSICall *_Nullable *_Nullable calls; + uint32_t calls_tail; + uint32_t calls_head; - void *user_data; + void *_Nullable user_data; - msi_send_packet_cb *send_packet; - void *send_packet_user_data; + msi_send_packet_cb *_Nonnull send_packet; + void *_Nullable send_packet_user_data; pthread_mutex_t mutex[1]; diff --git a/toxav/ring_buffer.h b/toxav/ring_buffer.h index d4208be5..1adae738 100644 --- a/toxav/ring_buffer.h +++ b/toxav/ring_buffer.h @@ -9,20 +9,22 @@ #include #include +#include "../toxcore/attributes.h" + #ifdef __cplusplus extern "C" { #endif /** Ring buffer */ typedef struct RingBuffer RingBuffer; -bool rb_full(const RingBuffer *b); -bool rb_empty(const RingBuffer *b); -void *rb_write(RingBuffer *b, void *p); -bool rb_read(RingBuffer *b, void **p); -RingBuffer *rb_new(int size); -void rb_kill(RingBuffer *b); -uint16_t rb_size(const RingBuffer *b); -uint16_t rb_data(const RingBuffer *b, void **dest); +bool rb_full(const RingBuffer *_Nonnull b); +bool rb_empty(const RingBuffer *_Nonnull b); +void *_Nullable rb_write(RingBuffer *_Nullable b, void *_Nullable p); +bool rb_read(RingBuffer *_Nonnull b, void *_Nonnull *_Nullable p); +RingBuffer *_Nullable rb_new(int size); +void rb_kill(RingBuffer *_Nullable b); +uint16_t rb_size(const RingBuffer *_Nonnull b); +uint16_t rb_data(const RingBuffer *_Nonnull b, void *_Nonnull *_Nonnull dest); #ifdef __cplusplus } /* extern "C" */ diff --git a/toxav/rtp.c b/toxav/rtp.c index 38f3baa9..0aaffa6e 100644 --- a/toxav/rtp.c +++ b/toxav/rtp.c @@ -171,6 +171,9 @@ uint32_t rtp_message_data_length_full(const RTPMessage *msg) bool rtp_session_is_receiving_active(const RTPSession *session) { + if (session == nullptr) { + return false; + } return session->rtp_receive_active; } @@ -191,8 +194,8 @@ void rtp_session_set_ssrc(RTPSession *session, uint32_t ssrc) #define VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS 15 // allocate_len is NOT including header! -static struct RTPMessage *new_message(const Logger *log, const struct RTPHeader *header, size_t allocate_len, - const uint8_t *data, uint16_t data_length) +static struct RTPMessage *_Nullable new_message(const Logger *_Nonnull log, const struct RTPHeader *_Nonnull header, size_t allocate_len, + const uint8_t *_Nonnull data, uint16_t data_length) { if (allocate_len < data_length) { LOGGER_WARNING(log, "new_message: allocate_len (%zu) < data_length (%u)", allocate_len, data_length); @@ -234,8 +237,8 @@ static struct RTPMessage *new_message(const Logger *log, const struct RTPHeader * do not kick it out right away if all slots are full instead kick out the new * incoming interframe. */ -static int8_t get_slot(const Logger *log, struct RTPWorkBufferList *wkbl, bool is_keyframe, - const struct RTPHeader *header, bool is_multipart) +static int8_t get_slot(const Logger *_Nonnull log, struct RTPWorkBufferList *_Nonnull wkbl, bool is_keyframe, + const struct RTPHeader *_Nonnull header, bool is_multipart) { if (is_multipart) { // This RTP message is part of a multipart frame, so we try to find an @@ -350,7 +353,7 @@ static int8_t get_slot(const Logger *log, struct RTPWorkBufferList *wkbl, bool i * non-NULL, it transfers ownership of the message to the caller, i.e. the * caller is responsible for storing it elsewhere or calling `free()`. */ -static struct RTPMessage *process_frame(const Logger *log, struct RTPWorkBufferList *wkbl, uint8_t slot_id) +static struct RTPMessage *_Nullable process_frame(const Logger *_Nonnull log, struct RTPWorkBufferList *_Nonnull wkbl, uint8_t slot_id) { assert(wkbl->next_free_entry >= 0); @@ -390,7 +393,7 @@ static struct RTPMessage *process_frame(const Logger *log, struct RTPWorkBufferL --wkbl->next_free_entry; // Clear the newly freed entry. - const struct RTPWorkBuffer empty = {0}; + const struct RTPWorkBuffer empty = {false}; wkbl->work_buffer[wkbl->next_free_entry] = empty; // Move ownership of the frame to the caller. @@ -406,9 +409,9 @@ static struct RTPMessage *process_frame(const Logger *log, struct RTPWorkBufferL * @param incoming_data The pure payload without header. * @param incoming_data_length The length in bytes of the incoming data payload. */ -static bool fill_data_into_slot(const Logger *log, struct RTPWorkBufferList *wkbl, const uint8_t slot_id, - bool is_keyframe, const struct RTPHeader *header, - const uint8_t *incoming_data, uint16_t incoming_data_length) +static bool fill_data_into_slot(const Logger *_Nonnull log, struct RTPWorkBufferList *_Nonnull wkbl, const uint8_t slot_id, + bool is_keyframe, const struct RTPHeader *_Nonnull header, + const uint8_t *_Nonnull incoming_data, uint16_t incoming_data_length) { // We're either filling the data into an existing slot, or in a new one that // is the next free entry. @@ -483,20 +486,20 @@ static bool fill_data_into_slot(const Logger *log, struct RTPWorkBufferList *wkb return slot->received_len == header->data_length_full; } -static void update_bwc_values(RTPSession *session, const struct RTPMessage *msg) +static void update_bwc_values(RTPSession *_Nonnull session, const struct RTPMessage *_Nonnull msg) { if (session->first_packets_counter < DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT) { ++session->first_packets_counter; } else { const uint32_t data_length_full = msg->header.data_length_full; // without header const uint32_t received_length_full = msg->header.received_length_full; // without header - if (session->add_recv) { + if (session->add_recv != nullptr) { session->add_recv(session->bwc_user_data, data_length_full); } if (received_length_full < data_length_full) { LOGGER_DEBUG(session->log, "BWC: full length=%u received length=%u", data_length_full, received_length_full); - if (session->add_lost) { + if (session->add_lost != nullptr) { session->add_lost(session->bwc_user_data, data_length_full - received_length_full); } } @@ -520,8 +523,8 @@ static void update_bwc_values(RTPSession *session, const struct RTPMessage *msg) * @retval -1 on error. * @retval 0 on success. */ -static int handle_video_packet(const Logger *log, RTPSession *session, const struct RTPHeader *header, - const uint8_t *incoming_data, uint16_t incoming_data_length) +static int handle_video_packet(const Logger *_Nonnull log, RTPSession *_Nonnull session, const struct RTPHeader *_Nonnull header, + const uint8_t *_Nonnull incoming_data, uint16_t incoming_data_length) { // Full frame length in bytes. The frame may be split into multiple packets, // but this value is the complete assembled frame size. @@ -679,7 +682,7 @@ void rtp_receive_packet(RTPSession *session, const uint8_t *data, size_t length) /* Message is not late; pick up the latest parameters */ session->rsequnum = header.sequnum; session->rtimestamp = header.timestamp; - if (session->add_recv) { + if (session->add_recv != nullptr) { session->add_recv(session->bwc_user_data, payload_size); } @@ -723,7 +726,7 @@ void rtp_receive_packet(RTPSession *session, const uint8_t *data, size_t length) memcpy(session->mp->data + header.offset_lower, &payload[RTP_HEADER_SIZE], payload_size - RTP_HEADER_SIZE); session->mp->len += payload_size - RTP_HEADER_SIZE; - if (session->add_recv) { + if (session->add_recv != nullptr) { session->add_recv(session->bwc_user_data, payload_size); } @@ -765,7 +768,7 @@ NEW_MULTIPARTED: /* Message is not late; pick up the latest parameters */ session->rsequnum = header.sequnum; session->rtimestamp = header.timestamp; - if (session->add_recv) { + if (session->add_recv != nullptr) { session->add_recv(session->bwc_user_data, payload_size); } @@ -849,10 +852,10 @@ static uint32_t rtp_random_u32(void) return randombytes_random(); } -RTPSession *rtp_new(const Logger *log, int payload_type, Mono_Time *mono_time, - rtp_send_packet_cb *send_packet, void *send_packet_user_data, - rtp_add_recv_cb *add_recv, rtp_add_lost_cb *add_lost, void *bwc_user_data, - void *cs, rtp_m_cb *mcb) +RTPSession *_Nullable rtp_new(const Logger *_Nonnull log, int payload_type, Mono_Time *_Nonnull mono_time, + rtp_send_packet_cb *_Nullable send_packet, void *_Nullable send_packet_user_data, + rtp_add_recv_cb *_Nullable add_recv, rtp_add_lost_cb *_Nullable add_lost, void *_Nullable bwc_user_data, + void *_Nonnull cs, rtp_m_cb *_Nonnull mcb) { assert(mcb != nullptr); assert(cs != nullptr); @@ -898,7 +901,7 @@ RTPSession *rtp_new(const Logger *log, int payload_type, Mono_Time *mono_time, return session; } -void rtp_kill(const Logger *log, RTPSession *session) +void rtp_kill(const Logger *_Nonnull log, RTPSession *_Nullable session) { if (session == nullptr) { LOGGER_WARNING(log, "No session"); @@ -909,7 +912,7 @@ void rtp_kill(const Logger *log, RTPSession *session) LOGGER_DEBUG(log, "Terminated RTP session V3 work_buffer_list->next_free_entry: %d", (int)session->work_buffer_list->next_free_entry); - if (session->work_buffer_list) { + if (session->work_buffer_list != nullptr) { for (int8_t i = 0; i < session->work_buffer_list->next_free_entry; ++i) { free(session->work_buffer_list->work_buffer[i].buf); } @@ -919,34 +922,34 @@ void rtp_kill(const Logger *log, RTPSession *session) free(session); } -void rtp_allow_receiving_mark(RTPSession *session) +void rtp_allow_receiving_mark(RTPSession *_Nullable session) { if (session != nullptr) { session->rtp_receive_active = true; } } -void rtp_stop_receiving_mark(RTPSession *session) +void rtp_stop_receiving_mark(RTPSession *_Nullable session) { if (session != nullptr) { session->rtp_receive_active = false; } } -static void rtp_send_piece(RTPSession *session, const struct RTPHeader *header, - const uint8_t *data, uint8_t *rdata, uint16_t length) +static void rtp_send_piece(RTPSession *_Nonnull session, const struct RTPHeader *_Nonnull header, + const uint8_t *_Nonnull data, uint8_t *_Nonnull rdata, uint16_t length) { rtp_header_pack(rdata + 1, header); memcpy(rdata + 1 + RTP_HEADER_SIZE, data, length); const uint16_t rdata_size = length + RTP_HEADER_SIZE + 1; - if (session->send_packet) { + if (session->send_packet != nullptr) { session->send_packet(session->send_packet_user_data, rdata, rdata_size); } } -static struct RTPHeader rtp_default_header(const RTPSession *session, uint32_t length, bool is_keyframe) +static struct RTPHeader rtp_default_header(const RTPSession *_Nonnull session, uint32_t length, bool is_keyframe) { uint16_t length_safe = (uint16_t)length; diff --git a/toxav/rtp.h b/toxav/rtp.h index e71c3792..24a6c83b 100644 --- a/toxav/rtp.h +++ b/toxav/rtp.h @@ -56,28 +56,28 @@ typedef struct RTPMessage RTPMessage; typedef struct RTPSession RTPSession; /* RTPMessage accessors */ -const uint8_t *rtp_message_data(const RTPMessage *msg); -uint32_t rtp_message_len(const RTPMessage *msg); -uint8_t rtp_message_pt(const RTPMessage *msg); -uint16_t rtp_message_sequnum(const RTPMessage *msg); -uint64_t rtp_message_flags(const RTPMessage *msg); -uint32_t rtp_message_data_length_full(const RTPMessage *msg); +const uint8_t *_Nonnull rtp_message_data(const RTPMessage *_Nonnull msg); +uint32_t rtp_message_len(const RTPMessage *_Nonnull msg); +uint8_t rtp_message_pt(const RTPMessage *_Nonnull msg); +uint16_t rtp_message_sequnum(const RTPMessage *_Nonnull msg); +uint64_t rtp_message_flags(const RTPMessage *_Nonnull msg); +uint32_t rtp_message_data_length_full(const RTPMessage *_Nonnull msg); /* RTPSession accessors */ -bool rtp_session_is_receiving_active(const RTPSession *session); -uint32_t rtp_session_get_ssrc(const RTPSession *session); -void rtp_session_set_ssrc(RTPSession *session, uint32_t ssrc); +bool rtp_session_is_receiving_active(const RTPSession *_Nullable session); +uint32_t rtp_session_get_ssrc(const RTPSession *_Nonnull session); +void rtp_session_set_ssrc(RTPSession *_Nonnull session, uint32_t ssrc); #define USED_RTP_WORKBUFFER_COUNT 3 #define DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT 10 -typedef int rtp_m_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg); +typedef int rtp_m_cb(const Mono_Time *_Nonnull mono_time, void *_Nonnull cs, RTPMessage *_Nonnull msg); -typedef int rtp_send_packet_cb(void *user_data, const uint8_t *data, uint16_t length); -typedef void rtp_add_recv_cb(void *user_data, uint32_t bytes); -typedef void rtp_add_lost_cb(void *user_data, uint32_t bytes); +typedef int rtp_send_packet_cb(void *_Nullable user_data, const uint8_t *_Nonnull data, uint16_t length); +typedef void rtp_add_recv_cb(void *_Nullable user_data, uint32_t bytes); +typedef void rtp_add_lost_cb(void *_Nullable user_data, uint32_t bytes); -void rtp_receive_packet(RTPSession *session, const uint8_t *data, size_t length); +void rtp_receive_packet(RTPSession *_Nonnull session, const uint8_t *_Nonnull data, size_t length); /** * Serialise an RTPHeader to bytes to be sent over the network. @@ -87,7 +87,7 @@ void rtp_receive_packet(RTPSession *session, const uint8_t *data, size_t length) * to this function. * @param header The RTPHeader to serialise. */ -size_t rtp_header_pack(uint8_t *rdata, const struct RTPHeader *header); +size_t rtp_header_pack(uint8_t *_Nonnull rdata, const struct RTPHeader *_Nonnull header); /** * Deserialise an RTPHeader from bytes received over the network. @@ -95,15 +95,15 @@ size_t rtp_header_pack(uint8_t *rdata, const struct RTPHeader *header); * @param data A byte array of length RTP_HEADER_SIZE. * @param header The RTPHeader to write the unpacked values to. */ -size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header); +size_t rtp_header_unpack(const uint8_t *_Nonnull data, struct RTPHeader *_Nonnull header); -RTPSession *rtp_new(const Logger *log, int payload_type, Mono_Time *mono_time, - rtp_send_packet_cb *send_packet, void *send_packet_user_data, - rtp_add_recv_cb *add_recv, rtp_add_lost_cb *add_lost, void *bwc_user_data, - void *cs, rtp_m_cb *mcb); -void rtp_kill(const Logger *log, RTPSession *session); -void rtp_allow_receiving_mark(RTPSession *session); -void rtp_stop_receiving_mark(RTPSession *session); +RTPSession *_Nullable rtp_new(const Logger *_Nonnull log, int payload_type, Mono_Time *_Nonnull mono_time, + rtp_send_packet_cb *_Nullable send_packet, void *_Nullable send_packet_user_data, + rtp_add_recv_cb *_Nullable add_recv, rtp_add_lost_cb *_Nullable add_lost, void *_Nullable bwc_user_data, + void *_Nonnull cs, rtp_m_cb *_Nonnull mcb); +void rtp_kill(const Logger *_Nonnull log, RTPSession *_Nullable session); +void rtp_allow_receiving_mark(RTPSession *_Nullable session); +void rtp_stop_receiving_mark(RTPSession *_Nullable session); /** * @brief Send a frame of audio or video data, chunked in @ref RTPMessage instances. @@ -114,7 +114,7 @@ void rtp_stop_receiving_mark(RTPSession *session); * @param is_keyframe Whether this video frame is a key frame. If it is an * audio frame, this parameter is ignored. */ -int rtp_send_data(const Logger *log, RTPSession *session, const uint8_t *data, uint32_t length, +int rtp_send_data(const Logger *_Nonnull log, RTPSession *_Nonnull session, const uint8_t *_Nonnull data, uint32_t length, bool is_keyframe); #ifdef __cplusplus diff --git a/toxav/rtp_bench.cc b/toxav/rtp_bench.cc new file mode 100644 index 00000000..2e834ec2 --- /dev/null +++ b/toxav/rtp_bench.cc @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2025 The TokTok team. + */ + +#include + +#include +#include + +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/os_memory.h" +#include "av_test_support.hh" +#include "rtp.h" + +namespace { + +class RtpBench : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State &) override + { + const Memory *mem = os_memory(); + log = logger_new(mem); + mono_time = mono_time_new(mem, nullptr, nullptr); + + mock.store_last_packet_only = true; + + session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &mock, nullptr, + nullptr, nullptr, &mock, RtpMock::noop_cb); + } + + void TearDown(const ::benchmark::State &) override + { + const Memory *mem = os_memory(); + rtp_kill(log, session); + mono_time_free(mem, mono_time); + logger_kill(log); + } + + Logger *log = nullptr; + Mono_Time *mono_time = nullptr; + RTPSession *session = nullptr; + RtpMock mock; +}; + +BENCHMARK_DEFINE_F(RtpBench, SendData)(benchmark::State &state) +{ + size_t data_size = static_cast(state.range(0)); + std::vector data(data_size, 0xAA); + + for (auto _ : state) { + rtp_send_data(log, session, data.data(), static_cast(data.size()), false); + benchmark::DoNotOptimize(mock.captured_packets.back()); + } +} +BENCHMARK_REGISTER_F(RtpBench, SendData)->Arg(100)->Arg(1000)->Arg(5000); + +BENCHMARK_DEFINE_F(RtpBench, ReceivePacket)(benchmark::State &state) +{ + size_t data_size = static_cast(state.range(0)); + std::vector data(data_size, 0xAA); + rtp_send_data(log, session, data.data(), static_cast(data.size()), false); + std::vector packet = mock.captured_packets.back(); + + for (auto _ : state) { + rtp_receive_packet(session, packet.data(), packet.size()); + } +} +BENCHMARK_REGISTER_F(RtpBench, ReceivePacket)->Arg(100)->Arg(1000); + +} // namespace + +BENCHMARK_MAIN(); diff --git a/toxav/rtp_fuzz_test.cc b/toxav/rtp_fuzz_test.cc index 4b30b47f..12d86ed0 100644 --- a/toxav/rtp_fuzz_test.cc +++ b/toxav/rtp_fuzz_test.cc @@ -1,15 +1,18 @@ #include "rtp.h" #include +#include #include -#include "../testing/fuzzing/fuzz_support.hh" +#include "../testing/support/public/fuzz_data.hh" #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/os_memory.h" namespace { +using tox::test::Fuzz_Data; + struct MockSessionData { }; static int mock_send_packet(void * /*user_data*/, const uint8_t * /*data*/, uint16_t /*length*/) diff --git a/toxav/rtp_test.cc b/toxav/rtp_test.cc index a6501f8a..a764bcd4 100644 --- a/toxav/rtp_test.cc +++ b/toxav/rtp_test.cc @@ -183,7 +183,8 @@ TEST_F(RtpPublicTest, HandlingInvalidPackets) mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb); // Packet too short to even contain the Tox packet ID - rtp_receive_packet(session, nullptr, 0); + uint8_t empty[1]; + rtp_receive_packet(session, empty, 0); // Packet too short (less than RTP_HEADER_SIZE + 1) uint8_t short_pkt[10] = {RTP_TYPE_AUDIO}; @@ -214,6 +215,8 @@ TEST_F(RtpPublicTest, ReceiveActiveToggle) rtp_allow_receiving_mark(session); EXPECT_TRUE(rtp_session_is_receiving_active(session)); + EXPECT_FALSE(rtp_session_is_receiving_active(nullptr)); + rtp_kill(log, session); } diff --git a/toxav/toxav.c b/toxav/toxav.c index 79bcb083..2b71145c 100644 --- a/toxav/toxav.c +++ b/toxav/toxav.c @@ -9,11 +9,11 @@ #include #include +#include "audio.h" +#include "bwcontroller.h" #include "msi.h" #include "rtp.h" -#include "audio.h" #include "video.h" -#include "bwcontroller.h" #include "../toxcore/Messenger.h" #include "../toxcore/ccompat.h" @@ -33,9 +33,9 @@ typedef struct ToxAVCall ToxAVCall; -static ToxAVCall *call_get(ToxAV *av, uint32_t friend_number); -static RTPSession *rtp_session_get(ToxAVCall *call, int payload_type); -static BWController *bwc_controller_get(const ToxAVCall *call); +static ToxAVCall *_Nullable call_get(ToxAV *_Nonnull av, uint32_t friend_number); +static RTPSession *_Nullable rtp_session_get(ToxAVCall *_Nullable call, int payload_type); +static BWController *_Nullable bwc_controller_get(const ToxAVCall *_Nullable call); struct ToxAVCall { ToxAV *av; @@ -63,6 +63,9 @@ struct ToxAVCall { toxav_audio_receive_frame_cb *acb; void *acb_user_data; + toxav_video_receive_frame_cb *vcb; + void *vcb_user_data; + pthread_mutex_t toxav_call_mutex[1]; struct ToxAVCall *prev; @@ -93,6 +96,7 @@ struct ToxAV { uint32_t calls_tail; uint32_t calls_head; pthread_mutex_t mutex[1]; + pthread_mutex_t *mutable_mutex; /* Call callback */ toxav_call_cb *ccb; @@ -120,9 +124,9 @@ struct ToxAV { Mono_Time *toxav_mono_time; // ToxAV's own mono_time instance }; -static void callback_bwc(BWController *bwc, Tox_Friend_Number friend_number, float loss, void *user_data); +static void callback_bwc(BWController *_Nonnull bwc, Tox_Friend_Number friend_number, float loss, void *_Nonnull user_data); -static int msi_send_packet(void *user_data, uint32_t friend_number, const uint8_t *data, size_t length) +static int msi_send_packet(void *_Nonnull user_data, uint32_t friend_number, const uint8_t *_Nonnull data, size_t length) { Tox *tox = (Tox *)user_data; const size_t length_new = length + 1; @@ -135,8 +139,8 @@ static int msi_send_packet(void *user_data, uint32_t friend_number, const uint8_ return error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK ? 0 : -1; } -static void handle_msi_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, - void *user_data) +static void handle_msi_packet(Tox *_Nonnull tox, uint32_t friend_number, const uint8_t *_Nonnull data, size_t length, + void *_Nullable user_data) { ToxAV *toxav = (ToxAV *)tox_get_av_object(tox); @@ -152,7 +156,7 @@ static void handle_msi_packet(Tox *tox, uint32_t friend_number, const uint8_t *d msi_handle_packet(toxav->msi, toxav->log, friend_number, data + 1, length - 1); } -static int rtp_send_packet(void *user_data, const uint8_t *data, uint16_t length) +static int rtp_send_packet(void *_Nonnull user_data, const uint8_t *_Nonnull data, uint16_t length) { ToxAVCall *call = (ToxAVCall *)user_data; Tox_Err_Friend_Custom_Packet error; @@ -160,19 +164,19 @@ static int rtp_send_packet(void *user_data, const uint8_t *data, uint16_t length return error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK ? 0 : -1; } -static void rtp_add_recv(void *user_data, uint32_t bytes) +static void rtp_add_recv(void *_Nullable user_data, uint32_t bytes) { BWController *bwc = (BWController *)user_data; bwc_add_recv(bwc, bytes); } -static void rtp_add_lost(void *user_data, uint32_t bytes) +static void rtp_add_lost(void *_Nullable user_data, uint32_t bytes) { BWController *bwc = (BWController *)user_data; bwc_add_lost(bwc, bytes); } -static void handle_rtp_packet(Tox *tox, Tox_Friend_Number friend_number, const uint8_t *data, size_t length, void *user_data) +static void handle_rtp_packet(Tox *_Nonnull tox, Tox_Friend_Number friend_number, const uint8_t *_Nonnull data, size_t length, void *_Nullable user_data) { ToxAV *toxav = (ToxAV *)tox_get_av_object(tox); @@ -199,7 +203,7 @@ static void handle_rtp_packet(Tox *tox, Tox_Friend_Number friend_number, const u rtp_receive_packet(session, data, length); } -static void handle_bwc_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data) +static void handle_bwc_packet(Tox *_Nonnull tox, uint32_t friend_number, const uint8_t *_Nonnull data, size_t length, void *_Nullable user_data) { ToxAV *toxav = (ToxAV *)tox_get_av_object(tox); @@ -222,12 +226,14 @@ static void handle_bwc_packet(Tox *tox, uint32_t friend_number, const uint8_t *d bwc_handle_packet(bwc, data, length); } -static void handle_audio_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, - uint32_t sampling_rate, void *user_data) +static void handle_audio_frame(uint32_t friend_number, const int16_t *_Nonnull pcm, size_t sample_count, uint8_t channels, + uint32_t sampling_rate, void *_Nullable user_data) { ToxAVCall *call = (ToxAVCall *)user_data; + pthread_mutex_lock(call->toxav_call_mutex); toxav_audio_receive_frame_cb *acb = call->acb; void *acb_user_data = call->acb_user_data; + pthread_mutex_unlock(call->toxav_call_mutex); if (acb != nullptr) { acb(call->av, friend_number, pcm, sample_count, channels, sampling_rate, acb_user_data); @@ -235,13 +241,15 @@ static void handle_audio_frame(uint32_t friend_number, const int16_t *pcm, size_ } static void handle_video_frame(uint32_t friend_number, uint16_t width, uint16_t height, - const uint8_t *y, const uint8_t *u, const uint8_t *v, + const uint8_t *_Nonnull y, const uint8_t *_Nonnull u, const uint8_t *_Nonnull v, int32_t ystride, int32_t ustride, int32_t vstride, - void *user_data) + void *_Nullable user_data) { ToxAVCall *call = (ToxAVCall *)user_data; - toxav_video_receive_frame_cb *vcb = call->av->vcb; - void *vcb_user_data = call->av->vcb_user_data; + pthread_mutex_lock(call->toxav_call_mutex); + toxav_video_receive_frame_cb *vcb = call->vcb; + void *vcb_user_data = call->vcb_user_data; + pthread_mutex_unlock(call->toxav_call_mutex); if (vcb != nullptr) { vcb(call->av, friend_number, width, height, y, u, v, ystride, ustride, vstride, vcb_user_data); @@ -256,11 +264,11 @@ static int callback_capabilities(void *object, MSICall *call); static bool audio_bit_rate_invalid(uint32_t bit_rate); static bool video_bit_rate_invalid(uint32_t bit_rate); -static bool invoke_call_state_callback(ToxAV *av, Tox_Friend_Number friend_number, uint32_t state); -static ToxAVCall *call_new(ToxAV *av, Tox_Friend_Number friend_number, Toxav_Err_Call *error); -static ToxAVCall *call_remove(ToxAVCall *call); -static bool call_prepare_transmission(ToxAVCall *call); -static void call_kill_transmission(ToxAVCall *call); +static bool invoke_call_state_callback(ToxAV *_Nonnull av, Tox_Friend_Number friend_number, uint32_t state); +static ToxAVCall *_Nullable call_new(ToxAV *_Nonnull av, Tox_Friend_Number friend_number, Toxav_Err_Call *_Nullable error); +static ToxAVCall *_Nullable call_remove(ToxAVCall *_Nullable call); +static bool call_prepare_transmission(ToxAVCall *_Nullable call); +static void call_kill_transmission(ToxAVCall *_Nullable call); static ToxAVCall *call_get(ToxAV *av, uint32_t friend_number) { @@ -302,7 +310,7 @@ static BWController *bwc_controller_get(const ToxAVCall *call) * @brief initialize d with default values * @param d struct to be initialized, must not be nullptr */ -static void init_decode_time_stats(DecodeTimeStats *d) +static void init_decode_time_stats(DecodeTimeStats *_Nonnull d) { assert(d != nullptr); d->count = 0; @@ -341,6 +349,7 @@ ToxAV *toxav_new(Tox *tox, Toxav_Err_New *error) rc = TOXAV_ERR_NEW_MALLOC; goto RETURN; } + av->mutable_mutex = av->mutex; av->mem = tox->sys.mem; av->log = tox->m->log; @@ -431,7 +440,11 @@ void toxav_kill(ToxAV *av) Tox *toxav_get_tox(const ToxAV *av) { - return av->tox; + Tox *tox; + pthread_mutex_lock(av->mutable_mutex); + tox = av->tox; + pthread_mutex_unlock(av->mutable_mutex); + return tox; } uint32_t toxav_audio_iteration_interval(const ToxAV *av) @@ -457,7 +470,7 @@ uint32_t toxav_iteration_interval(const ToxAV *av) * @param frame_time the duration of the current frame in ms * @param start_time the timestamp when decoding of this frame started */ -static void calc_interval(const ToxAV *av, DecodeTimeStats *stats, int32_t frame_time, uint64_t start_time) +static void calc_interval(const ToxAV *_Nonnull av, DecodeTimeStats *_Nonnull stats, int32_t frame_time, uint64_t start_time) { stats->interval = frame_time < stats->average ? 0 : (frame_time - stats->average); stats->total += current_time_monotonic(av->toxav_mono_time) - start_time; @@ -475,7 +488,7 @@ static void calc_interval(const ToxAV *av, DecodeTimeStats *stats, int32_t frame * @param av pointer to ToxAV structure of current instance * @param audio if true, iterate audio, video else */ -static void iterate_common(ToxAV *av, bool audio) +static void iterate_common(ToxAV *_Nonnull av, bool audio) { pthread_mutex_lock(av->mutex); @@ -671,7 +684,7 @@ void toxav_callback_call_state(ToxAV *av, toxav_call_state_cb *callback, void *u pthread_mutex_unlock(av->mutex); } -static Toxav_Err_Call_Control call_control_handle_resume(const ToxAVCall *call) +static Toxav_Err_Call_Control call_control_handle_resume(const ToxAVCall *_Nonnull call) { /* Only act if paused and had media transfer active before */ if (call->msi_call->self_capabilities != 0 || call->previous_self_capabilities == 0) { @@ -688,7 +701,7 @@ static Toxav_Err_Call_Control call_control_handle_resume(const ToxAVCall *call) return TOXAV_ERR_CALL_CONTROL_OK; } -static Toxav_Err_Call_Control call_control_handle_pause(ToxAVCall *call) +static Toxav_Err_Call_Control call_control_handle_pause(ToxAVCall *_Nonnull call) { /* Only act if not already paused */ if (call->msi_call->self_capabilities == 0) { @@ -706,7 +719,7 @@ static Toxav_Err_Call_Control call_control_handle_pause(ToxAVCall *call) return TOXAV_ERR_CALL_CONTROL_OK; } -static Toxav_Err_Call_Control call_control_handle_cancel(ToxAVCall *call) +static Toxav_Err_Call_Control call_control_handle_cancel(ToxAVCall *_Nonnull call) { /* Hang up */ pthread_mutex_lock(call->toxav_call_mutex); @@ -725,7 +738,7 @@ static Toxav_Err_Call_Control call_control_handle_cancel(ToxAVCall *call) return TOXAV_ERR_CALL_CONTROL_OK; } -static Toxav_Err_Call_Control call_control_handle_mute_audio(const ToxAVCall *call) +static Toxav_Err_Call_Control call_control_handle_mute_audio(const ToxAVCall *_Nonnull call) { if ((call->msi_call->self_capabilities & MSI_CAP_R_AUDIO) == 0) { return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; @@ -740,7 +753,7 @@ static Toxav_Err_Call_Control call_control_handle_mute_audio(const ToxAVCall *ca rtp_stop_receiving_mark(call->audio_rtp); return TOXAV_ERR_CALL_CONTROL_OK; } -static Toxav_Err_Call_Control call_control_handle_unmute_audio(const ToxAVCall *call) +static Toxav_Err_Call_Control call_control_handle_unmute_audio(const ToxAVCall *_Nonnull call) { if ((call->msi_call->self_capabilities ^ MSI_CAP_R_AUDIO) == 0) { return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; @@ -754,7 +767,7 @@ static Toxav_Err_Call_Control call_control_handle_unmute_audio(const ToxAVCall * rtp_allow_receiving_mark(call->audio_rtp); return TOXAV_ERR_CALL_CONTROL_OK; } -static Toxav_Err_Call_Control call_control_handle_hide_video(const ToxAVCall *call) +static Toxav_Err_Call_Control call_control_handle_hide_video(const ToxAVCall *_Nonnull call) { if ((call->msi_call->self_capabilities & MSI_CAP_R_VIDEO) == 0) { return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; @@ -768,7 +781,7 @@ static Toxav_Err_Call_Control call_control_handle_hide_video(const ToxAVCall *ca rtp_stop_receiving_mark(call->video_rtp); return TOXAV_ERR_CALL_CONTROL_OK; } -static Toxav_Err_Call_Control call_control_handle_show_video(const ToxAVCall *call) +static Toxav_Err_Call_Control call_control_handle_show_video(const ToxAVCall *_Nonnull call) { if ((call->msi_call->self_capabilities ^ MSI_CAP_R_VIDEO) == 0) { return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; @@ -782,7 +795,7 @@ static Toxav_Err_Call_Control call_control_handle_show_video(const ToxAVCall *ca rtp_allow_receiving_mark(call->video_rtp); return TOXAV_ERR_CALL_CONTROL_OK; } -static Toxav_Err_Call_Control call_control_handle(ToxAVCall *call, Toxav_Call_Control control) +static Toxav_Err_Call_Control call_control_handle(ToxAVCall *_Nonnull call, Toxav_Call_Control control) { switch (control) { case TOXAV_CALL_CONTROL_RESUME: @@ -809,7 +822,7 @@ static Toxav_Err_Call_Control call_control_handle(ToxAVCall *call, Toxav_Call_Co return TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; } -static Toxav_Err_Call_Control call_control(ToxAV *av, Tox_Friend_Number friend_number, Toxav_Call_Control control) +static Toxav_Err_Call_Control call_control(ToxAV *_Nonnull av, Tox_Friend_Number friend_number, Toxav_Call_Control control) { if (!tox_friend_exists(av->tox, friend_number)) { return TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND; @@ -1092,7 +1105,7 @@ static Toxav_Err_Send_Frame send_frames(const ToxAV *av, ToxAVCall *call) uint32_t size; bool is_keyframe; - while (vc_get_cx_data(call->video, &data, &size, &is_keyframe)) { + while (vc_get_cx_data(call->video, &data, &size, &is_keyframe) != 0) { const int res = rtp_send_data( av->log, call->video_rtp, @@ -1218,6 +1231,16 @@ void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb pthread_mutex_lock(av->mutex); av->vcb = callback; av->vcb_user_data = user_data; + + if (av->calls != nullptr) { + for (ToxAVCall *i = av->calls[av->calls_head]; i != nullptr; i = i->next) { + pthread_mutex_lock(i->toxav_call_mutex); + i->vcb = callback; + i->vcb_user_data = user_data; + pthread_mutex_unlock(i->toxav_call_mutex); + } + } + pthread_mutex_unlock(av->mutex); } @@ -1272,7 +1295,7 @@ static void callback_bwc(BWController *bwc, Tox_Friend_Number friend_number, flo pthread_mutex_unlock(call->av->mutex); } -static int callback_invite(void *object, MSICall *call) +static int callback_invite(void *_Nonnull object, MSICall *_Nonnull call) { ToxAV *toxav = (ToxAV *)object; pthread_mutex_lock(toxav->mutex); @@ -1289,8 +1312,8 @@ static int callback_invite(void *object, MSICall *call) av_call->msi_call = call; if (toxav->ccb != nullptr) { - toxav->ccb(toxav, call->friend_number, call->peer_capabilities & MSI_CAP_S_AUDIO, - call->peer_capabilities & MSI_CAP_S_VIDEO, toxav->ccb_user_data); + toxav->ccb(toxav, call->friend_number, (call->peer_capabilities & MSI_CAP_S_AUDIO) != 0, + (call->peer_capabilities & MSI_CAP_S_VIDEO) != 0, toxav->ccb_user_data); } else { /* No handler to capture the call request, send failure */ pthread_mutex_unlock(toxav->mutex); @@ -1301,7 +1324,7 @@ static int callback_invite(void *object, MSICall *call) return 0; } -static int callback_start(void *object, MSICall *call) +static int callback_start(void *_Nonnull object, MSICall *_Nonnull call) { ToxAV *toxav = (ToxAV *)object; pthread_mutex_lock(toxav->mutex); @@ -1330,7 +1353,7 @@ static int callback_start(void *object, MSICall *call) return 0; } -static int callback_end(void *object, MSICall *call) +static int callback_end(void *_Nonnull object, MSICall *_Nonnull call) { ToxAV *toxav = (ToxAV *)object; pthread_mutex_lock(toxav->mutex); @@ -1346,7 +1369,7 @@ static int callback_end(void *object, MSICall *call) return 0; } -static int callback_error(void *object, MSICall *call) +static int callback_error(void *_Nonnull object, MSICall *_Nonnull call) { ToxAV *toxav = (ToxAV *)object; pthread_mutex_lock(toxav->mutex); @@ -1362,7 +1385,7 @@ static int callback_error(void *object, MSICall *call) return 0; } -static int callback_capabilities(void *object, MSICall *call) +static int callback_capabilities(void *_Nonnull object, MSICall *_Nonnull call) { ToxAV *toxav = (ToxAV *)object; pthread_mutex_lock(toxav->mutex); @@ -1404,7 +1427,7 @@ static bool video_bit_rate_invalid(uint32_t bit_rate) return bit_rate > 1000000; } -static bool invoke_call_state_callback(ToxAV *av, Tox_Friend_Number friend_number, uint32_t state) +static bool invoke_call_state_callback(ToxAV *_Nonnull av, Tox_Friend_Number friend_number, uint32_t state) { if (av->scb != nullptr) { av->scb(av, friend_number, state, av->scb_user_data); @@ -1415,7 +1438,7 @@ static bool invoke_call_state_callback(ToxAV *av, Tox_Friend_Number friend_numbe return true; } -static ToxAVCall *call_new(ToxAV *av, Tox_Friend_Number friend_number, Toxav_Err_Call *error) +static ToxAVCall *_Nullable call_new(ToxAV *_Nonnull av, Tox_Friend_Number friend_number, Toxav_Err_Call *_Nullable error) { /* Assumes mutex locked */ Toxav_Err_Call rc = TOXAV_ERR_CALL_OK; @@ -1510,7 +1533,7 @@ RETURN: return call; } -static ToxAVCall *call_remove(ToxAVCall *call) +static ToxAVCall *_Nullable call_remove(ToxAVCall *_Nullable call) { if (call == nullptr) { return nullptr; @@ -1560,7 +1583,7 @@ CLEAR: return nullptr; } -static bool call_prepare_transmission(ToxAVCall *call) +static bool call_prepare_transmission(ToxAVCall *_Nullable call) { /* Assumes mutex locked */ @@ -1612,6 +1635,8 @@ static bool call_prepare_transmission(ToxAVCall *call) } } { /* Prepare video */ + call->vcb = av->vcb; + call->vcb_user_data = av->vcb_user_data; call->video = vc_new(av->log, av->toxav_mono_time, call->friend_number, handle_video_frame, call); if (call->video == nullptr) { @@ -1649,7 +1674,7 @@ FAILURE_2: return false; } -static void call_kill_transmission(ToxAVCall *call) +static void call_kill_transmission(ToxAVCall *_Nullable call) { if (call == nullptr || !call->active) { return; diff --git a/toxav/video.c b/toxav/video.c index e50032c3..ebb031c0 100644 --- a/toxav/video.c +++ b/toxav/video.c @@ -42,6 +42,7 @@ struct VCSession { void *user_data; pthread_mutex_t queue_mutex[1]; + pthread_mutex_t *mutable_queue_mutex; const Logger *log; vpx_codec_iter_t iter; @@ -71,11 +72,11 @@ struct VCSession { #define VIDEO_MAX_FRAME_SIZE (10 * 1024 * 1024) #define VIDEO_MAX_RESOLUTION_LIMIT 4096 -static vpx_codec_iface_t *video_codec_decoder_interface(void) +static vpx_codec_iface_t *_Nonnull video_codec_decoder_interface(void) { return vpx_codec_vp8_dx(); } -static vpx_codec_iface_t *video_codec_encoder_interface(void) +static vpx_codec_iface_t *_Nonnull video_codec_encoder_interface(void) { return vpx_codec_vp8_cx(); } @@ -89,7 +90,7 @@ static vpx_codec_iface_t *video_codec_encoder_interface(void) #define VPX_MAX_DECODER_THREADS 4 #define VIDEO_VP8_DECODER_POST_PROCESSING_ENABLED 0 -static void vc_init_encoder_cfg(const Logger *log, vpx_codec_enc_cfg_t *cfg, int16_t kf_max_dist) +static void vc_init_encoder_cfg(const Logger *_Nonnull log, vpx_codec_enc_cfg_t *_Nonnull cfg, int16_t kf_max_dist) { const vpx_codec_err_t rc = vpx_codec_enc_config_default(video_codec_encoder_interface(), cfg, 0); @@ -178,6 +179,7 @@ VCSession *vc_new(const Logger *log, const Mono_Time *mono_time, uint32_t friend free(vc); return nullptr; } + vc->mutable_queue_mutex = vc->queue_mutex; const int cpu_used_value = VP8E_SET_CPUUSED_VALUE; @@ -224,7 +226,7 @@ VCSession *vc_new(const Logger *log, const Mono_Time *mono_time, uint32_t friend } } else { vp8_postproc_cfg_t pp = {0, 0, 0}; - vpx_codec_err_t cc_res = vpx_codec_control(vc->decoder, VP8_SET_POSTPROC, &pp); + const vpx_codec_err_t cc_res = vpx_codec_control(vc->decoder, VP8_SET_POSTPROC, &pp); if (cc_res != VPX_CODEC_OK) { LOGGER_WARNING(log, "Failed to turn OFF postproc"); @@ -509,9 +511,9 @@ int vc_encode(VCSession *vc, uint16_t width, uint16_t height, const uint8_t *y, /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." * http://fourcc.org/yuv.php#IYUV */ - memcpy(img.planes[VPX_PLANE_Y], y, width * height); - memcpy(img.planes[VPX_PLANE_U], u, (width / 2) * (height / 2)); - memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); + memcpy(img.planes[VPX_PLANE_Y], y, (size_t)width * height); + memcpy(img.planes[VPX_PLANE_U], u, ((size_t)width / 2) * (height / 2)); + memcpy(img.planes[VPX_PLANE_V], v, ((size_t)width / 2) * (height / 2)); } int vpx_flags = 0; @@ -555,7 +557,11 @@ int vc_get_cx_data(VCSession *vc, uint8_t **data, uint32_t *size, bool *is_keyfr uint32_t vc_get_lcfd(const VCSession *vc) { - return vc->lcfd; + uint32_t lcfd; + pthread_mutex_lock(vc->mutable_queue_mutex); + lcfd = vc->lcfd; + pthread_mutex_unlock(vc->mutable_queue_mutex); + return lcfd; } pthread_mutex_t *vc_get_queue_mutex(VCSession *vc) diff --git a/toxav/video.h b/toxav/video.h index c3ff62e5..35dd9978 100644 --- a/toxav/video.h +++ b/toxav/video.h @@ -16,9 +16,9 @@ extern "C" { #endif typedef void vc_video_receive_frame_cb(uint32_t friend_number, uint16_t width, uint16_t height, - const uint8_t *y, const uint8_t *u, const uint8_t *v, + const uint8_t *_Nonnull y, const uint8_t *_Nonnull u, const uint8_t *_Nonnull v, int32_t ystride, int32_t ustride, int32_t vstride, - void *user_data); + void *_Nullable user_data); typedef struct VCSession VCSession; @@ -27,21 +27,21 @@ typedef struct VCSession VCSession; struct RTPMessage; -VCSession *vc_new(const Logger *log, const Mono_Time *mono_time, uint32_t friend_number, - vc_video_receive_frame_cb *cb, void *user_data); -void vc_kill(VCSession *vc); -void vc_iterate(VCSession *vc); +VCSession *_Nullable vc_new(const Logger *_Nonnull log, const Mono_Time *_Nonnull mono_time, uint32_t friend_number, + vc_video_receive_frame_cb *_Nullable cb, void *_Nullable user_data); +void vc_kill(VCSession *_Nullable vc); +void vc_iterate(VCSession *_Nullable vc); -int vc_queue_message(const Mono_Time *mono_time, void *cs, struct RTPMessage *msg); -int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height, int16_t kf_max_dist); +int vc_queue_message(const Mono_Time *_Nonnull mono_time, void *_Nullable cs, struct RTPMessage *_Nullable msg); +int vc_reconfigure_encoder(VCSession *_Nullable vc, uint32_t bit_rate, uint16_t width, uint16_t height, int16_t kf_max_dist); -int vc_encode(VCSession *vc, uint16_t width, uint16_t height, const uint8_t *y, - const uint8_t *u, const uint8_t *v, int encode_flags); +int vc_encode(VCSession *_Nonnull vc, uint16_t width, uint16_t height, const uint8_t *_Nonnull y, + const uint8_t *_Nonnull u, const uint8_t *_Nonnull v, int encode_flags); -int vc_get_cx_data(VCSession *vc, uint8_t **data, uint32_t *size, bool *is_keyframe); -uint32_t vc_get_lcfd(const VCSession *vc); -pthread_mutex_t *vc_get_queue_mutex(VCSession *vc); -void vc_increment_frame_counter(VCSession *vc); +int vc_get_cx_data(VCSession *_Nonnull vc, uint8_t *_Nonnull *_Nonnull data, uint32_t *_Nonnull size, bool *_Nonnull is_keyframe); +uint32_t vc_get_lcfd(const VCSession *_Nonnull vc); +pthread_mutex_t *_Nonnull vc_get_queue_mutex(VCSession *_Nonnull vc); +void vc_increment_frame_counter(VCSession *_Nonnull vc); #ifdef __cplusplus } /* extern "C" */ diff --git a/toxav/video_bench.cc b/toxav/video_bench.cc new file mode 100644 index 00000000..4b376fb0 --- /dev/null +++ b/toxav/video_bench.cc @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2025 The TokTok team. + */ + +#include + +#include + +#include "../toxcore/logger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/os_memory.h" +#include "av_test_support.hh" +#include "rtp.h" +#include "video.h" + +namespace { + +class VideoBench : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State &state) override + { + const Memory *mem = os_memory(); + log = logger_new(mem); + tm.t = 1000; + mono_time = mono_time_new(mem, mock_time_cb, &tm); + vc = vc_new(log, mono_time, 123, nullptr, nullptr); + + width = static_cast(state.range(0)); + height = static_cast(state.range(1)); + // Use a standard bitrate for benchmarks + vc_reconfigure_encoder(vc, 2000, width, height, -1); + + y.resize(static_cast(width) * height); + u.resize((static_cast(width) / 2) * (static_cast(height) / 2)); + v.resize((static_cast(width) / 2) * (static_cast(height) / 2)); + + rtp_mock.capture_packets = false; // Disable capturing for benchmarks + rtp_mock.auto_forward = true; + + rtp_mock.recv_session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + } + + void TearDown(const ::benchmark::State &state) override + { + const Memory *mem = os_memory(); + if (rtp_mock.recv_session) { + rtp_kill(log, rtp_mock.recv_session); + } + if (vc) { + vc_kill(vc); + } + if (mono_time) { + mono_time_free(mem, mono_time); + } + if (log) { + logger_kill(log); + } + } + + Logger *log = nullptr; + Mono_Time *mono_time = nullptr; + MockTime tm; + VCSession *vc = nullptr; + RtpMock rtp_mock; + uint16_t width = 0, height = 0; + std::vector y, u, v; +}; + +// Benchmark encoding a sequence of frames. +// Measures how the encoder performs as it builds up temporal state. +BENCHMARK_DEFINE_F(VideoBench, EncodeSequence)(benchmark::State &state) +{ + int frame_index = 0; + // Pre-fill frames to avoid measuring fill_frame time + const int num_prefilled = 100; + std::vector> ys(num_prefilled, std::vector(width * height)); + std::vector> us( + num_prefilled, std::vector((width / 2) * (height / 2))); + std::vector> vs( + num_prefilled, std::vector((width / 2) * (height / 2))); + for (int i = 0; i < num_prefilled; ++i) { + fill_video_frame(width, height, i, ys[i], us[i], vs[i]); + } + + for (auto _ : state) { + int idx = frame_index % num_prefilled; + // Force a keyframe every 100 frames to simulate real-world periodic keyframes + int flags = (frame_index % 100 == 0) ? VC_EFLAG_FORCE_KF : VC_EFLAG_NONE; + vc_encode(vc, width, height, ys[idx].data(), us[idx].data(), vs[idx].data(), flags); + vc_increment_frame_counter(vc); + + uint8_t *pkt_data; + uint32_t pkt_size; + bool is_keyframe; + while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) { + benchmark::DoNotOptimize(pkt_data); + benchmark::DoNotOptimize(pkt_size); + } + frame_index++; + } +} + +BENCHMARK_REGISTER_F(VideoBench, EncodeSequence) + ->Args({320, 240}) + ->Args({640, 480}) + ->Args({1280, 720}) + ->Args({1920, 1080}); + +// Benchmark decoding a sequence of frames. +// First pre-encodes a sequence, then measures decoding performance. +BENCHMARK_DEFINE_F(VideoBench, DecodeSequence)(benchmark::State &state) +{ + const int num_frames = 100; + std::vector> encoded_frames(num_frames); + std::vector is_keyframe_list(num_frames); + + // Pre-encode + for (int i = 0; i < num_frames; ++i) { + fill_video_frame(width, height, i, y, u, v); + int flags = (i == 0) ? VC_EFLAG_FORCE_KF : VC_EFLAG_NONE; + vc_encode(vc, width, height, y.data(), u.data(), v.data(), flags); + vc_increment_frame_counter(vc); + + uint8_t *pkt_data; + uint32_t pkt_size; + bool is_kf; + while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_kf)) { + encoded_frames[i].insert(encoded_frames[i].end(), pkt_data, pkt_data + pkt_size); + is_keyframe_list[i] = is_kf; + } + } + + int frame_index = 0; + for (auto _ : state) { + int idx = frame_index % num_frames; + const auto &encoded_data = encoded_frames[idx]; + rtp_send_data(log, rtp_mock.recv_session, encoded_data.data(), + static_cast(encoded_data.size()), is_keyframe_list[idx]); + vc_iterate(vc); + frame_index++; + } +} + +BENCHMARK_REGISTER_F(VideoBench, DecodeSequence) + ->Args({320, 240}) + ->Args({640, 480}) + ->Args({1280, 720}) + ->Args({1920, 1080}); + +// Full end-to-end sequence benchmark (Encode -> RTP -> Decode) +BENCHMARK_DEFINE_F(VideoBench, FullSequence)(benchmark::State &state) +{ + int frame_index = 0; + const int num_prefilled = 100; + std::vector> ys(num_prefilled, std::vector(width * height)); + std::vector> us( + num_prefilled, std::vector((width / 2) * (height / 2))); + std::vector> vs( + num_prefilled, std::vector((width / 2) * (height / 2))); + for (int i = 0; i < num_prefilled; ++i) { + fill_video_frame(width, height, i, ys[i], us[i], vs[i]); + } + + for (auto _ : state) { + int idx = frame_index % num_prefilled; + int flags = (frame_index % 100 == 0) ? VC_EFLAG_FORCE_KF : VC_EFLAG_NONE; + vc_encode(vc, width, height, ys[idx].data(), us[idx].data(), vs[idx].data(), flags); + vc_increment_frame_counter(vc); + + uint8_t *pkt_data; + uint32_t pkt_size; + bool is_keyframe = false; + // We need to collect all packets for the frame before sending to decoder + std::vector frame_data; + while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) { + frame_data.insert(frame_data.end(), pkt_data, pkt_data + pkt_size); + } + + rtp_send_data(log, rtp_mock.recv_session, frame_data.data(), + static_cast(frame_data.size()), is_keyframe); + vc_iterate(vc); + + frame_index++; + } +} + +BENCHMARK_REGISTER_F(VideoBench, FullSequence) + ->Args({320, 240}) + ->Args({640, 480}) + ->Args({1280, 720}) + ->Args({1920, 1080}); + +} + +BENCHMARK_MAIN(); diff --git a/toxav/video_test.cc b/toxav/video_test.cc index cb52643a..db5032d7 100644 --- a/toxav/video_test.cc +++ b/toxav/video_test.cc @@ -3,123 +3,19 @@ #include #include +#include #include #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/network.h" #include "../toxcore/os_memory.h" +#include "av_test_support.hh" #include "rtp.h" namespace { -struct VideoTimeMock { - uint64_t t; -}; - -uint64_t video_mock_time_cb(void *ud) { return static_cast(ud)->t; } - -void test_logger_cb(void *context, Logger_Level level, const char *file, uint32_t line, - const char *func, const char *message, void *userdata) -{ - (void)context; - (void)userdata; - const char *level_str = "UNKNOWN"; - switch (level) { - case LOGGER_LEVEL_TRACE: - level_str = "TRACE"; - break; - case LOGGER_LEVEL_DEBUG: - level_str = "DEBUG"; - break; - case LOGGER_LEVEL_INFO: - level_str = "INFO"; - break; - case LOGGER_LEVEL_WARNING: - level_str = "WARN"; - break; - case LOGGER_LEVEL_ERROR: - level_str = "ERROR"; - break; - } - printf("[%s] %s:%u %s: %s\n", level_str, file, line, func, message); -} - -struct VideoTestData { - uint32_t friend_number = 0; - uint16_t width = 0; - uint16_t height = 0; - std::vector y, u, v; - int32_t ystride = 0, ustride = 0, vstride = 0; - - VideoTestData(); - ~VideoTestData(); - - static void receive_frame(uint32_t friend_number, uint16_t width, uint16_t height, - const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, - int32_t vstride, void *user_data) - { - auto *self = static_cast(user_data); - self->friend_number = friend_number; - self->width = width; - self->height = height; - self->ystride = ystride; - self->ustride = ustride; - self->vstride = vstride; - - self->y.assign(y, y + static_cast(std::abs(ystride)) * height); - self->u.assign(u, u + static_cast(std::abs(ustride)) * (height / 2)); - self->v.assign(v, v + static_cast(std::abs(vstride)) * (height / 2)); - } -}; - -VideoTestData::VideoTestData() = default; -VideoTestData::~VideoTestData() = default; - -struct VideoRtpMock { - RTPSession *recv_session = nullptr; - std::vector> captured_packets; - bool auto_forward = true; - - static int send_packet(void *user_data, const uint8_t *data, uint16_t length) - { - auto *self = static_cast(user_data); - self->captured_packets.push_back(std::vector(data, data + length)); - if (self->auto_forward && self->recv_session) { - rtp_receive_packet(self->recv_session, data, length); - } - return 0; - } - - static int video_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg) - { - return vc_queue_message(mono_time, cs, msg); - } -}; - -class VideoTest : public ::testing::Test { -protected: - void SetUp() override - { - const Memory *mem = os_memory(); - log = logger_new(mem); - logger_callback_log(log, test_logger_cb, nullptr, nullptr); - tm.t = 1000; - mono_time = mono_time_new(mem, video_mock_time_cb, &tm); - mono_time_update(mono_time); - } - - void TearDown() override - { - const Memory *mem = os_memory(); - mono_time_free(mem, mono_time); - logger_kill(log); - } - - Logger *log; - Mono_Time *mono_time; - VideoTimeMock tm; -}; +using VideoTest = AvTest; TEST_F(VideoTest, BasicNewKill) { @@ -135,11 +31,11 @@ TEST_F(VideoTest, EncodeDecodeLoop) VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data); ASSERT_NE(vc, nullptr); - VideoRtpMock rtp_mock; - RTPSession *send_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, VideoRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, vc, VideoRtpMock::video_cb); - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, VideoRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, vc, VideoRtpMock::video_cb); + RtpMock rtp_mock; + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, vc, RtpMock::video_cb); rtp_mock.recv_session = recv_rtp; uint16_t width = 320; @@ -176,6 +72,197 @@ TEST_F(VideoTest, EncodeDecodeLoop) vc_kill(vc); } +TEST_F(VideoTest, EncodeDecodeSequence) +{ + VideoTestData data; + VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data); + ASSERT_NE(vc, nullptr); + + RtpMock rtp_mock; + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + rtp_mock.recv_session = recv_rtp; + + uint16_t width = 320; + uint16_t height = 240; + uint32_t bitrate = 2000; + + ASSERT_EQ(vc_reconfigure_encoder(vc, bitrate, width, height, -1), 0); + + for (int i = 0; i < 20; ++i) { + std::vector y(width * height); + std::vector u((width / 2) * (height / 2)); + std::vector v((width / 2) * (height / 2)); + + // Background + std::fill(y.begin(), y.end(), 16); + std::fill(u.begin(), u.end(), 128); + std::fill(v.begin(), v.end(), 128); + + // Moving square + int sq_size = 64; + int x0 = (i * 8) % (width - sq_size); + int y0 = (i * 4) % (height - sq_size); + for (int r = 0; r < sq_size; ++r) { + for (int c = 0; c < sq_size; ++c) { + y[(y0 + r) * width + (x0 + c)] = 200; + } + } + + ASSERT_EQ(vc_encode(vc, width, height, y.data(), u.data(), v.data(), + i == 0 ? VC_EFLAG_FORCE_KF : VC_EFLAG_NONE), + 0); + vc_increment_frame_counter(vc); + + uint8_t *pkt_data; + uint32_t pkt_size; + bool is_keyframe; + + while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) { + rtp_send_data(log, send_rtp, pkt_data, pkt_size, is_keyframe); + } + + vc_iterate(vc); + + ASSERT_EQ(data.width, width); + ASSERT_EQ(data.height, height); + + double mse = data.calculate_mse(y); + // Expect MSE to be reasonably low for high bitrate + EXPECT_LT(mse, 100.0) << "Frame " << i << " MSE too high: " << mse; + } + + rtp_kill(log, send_rtp); + rtp_kill(log, recv_rtp); + vc_kill(vc); +} + +TEST_F(VideoTest, EncodeDecodeResolutionChange) +{ + VideoTestData data; + VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data); + ASSERT_NE(vc, nullptr); + + RtpMock rtp_mock; + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + rtp_mock.recv_session = recv_rtp; + + uint16_t widths[] = {320, 160, 480}; + uint16_t heights[] = {240, 120, 360}; + + for (int res = 0; res < 3; ++res) { + uint16_t width = widths[res]; + uint16_t height = heights[res]; + ASSERT_EQ(vc_reconfigure_encoder(vc, 2000, width, height, -1), 0); + + for (int i = 0; i < 5; ++i) { + std::vector y(width * height); + std::vector u((width / 2) * (height / 2)); + std::vector v((width / 2) * (height / 2)); + + std::fill(y.begin(), y.end(), static_cast((res * 50 + i * 10) % 256)); + std::fill(u.begin(), u.end(), 128); + std::fill(v.begin(), v.end(), 128); + + ASSERT_EQ(vc_encode(vc, width, height, y.data(), u.data(), v.data(), + i == 0 ? VC_EFLAG_FORCE_KF : VC_EFLAG_NONE), + 0); + vc_increment_frame_counter(vc); + + uint8_t *pkt_data; + uint32_t pkt_size; + bool is_keyframe; + + while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) { + rtp_send_data(log, send_rtp, pkt_data, pkt_size, is_keyframe); + } + + vc_iterate(vc); + + ASSERT_EQ(data.width, width); + ASSERT_EQ(data.height, height); + + double mse = data.calculate_mse(y); + EXPECT_LT(mse, 100.0) << "Res " << res << " Frame " << i << " MSE too high: " << mse; + } + } + + rtp_kill(log, send_rtp); + rtp_kill(log, recv_rtp); + vc_kill(vc); +} + +TEST_F(VideoTest, EncodeDecodeBitrateImpact) +{ + uint32_t bitrates[] = {100, 500, 2000}; + double mses[3]; + + uint16_t width = 320; + uint16_t height = 240; + + for (int b = 0; b < 3; ++b) { + VideoTestData data; + VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data); + ASSERT_NE(vc, nullptr); + + RtpMock rtp_mock; + RTPSession *send_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + rtp_mock.recv_session = recv_rtp; + + ASSERT_EQ(vc_reconfigure_encoder(vc, bitrates[b], width, height, -1), 0); + + double total_mse = 0; + int frames = 10; + for (int i = 0; i < frames; ++i) { + std::vector y(width * height); + std::vector u((width / 2) * (height / 2)); + std::vector v((width / 2) * (height / 2)); + + for (size_t j = 0; j < y.size(); ++j) + y[j] = static_cast((j + i * 10) % 256); + std::fill(u.begin(), u.end(), 128); + std::fill(v.begin(), v.end(), 128); + + ASSERT_EQ(vc_encode(vc, width, height, y.data(), u.data(), v.data(), + i == 0 ? VC_EFLAG_FORCE_KF : VC_EFLAG_NONE), + 0); + vc_increment_frame_counter(vc); + + uint8_t *pkt_data; + uint32_t pkt_size; + bool is_keyframe; + + while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) { + rtp_send_data(log, send_rtp, pkt_data, pkt_size, is_keyframe); + } + + vc_iterate(vc); + total_mse += data.calculate_mse(y); + } + + mses[b] = total_mse / frames; + + rtp_kill(log, send_rtp); + rtp_kill(log, recv_rtp); + vc_kill(vc); + } + + // Quality should generally improve (MSE decrease) as bitrate increases. + // 100kbps should have significantly higher MSE than 2000kbps for this complex pattern. + EXPECT_GT(mses[0], mses[1]); + EXPECT_GT(mses[1], mses[2]); + + printf("MSE results: 100kbps: %f, 500kbps: %f, 2000kbps: %f\n", mses[0], mses[1], mses[2]); +} + TEST_F(VideoTest, ReconfigureEncoder) { VideoTestData data; @@ -215,12 +302,12 @@ TEST_F(VideoTest, QueueInvalidMessage) VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data); ASSERT_NE(vc, nullptr); - VideoRtpMock rtp_mock; + RtpMock rtp_mock; // Create an audio RTP session but try to queue to video session - RTPSession *audio_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, VideoRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, vc, VideoRtpMock::video_cb); - RTPSession *video_recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, VideoRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, vc, VideoRtpMock::video_cb); + RTPSession *audio_rtp = rtp_new(log, RTP_TYPE_AUDIO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, vc, RtpMock::video_cb); + RTPSession *video_recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, vc, RtpMock::video_cb); rtp_mock.recv_session = video_recv_rtp; std::vector dummy_audio(100, 0); @@ -262,9 +349,9 @@ TEST_F(VideoTest, LcfdAndSpecialPackets) VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data); ASSERT_NE(vc, nullptr); - VideoRtpMock rtp_mock; - RTPSession *video_recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, VideoRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, vc, VideoRtpMock::video_cb); + RtpMock rtp_mock; + RTPSession *video_recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, vc, RtpMock::video_cb); rtp_mock.recv_session = video_recv_rtp; // 1. Test lcfd update @@ -286,8 +373,8 @@ TEST_F(VideoTest, LcfdAndSpecialPackets) EXPECT_EQ(vc_get_lcfd(vc), 50u); // Should still be 50 // 3. Test dummy packet PT = (RTP_TYPE_VIDEO + 2) % 128 - RTPSession *dummy_rtp = rtp_new(log, (RTP_TYPE_VIDEO + 2), mono_time, VideoRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, vc, VideoRtpMock::video_cb); + RTPSession *dummy_rtp = rtp_new(log, (RTP_TYPE_VIDEO + 2), mono_time, RtpMock::send_packet, + &rtp_mock, nullptr, nullptr, nullptr, vc, RtpMock::video_cb); rtp_mock.recv_session = dummy_rtp; rtp_send_data( log, dummy_rtp, dummy_frame.data(), static_cast(dummy_frame.size()), false); @@ -321,13 +408,6 @@ TEST_F(VideoTest, MultiReconfigureEncode) vc_kill(vc); } -TEST_F(VideoTest, NewWithNullMonoTime) -{ - VideoTestData data; - VCSession *vc = vc_new(log, nullptr, 123, VideoTestData::receive_frame, &data); - EXPECT_EQ(vc, nullptr); -} - TEST_F(VideoTest, ReconfigureFailDoS) { VideoTestData data; @@ -354,9 +434,9 @@ TEST_F(VideoTest, LyingLengthOOB) VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data); ASSERT_NE(vc, nullptr); - VideoRtpMock rtp_mock; - RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, VideoRtpMock::send_packet, - &rtp_mock, nullptr, nullptr, nullptr, vc, VideoRtpMock::video_cb); + RtpMock rtp_mock; + RTPSession *recv_rtp = rtp_new(log, RTP_TYPE_VIDEO, mono_time, RtpMock::send_packet, &rtp_mock, + nullptr, nullptr, nullptr, vc, RtpMock::video_cb); rtp_mock.recv_session = recv_rtp; // Craft a malicious RTP packet diff --git a/toxcore/BUILD.bazel b/toxcore/BUILD.bazel index eb67f237..cd3e8ee5 100644 --- a/toxcore/BUILD.bazel +++ b/toxcore/BUILD.bazel @@ -26,8 +26,8 @@ cc_test( srcs = ["test_util_test.cc"], deps = [ ":crypto_core", - ":crypto_core_test_util", ":test_util", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -118,19 +118,6 @@ cc_library( ], ) -cc_library( - name = "mem_test_util", - testonly = True, - srcs = ["mem_test_util.cc"], - hdrs = ["mem_test_util.hh"], - deps = [ - ":mem", - ":os_memory", - ":test_util", - ":tox_memory", - ], -) - cc_test( name = "mem_test", size = "small", @@ -326,8 +313,8 @@ cc_test( deps = [ ":crypto_core", ":crypto_core_test_util", - ":mem_test_util", ":util", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -373,7 +360,7 @@ cc_library( srcs = ["mono_time.c"], hdrs = ["mono_time.h"], visibility = [ - "//c-toxcore/auto_tests:__pkg__", + "//c-toxcore/auto_tests:__subpackages__", "//c-toxcore/other:__pkg__", "//c-toxcore/other/bootstrap_daemon:__pkg__", "//c-toxcore/testing:__pkg__", @@ -388,13 +375,25 @@ cc_library( ], ) +cc_library( + name = "mono_time_test_util", + testonly = True, + srcs = ["mono_time_test_util.cc"], + hdrs = ["mono_time_test_util.hh"], + deps = [ + ":mono_time", + "//c-toxcore/testing/support", + ], +) + cc_test( name = "mono_time_test", size = "small", srcs = ["mono_time_test.cc"], deps = [ - ":mem_test_util", ":mono_time", + ":mono_time_test_util", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -448,10 +447,11 @@ cc_library( "network.h", ], visibility = [ - "//c-toxcore/auto_tests:__pkg__", + "//c-toxcore/auto_tests:__subpackages__", "//c-toxcore/other:__pkg__", "//c-toxcore/other/bootstrap_daemon:__pkg__", "//c-toxcore/testing/fuzzing:__pkg__", + "//c-toxcore/testing/support:__pkg__", "//c-toxcore/toxav:__pkg__", ], deps = [ @@ -490,6 +490,7 @@ cc_test( deps = [ ":network", ":network_test_util", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -527,9 +528,9 @@ cc_test( srcs = ["ping_array_test.cc"], deps = [ ":crypto_core_test_util", - ":mem_test_util", ":mono_time", ":ping_array", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -597,9 +598,13 @@ cc_library( ":DHT", ":crypto_core", ":crypto_core_test_util", + ":logger", + ":mono_time", + ":net_crypto", ":network", ":network_test_util", ":test_util", + "//c-toxcore/testing/support", ], ) @@ -613,11 +618,11 @@ cc_test( ":crypto_core", ":crypto_core_test_util", ":logger", - ":mem_test_util", ":mono_time", ":network", ":network_test_util", ":test_util", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -631,9 +636,8 @@ cc_fuzz_test( corpus = ["//tools/toktok-fuzzer/corpus:DHT_fuzz_test"], deps = [ ":DHT", - ":mem_test_util", ":net_profile", - "//c-toxcore/testing/fuzzing:fuzz_support", + "//c-toxcore/testing/support", ], ) @@ -689,8 +693,7 @@ cc_fuzz_test( corpus = ["//tools/toktok-fuzzer/corpus:forwarding_fuzz_test"], deps = [ ":forwarding", - "//c-toxcore/testing/fuzzing:fuzz_support", - "//c-toxcore/testing/fuzzing:fuzz_tox", + "//c-toxcore/testing/support", ], ) @@ -821,6 +824,25 @@ cc_test( ], ) +cc_test( + name = "TCP_client_test", + size = "small", + srcs = ["TCP_client_test.cc"], + deps = [ + ":TCP_client", + ":TCP_common", + ":crypto_core", + ":logger", + ":mono_time", + ":net_profile", + ":network", + ":util", + "//c-toxcore/testing/support", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "net_crypto", srcs = ["net_crypto.c"], @@ -848,6 +870,44 @@ cc_library( ], ) +cc_test( + name = "net_crypto_test", + size = "small", + srcs = ["net_crypto_test.cc"], + deps = [ + ":DHT_test_util", + ":crypto_core", + ":logger", + ":mono_time", + ":net_crypto", + ":net_profile", + ":network", + "//c-toxcore/testing/support", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "friend_connection_test", + size = "small", + srcs = ["friend_connection_test.cc"], + deps = [ + ":DHT_test_util", + ":crypto_core", + ":friend_connection", + ":logger", + ":mono_time", + ":net_crypto", + ":net_profile", + ":network", + ":onion_client", + "//c-toxcore/testing/support", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + cc_fuzz_test( name = "net_crypto_fuzz_test", size = "small", @@ -857,12 +917,10 @@ cc_fuzz_test( deps = [ ":DHT", ":TCP_client", - ":mem_test_util", ":net_crypto", ":net_profile", ":network", - "//c-toxcore/testing/fuzzing:fuzz_support", - "//c-toxcore/testing/fuzzing:fuzz_tox", + "//c-toxcore/testing/support", ], ) @@ -925,9 +983,10 @@ cc_test( ":crypto_core", ":group_announce", ":logger", - ":mem_test_util", ":mono_time", + ":mono_time_test_util", ":network", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -941,8 +1000,7 @@ cc_fuzz_test( corpus = ["//tools/toktok-fuzzer/corpus:group_announce_fuzz_test"], deps = [ ":group_announce", - ":mem_test_util", - "//c-toxcore/testing/fuzzing:fuzz_support", + "//c-toxcore/testing/support", ], ) @@ -984,6 +1042,7 @@ cc_library( ":crypto_core", ":group_announce", ":group_onion_announce", + ":list", ":logger", ":mem", ":mono_time", @@ -998,6 +1057,42 @@ cc_library( ], ) +cc_test( + name = "onion_client_test", + size = "small", + srcs = ["onion_client_test.cc"], + deps = [ + ":DHT_test_util", + ":crypto_core", + ":logger", + ":mono_time", + ":net_crypto", + ":net_profile", + ":network", + ":onion", + ":onion_announce", + ":onion_client", + "//c-toxcore/testing/support", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_fuzz_test( + name = "onion_client_fuzz_test", + size = "small", + testonly = True, + srcs = ["onion_client_fuzz_test.cc"], + # corpus = ["//tools/toktok-fuzzer/corpus:onion_client_fuzz_test"], + deps = [ + ":DHT", + ":net_crypto", + ":net_profile", + ":onion_client", + "//c-toxcore/testing/support", + ], +) + cc_library( name = "friend_connection", srcs = ["friend_connection.c"], @@ -1073,8 +1168,8 @@ cc_test( ":crypto_core_test_util", ":group_moderation", ":logger", - ":mem_test_util", ":util", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -1088,8 +1183,7 @@ cc_fuzz_test( corpus = ["//tools/toktok-fuzzer/corpus:group_moderation_fuzz_test"], deps = [ ":group_moderation", - ":mem_test_util", - "//c-toxcore/testing/fuzzing:fuzz_support", + "//c-toxcore/testing/support", ], ) @@ -1242,6 +1336,7 @@ cc_test( ":tox", ":tox_log_level", ":tox_options", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -1309,6 +1404,7 @@ cc_test( ":crypto_core", ":tox", ":tox_events", + "//c-toxcore/testing/support", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -1322,7 +1418,7 @@ cc_fuzz_test( deps = [ ":tox_dispatch", ":tox_events", - "//c-toxcore/testing/fuzzing:fuzz_support", + "//c-toxcore/testing/support", ], ) diff --git a/toxcore/DHT.c b/toxcore/DHT.c index 9975f8d8..aeab20e1 100644 --- a/toxcore/DHT.c +++ b/toxcore/DHT.c @@ -56,8 +56,8 @@ #define KEYS_TIMEOUT 600 typedef struct DHT_Friend_Callback { - dht_ip_cb *ip_callback; - void *data; + dht_ip_cb *_Nullable ip_callback; + void *_Nullable data; int32_t number; } DHT_Friend_Callback; @@ -87,17 +87,17 @@ const Node_format empty_node_format = {{0}}; static_assert(sizeof(empty_dht_friend.lock_flags) * 8 == DHT_FRIEND_MAX_LOCKS, "Bitfield size and number of locks don't match"); typedef struct Cryptopacket_Handler { - cryptopacket_handler_cb *function; - void *object; + cryptopacket_handler_cb *_Nullable function; + void *_Nullable object; } Cryptopacket_Handler; struct DHT { - const Logger *log; - const Network *ns; - Mono_Time *mono_time; - const Memory *mem; - const Random *rng; - Networking_Core *net; + const Logger *_Nonnull log; + const Network *_Nonnull ns; + Mono_Time *_Nonnull mono_time; + const Memory *_Nonnull mem; + const Random *_Nonnull rng; + Networking_Core *_Nonnull net; bool hole_punching_enabled; bool lan_discovery_enabled; @@ -110,18 +110,18 @@ struct DHT { uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; - DHT_Friend *friends_list; + DHT_Friend *_Nullable friends_list; uint16_t num_friends; - Node_format *loaded_nodes_list; + Node_format *_Nullable loaded_nodes_list; uint32_t loaded_num_nodes; unsigned int loaded_nodes_index; - Shared_Key_Cache *shared_keys_recv; - Shared_Key_Cache *shared_keys_sent; + Shared_Key_Cache *_Nonnull shared_keys_recv; + Shared_Key_Cache *_Nonnull shared_keys_sent; - struct Ping *ping; - Ping_Array *dht_ping_array; + struct Ping *_Nonnull ping; + Ping_Array *_Nonnull dht_ping_array; uint64_t cur_time; Cryptopacket_Handler cryptopackethandlers[256]; @@ -129,7 +129,7 @@ struct DHT { Node_format to_bootstrap[MAX_CLOSE_TO_BOOTSTRAP_NODES]; unsigned int num_to_bootstrap; - dht_nodes_response_cb *nodes_response_callback; + dht_nodes_response_cb *_Nullable nodes_response_callback; }; const uint8_t *dht_friend_public_key(const DHT_Friend *dht_friend) @@ -855,9 +855,9 @@ static bool store_node_ok(const Client_data *_Nonnull client, uint64_t cur_time, } typedef struct Client_data_Cmp { - const Memory *mem; + const Memory *_Nonnull mem; uint64_t cur_time; - const uint8_t *comp_public_key; + const uint8_t *_Nonnull comp_public_key; } Client_data_Cmp; static int client_data_cmp(const Client_data_Cmp *_Nonnull cmp, const Client_data *_Nonnull entry1, const Client_data *_Nonnull entry2) @@ -2019,7 +2019,7 @@ static uint32_t foreach_ip_port(const DHT *_Nonnull dht, const DHT_Friend *_Nonn static bool send_packet_to_friend(const DHT *_Nonnull dht, const IP_Port *_Nonnull ip_port, uint32_t *_Nonnull n, void *_Nonnull userdata) { - const Packet *packet = (const Packet *)userdata; + const Net_Packet *packet = (const Net_Packet *)userdata; const int retval = net_send_packet(dht->net, ip_port, *packet); if ((uint32_t)retval == packet->length) { @@ -2037,7 +2037,7 @@ static bool send_packet_to_friend(const DHT *_Nonnull dht, const IP_Port *_Nonnu * @return ip for friend. * @return number of nodes the packet was sent to. (Only works if more than (MAX_FRIEND_CLIENTS / 4). */ -uint32_t route_to_friend(const DHT *dht, const uint8_t *friend_id, const Packet *packet) +uint32_t route_to_friend(const DHT *dht, const uint8_t *friend_id, const Net_Packet *packet) { const uint32_t num = index_of_friend_pk(dht->friends_list, dht->num_friends, friend_id); @@ -2053,7 +2053,7 @@ uint32_t route_to_friend(const DHT *dht, const uint8_t *friend_id, const Packet } const DHT_Friend *const dht_friend = &dht->friends_list[num]; - Packet packet_userdata = *packet; // Copy because it needs to be non-const. + Net_Packet packet_userdata = *packet; // Copy because it needs to be non-const. return foreach_ip_port(dht, dht_friend, send_packet_to_friend, &packet_userdata); } @@ -2070,7 +2070,7 @@ static bool get_ip_port(const DHT *_Nonnull dht, const IP_Port *_Nonnull ip_port * * @return number of nodes the packet was sent to. */ -static uint32_t routeone_to_friend(const DHT *_Nonnull dht, const uint8_t *_Nonnull friend_id, const Packet *_Nonnull packet) +static uint32_t routeone_to_friend(const DHT *_Nonnull dht, const uint8_t *_Nonnull friend_id, const Net_Packet *_Nonnull packet) { const uint32_t num = index_of_friend_pk(dht->friends_list, dht->num_friends, friend_id); @@ -2119,7 +2119,7 @@ static int send_nat_ping(const DHT *_Nonnull dht, const uint8_t *_Nonnull public assert(len <= UINT16_MAX); uint32_t num = 0; - const Packet packet = {packet_data, (uint16_t)len}; + const Net_Packet packet = {packet_data, (uint16_t)len}; if (type == 0) { /* If packet is request use many people to route it. */ num = route_to_friend(dht, public_key, &packet); @@ -2515,14 +2515,16 @@ DHT *new_dht(const Logger *log, const Memory *mem, const Random *rng, const Netw dht->hole_punching_enabled = hole_punching_enabled; dht->lan_discovery_enabled = lan_discovery_enabled; - dht->ping = ping_new(mem, mono_time, rng, dht, net); + struct Ping *temp_ping = ping_new(mem, mono_time, rng, dht, net); - if (dht->ping == nullptr) { + if (temp_ping == nullptr) { LOGGER_ERROR(log, "failed to initialise ping"); kill_dht(dht); return nullptr; } + dht->ping = temp_ping; + networking_registerhandler(dht->net, NET_PACKET_NODES_REQUEST, &handle_nodes_request, dht); networking_registerhandler(dht->net, NET_PACKET_NODES_RESPONSE, &handle_nodes_response, dht); networking_registerhandler(dht->net, NET_PACKET_CRYPTO, &cryptopacket_handle, dht); @@ -2535,23 +2537,36 @@ DHT *new_dht(const Logger *log, const Memory *mem, const Random *rng, const Netw crypto_new_keypair(rng, dht->self_public_key, dht->self_secret_key); - dht->shared_keys_recv = shared_key_cache_new(log, mono_time, mem, dht->self_secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); - dht->shared_keys_sent = shared_key_cache_new(log, mono_time, mem, dht->self_secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); + Shared_Key_Cache *const temp_shared_keys_recv = shared_key_cache_new(log, mono_time, mem, dht->self_secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); - if (dht->shared_keys_recv == nullptr || dht->shared_keys_sent == nullptr) { + if (temp_shared_keys_recv == nullptr) { LOGGER_ERROR(log, "failed to initialise shared key cache"); kill_dht(dht); return nullptr; } - dht->dht_ping_array = ping_array_new(mem, DHT_PING_ARRAY_SIZE, PING_TIMEOUT); + dht->shared_keys_recv = temp_shared_keys_recv; - if (dht->dht_ping_array == nullptr) { + Shared_Key_Cache *const temp_shared_keys_sent = shared_key_cache_new(log, mono_time, mem, dht->self_secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); + + if (temp_shared_keys_sent == nullptr) { + LOGGER_ERROR(log, "failed to initialise shared key cache"); + kill_dht(dht); + return nullptr; + } + + dht->shared_keys_sent = temp_shared_keys_sent; + + Ping_Array *const temp_ping_array = ping_array_new(mem, DHT_PING_ARRAY_SIZE, PING_TIMEOUT); + + if (temp_ping_array == nullptr) { LOGGER_ERROR(log, "failed to initialise ping array"); kill_dht(dht); return nullptr; } + dht->dht_ping_array = temp_ping_array; + for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) { uint8_t random_public_key_bytes[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t random_secret_key_bytes[CRYPTO_SECRET_KEY_SIZE]; diff --git a/toxcore/DHT.h b/toxcore/DHT.h index 6279053f..dc65d046 100644 --- a/toxcore/DHT.h +++ b/toxcore/DHT.h @@ -212,7 +212,7 @@ int unpack_nodes(Node_format *_Nonnull nodes, uint16_t max_num_nodes, uint16_t * uint16_t length, bool tcp_enabled); /*----------------------------------------------------------------------------------*/ -typedef int cryptopacket_handler_cb(void *_Nonnull object, const IP_Port *_Nonnull source, const uint8_t *_Nonnull source_pubkey, +typedef int cryptopacket_handler_cb(void *_Nullable object, const IP_Port *_Nonnull source, const uint8_t *_Nonnull source_pubkey, const uint8_t *_Nonnull packet, uint16_t length, void *_Nullable userdata); typedef struct DHT DHT; @@ -398,7 +398,7 @@ int route_packet(const DHT *_Nonnull dht, const uint8_t *_Nonnull public_key, co * @return ip for friend. * @return number of nodes the packet was sent to. (Only works if more than (MAX_FRIEND_CLIENTS / 4). */ -uint32_t route_to_friend(const DHT *_Nonnull dht, const uint8_t *_Nonnull friend_id, const Packet *_Nonnull packet); +uint32_t route_to_friend(const DHT *_Nonnull dht, const uint8_t *_Nonnull friend_id, const Net_Packet *_Nonnull packet); /** Function to handle crypto packets. */ void cryptopacket_registerhandler(DHT *_Nonnull dht, uint8_t byte, cryptopacket_handler_cb *_Nullable cb, void *_Nullable object); diff --git a/toxcore/DHT_fuzz_test.cc b/toxcore/DHT_fuzz_test.cc index cedec782..c3ab1969 100644 --- a/toxcore/DHT_fuzz_test.cc +++ b/toxcore/DHT_fuzz_test.cc @@ -5,14 +5,18 @@ #include #include -#include "../testing/fuzzing/fuzz_support.hh" -#include "mem_test_util.hh" +#include "../testing/support/public/fuzz_data.hh" +#include "../testing/support/public/simulated_environment.hh" namespace { +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + void TestHandleRequest(Fuzz_Data &input) { - const Test_Memory mem; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); CONSUME_OR_RETURN(const uint8_t *self_public_key, input, CRYPTO_PUBLIC_KEY_SIZE); CONSUME_OR_RETURN(const uint8_t *self_secret_key, input, CRYPTO_SECRET_KEY_SIZE); @@ -20,7 +24,7 @@ void TestHandleRequest(Fuzz_Data &input) uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t request[MAX_CRYPTO_REQUEST_SIZE]; uint8_t request_id; - handle_request(mem, self_public_key, self_secret_key, public_key, request, &request_id, + handle_request(&c_mem, self_public_key, self_secret_key, public_key, request, &request_id, input.data(), input.size()); } @@ -34,8 +38,9 @@ void TestUnpackNodes(Fuzz_Data &input) const int packed_count = unpack_nodes( nodes, node_count, &processed_data_len, input.data(), input.size(), tcp_enabled); if (packed_count > 0) { - const Memory *mem = os_memory(); - Logger *logger = logger_new(mem); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Logger *logger = logger_new(&c_mem); std::vector packed(packed_count * PACKED_NODE_SIZE_IP6); const int packed_size = pack_nodes(logger, packed.data(), packed.size(), nodes, packed_count); @@ -63,6 +68,6 @@ void TestUnpackNodes(Fuzz_Data &input) extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - fuzz_select_target(data, size); + tox::test::fuzz_select_target(data, size); return 0; } diff --git a/toxcore/DHT_test.cc b/toxcore/DHT_test.cc index f817efe9..63e547a5 100644 --- a/toxcore/DHT_test.cc +++ b/toxcore/DHT_test.cc @@ -8,11 +8,11 @@ #include #include +#include "../testing/support/public/simulated_environment.hh" #include "DHT_test_util.hh" #include "crypto_core.h" #include "crypto_core_test_util.hh" #include "logger.h" -#include "mem_test_util.hh" #include "mono_time.h" #include "network.h" #include "network_test_util.hh" @@ -25,6 +25,7 @@ using ::testing::ElementsAre; using ::testing::Eq; using ::testing::PrintToString; using ::testing::UnorderedElementsAre; +using namespace tox::test; using SecretKey = std::array; @@ -37,13 +38,14 @@ struct KeyPair { TEST(IdClosest, KeyIsClosestToItself) { - Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); - PublicKey pk0 = random_pk(rng); + PublicKey pk0 = random_pk(&c_rng); PublicKey pk1; do { // Get a random key that's not the same as pk0. - pk1 = random_pk(rng); + pk1 = random_pk(&c_rng); } while (pk0 == pk1); EXPECT_EQ(id_closest(pk0.data(), pk0.data(), pk1.data()), 1); @@ -51,21 +53,23 @@ TEST(IdClosest, KeyIsClosestToItself) TEST(IdClosest, IdenticalKeysAreSameDistance) { - Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); - PublicKey pk0 = random_pk(rng); - PublicKey pk1 = random_pk(rng); + PublicKey pk0 = random_pk(&c_rng); + PublicKey pk1 = random_pk(&c_rng); EXPECT_EQ(id_closest(pk0.data(), pk1.data(), pk1.data()), 0); } TEST(IdClosest, DistanceIsCommutative) { - Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); - PublicKey pk0 = random_pk(rng); - PublicKey pk1 = random_pk(rng); - PublicKey pk2 = random_pk(rng); + PublicKey pk0 = random_pk(&c_rng); + PublicKey pk1 = random_pk(&c_rng); + PublicKey pk2 = random_pk(&c_rng); ASSERT_NE(pk1, pk2); // RNG can't produce the same random key twice @@ -147,17 +151,18 @@ Node_format fill(Node_format v, PublicKey const &pk, IP_Port const &ip_port) TEST(AddToList, AddsFirstKeysInOrder) { - Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); // Make cmp_key the furthest away from 00000... as possible, so all initial inserts succeed. PublicKey const cmp_pk{0xff, 0xff, 0xff, 0xff}; // Generate a bunch of other keys, sorted by distance from cmp_pk. auto const keys - = sorted(array_of<20>(random_pk, rng), [&cmp_pk](auto const &pk1, auto const &pk2) { + = sorted(array_of<20>(random_pk, &c_rng), [&cmp_pk](auto const &pk1, auto const &pk2) { return id_closest(cmp_pk.data(), pk1.data(), pk2.data()) == 1; }); - auto const ips = array_of<20>(increasing_ip_port(0, rng)); + auto const ips = array_of<20>(increasing_ip_port(0, &c_rng)); std::vector nodes(4); @@ -199,7 +204,7 @@ TEST(AddToList, AddsFirstKeysInOrder) << " nodes_list = " << PrintToString(nodes); // Now shuffle each time we add a node, which should work fine. - std::mt19937 mt_rng; + std::minstd_rand mt_rng; // Adding one that's closer will happen. std::shuffle(nodes.begin(), nodes.end(), mt_rng); @@ -249,16 +254,17 @@ TEST(AddToList, AddsFirstKeysInOrder) TEST(AddToList, KeepsKeysInOrder) { - Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); // Any random cmp_pk should work, as well as the smallest or (approximately) largest pk. - for (PublicKey const cmp_pk : {random_pk(rng), PublicKey{0x00}, PublicKey{0xff, 0xff}}) { + for (PublicKey const cmp_pk : {random_pk(&c_rng), PublicKey{0x00}, PublicKey{0xff, 0xff}}) { auto const by_distance = [&cmp_pk](auto const &node1, auto const &node2) { return id_closest(cmp_pk.data(), node1.public_key, node2.public_key) == 1; }; // Generate a bunch of other keys, not sorted. - auto const nodes = vector_of(16, random_node_format, rng); + auto const nodes = vector_of(16, random_node_format, &c_rng); std::vector node_list(4); @@ -274,12 +280,13 @@ TEST(AddToList, KeepsKeysInOrder) TEST(Request, CreateAndParse) { - Test_Memory mem; - Test_Random rng; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); // Peers. - const KeyPair sender(rng); - const KeyPair receiver(rng); + const KeyPair sender(&c_rng); + const KeyPair receiver(&c_rng); const uint8_t sent_pkt_id = CRYPTO_PACKET_FRIEND_REQ; // Encoded packet. @@ -292,33 +299,33 @@ TEST(Request, CreateAndParse) // Request data: maximum payload is 918 bytes, so create a payload 1 byte larger than max. std::vector outgoing(919); - random_bytes(rng, outgoing.data(), outgoing.size()); + random_bytes(&c_rng, outgoing.data(), outgoing.size()); - EXPECT_LT(create_request(mem, rng, sender.pk.data(), sender.sk.data(), packet.data(), + EXPECT_LT(create_request(&c_mem, &c_rng, sender.pk.data(), sender.sk.data(), packet.data(), receiver.pk.data(), outgoing.data(), outgoing.size(), sent_pkt_id), 0); // Pop one element so the payload is 918 bytes. Packing should now succeed. outgoing.pop_back(); - const int max_sent_length = create_request(mem, rng, sender.pk.data(), sender.sk.data(), + const int max_sent_length = create_request(&c_mem, &c_rng, sender.pk.data(), sender.sk.data(), packet.data(), receiver.pk.data(), outgoing.data(), outgoing.size(), sent_pkt_id); ASSERT_GT(max_sent_length, 0); // success. // Check that handle_request rejects packets larger than the maximum created packet size. - EXPECT_LT(handle_request(mem, receiver.pk.data(), receiver.sk.data(), pk.data(), + EXPECT_LT(handle_request(&c_mem, receiver.pk.data(), receiver.sk.data(), pk.data(), incoming.data(), &recvd_pkt_id, packet.data(), max_sent_length + 1), 0); // Now try all possible packet sizes from max (918) to 0. while (!outgoing.empty()) { // Pack: - const int sent_length = create_request(mem, rng, sender.pk.data(), sender.sk.data(), + const int sent_length = create_request(&c_mem, &c_rng, sender.pk.data(), sender.sk.data(), packet.data(), receiver.pk.data(), outgoing.data(), outgoing.size(), sent_pkt_id); ASSERT_GT(sent_length, 0); // Unpack: - const int recvd_length = handle_request(mem, receiver.pk.data(), receiver.sk.data(), + const int recvd_length = handle_request(&c_mem, receiver.pk.data(), receiver.sk.data(), pk.data(), incoming.data(), &recvd_pkt_id, packet.data(), sent_length); ASSERT_GE(recvd_length, 0); @@ -331,24 +338,40 @@ TEST(Request, CreateAndParse) TEST(AnnounceNodes, SetAndTest) { - Test_Random rng; - Test_Memory mem; - Test_Network ns; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); - Logger *log = logger_new(mem); + // Use FakeNetwork instead of Test_Network (which wrapped os_network) + // Create endpoint bound to virtual port + auto node = env.create_node(33445); + struct Network net_struct = node->c_network; + + Logger *log = logger_new(&c_mem); ASSERT_NE(log, nullptr); - Mono_Time *mono_time = mono_time_new(mem, nullptr, nullptr); + + Mono_Time *mono_time = mono_time_new(&c_mem, nullptr, nullptr); ASSERT_NE(mono_time, nullptr); - Ptr net(new_networking_no_udp(log, mem, ns)); + + // Hook up simulation clock to mono_time + mono_time_set_current_time_callback( + mono_time, + [](void *user_data) -> uint64_t { + auto *clock = static_cast(user_data); + return clock->current_time_ms(); + }, + &env.fake_clock()); + + Ptr net(new_networking_no_udp(log, &c_mem, &net_struct)); ASSERT_NE(net, nullptr); - Ptr dht(new_dht(log, mem, rng, ns, mono_time, net.get(), true, true)); + Ptr dht(new_dht(log, &c_mem, &c_rng, &net_struct, mono_time, net.get(), true, true)); ASSERT_NE(dht, nullptr); uint8_t pk_data[CRYPTO_PUBLIC_KEY_SIZE]; memcpy(pk_data, dht_get_self_public_key(dht.get()), sizeof(pk_data)); PublicKey self_pk(to_array(pk_data)); - PublicKey pk1 = random_pk(rng); + PublicKey pk1 = random_pk(&c_rng); ASSERT_NE(pk1, self_pk); // Test with maximally close key to self @@ -373,7 +396,7 @@ TEST(AnnounceNodes, SetAndTest) EXPECT_EQ( 2, get_close_nodes(dht.get(), self_pk.data(), nodes, net_family_unspec(), true, true)); - mono_time_free(mem, mono_time); + mono_time_free(&c_mem, mono_time); logger_kill(log); } diff --git a/toxcore/DHT_test_util.cc b/toxcore/DHT_test_util.cc index b87b5859..2f70e6aa 100644 --- a/toxcore/DHT_test_util.cc +++ b/toxcore/DHT_test_util.cc @@ -1,14 +1,156 @@ #include "DHT_test_util.hh" +#include #include #include +#include "../testing/support/public/simulated_environment.hh" #include "DHT.h" #include "crypto_core.h" #include "crypto_core_test_util.hh" #include "network.h" #include "network_test_util.hh" +using tox::test::FakeClock; + +// --- Mock DHT Implementation --- + +MockDHT::MockDHT(const Random *rng) { crypto_new_keypair(rng, self_public_key, self_secret_key); } + +const uint8_t *MockDHT::get_shared_key(const uint8_t *pk) +{ + std::array pk_arr; + std::copy(pk, pk + CRYPTO_PUBLIC_KEY_SIZE, pk_arr.begin()); + auto it = shared_keys.find(pk_arr); + if (it != shared_keys.end()) { + return it->second.data(); + } + + ++computation_count; + + // Compute new shared key + std::array sk; + encrypt_precompute(pk, self_secret_key, sk.data()); + shared_keys[pk_arr] = sk; + return shared_keys[pk_arr].data(); +} + +const Net_Crypto_DHT_Funcs MockDHT::funcs = { + [](void *obj, const uint8_t *public_key) { + return static_cast(obj)->get_shared_key(public_key); + }, + [](const void *obj) { return static_cast(obj)->self_public_key; }, + [](const void *obj) { return static_cast(obj)->self_secret_key; }, +}; + +// --- WrappedMockDHT Implementation --- + +WrappedMockDHT::WrappedMockDHT(tox::test::SimulatedEnvironment &env, uint16_t port) + : node_(env.create_node(0)) + , logger_(logger_new(&node_->c_memory), [](Logger *l) { logger_kill(l); }) + , mono_time_(mono_time_new( + &node_->c_memory, + [](void *ud) -> uint64_t { + return static_cast(ud)->current_time_ms(); + }, + &env.fake_clock()), + [mem = &node_->c_memory](Mono_Time *t) { mono_time_free(mem, t); }) + , networking_(nullptr, [](Networking_Core *n) { kill_networking(n); }) + , dht_(&node_->c_random) +{ + // Setup Networking + IP ip; + ip_init(&ip, false); + unsigned int error = 0; + networking_.reset(new_networking_ex( + logger_.get(), &node_->c_memory, &node_->c_network, &ip, port, port + 1, &error)); + assert(error == 0); + + node_->endpoint = node_->node->get_primary_socket(); + assert(node_->endpoint != nullptr); + assert(node_->endpoint->local_port() == port); +} + +WrappedMockDHT::~WrappedMockDHT() = default; + +IP_Port WrappedMockDHT::get_ip_port() const +{ + IP_Port ip_port; + ip_port.ip = node_->node->ip; + ip_port.port = net_htons(node_->endpoint->local_port()); + return ip_port; +} + +void WrappedMockDHT::poll() +{ + mono_time_update(mono_time_.get()); + networking_poll(networking_.get(), nullptr); +} + +const Net_Crypto_DHT_Funcs WrappedMockDHT::funcs = MockDHT::funcs; + +// --- WrappedDHT Implementation --- + +WrappedDHT::WrappedDHT(tox::test::SimulatedEnvironment &env, uint16_t port) + : node_(env.create_node(0)) + , logger_(logger_new(&node_->c_memory), [](Logger *l) { logger_kill(l); }) + , mono_time_( + mono_time_new( + &node_->c_memory, + [](void *ud) -> uint64_t { return static_cast(ud)->current_time_ms(); }, + &env.fake_clock()), + [mem = &node_->c_memory](Mono_Time *t) { mono_time_free(mem, t); }) + , networking_(nullptr, [](Networking_Core *n) { kill_networking(n); }) + , dht_(nullptr, [](DHT *d) { kill_dht(d); }) +{ + // Setup Networking + IP ip; + ip_init(&ip, false); + unsigned int error = 0; + networking_.reset(new_networking_ex( + logger_.get(), &node_->c_memory, &node_->c_network, &ip, port, port + 1, &error)); + assert(error == 0); + + node_->endpoint = node_->node->get_primary_socket(); + assert(node_->endpoint != nullptr); + assert(node_->endpoint->local_port() == port); + + // Setup DHT + dht_.reset(new_dht(logger_.get(), &node_->c_memory, &node_->c_random, &node_->c_network, + mono_time_.get(), networking_.get(), true, true)); +} + +WrappedDHT::~WrappedDHT() = default; + +const uint8_t *WrappedDHT::dht_public_key() const { return dht_get_self_public_key(dht_.get()); } + +const uint8_t *WrappedDHT::dht_secret_key() const { return dht_get_self_secret_key(dht_.get()); } + +IP_Port WrappedDHT::get_ip_port() const +{ + IP_Port ip_port; + ip_port.ip = node_->node->ip; + ip_port.port = net_htons(node_->endpoint->local_port()); + return ip_port; +} + +void WrappedDHT::poll() +{ + mono_time_update(mono_time_.get()); + networking_poll(networking_.get(), nullptr); + do_dht(dht_.get()); +} + +const Net_Crypto_DHT_Funcs WrappedDHT::funcs = { + [](void *obj, const uint8_t *public_key) { + return dht_get_shared_key_sent(static_cast(obj), public_key); + }, + [](const void *obj) { return dht_get_self_public_key(static_cast(obj)); }, + [](const void *obj) { return dht_get_self_secret_key(static_cast(obj)); }, +}; + +// --- Test Util Functions --- + Node_format random_node_format(const Random *rng) { Node_format node; diff --git a/toxcore/DHT_test_util.hh b/toxcore/DHT_test_util.hh index 9c510c45..cb327fb3 100644 --- a/toxcore/DHT_test_util.hh +++ b/toxcore/DHT_test_util.hh @@ -1,12 +1,25 @@ #ifndef C_TOXCORE_TOXCORE_DHT_TEST_UTIL_H #define C_TOXCORE_TOXCORE_DHT_TEST_UTIL_H +#include +#include #include +#include +#include +#include #include "DHT.h" #include "crypto_core.h" +#include "logger.h" +#include "mono_time.h" +#include "net_crypto.h" #include "test_util.hh" +namespace tox::test { +class SimulatedEnvironment; +struct ScopedToxSystem; +} + template <> struct Deleter : Function_Deleter { }; @@ -16,4 +29,88 @@ std::ostream &operator<<(std::ostream &out, Node_format const &v); Node_format random_node_format(const Random *rng); +// --- Mock DHT --- +struct MockDHT { + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + // Cache for shared keys: Public Key -> Shared Key + std::map, + std::array> + shared_keys; + int computation_count = 0; + + explicit MockDHT(const Random *rng); + + const uint8_t *get_shared_key(const uint8_t *pk); + + static const Net_Crypto_DHT_Funcs funcs; +}; + +// --- Mock DHT Wrapper --- +// Wraps a MockDHT instance and its dependencies (networking, etc.) within a SimulatedEnvironment +class WrappedMockDHT { +public: + WrappedMockDHT(tox::test::SimulatedEnvironment &env, uint16_t port); + + MockDHT *get_dht() { return &dht_; } + const uint8_t *dht_public_key() const { return dht_.self_public_key; } + const uint8_t *dht_secret_key() const { return dht_.self_secret_key; } + int dht_computation_count() const { return dht_.computation_count; } + + // Returns a valid IP_Port for this node in the simulation (Localhost IPv6) + IP_Port get_ip_port() const; + + void poll(); + + tox::test::ScopedToxSystem &node() { return *node_; } + const tox::test::ScopedToxSystem &node() const { return *node_; } + Networking_Core *networking() { return networking_.get(); } + Mono_Time *mono_time() { return mono_time_.get(); } + Logger *logger() { return logger_.get(); } + + ~WrappedMockDHT(); + + static const Net_Crypto_DHT_Funcs funcs; + +private: + std::unique_ptr node_; + std::unique_ptr logger_; + std::unique_ptr> mono_time_; + std::unique_ptr networking_; + MockDHT dht_; +}; + +// --- Real DHT Wrapper --- +// Wraps a DHT instance and its dependencies within a SimulatedEnvironment +class WrappedDHT { +public: + WrappedDHT(tox::test::SimulatedEnvironment &env, uint16_t port); + + DHT *get_dht() { return dht_.get(); } + const uint8_t *dht_public_key() const; + const uint8_t *dht_secret_key() const; + + // Returns a valid IP_Port for this node in the simulation (Localhost IPv6) + IP_Port get_ip_port() const; + + void poll(); + + tox::test::ScopedToxSystem &node() { return *node_; } + const tox::test::ScopedToxSystem &node() const { return *node_; } + Networking_Core *networking() { return networking_.get(); } + Mono_Time *mono_time() { return mono_time_.get(); } + Logger *logger() { return logger_.get(); } + + ~WrappedDHT(); + + static const Net_Crypto_DHT_Funcs funcs; + +private: + std::unique_ptr node_; + std::unique_ptr logger_; + std::unique_ptr> mono_time_; + std::unique_ptr networking_; + std::unique_ptr dht_; +}; + #endif // C_TOXCORE_TOXCORE_DHT_TEST_UTIL_H diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index 8902311f..3ae88fb6 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c @@ -47,6 +47,30 @@ static_assert(MAX_CONCURRENT_FILE_PIPES <= UINT8_MAX + 1, static const Friend empty_friend = {{0}}; +static const uint8_t *_Nullable nc_dht_get_shared_key_sent_wrapper(void *_Nonnull obj, const uint8_t *_Nonnull public_key) +{ + DHT *dht = (DHT *)obj; + return dht_get_shared_key_sent(dht, public_key); +} + +static const uint8_t *_Nonnull nc_dht_get_self_public_key_wrapper(const void *_Nonnull obj) +{ + const DHT *dht = (const DHT *)obj; + return dht_get_self_public_key(dht); +} + +static const uint8_t *_Nonnull nc_dht_get_self_secret_key_wrapper(const void *_Nonnull obj) +{ + const DHT *dht = (const DHT *)obj; + return dht_get_self_secret_key(dht); +} + +static const Net_Crypto_DHT_Funcs m_dht_funcs = { + nc_dht_get_shared_key_sent_wrapper, + nc_dht_get_self_public_key_wrapper, + nc_dht_get_self_secret_key_wrapper, +}; + /** * Determines if the friendnumber passed is valid in the Messenger object. * @@ -1555,7 +1579,7 @@ int send_file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber * @return true if there's still work to do, false otherwise. * */ -static bool do_all_filetransfers(Messenger *_Nonnull m, int32_t friendnumber, void *_Nonnull userdata, uint32_t *_Nonnull free_slots) +static bool do_all_filetransfers(Messenger *_Nonnull m, int32_t friendnumber, void *_Nullable userdata, uint32_t *_Nonnull free_slots) { Friend *const friendcon = &m->friendlist[friendnumber]; @@ -3374,21 +3398,24 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * m->mem = mem; m->rng = rng; m->ns = ns; + m->forwarding = nullptr; + m->announce = nullptr; + m->tcp_server = nullptr; - m->fr = friendreq_new(mem); - - if (m->fr == nullptr) { + Friend_Requests *fr = friendreq_new(mem); + if (fr == nullptr) { mem_delete(mem, m); return nullptr; } + m->fr = fr; - m->log = logger_new(mem); - - if (m->log == nullptr) { + Logger *log = logger_new(mem); + if (log == nullptr) { friendreq_kill(m->fr); mem_delete(mem, m); return nullptr; } + m->log = log; logger_callback_log(m->log, options->log_callback, options->log_context, options->log_user_data); @@ -3400,15 +3427,16 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * options->udp_disabled = true; } + Networking_Core *net; if (options->udp_disabled) { - m->net = new_networking_no_udp(m->log, m->mem, m->ns); + net = new_networking_no_udp(m->log, m->mem, m->ns); } else { IP ip; ip_init(&ip, options->ipv6enabled); - m->net = new_networking_ex(m->log, m->mem, m->ns, &ip, options->port_range[0], options->port_range[1], &net_err); + net = new_networking_ex(m->log, m->mem, m->ns, &ip, options->port_range[0], options->port_range[1], &net_err); } - if (m->net == nullptr) { + if (net == nullptr) { friendreq_kill(m->fr); if (error != nullptr && net_err == 1) { @@ -3420,20 +3448,20 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * mem_delete(mem, m); return nullptr; } + m->net = net; - m->dht = new_dht(m->log, m->mem, m->rng, m->ns, m->mono_time, m->net, options->hole_punching_enabled, options->local_discovery_enabled); - - if (m->dht == nullptr) { + DHT *dht = new_dht(m->log, m->mem, m->rng, m->ns, m->mono_time, m->net, options->hole_punching_enabled, options->local_discovery_enabled); + if (dht == nullptr) { kill_networking(m->net); friendreq_kill(m->fr); logger_kill(m->log); mem_delete(mem, m); return nullptr; } + m->dht = dht; - m->tcp_np = netprof_new(m->log, mem); - - if (m->tcp_np == nullptr) { + Net_Profile *tcp_np = netprof_new(m->log, mem); + if (tcp_np == nullptr) { LOGGER_WARNING(m->log, "TCP netprof initialisation failed"); kill_dht(m->dht); kill_networking(m->net); @@ -3442,10 +3470,11 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * mem_delete(mem, m); return nullptr; } + m->tcp_np = tcp_np; - m->net_crypto = new_net_crypto(m->log, m->mem, m->rng, m->ns, m->mono_time, m->net, m->dht, &options->proxy_info, m->tcp_np); + Net_Crypto *net_crypto = new_net_crypto(m->log, m->mem, m->rng, m->ns, m->mono_time, m->net, m->dht, &m_dht_funcs, &options->proxy_info, m->tcp_np); - if (m->net_crypto == nullptr) { + if (net_crypto == nullptr) { LOGGER_WARNING(m->log, "net_crypto initialisation failed"); netprof_kill(mem, m->tcp_np); @@ -3456,10 +3485,10 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * mem_delete(mem, m); return nullptr; } + m->net_crypto = net_crypto; - m->group_announce = new_gca_list(m->mem); - - if (m->group_announce == nullptr) { + GC_Announces_List *group_announce = new_gca_list(m->mem); + if (group_announce == nullptr) { LOGGER_WARNING(m->log, "DHT group chats initialisation failed"); kill_net_crypto(m->net_crypto); @@ -3471,35 +3500,33 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * mem_delete(mem, m); return nullptr; } + m->group_announce = group_announce; if (options->dht_announcements_enabled) { m->forwarding = new_forwarding(m->log, m->mem, m->rng, m->mono_time, m->dht, m->net); if (m->forwarding != nullptr) { m->announce = new_announcements(m->log, m->mem, m->rng, m->mono_time, m->forwarding, m->dht, m->net); - } else { - m->announce = nullptr; } - } else { - m->forwarding = nullptr; - m->announce = nullptr; } - m->onion = new_onion(m->log, m->mem, m->mono_time, m->rng, m->dht, m->net); - m->onion_a = new_onion_announce(m->log, m->mem, m->rng, m->mono_time, m->dht, m->net); - m->onion_c = new_onion_client(m->log, m->mem, m->rng, m->mono_time, m->net_crypto, m->dht, m->net); - if (m->onion_c != nullptr) { - m->fr_c = new_friend_connections(m->log, m->mem, m->mono_time, m->ns, m->onion_c, m->dht, m->net_crypto, m->net, options->local_discovery_enabled); + Onion *onion = new_onion(m->log, m->mem, m->mono_time, m->rng, m->dht, m->net); + Onion_Announce *onion_a = new_onion_announce(m->log, m->mem, m->rng, m->mono_time, m->dht, m->net); + Onion_Client *onion_c = new_onion_client(m->log, m->mem, m->rng, m->mono_time, m->net_crypto, m->dht, m->net); + Friend_Connections *fr_c = nullptr; + + if (onion_c != nullptr) { + fr_c = new_friend_connections(m->log, m->mem, m->mono_time, m->ns, onion_c, m->dht, m->net_crypto, m->net, options->local_discovery_enabled); } if ((options->dht_announcements_enabled && (m->forwarding == nullptr || m->announce == nullptr)) || - m->onion == nullptr || m->onion_a == nullptr || m->onion_c == nullptr || m->fr_c == nullptr) { + onion == nullptr || onion_a == nullptr || onion_c == nullptr || fr_c == nullptr) { LOGGER_WARNING(m->log, "onion initialisation failed"); - kill_onion(m->onion); - kill_onion_announce(m->onion_a); - kill_onion_client(m->onion_c); + kill_onion(onion); + kill_onion_announce(onion_a); + kill_onion_client(onion_c); kill_gca(m->group_announce); - kill_friend_connections(m->fr_c); + kill_friend_connections(fr_c); kill_announcements(m->announce); kill_forwarding(m->forwarding); kill_net_crypto(m->net_crypto); @@ -3511,12 +3538,15 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * mem_delete(mem, m); return nullptr; } + m->onion = onion; + m->onion_a = onion_a; + m->onion_c = onion_c; + m->fr_c = fr_c; gca_onion_init(m->group_announce, m->onion_a); - m->group_handler = new_dht_groupchats(m); - - if (m->group_handler == nullptr) { + GC_Session *group_handler = new_dht_groupchats(m); + if (group_handler == nullptr) { LOGGER_WARNING(m->log, "conferences initialisation failed"); kill_onion(m->onion); @@ -3535,6 +3565,7 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * mem_delete(mem, m); return nullptr; } + m->group_handler = group_handler; if (options->tcp_server_port != 0) { m->tcp_server = new_tcp_server(m->log, m->mem, m->rng, m->ns, options->ipv6enabled, 1, diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h index b45c1dd3..d090abde 100644 --- a/toxcore/Messenger.h +++ b/toxcore/Messenger.h @@ -241,28 +241,28 @@ typedef struct Friend { } Friend; struct Messenger { - Logger *_Nullable log; - Mono_Time *_Nullable mono_time; - const Memory *_Nullable mem; - const Random *_Nullable rng; - const Network *_Nullable ns; + Logger *_Nonnull log; + Mono_Time *_Nonnull mono_time; + const Memory *_Nonnull mem; + const Random *_Nonnull rng; + const Network *_Nonnull ns; Networking_Core *_Nonnull net; Net_Crypto *_Nonnull net_crypto; - Net_Profile *_Nullable tcp_np; + Net_Profile *_Nonnull tcp_np; DHT *_Nonnull dht; Forwarding *_Nullable forwarding; Announcements *_Nullable announce; - Onion *_Nullable onion; - Onion_Announce *_Nullable onion_a; - Onion_Client *_Nullable onion_c; + Onion *_Nonnull onion; + Onion_Announce *_Nonnull onion_a; + Onion_Client *_Nonnull onion_c; - Friend_Connections *_Nullable fr_c; + Friend_Connections *_Nonnull fr_c; TCP_Server *_Nullable tcp_server; - Friend_Requests *_Nullable fr; + Friend_Requests *_Nonnull fr; uint8_t name[MAX_NAME_LENGTH]; uint16_t name_length; diff --git a/toxcore/TCP_client.c b/toxcore/TCP_client.c index 4defce7a..0e71ddc8 100644 --- a/toxcore/TCP_client.c +++ b/toxcore/TCP_client.c @@ -52,23 +52,23 @@ struct TCP_Client_Connection { uint64_t ping_request_id; TCP_Client_Conn connections[NUM_CLIENT_CONNECTIONS]; - tcp_routing_response_cb *response_callback; - void *response_callback_object; - tcp_routing_status_cb *status_callback; - void *status_callback_object; - tcp_routing_data_cb *data_callback; - void *data_callback_object; - tcp_oob_data_cb *oob_data_callback; - void *oob_data_callback_object; + tcp_routing_response_cb *_Nullable response_callback; + void *_Nullable response_callback_object; + tcp_routing_status_cb *_Nullable status_callback; + void *_Nullable status_callback_object; + tcp_routing_data_cb *_Nullable data_callback; + void *_Nullable data_callback_object; + tcp_oob_data_cb *_Nullable oob_data_callback; + void *_Nullable oob_data_callback_object; - tcp_onion_response_cb *onion_callback; - void *onion_callback_object; + tcp_onion_response_cb *_Nullable onion_callback; + void *_Nullable onion_callback_object; - forwarded_response_cb *forwarded_response_callback; - void *forwarded_response_callback_object; + forwarded_response_cb *_Nullable forwarded_response_callback; + void *_Nullable forwarded_response_callback_object; /* Can be used by user. */ - void *custom_object; + void *_Nullable custom_object; uint32_t custom_uint; }; @@ -399,6 +399,11 @@ int send_data(const Logger *logger, TCP_Client_Connection *con, uint8_t con_id, return 0; } + if (1 + length > MAX_PACKET_SIZE) { + LOGGER_ERROR(logger, "Packet length too long: %u", length); + return -1; + } + const uint16_t packet_size = 1 + length; VLA(uint8_t, packet, packet_size); packet[0] = con_id + NUM_RESERVED_PORTS; @@ -541,6 +546,11 @@ int send_disconnect_request(const Logger *logger, TCP_Client_Connection *con, ui */ int send_onion_request(const Logger *logger, TCP_Client_Connection *con, const uint8_t *data, uint16_t length) { + if (1 + length > MAX_PACKET_SIZE) { + LOGGER_ERROR(logger, "Packet length too long: %u", length); + return -1; + } + const uint16_t packet_size = 1 + length; VLA(uint8_t, packet, packet_size); packet[0] = TCP_PACKET_ONION_REQUEST; diff --git a/toxcore/TCP_client.h b/toxcore/TCP_client.h index 08c782d4..4fa477e3 100644 --- a/toxcore/TCP_client.h +++ b/toxcore/TCP_client.h @@ -18,6 +18,10 @@ #include "net_profile.h" #include "network.h" +#ifdef __cplusplus +extern "C" { +#endif + #define TCP_CONNECTION_TIMEOUT 10 typedef enum TCP_Proxy_Type { @@ -126,4 +130,8 @@ typedef int tcp_oob_data_cb(void *_Nonnull object, const uint8_t *_Nonnull publi int send_oob_packet(const Logger *_Nonnull logger, TCP_Client_Connection *_Nonnull con, const uint8_t *_Nonnull public_key, const uint8_t *_Nonnull data, uint16_t length); void oob_data_handler(TCP_Client_Connection *_Nonnull con, tcp_oob_data_cb *_Nonnull oob_data_callback, void *_Nonnull object); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* C_TOXCORE_TOXCORE_TCP_CLIENT_H */ diff --git a/toxcore/TCP_client_test.cc b/toxcore/TCP_client_test.cc new file mode 100644 index 00000000..92cdde10 --- /dev/null +++ b/toxcore/TCP_client_test.cc @@ -0,0 +1,365 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" +#include "TCP_client.h" +// clang-format on + +#include + +#include + +#include "TCP_common.h" +#include "crypto_core.h" +#include "logger.h" +#include "mono_time.h" +#include "net_profile.h" +#include "network.h" +#include "util.h" + +namespace { + +using namespace tox::test; + +class TCPClientTest : public ::testing::Test { +protected: + SimulatedEnvironment env; + + Mono_Time *create_mono_time(const Memory *mem) + { + Mono_Time *mt = mono_time_new(mem, nullptr, nullptr); + mono_time_set_current_time_callback( + mt, + [](void *user_data) -> uint64_t { + auto *clock = static_cast(user_data); + return clock->current_time_ms(); + }, + &env.fake_clock()); + return mt; + } + + static void log_cb(void *context, Logger_Level level, const char *file, uint32_t line, + const char *func, const char *message, void *userdata) + { + if (level > LOGGER_LEVEL_TRACE) { + fprintf(stderr, "[%d] %s:%u %s: %s\n", level, file, line, func, message); + } + } + + static void net_profile_deleter(Net_Profile *p, const Memory *mem) { netprof_kill(mem, p); } +}; + +TEST_F(TCPClientTest, ConnectsToRelay) +{ + auto server_node = env.create_node(33445); + auto client_node = env.create_node(0); // Ephemeral port + + Logger *server_log = logger_new(&server_node->c_memory); + logger_callback_log(server_log, &TCPClientTest::log_cb, nullptr, nullptr); + Logger *client_log = logger_new(&client_node->c_memory); + logger_callback_log(client_log, &TCPClientTest::log_cb, nullptr, nullptr); + + Mono_Time *client_time = create_mono_time(&client_node->c_memory); + + // 1. Setup Server Socket + Socket server_sock + = net_socket(&server_node->c_network, net_family_ipv4(), TOX_SOCK_STREAM, TOX_PROTO_TCP); + ASSERT_TRUE(sock_valid(server_sock)); + ASSERT_TRUE(set_socket_nonblock(&server_node->c_network, server_sock)); + ASSERT_TRUE(bind_to_port(&server_node->c_network, server_sock, net_family_ipv4(), 33445)); + ASSERT_EQ(0, net_listen(&server_node->c_network, server_sock, 5)); + + // Server Keys + uint8_t server_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t server_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(&server_node->c_random, server_pk, server_sk); + + // Client Keys + uint8_t client_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t client_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(&client_node->c_random, client_pk, client_sk); + + Net_Profile *client_profile = netprof_new(client_log, &client_node->c_memory); + + // 2. Client connects to Server + IP_Port server_ip_port; + server_ip_port.ip = server_node->node->ip; + server_ip_port.port = net_htons(33445); + + TCP_Client_Connection *client_conn = new_tcp_connection(client_log, &client_node->c_memory, + client_time, &client_node->c_random, &client_node->c_network, &server_ip_port, server_pk, + client_pk, client_sk, nullptr, client_profile); + ASSERT_NE(client_conn, nullptr); + + // 3. Simulation Loop + bool connected = false; + Socket accepted_sock = net_invalid_socket(); + uint64_t start_time = env.clock().current_time_ms(); + + while (env.clock().current_time_ms() - start_time < 5000) { + env.advance_time(10); + do_tcp_connection(client_log, client_time, client_conn, nullptr); + + // Server accepts connection + if (!sock_valid(accepted_sock)) { + accepted_sock = net_accept(&server_node->c_network, server_sock); + if (sock_valid(accepted_sock)) { + fprintf(stderr, "Server accepted connection! Socket: %d\n", accepted_sock.value); + set_socket_nonblock(&server_node->c_network, accepted_sock); + } + } + + // Server handles handshake + if (sock_valid(accepted_sock)) { + uint8_t buf[TCP_CLIENT_HANDSHAKE_SIZE]; + IP_Port remote = {{{0}}}; + int len = net_recv( + &server_node->c_network, server_log, accepted_sock, buf, sizeof(buf), &remote); + + if (len > 0) { + fprintf(stderr, "Server received %d bytes\n", len); + } + + if (len == TCP_CLIENT_HANDSHAKE_SIZE) { + // Verify client PK + EXPECT_EQ(0, memcmp(buf, client_pk, CRYPTO_PUBLIC_KEY_SIZE)); + + // Decrypt + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + encrypt_precompute(client_pk, server_sk, shared_key); + + uint8_t plain[TCP_HANDSHAKE_PLAIN_SIZE]; + const uint8_t *nonce_ptr = buf + CRYPTO_PUBLIC_KEY_SIZE; + const uint8_t *ciphertext_ptr = buf + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE; + + int res = decrypt_data_symmetric(&server_node->c_memory, shared_key, nonce_ptr, + ciphertext_ptr, + TCP_CLIENT_HANDSHAKE_SIZE - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE), + plain); + + if (res != TCP_HANDSHAKE_PLAIN_SIZE) { + fprintf(stderr, "Decryption failed: res=%d\n", res); + } + + if (res == TCP_HANDSHAKE_PLAIN_SIZE) { + // Generate Response + // [Nonce (24)] [Encrypted (PK(32)+Nonce(24)+MAC(16))] + + uint8_t resp_nonce[CRYPTO_NONCE_SIZE]; + random_nonce(&server_node->c_random, resp_nonce); + + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(&server_node->c_random, temp_pk, temp_sk); + + uint8_t resp_plain[TCP_HANDSHAKE_PLAIN_SIZE]; + memcpy(resp_plain, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(&server_node->c_random, resp_plain + CRYPTO_PUBLIC_KEY_SIZE); + + uint8_t response[TCP_SERVER_HANDSHAKE_SIZE]; + memcpy(response, resp_nonce, CRYPTO_NONCE_SIZE); + + encrypt_data_symmetric(&server_node->c_memory, shared_key, + resp_nonce, // nonce + resp_plain, // plain + TCP_HANDSHAKE_PLAIN_SIZE, // plain len + response + CRYPTO_NONCE_SIZE // dest + ); + + net_send(&server_node->c_network, server_log, accepted_sock, response, + sizeof(response), &remote, nullptr); + } + } + } + + if (tcp_con_status(client_conn) == TCP_CLIENT_CONFIRMED) { + connected = true; + break; + } + } + + EXPECT_TRUE(connected); + + // Cleanup + kill_tcp_connection(client_conn); + net_profile_deleter(client_profile, &client_node->c_memory); + kill_sock(&server_node->c_network, server_sock); + if (sock_valid(accepted_sock)) + kill_sock(&server_node->c_network, accepted_sock); + + logger_kill(client_log); + logger_kill(server_log); + mono_time_free(&client_node->c_memory, client_time); +} + +TEST_F(TCPClientTest, SendDataIntegerOverflow) +{ + auto server_node = env.create_node(33446); + auto client_node = env.create_node(0); + + Logger *server_log = logger_new(&server_node->c_memory); + logger_callback_log(server_log, &TCPClientTest::log_cb, nullptr, nullptr); + Logger *client_log = logger_new(&client_node->c_memory); + logger_callback_log(client_log, &TCPClientTest::log_cb, nullptr, nullptr); + + Mono_Time *client_time = create_mono_time(&client_node->c_memory); + + Socket server_sock + = net_socket(&server_node->c_network, net_family_ipv4(), TOX_SOCK_STREAM, TOX_PROTO_TCP); + ASSERT_TRUE(sock_valid(server_sock)); + ASSERT_TRUE(set_socket_nonblock(&server_node->c_network, server_sock)); + ASSERT_TRUE(bind_to_port(&server_node->c_network, server_sock, net_family_ipv4(), 33446)); + ASSERT_EQ(0, net_listen(&server_node->c_network, server_sock, 5)); + + uint8_t server_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t server_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(&server_node->c_random, server_pk, server_sk); + + uint8_t client_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t client_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(&client_node->c_random, client_pk, client_sk); + + Net_Profile *client_profile = netprof_new(client_log, &client_node->c_memory); + + IP_Port server_ip_port; + server_ip_port.ip = server_node->node->ip; + server_ip_port.port = net_htons(33446); + + TCP_Client_Connection *client_conn = new_tcp_connection(client_log, &client_node->c_memory, + client_time, &client_node->c_random, &client_node->c_network, &server_ip_port, server_pk, + client_pk, client_sk, nullptr, client_profile); + ASSERT_NE(client_conn, nullptr); + + bool connected = false; + Socket accepted_sock = net_invalid_socket(); + uint64_t start_time = env.clock().current_time_ms(); + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t sent_nonce[CRYPTO_NONCE_SIZE] = {0}; + uint8_t recv_nonce[CRYPTO_NONCE_SIZE] = {0}; + + // Helper to send encrypted packet from server to client + auto server_send_packet = [&](const uint8_t *data, uint16_t length) { + uint16_t packet_size = sizeof(uint16_t) + length + CRYPTO_MAC_SIZE; + std::vector packet(packet_size); + uint16_t c_length = net_htons(length + CRYPTO_MAC_SIZE); + memcpy(packet.data(), &c_length, sizeof(uint16_t)); + + encrypt_data_symmetric(&server_node->c_memory, shared_key, sent_nonce, data, length, + packet.data() + sizeof(uint16_t)); + increment_nonce(sent_nonce); + + IP_Port remote = {{{0}}}; + net_send(&server_node->c_network, server_log, accepted_sock, packet.data(), packet_size, + &remote, nullptr); + }; + + while (env.clock().current_time_ms() - start_time < 5000) { + env.advance_time(10); + do_tcp_connection(client_log, client_time, client_conn, nullptr); + + if (!sock_valid(accepted_sock)) { + accepted_sock = net_accept(&server_node->c_network, server_sock); + if (sock_valid(accepted_sock)) { + set_socket_nonblock(&server_node->c_network, accepted_sock); + } + } + + if (sock_valid(accepted_sock) && !connected) { + uint8_t buf[TCP_CLIENT_HANDSHAKE_SIZE]; + IP_Port remote = {{{0}}}; + int len = net_recv( + &server_node->c_network, server_log, accepted_sock, buf, sizeof(buf), &remote); + + if (len == TCP_CLIENT_HANDSHAKE_SIZE) { + encrypt_precompute(client_pk, server_sk, shared_key); + uint8_t plain[TCP_HANDSHAKE_PLAIN_SIZE]; + if (decrypt_data_symmetric(&server_node->c_memory, shared_key, + buf + CRYPTO_PUBLIC_KEY_SIZE, + buf + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, + TCP_CLIENT_HANDSHAKE_SIZE - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE), + plain) + == TCP_HANDSHAKE_PLAIN_SIZE) { + memcpy(recv_nonce, plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_NONCE_SIZE); + + uint8_t resp_nonce[CRYPTO_NONCE_SIZE]; + random_nonce(&server_node->c_random, resp_nonce); + memcpy(sent_nonce, resp_nonce, CRYPTO_NONCE_SIZE); + + uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t temp_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(&server_node->c_random, temp_pk, temp_sk); + + uint8_t resp_plain[TCP_HANDSHAKE_PLAIN_SIZE]; + memcpy(resp_plain, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(&server_node->c_random, resp_plain + CRYPTO_PUBLIC_KEY_SIZE); + + // FIX: Save the nonce that client will use for receiving + memcpy(sent_nonce, resp_plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_NONCE_SIZE); + + uint8_t response[TCP_SERVER_HANDSHAKE_SIZE]; + memcpy(response, resp_nonce, CRYPTO_NONCE_SIZE); + encrypt_data_symmetric(&server_node->c_memory, shared_key, resp_nonce, + resp_plain, TCP_HANDSHAKE_PLAIN_SIZE, response + CRYPTO_NONCE_SIZE); + net_send(&server_node->c_network, server_log, accepted_sock, response, + sizeof(response), &remote, nullptr); + + // FIX: Update shared key using Client's Ephemeral PK and Server's Ephemeral SK + encrypt_precompute(plain, temp_sk, shared_key); + } + } + } + + if (tcp_con_status(client_conn) == TCP_CLIENT_CONFIRMED) { + connected = true; + break; + } + } + ASSERT_TRUE(connected); + + // Establish sub-connection 0 + uint8_t con_id = 0; + uint8_t other_pk[CRYPTO_PUBLIC_KEY_SIZE] = {0}; // Dummy PK + // 1. Send Routing Response to set status=1 + { + uint8_t packet[1 + 1 + CRYPTO_PUBLIC_KEY_SIZE]; + packet[0] = TCP_PACKET_ROUTING_RESPONSE; + packet[1] = con_id + NUM_RESERVED_PORTS; + memcpy(packet + 2, other_pk, CRYPTO_PUBLIC_KEY_SIZE); + server_send_packet(packet, sizeof(packet)); + } + + // Pump loop to process packet + for (int i = 0; i < 10; ++i) { + env.advance_time(10); + do_tcp_connection(client_log, client_time, client_conn, nullptr); + } + + // 2. Send Connection Notification to set status=2 + { + uint8_t packet[1 + 1]; + packet[0] = TCP_PACKET_CONNECTION_NOTIFICATION; + packet[1] = con_id + NUM_RESERVED_PORTS; + server_send_packet(packet, sizeof(packet)); + } + + // Pump loop to process packet + for (int i = 0; i < 10; ++i) { + env.advance_time(10); + do_tcp_connection(client_log, client_time, client_conn, nullptr); + } + + // Now call send_data with 65535 bytes + std::vector large_data(65535); + // This should trigger integer overflow: 1 + 65535 = 0. VLA(0). packet[0] write -> Crash/UB + send_data(client_log, client_conn, con_id, large_data.data(), 65535); + + // Cleanup + kill_tcp_connection(client_conn); + net_profile_deleter(client_profile, &client_node->c_memory); + kill_sock(&server_node->c_network, server_sock); + if (sock_valid(accepted_sock)) + kill_sock(&server_node->c_network, accepted_sock); + logger_kill(client_log); + logger_kill(server_log); + mono_time_free(&client_node->c_memory, client_time); +} + +} // namespace diff --git a/toxcore/TCP_connection.c b/toxcore/TCP_connection.c index 6b2f9946..08cb944f 100644 --- a/toxcore/TCP_connection.c +++ b/toxcore/TCP_connection.c @@ -25,33 +25,33 @@ #include "util.h" struct TCP_Connections { - const Logger *logger; - const Memory *mem; - const Random *rng; - Mono_Time *mono_time; - const Network *ns; - DHT *dht; + const Logger *_Nonnull logger; + const Memory *_Nonnull mem; + const Random *_Nonnull rng; + Mono_Time *_Nonnull mono_time; + const Network *_Nonnull ns; + DHT *_Nonnull dht; uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; - TCP_Connection_to *connections; + TCP_Connection_to *_Nullable connections; uint32_t connections_length; /* Length of connections array. */ - TCP_con *tcp_connections; + TCP_con *_Nullable tcp_connections; uint32_t tcp_connections_length; /* Length of tcp_connections array. */ - tcp_data_cb *tcp_data_callback; - void *tcp_data_callback_object; + tcp_data_cb *_Nullable tcp_data_callback; + void *_Nullable tcp_data_callback_object; - tcp_oob_cb *tcp_oob_callback; - void *tcp_oob_callback_object; + tcp_oob_cb *_Nullable tcp_oob_callback; + void *_Nullable tcp_oob_callback_object; - tcp_onion_cb *tcp_onion_callback; - void *tcp_onion_callback_object; + tcp_onion_cb *_Nullable tcp_onion_callback; + void *_Nullable tcp_onion_callback_object; - forwarded_response_cb *tcp_forwarded_response_callback; - void *tcp_forwarded_response_callback_object; + forwarded_response_cb *_Nullable tcp_forwarded_response_callback; + void *_Nullable tcp_forwarded_response_callback_object; TCP_Proxy_Info proxy_info; @@ -59,7 +59,7 @@ struct TCP_Connections { uint16_t onion_num_conns; /* Network profile for all TCP client packets. */ - Net_Profile *net_profile; + Net_Profile *_Nullable net_profile; }; static const TCP_Connection_to empty_tcp_connection_to = {0}; @@ -80,7 +80,7 @@ uint32_t tcp_connections_count(const TCP_Connections *tcp_c) * @retval -1 if mem_vrealloc fails. * @retval 0 if it succeeds. */ -static int realloc_tcp_connection_to(const Memory *_Nonnull mem, TCP_Connection_to *_Nullable *_Nonnull array, size_t num) +static int realloc_tcp_connection_to(const Memory *_Nonnull mem, TCP_Connection_to *_Nullable *array, size_t num) { if (num == 0) { mem_delete(mem, *array); @@ -100,7 +100,7 @@ static int realloc_tcp_connection_to(const Memory *_Nonnull mem, TCP_Connection_ return 0; } -static int realloc_tcp_con(const Memory *_Nonnull mem, TCP_con *_Nullable *_Nonnull array, size_t num) +static int realloc_tcp_con(const Memory *_Nonnull mem, TCP_con *_Nullable *array, size_t num) { if (num == 0) { mem_delete(mem, *array); @@ -331,6 +331,10 @@ int send_packet_tcp_connection(const TCP_Connections *tcp_c, int connections_num continue; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for connection number %u", tcp_con_num); + continue; + } ret = send_data(tcp_c->logger, tcp_con->connection, connection_id, packet, length); if (ret == 0) { @@ -366,6 +370,10 @@ int send_packet_tcp_connection(const TCP_Connections *tcp_c, int connections_num continue; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for connection number %u", tcp_con_num); + continue; + } if (send_oob_packet(tcp_c->logger, tcp_con->connection, con_to->public_key, packet, length) == 1) { sent_any = true; } @@ -406,6 +414,10 @@ int get_random_tcp_onion_conn_number(const TCP_Connections *tcp_c) static int get_conn_number_by_ip_port(const TCP_Connections *_Nonnull tcp_c, const IP_Port *_Nonnull ip_port) { for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { + if (tcp_c->tcp_connections[i].connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for connection index %u", i); + continue; + } const IP_Port conn_ip_port = tcp_con_ip_port(tcp_c->tcp_connections[i].connection); if (ipport_equal(ip_port, &conn_ip_port) && @@ -430,6 +442,10 @@ bool tcp_get_random_conn_ip_port(const TCP_Connections *tcp_c, IP_Port *ip_port) return false; } + if (tcp_c->tcp_connections[index].connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for connection index %d", index); + return false; + } *ip_port = tcp_con_ip_port(tcp_c->tcp_connections[index].connection); return true; } @@ -447,6 +463,10 @@ int tcp_send_onion_request(TCP_Connections *tcp_c, uint32_t tcp_connections_numb } if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_CONNECTED) { + if (tcp_c->tcp_connections[tcp_connections_number].connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for connection number %u", tcp_connections_number); + return -1; + } const int ret = send_onion_request(tcp_c->logger, tcp_c->tcp_connections[tcp_connections_number].connection, data, length); @@ -477,6 +497,10 @@ int tcp_send_forward_request(const Logger *logger, TCP_Connections *tcp_c, const } if (chain_length == 0) { + if (tcp_c->tcp_connections[index].connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for connection index %d", index); + return -1; + } return send_forward_request_tcp(logger, tcp_c->tcp_connections[index].connection, dht_node, data, data_length) == 1 ? 0 : -1; } @@ -484,6 +508,10 @@ int tcp_send_forward_request(const Logger *logger, TCP_Connections *tcp_c, const const uint16_t len = forward_chain_packet_size(chain_length, data_length); VLA(uint8_t, packet, len); + if (tcp_c->tcp_connections[index].connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for connection index %d", index); + return -1; + } return create_forward_chain_packet(chain_keys, chain_length, data, data_length, packet) && send_forward_request_tcp(logger, tcp_c->tcp_connections[index].connection, dht_node, packet, len) == 1 ? 0 : -1; } @@ -506,6 +534,10 @@ int tcp_send_oob_packet(const TCP_Connections *tcp_c, unsigned int tcp_connectio return -1; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for tcp_con"); + return -1; + } const int ret = send_oob_packet(tcp_c->logger, tcp_con->connection, public_key, packet, length); if (ret == 1) { @@ -623,6 +655,10 @@ static int find_tcp_connection_relay(const TCP_Connections *tcp_c, const uint8_t return i; } } else { + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for tcp_con"); + return -1; + } if (pk_equal(tcp_con_public_key(tcp_con->connection), relay_pk)) { return i; } @@ -690,6 +726,10 @@ int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number) } if (tcp_con->status == TCP_CONN_CONNECTED) { + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for tcp_con"); + continue; + } send_disconnect_request(tcp_c->logger, tcp_con->connection, con_to->connections[i].connection_id); } @@ -907,6 +947,10 @@ static int reconnect_tcp_relay_connection(TCP_Connections *_Nonnull tcp_c, int t return -1; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for tcp_con"); + return -1; + } const IP_Port ip_port = tcp_con_ip_port(tcp_con->connection); uint8_t relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; memcpy(relay_pk, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); @@ -957,6 +1001,10 @@ static int sleep_tcp_relay_connection(TCP_Connections *_Nonnull tcp_c, int tcp_c return -1; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for tcp_con"); + return -1; + } tcp_con->ip_port = tcp_con_ip_port(tcp_con->connection); memcpy(tcp_con->relay_pk, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); @@ -1032,6 +1080,10 @@ static int send_tcp_relay_routing_request(const TCP_Connections *_Nonnull tcp_c, return -1; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for tcp_con"); + return -1; + } if (send_routing_request(tcp_c->logger, tcp_con->connection, public_key) != 1) { return -1; } @@ -1067,6 +1119,10 @@ static int tcp_response_callback(void *_Nonnull object, uint8_t connection_id, c return -1; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for tcp_con"); + return -1; + } set_tcp_connection_number(tcp_con->connection, connection_id, connections_number); return 0; @@ -1427,6 +1483,10 @@ static bool copy_tcp_relay_conn(const TCP_Connections *_Nonnull tcp_c, Node_form return false; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(tcp_c->logger, "TCP connection is null for tcp_con"); + return false; + } memcpy(tcp_relay->public_key, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); tcp_relay->ip_port = tcp_con_ip_port(tcp_con->connection); @@ -1600,6 +1660,10 @@ static void do_tcp_conns(const Logger *_Nonnull logger, TCP_Connections *_Nonnul } if (tcp_con->status != TCP_CONN_SLEEPING) { + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(logger, "TCP connection is null for tcp_con"); + continue; + } do_tcp_connection(logger, tcp_c->mono_time, tcp_con->connection, userdata); /* callbacks can change TCP connection address. */ @@ -1608,6 +1672,10 @@ static void do_tcp_conns(const Logger *_Nonnull logger, TCP_Connections *_Nonnul // Make sure the TCP connection wasn't dropped in any of the callbacks. assert(tcp_con != nullptr); + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(logger, "TCP connection is null for tcp_con"); + continue; + } if (tcp_con_status(tcp_con->connection) == TCP_CLIENT_DISCONNECTED) { if (tcp_con->status == TCP_CONN_CONNECTED) { reconnect_tcp_relay_connection(tcp_c, i); @@ -1618,6 +1686,10 @@ static void do_tcp_conns(const Logger *_Nonnull logger, TCP_Connections *_Nonnul continue; } + if (tcp_con->connection == nullptr) { + LOGGER_ERROR(logger, "TCP connection is null for tcp_con"); + continue; + } if (tcp_con->status == TCP_CONN_VALID && tcp_con_status(tcp_con->connection) == TCP_CLIENT_CONFIRMED) { tcp_relay_on_online(tcp_c, i); } diff --git a/toxcore/TCP_server.c b/toxcore/TCP_server.c index cad12ee3..1cf525bb 100644 --- a/toxcore/TCP_server.c +++ b/toxcore/TCP_server.c @@ -64,18 +64,18 @@ typedef struct TCP_Secure_Connection { static const TCP_Secure_Connection empty_tcp_secure_connection = {{nullptr}}; struct TCP_Server { - const Logger *logger; - const Memory *mem; - const Random *rng; - const Network *ns; - Onion *onion; - Forwarding *forwarding; + const Logger *_Nonnull logger; + const Memory *_Nonnull mem; + const Random *_Nonnull rng; + const Network *_Nonnull ns; + Onion *_Nullable onion; + Forwarding *_Nullable forwarding; #ifdef TCP_SERVER_USE_EPOLL int efd; uint64_t last_run_pinged; #endif /* TCP_SERVER_USE_EPOLL */ - Socket *socks_listening; + Socket *_Nullable socks_listening; unsigned int num_listening_socks; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; @@ -85,7 +85,7 @@ struct TCP_Server { TCP_Secure_Connection unconfirmed_connection_queue[MAX_INCOMING_CONNECTIONS]; uint16_t unconfirmed_connection_queue_index; - TCP_Secure_Connection *accepted_connection_array; + TCP_Secure_Connection *_Nullable accepted_connection_array; uint32_t size_accepted_connections; uint32_t num_accepted_connections; @@ -94,7 +94,7 @@ struct TCP_Server { BS_List accepted_key_list; /* Network profile for all TCP server packets. */ - Net_Profile *net_profile; + Net_Profile *_Nullable net_profile; }; static_assert(sizeof(TCP_Server) < 7 * 1024 * 1024, diff --git a/toxcore/TCP_server.h b/toxcore/TCP_server.h index 088c0f89..de8ba74c 100644 --- a/toxcore/TCP_server.h +++ b/toxcore/TCP_server.h @@ -19,6 +19,10 @@ #include "network.h" #include "onion.h" +#ifdef __cplusplus +extern "C" { +#endif + #define MAX_INCOMING_CONNECTIONS 256 #define TCP_MAX_BACKLOG MAX_INCOMING_CONNECTIONS @@ -51,4 +55,9 @@ void kill_tcp_server(TCP_Server *_Nullable tcp_server); * Returns null if `tcp_server` is null. */ const Net_Profile *_Nullable tcp_server_get_net_profile(const TCP_Server *_Nullable tcp_server); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* C_TOXCORE_TOXCORE_TCP_SERVER_H */ diff --git a/toxcore/announce.c b/toxcore/announce.c index 382e0188..45e9a927 100644 --- a/toxcore/announce.c +++ b/toxcore/announce.c @@ -51,22 +51,22 @@ uint8_t announce_response_of_request_type(uint8_t request_type) typedef struct Announce_Entry { uint64_t store_until; uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE]; - uint8_t *data; + uint8_t *_Nullable data; uint32_t length; } Announce_Entry; struct Announcements { - const Logger *log; - const Memory *mem; - const Random *rng; - Forwarding *forwarding; - const Mono_Time *mono_time; - DHT *dht; - Networking_Core *net; - const uint8_t *public_key; - const uint8_t *secret_key; + const Logger *_Nonnull log; + const Memory *_Nonnull mem; + const Random *_Nonnull rng; + Forwarding *_Nonnull forwarding; + const Mono_Time *_Nonnull mono_time; + DHT *_Nonnull dht; + Networking_Core *_Nonnull net; + const uint8_t *_Nonnull public_key; + const uint8_t *_Nonnull secret_key; - Shared_Key_Cache *shared_keys; + Shared_Key_Cache *_Nonnull shared_keys; uint8_t hmac_key[CRYPTO_HMAC_KEY_SIZE]; int32_t synch_offset; @@ -328,6 +328,10 @@ static int create_reply_plain_data_search_request(Announcements *_Nonnull announ } else { *p = 1; ++p; + if (stored->data == nullptr) { + LOGGER_ERROR(announce->log, "Stored data is null for public key."); + return -1; + } crypto_sha256(p, stored->data, stored->length); p += CRYPTO_SHA256_SIZE; } @@ -462,6 +466,10 @@ static int create_reply_plain_store_announce_request(Announcements *_Nonnull ann } uint8_t stored_hash[CRYPTO_SHA256_SIZE]; + if (stored->data == nullptr) { + LOGGER_ERROR(announce->log, "Stored data is null for public key."); + return -1; + } crypto_sha256(stored_hash, stored->data, stored->length); if (!crypto_sha256_eq(announcement, stored_hash)) { @@ -635,11 +643,12 @@ Announcements *new_announcements(const Logger *log, const Memory *mem, const Ran announce->public_key = dht_get_self_public_key(announce->dht); announce->secret_key = dht_get_self_secret_key(announce->dht); new_hmac_key(announce->rng, announce->hmac_key); - announce->shared_keys = shared_key_cache_new(log, mono_time, mem, announce->secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); - if (announce->shared_keys == nullptr) { + Shared_Key_Cache *const shared_keys = shared_key_cache_new(log, mono_time, mem, announce->secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); + if (shared_keys == nullptr) { mem_delete(announce->mem, announce); return nullptr; } + announce->shared_keys = shared_keys; announce->start_time = mono_time_get(announce->mono_time); diff --git a/toxcore/bin_pack.c b/toxcore/bin_pack.c index 595bb0dc..ab5df3d0 100644 --- a/toxcore/bin_pack.c +++ b/toxcore/bin_pack.c @@ -13,7 +13,7 @@ #include "logger.h" struct Bin_Pack { - uint8_t *bytes; + uint8_t *_Nullable bytes; uint32_t bytes_size; uint32_t bytes_pos; cmp_ctx_t ctx; diff --git a/toxcore/bin_unpack.c b/toxcore/bin_unpack.c index dfbb8cc7..883071e0 100644 --- a/toxcore/bin_unpack.c +++ b/toxcore/bin_unpack.c @@ -13,9 +13,9 @@ #include "mem.h" struct Bin_Unpack { - const Memory *mem; + const Memory *_Nonnull mem; - const uint8_t *bytes; + const uint8_t *_Nonnull bytes; uint32_t bytes_size; cmp_ctx_t ctx; }; diff --git a/toxcore/crypto_core_test.cc b/toxcore/crypto_core_test.cc index b18f0c18..c85c2d49 100644 --- a/toxcore/crypto_core_test.cc +++ b/toxcore/crypto_core_test.cc @@ -1,4 +1,7 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" #include "crypto_core.h" +// clang-format on #include @@ -7,7 +10,6 @@ #include #include "crypto_core_test_util.hh" -#include "mem_test_util.hh" namespace { @@ -17,29 +19,31 @@ using SecretKey = std::array; using Signature = std::array; using Nonce = std::array; +using tox::test::SimulatedEnvironment; + TEST(PkEqual, TwoRandomIdsAreNotEqual) { - std::mt19937 rng; - std::uniform_int_distribution dist{0, UINT8_MAX}; + SimulatedEnvironment env; + auto &rng = env.fake_random(); uint8_t pk1[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t pk2[CRYPTO_PUBLIC_KEY_SIZE]; - std::generate(std::begin(pk1), std::end(pk1), [&]() { return dist(rng); }); - std::generate(std::begin(pk2), std::end(pk2), [&]() { return dist(rng); }); + rng.bytes(pk1, sizeof(pk1)); + rng.bytes(pk2, sizeof(pk2)); EXPECT_FALSE(pk_equal(pk1, pk2)); } TEST(PkEqual, IdCopyMakesKeysEqual) { - std::mt19937 rng; - std::uniform_int_distribution dist{0, UINT8_MAX}; + SimulatedEnvironment env; + auto &rng = env.fake_random(); uint8_t pk1[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t pk2[CRYPTO_PUBLIC_KEY_SIZE] = {0}; - std::generate(std::begin(pk1), std::end(pk1), [&]() { return dist(rng); }); + rng.bytes(pk1, sizeof(pk1)); pk_copy(pk2, pk1); @@ -48,20 +52,21 @@ TEST(PkEqual, IdCopyMakesKeysEqual) TEST(CryptoCore, EncryptLargeData) { - Test_Memory mem; - Test_Random rng; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); Nonce nonce{}; PublicKey pk; SecretKey sk; - crypto_new_keypair(rng, pk.data(), sk.data()); + crypto_new_keypair(&c_rng, pk.data(), sk.data()); // 100 MiB of data (all zeroes, doesn't matter what's inside). std::vector plain(100 * 1024 * 1024); std::vector encrypted(plain.size() + CRYPTO_MAC_SIZE); encrypt_data( - mem, pk.data(), sk.data(), nonce.data(), plain.data(), plain.size(), encrypted.data()); + &c_mem, pk.data(), sk.data(), nonce.data(), plain.data(), plain.size(), encrypted.data()); } TEST(CryptoCore, IncrementNonce) @@ -99,12 +104,13 @@ TEST(CryptoCore, IncrementNonceNumber) TEST(CryptoCore, Signatures) { - Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); Extended_Public_Key pk; Extended_Secret_Key sk; - EXPECT_TRUE(create_extended_keypair(&pk, &sk, rng)); + EXPECT_TRUE(create_extended_keypair(&pk, &sk, &c_rng)); std::vector message{0}; message.clear(); @@ -117,16 +123,17 @@ TEST(CryptoCore, Signatures) EXPECT_TRUE(crypto_signature_verify( signature.data(), message.data(), message.size(), get_sig_pk(&pk))); - message.push_back(random_u08(rng)); + message.push_back(random_u08(&c_rng)); } } TEST(CryptoCore, Hmac) { - Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); HmacKey sk; - new_hmac_key(rng, sk.data()); + new_hmac_key(&c_rng, sk.data()); std::vector message{0}; message.clear(); @@ -137,7 +144,7 @@ TEST(CryptoCore, Hmac) crypto_hmac(auth.data(), sk.data(), message.data(), message.size()); EXPECT_TRUE(crypto_hmac_verify(auth.data(), sk.data(), message.data(), message.size())); - message.push_back(random_u08(rng)); + message.push_back(random_u08(&c_rng)); } } diff --git a/toxcore/crypto_core_test_util.cc b/toxcore/crypto_core_test_util.cc index 19d8ff94..9eb3346e 100644 --- a/toxcore/crypto_core_test_util.cc +++ b/toxcore/crypto_core_test_util.cc @@ -5,25 +5,6 @@ #include "crypto_core.h" #include "test_util.hh" -#include "tox_random_impl.h" - -Tox_Random_Funcs const Random_Class::vtable = { - Method::invoke<&Random_Class::random_bytes>, - Method::invoke<&Random_Class::random_uniform>, -}; - -Random_Class::~Random_Class() = default; - -void Test_Random::random_bytes(void *obj, uint8_t *bytes, uint32_t length) -{ - std::generate(bytes, &bytes[length], std::ref(lcg)); -} - -uint32_t Test_Random::random_uniform(void *obj, uint32_t upper_bound) -{ - std::uniform_int_distribution distrib(0, upper_bound); - return distrib(lcg); -} PublicKey random_pk(const Random *rng) { diff --git a/toxcore/crypto_core_test_util.hh b/toxcore/crypto_core_test_util.hh index 53b9b07d..5f85d04c 100644 --- a/toxcore/crypto_core_test_util.hh +++ b/toxcore/crypto_core_test_util.hh @@ -4,41 +4,9 @@ #include #include #include -#include #include "crypto_core.h" #include "test_util.hh" -#include "tox_random_impl.h" - -struct Random_Class { - static Tox_Random_Funcs const vtable; - Tox_Random const self; - - operator Tox_Random const *() const { return &self; } - - Random_Class(Random_Class const &) = default; - Random_Class() - : self{&vtable, this} - { - } - - virtual ~Random_Class(); - virtual tox_random_bytes_cb random_bytes = 0; - virtual tox_random_uniform_cb random_uniform = 0; -}; - -/** - * A very simple, fast, and deterministic PRNG just for testing. - * - * We generally don't want to use system_random(), since it's a - * cryptographically secure PRNG and we don't need that in unit tests. - */ -class Test_Random : public Random_Class { - std::minstd_rand lcg; - - void random_bytes(void *obj, uint8_t *bytes, uint32_t length) override; - uint32_t random_uniform(void *obj, uint32_t upper_bound) override; -}; struct PublicKey : private std::array { using Base = std::array; diff --git a/toxcore/events/conference_connected.c b/toxcore/events/conference_connected.c index bf1f9e3a..cf03b355 100644 --- a/toxcore/events/conference_connected.c +++ b/toxcore/events/conference_connected.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -86,7 +86,7 @@ Tox_Event_Conference_Connected *tox_event_conference_connected_new(const Memory void tox_event_conference_connected_free(Tox_Event_Conference_Connected *conference_connected, const Memory *mem) { if (conference_connected != nullptr) { - tox_event_conference_connected_destruct(conference_connected, mem); + tox_event_conference_connected_destruct((Tox_Event_Conference_Connected * _Nonnull)conference_connected, mem); } mem_delete(mem, conference_connected); } @@ -124,11 +124,8 @@ bool tox_event_conference_connected_unpack( return tox_event_conference_connected_unpack_into(*event, bu); } -static Tox_Event_Conference_Connected *tox_event_conference_connected_alloc(void *_Nonnull user_data) +static Tox_Event_Conference_Connected *tox_event_conference_connected_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -153,7 +150,8 @@ void tox_events_handle_conference_connected( Tox *tox, uint32_t conference_number, void *user_data) { - Tox_Event_Conference_Connected *conference_connected = tox_event_conference_connected_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Conference_Connected *conference_connected = tox_event_conference_connected_alloc(state); if (conference_connected == nullptr) { return; diff --git a/toxcore/events/conference_invite.c b/toxcore/events/conference_invite.c index 01523f96..856eec1e 100644 --- a/toxcore/events/conference_invite.c +++ b/toxcore/events/conference_invite.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -55,11 +54,11 @@ Tox_Conference_Type tox_event_conference_invite_get_type(const Tox_Event_Confere } static bool tox_event_conference_invite_set_cookie(Tox_Event_Conference_Invite *_Nonnull conference_invite, - const uint8_t *_Nullable cookie, uint32_t cookie_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable cookie, uint32_t cookie_length) { assert(conference_invite != nullptr); if (conference_invite->cookie != nullptr) { - free(conference_invite->cookie); + mem_delete(mem, conference_invite->cookie); conference_invite->cookie = nullptr; conference_invite->cookie_length = 0; } @@ -69,7 +68,7 @@ static bool tox_event_conference_invite_set_cookie(Tox_Event_Conference_Invite * return true; } - uint8_t *cookie_copy = (uint8_t *)malloc(cookie_length); + uint8_t *cookie_copy = (uint8_t *)mem_balloc(mem, cookie_length); if (cookie_copy == nullptr) { return false; @@ -99,7 +98,7 @@ static void tox_event_conference_invite_construct(Tox_Event_Conference_Invite *_ } static void tox_event_conference_invite_destruct(Tox_Event_Conference_Invite *_Nonnull conference_invite, const Memory *_Nonnull mem) { - free(conference_invite->cookie); + mem_delete(mem, conference_invite->cookie); } bool tox_event_conference_invite_pack( @@ -150,7 +149,7 @@ Tox_Event_Conference_Invite *tox_event_conference_invite_new(const Memory *mem) void tox_event_conference_invite_free(Tox_Event_Conference_Invite *conference_invite, const Memory *mem) { if (conference_invite != nullptr) { - tox_event_conference_invite_destruct(conference_invite, mem); + tox_event_conference_invite_destruct((Tox_Event_Conference_Invite * _Nonnull)conference_invite, mem); } mem_delete(mem, conference_invite); } @@ -188,11 +187,8 @@ bool tox_event_conference_invite_unpack( return tox_event_conference_invite_unpack_into(*event, bu); } -static Tox_Event_Conference_Invite *tox_event_conference_invite_alloc(void *_Nonnull user_data) +static Tox_Event_Conference_Invite *tox_event_conference_invite_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -217,7 +213,8 @@ void tox_events_handle_conference_invite( Tox *tox, uint32_t friend_number, Tox_Conference_Type type, const uint8_t *cookie, size_t length, void *user_data) { - Tox_Event_Conference_Invite *conference_invite = tox_event_conference_invite_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Conference_Invite *conference_invite = tox_event_conference_invite_alloc(state); if (conference_invite == nullptr) { return; @@ -225,5 +222,5 @@ void tox_events_handle_conference_invite( tox_event_conference_invite_set_friend_number(conference_invite, friend_number); tox_event_conference_invite_set_type(conference_invite, type); - tox_event_conference_invite_set_cookie(conference_invite, cookie, length); + tox_event_conference_invite_set_cookie(conference_invite, state->mem, cookie, length); } diff --git a/toxcore/events/conference_message.c b/toxcore/events/conference_message.c index 3ee530e8..b59e86bc 100644 --- a/toxcore/events/conference_message.c +++ b/toxcore/events/conference_message.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -67,11 +66,11 @@ Tox_Message_Type tox_event_conference_message_get_type(const Tox_Event_Conferenc } static bool tox_event_conference_message_set_message(Tox_Event_Conference_Message *_Nonnull conference_message, - const uint8_t *_Nullable message, uint32_t message_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable message, uint32_t message_length) { assert(conference_message != nullptr); if (conference_message->message != nullptr) { - free(conference_message->message); + mem_delete(mem, conference_message->message); conference_message->message = nullptr; conference_message->message_length = 0; } @@ -81,7 +80,7 @@ static bool tox_event_conference_message_set_message(Tox_Event_Conference_Messag return true; } - uint8_t *message_copy = (uint8_t *)malloc(message_length); + uint8_t *message_copy = (uint8_t *)mem_balloc(mem, message_length); if (message_copy == nullptr) { return false; @@ -111,7 +110,7 @@ static void tox_event_conference_message_construct(Tox_Event_Conference_Message } static void tox_event_conference_message_destruct(Tox_Event_Conference_Message *_Nonnull conference_message, const Memory *_Nonnull mem) { - free(conference_message->message); + mem_delete(mem, conference_message->message); } bool tox_event_conference_message_pack( @@ -164,7 +163,7 @@ Tox_Event_Conference_Message *tox_event_conference_message_new(const Memory *mem void tox_event_conference_message_free(Tox_Event_Conference_Message *conference_message, const Memory *mem) { if (conference_message != nullptr) { - tox_event_conference_message_destruct(conference_message, mem); + tox_event_conference_message_destruct((Tox_Event_Conference_Message * _Nonnull)conference_message, mem); } mem_delete(mem, conference_message); } @@ -202,11 +201,8 @@ bool tox_event_conference_message_unpack( return tox_event_conference_message_unpack_into(*event, bu); } -static Tox_Event_Conference_Message *tox_event_conference_message_alloc(void *_Nonnull user_data) +static Tox_Event_Conference_Message *tox_event_conference_message_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -231,7 +227,8 @@ void tox_events_handle_conference_message( Tox *tox, uint32_t conference_number, uint32_t peer_number, Tox_Message_Type type, const uint8_t *message, size_t length, void *user_data) { - Tox_Event_Conference_Message *conference_message = tox_event_conference_message_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Conference_Message *conference_message = tox_event_conference_message_alloc(state); if (conference_message == nullptr) { return; @@ -240,5 +237,5 @@ void tox_events_handle_conference_message( tox_event_conference_message_set_conference_number(conference_message, conference_number); tox_event_conference_message_set_peer_number(conference_message, peer_number); tox_event_conference_message_set_type(conference_message, type); - tox_event_conference_message_set_message(conference_message, message, length); + tox_event_conference_message_set_message(conference_message, state->mem, message, length); } diff --git a/toxcore/events/conference_peer_list_changed.c b/toxcore/events/conference_peer_list_changed.c index d05dda82..b821511a 100644 --- a/toxcore/events/conference_peer_list_changed.c +++ b/toxcore/events/conference_peer_list_changed.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -86,7 +86,7 @@ Tox_Event_Conference_Peer_List_Changed *tox_event_conference_peer_list_changed_n void tox_event_conference_peer_list_changed_free(Tox_Event_Conference_Peer_List_Changed *conference_peer_list_changed, const Memory *mem) { if (conference_peer_list_changed != nullptr) { - tox_event_conference_peer_list_changed_destruct(conference_peer_list_changed, mem); + tox_event_conference_peer_list_changed_destruct((Tox_Event_Conference_Peer_List_Changed * _Nonnull)conference_peer_list_changed, mem); } mem_delete(mem, conference_peer_list_changed); } @@ -124,11 +124,8 @@ bool tox_event_conference_peer_list_changed_unpack( return tox_event_conference_peer_list_changed_unpack_into(*event, bu); } -static Tox_Event_Conference_Peer_List_Changed *tox_event_conference_peer_list_changed_alloc(void *_Nonnull user_data) +static Tox_Event_Conference_Peer_List_Changed *tox_event_conference_peer_list_changed_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -153,7 +150,8 @@ void tox_events_handle_conference_peer_list_changed( Tox *tox, uint32_t conference_number, void *user_data) { - Tox_Event_Conference_Peer_List_Changed *conference_peer_list_changed = tox_event_conference_peer_list_changed_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Conference_Peer_List_Changed *conference_peer_list_changed = tox_event_conference_peer_list_changed_alloc(state); if (conference_peer_list_changed == nullptr) { return; diff --git a/toxcore/events/conference_peer_name.c b/toxcore/events/conference_peer_name.c index 285d7a48..c3f934cd 100644 --- a/toxcore/events/conference_peer_name.c +++ b/toxcore/events/conference_peer_name.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -53,11 +52,11 @@ uint32_t tox_event_conference_peer_name_get_peer_number(const Tox_Event_Conferen } static bool tox_event_conference_peer_name_set_name(Tox_Event_Conference_Peer_Name *_Nonnull conference_peer_name, - const uint8_t *_Nullable name, uint32_t name_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable name, uint32_t name_length) { assert(conference_peer_name != nullptr); if (conference_peer_name->name != nullptr) { - free(conference_peer_name->name); + mem_delete(mem, conference_peer_name->name); conference_peer_name->name = nullptr; conference_peer_name->name_length = 0; } @@ -67,7 +66,7 @@ static bool tox_event_conference_peer_name_set_name(Tox_Event_Conference_Peer_Na return true; } - uint8_t *name_copy = (uint8_t *)malloc(name_length); + uint8_t *name_copy = (uint8_t *)mem_balloc(mem, name_length); if (name_copy == nullptr) { return false; @@ -97,7 +96,7 @@ static void tox_event_conference_peer_name_construct(Tox_Event_Conference_Peer_N } static void tox_event_conference_peer_name_destruct(Tox_Event_Conference_Peer_Name *_Nonnull conference_peer_name, const Memory *_Nonnull mem) { - free(conference_peer_name->name); + mem_delete(mem, conference_peer_name->name); } bool tox_event_conference_peer_name_pack( @@ -148,7 +147,7 @@ Tox_Event_Conference_Peer_Name *tox_event_conference_peer_name_new(const Memory void tox_event_conference_peer_name_free(Tox_Event_Conference_Peer_Name *conference_peer_name, const Memory *mem) { if (conference_peer_name != nullptr) { - tox_event_conference_peer_name_destruct(conference_peer_name, mem); + tox_event_conference_peer_name_destruct((Tox_Event_Conference_Peer_Name * _Nonnull)conference_peer_name, mem); } mem_delete(mem, conference_peer_name); } @@ -186,11 +185,8 @@ bool tox_event_conference_peer_name_unpack( return tox_event_conference_peer_name_unpack_into(*event, bu); } -static Tox_Event_Conference_Peer_Name *tox_event_conference_peer_name_alloc(void *_Nonnull user_data) +static Tox_Event_Conference_Peer_Name *tox_event_conference_peer_name_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -215,7 +211,8 @@ void tox_events_handle_conference_peer_name( Tox *tox, uint32_t conference_number, uint32_t peer_number, const uint8_t *name, size_t length, void *user_data) { - Tox_Event_Conference_Peer_Name *conference_peer_name = tox_event_conference_peer_name_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Conference_Peer_Name *conference_peer_name = tox_event_conference_peer_name_alloc(state); if (conference_peer_name == nullptr) { return; @@ -223,5 +220,5 @@ void tox_events_handle_conference_peer_name( tox_event_conference_peer_name_set_conference_number(conference_peer_name, conference_number); tox_event_conference_peer_name_set_peer_number(conference_peer_name, peer_number); - tox_event_conference_peer_name_set_name(conference_peer_name, name, length); + tox_event_conference_peer_name_set_name(conference_peer_name, state->mem, name, length); } diff --git a/toxcore/events/conference_title.c b/toxcore/events/conference_title.c index 7baeaee8..2196af42 100644 --- a/toxcore/events/conference_title.c +++ b/toxcore/events/conference_title.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -53,11 +52,11 @@ uint32_t tox_event_conference_title_get_peer_number(const Tox_Event_Conference_T } static bool tox_event_conference_title_set_title(Tox_Event_Conference_Title *_Nonnull conference_title, - const uint8_t *_Nullable title, uint32_t title_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable title, uint32_t title_length) { assert(conference_title != nullptr); if (conference_title->title != nullptr) { - free(conference_title->title); + mem_delete(mem, conference_title->title); conference_title->title = nullptr; conference_title->title_length = 0; } @@ -67,7 +66,7 @@ static bool tox_event_conference_title_set_title(Tox_Event_Conference_Title *_No return true; } - uint8_t *title_copy = (uint8_t *)malloc(title_length); + uint8_t *title_copy = (uint8_t *)mem_balloc(mem, title_length); if (title_copy == nullptr) { return false; @@ -97,7 +96,7 @@ static void tox_event_conference_title_construct(Tox_Event_Conference_Title *_No } static void tox_event_conference_title_destruct(Tox_Event_Conference_Title *_Nonnull conference_title, const Memory *_Nonnull mem) { - free(conference_title->title); + mem_delete(mem, conference_title->title); } bool tox_event_conference_title_pack( @@ -148,7 +147,7 @@ Tox_Event_Conference_Title *tox_event_conference_title_new(const Memory *mem) void tox_event_conference_title_free(Tox_Event_Conference_Title *conference_title, const Memory *mem) { if (conference_title != nullptr) { - tox_event_conference_title_destruct(conference_title, mem); + tox_event_conference_title_destruct((Tox_Event_Conference_Title * _Nonnull)conference_title, mem); } mem_delete(mem, conference_title); } @@ -186,11 +185,8 @@ bool tox_event_conference_title_unpack( return tox_event_conference_title_unpack_into(*event, bu); } -static Tox_Event_Conference_Title *tox_event_conference_title_alloc(void *_Nonnull user_data) +static Tox_Event_Conference_Title *tox_event_conference_title_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -215,7 +211,8 @@ void tox_events_handle_conference_title( Tox *tox, uint32_t conference_number, uint32_t peer_number, const uint8_t *title, size_t length, void *user_data) { - Tox_Event_Conference_Title *conference_title = tox_event_conference_title_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Conference_Title *conference_title = tox_event_conference_title_alloc(state); if (conference_title == nullptr) { return; @@ -223,5 +220,5 @@ void tox_events_handle_conference_title( tox_event_conference_title_set_conference_number(conference_title, conference_number); tox_event_conference_title_set_peer_number(conference_title, peer_number); - tox_event_conference_title_set_title(conference_title, title, length); + tox_event_conference_title_set_title(conference_title, state->mem, title, length); } diff --git a/toxcore/events/dht_nodes_response.c b/toxcore/events/dht_nodes_response.c index a4b9dc15..6ddee228 100644 --- a/toxcore/events/dht_nodes_response.c +++ b/toxcore/events/dht_nodes_response.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2022-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -15,7 +15,6 @@ #include "../tox.h" #include "../tox_event.h" #include "../tox_events.h" -#include "../tox_private.h" /***************************************************** * @@ -32,49 +31,62 @@ struct Tox_Event_Dht_Nodes_Response { static bool tox_event_dht_nodes_response_set_public_key(Tox_Event_Dht_Nodes_Response *_Nonnull dht_nodes_response, const uint8_t public_key[TOX_PUBLIC_KEY_SIZE]) { + assert(dht_nodes_response != nullptr); memcpy(dht_nodes_response->public_key, public_key, TOX_PUBLIC_KEY_SIZE); return true; } -const uint8_t *_Nonnull tox_event_dht_nodes_response_get_public_key(const Tox_Event_Dht_Nodes_Response *dht_nodes_response) +const uint8_t *tox_event_dht_nodes_response_get_public_key(const Tox_Event_Dht_Nodes_Response *dht_nodes_response) { + assert(dht_nodes_response != nullptr); return dht_nodes_response->public_key; } -static bool tox_event_dht_nodes_response_set_ip(Tox_Event_Dht_Nodes_Response *_Nonnull dht_nodes_response, const char *_Nonnull ip, uint32_t ip_length, const Memory *_Nonnull mem) +static bool tox_event_dht_nodes_response_set_ip(Tox_Event_Dht_Nodes_Response *_Nonnull dht_nodes_response, + const Memory *_Nonnull mem, const uint8_t *_Nullable ip, uint32_t ip_length) { + assert(dht_nodes_response != nullptr); if (dht_nodes_response->ip != nullptr) { mem_delete(mem, dht_nodes_response->ip); dht_nodes_response->ip = nullptr; dht_nodes_response->ip_length = 0; } - uint8_t *ip_tmp = (uint8_t *)mem_balloc(mem, ip_length + 1); + if (ip == nullptr) { + assert(ip_length == 0); + return true; + } - if (ip_tmp == nullptr) { + uint8_t *ip_copy = (uint8_t *)mem_balloc(mem, ip_length + 1); + + if (ip_copy == nullptr) { return false; } - memcpy(ip_tmp, ip, ip_length + 1); - dht_nodes_response->ip = ip_tmp; + memcpy(ip_copy, ip, ip_length); + ip_copy[ip_length] = 0; + dht_nodes_response->ip = ip_copy; dht_nodes_response->ip_length = ip_length; return true; } uint32_t tox_event_dht_nodes_response_get_ip_length(const Tox_Event_Dht_Nodes_Response *dht_nodes_response) { + assert(dht_nodes_response != nullptr); return dht_nodes_response->ip_length; } const uint8_t *tox_event_dht_nodes_response_get_ip(const Tox_Event_Dht_Nodes_Response *dht_nodes_response) { + assert(dht_nodes_response != nullptr); return dht_nodes_response->ip; } -static bool tox_event_dht_nodes_response_set_port(Tox_Event_Dht_Nodes_Response *_Nonnull dht_nodes_response, uint16_t port) +static void tox_event_dht_nodes_response_set_port(Tox_Event_Dht_Nodes_Response *_Nonnull dht_nodes_response, uint16_t port) { + assert(dht_nodes_response != nullptr); dht_nodes_response->port = port; - return true; } uint16_t tox_event_dht_nodes_response_get_port(const Tox_Event_Dht_Nodes_Response *dht_nodes_response) { + assert(dht_nodes_response != nullptr); return dht_nodes_response->port; } @@ -102,6 +114,7 @@ bool tox_event_dht_nodes_response_pack( static bool tox_event_dht_nodes_response_unpack_into(Tox_Event_Dht_Nodes_Response *_Nonnull event, Bin_Unpack *_Nonnull bu) { + assert(event != nullptr); if (!bin_unpack_array_fixed(bu, 3, nullptr)) { return false; } @@ -111,8 +124,13 @@ static bool tox_event_dht_nodes_response_unpack_into(Tox_Event_Dht_Nodes_Respons && bin_unpack_u16(bu, &event->port); } -const Tox_Event_Dht_Nodes_Response *tox_event_get_dht_nodes_response( - const Tox_Event *event) +/***************************************************** + * + * :: new/free/add/get/size/unpack + * + *****************************************************/ + +const Tox_Event_Dht_Nodes_Response *tox_event_get_dht_nodes_response(const Tox_Event *event) { return event->type == TOX_EVENT_DHT_NODES_RESPONSE ? event->data.dht_nodes_response : nullptr; } @@ -133,7 +151,7 @@ Tox_Event_Dht_Nodes_Response *tox_event_dht_nodes_response_new(const Memory *mem void tox_event_dht_nodes_response_free(Tox_Event_Dht_Nodes_Response *dht_nodes_response, const Memory *mem) { if (dht_nodes_response != nullptr) { - tox_event_dht_nodes_response_destruct(dht_nodes_response, mem); + tox_event_dht_nodes_response_destruct((Tox_Event_Dht_Nodes_Response * _Nonnull)dht_nodes_response, mem); } mem_delete(mem, dht_nodes_response); } @@ -160,6 +178,8 @@ static Tox_Event_Dht_Nodes_Response *tox_events_add_dht_nodes_response(Tox_Event bool tox_event_dht_nodes_response_unpack( Tox_Event_Dht_Nodes_Response **event, Bin_Unpack *bu, const Memory *mem) { + assert(event != nullptr); + assert(*event == nullptr); *event = tox_event_dht_nodes_response_new(mem); if (*event == nullptr) { @@ -169,11 +189,8 @@ bool tox_event_dht_nodes_response_unpack( return tox_event_dht_nodes_response_unpack_into(*event, bu); } -static Tox_Event_Dht_Nodes_Response *tox_event_dht_nodes_response_alloc(void *_Nonnull user_data) +static Tox_Event_Dht_Nodes_Response *tox_event_dht_nodes_response_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -195,18 +212,17 @@ static Tox_Event_Dht_Nodes_Response *tox_event_dht_nodes_response_alloc(void *_N *****************************************************/ void tox_events_handle_dht_nodes_response( - Tox *tox, const uint8_t public_key[TOX_PUBLIC_KEY_SIZE], - const char *ip, uint32_t ip_length, uint16_t port, void *user_data) + Tox *tox, const uint8_t *public_key, const char *ip, uint32_t ip_length, uint16_t port, + void *user_data) { - Tox_Event_Dht_Nodes_Response *dht_nodes_response = tox_event_dht_nodes_response_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Dht_Nodes_Response *dht_nodes_response = tox_event_dht_nodes_response_alloc(state); if (dht_nodes_response == nullptr) { return; } - const Tox_System *sys = tox_get_system(tox); - tox_event_dht_nodes_response_set_public_key(dht_nodes_response, public_key); - tox_event_dht_nodes_response_set_ip(dht_nodes_response, ip, ip_length, sys->mem); + tox_event_dht_nodes_response_set_ip(dht_nodes_response, state->mem, (const uint8_t *)ip, ip_length); tox_event_dht_nodes_response_set_port(dht_nodes_response, port); } diff --git a/toxcore/events/file_chunk_request.c b/toxcore/events/file_chunk_request.c index 5562e553..96731dd1 100644 --- a/toxcore/events/file_chunk_request.c +++ b/toxcore/events/file_chunk_request.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -133,7 +133,7 @@ Tox_Event_File_Chunk_Request *tox_event_file_chunk_request_new(const Memory *mem void tox_event_file_chunk_request_free(Tox_Event_File_Chunk_Request *file_chunk_request, const Memory *mem) { if (file_chunk_request != nullptr) { - tox_event_file_chunk_request_destruct(file_chunk_request, mem); + tox_event_file_chunk_request_destruct((Tox_Event_File_Chunk_Request * _Nonnull)file_chunk_request, mem); } mem_delete(mem, file_chunk_request); } @@ -171,11 +171,8 @@ bool tox_event_file_chunk_request_unpack( return tox_event_file_chunk_request_unpack_into(*event, bu); } -static Tox_Event_File_Chunk_Request *tox_event_file_chunk_request_alloc(void *_Nonnull user_data) +static Tox_Event_File_Chunk_Request *tox_event_file_chunk_request_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -200,7 +197,8 @@ void tox_events_handle_file_chunk_request( Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length, void *user_data) { - Tox_Event_File_Chunk_Request *file_chunk_request = tox_event_file_chunk_request_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_File_Chunk_Request *file_chunk_request = tox_event_file_chunk_request_alloc(state); if (file_chunk_request == nullptr) { return; diff --git a/toxcore/events/file_recv.c b/toxcore/events/file_recv.c index 00b015da..0391ea3c 100644 --- a/toxcore/events/file_recv.c +++ b/toxcore/events/file_recv.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -77,11 +76,11 @@ uint64_t tox_event_file_recv_get_file_size(const Tox_Event_File_Recv *file_recv) } static bool tox_event_file_recv_set_filename(Tox_Event_File_Recv *_Nonnull file_recv, - const uint8_t *_Nullable filename, uint32_t filename_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable filename, uint32_t filename_length) { assert(file_recv != nullptr); if (file_recv->filename != nullptr) { - free(file_recv->filename); + mem_delete(mem, file_recv->filename); file_recv->filename = nullptr; file_recv->filename_length = 0; } @@ -91,7 +90,7 @@ static bool tox_event_file_recv_set_filename(Tox_Event_File_Recv *_Nonnull file_ return true; } - uint8_t *filename_copy = (uint8_t *)malloc(filename_length); + uint8_t *filename_copy = (uint8_t *)mem_balloc(mem, filename_length); if (filename_copy == nullptr) { return false; @@ -121,7 +120,7 @@ static void tox_event_file_recv_construct(Tox_Event_File_Recv *_Nonnull file_rec } static void tox_event_file_recv_destruct(Tox_Event_File_Recv *_Nonnull file_recv, const Memory *_Nonnull mem) { - free(file_recv->filename); + mem_delete(mem, file_recv->filename); } bool tox_event_file_recv_pack( @@ -176,7 +175,7 @@ Tox_Event_File_Recv *tox_event_file_recv_new(const Memory *mem) void tox_event_file_recv_free(Tox_Event_File_Recv *file_recv, const Memory *mem) { if (file_recv != nullptr) { - tox_event_file_recv_destruct(file_recv, mem); + tox_event_file_recv_destruct((Tox_Event_File_Recv * _Nonnull)file_recv, mem); } mem_delete(mem, file_recv); } @@ -214,11 +213,8 @@ bool tox_event_file_recv_unpack( return tox_event_file_recv_unpack_into(*event, bu); } -static Tox_Event_File_Recv *tox_event_file_recv_alloc(void *_Nonnull user_data) +static Tox_Event_File_Recv *tox_event_file_recv_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -243,7 +239,8 @@ void tox_events_handle_file_recv( Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, const uint8_t *filename, size_t filename_length, void *user_data) { - Tox_Event_File_Recv *file_recv = tox_event_file_recv_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_File_Recv *file_recv = tox_event_file_recv_alloc(state); if (file_recv == nullptr) { return; @@ -253,5 +250,5 @@ void tox_events_handle_file_recv( tox_event_file_recv_set_file_number(file_recv, file_number); tox_event_file_recv_set_kind(file_recv, kind); tox_event_file_recv_set_file_size(file_recv, file_size); - tox_event_file_recv_set_filename(file_recv, filename, filename_length); + tox_event_file_recv_set_filename(file_recv, state->mem, filename, filename_length); } diff --git a/toxcore/events/file_recv_chunk.c b/toxcore/events/file_recv_chunk.c index 09a1eda8..bfedf77b 100644 --- a/toxcore/events/file_recv_chunk.c +++ b/toxcore/events/file_recv_chunk.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -65,11 +64,11 @@ uint64_t tox_event_file_recv_chunk_get_position(const Tox_Event_File_Recv_Chunk } static bool tox_event_file_recv_chunk_set_data(Tox_Event_File_Recv_Chunk *_Nonnull file_recv_chunk, - const uint8_t *_Nullable data, uint32_t data_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable data, uint32_t data_length) { assert(file_recv_chunk != nullptr); if (file_recv_chunk->data != nullptr) { - free(file_recv_chunk->data); + mem_delete(mem, file_recv_chunk->data); file_recv_chunk->data = nullptr; file_recv_chunk->data_length = 0; } @@ -79,7 +78,7 @@ static bool tox_event_file_recv_chunk_set_data(Tox_Event_File_Recv_Chunk *_Nonnu return true; } - uint8_t *data_copy = (uint8_t *)malloc(data_length); + uint8_t *data_copy = (uint8_t *)mem_balloc(mem, data_length); if (data_copy == nullptr) { return false; @@ -109,7 +108,7 @@ static void tox_event_file_recv_chunk_construct(Tox_Event_File_Recv_Chunk *_Nonn } static void tox_event_file_recv_chunk_destruct(Tox_Event_File_Recv_Chunk *_Nonnull file_recv_chunk, const Memory *_Nonnull mem) { - free(file_recv_chunk->data); + mem_delete(mem, file_recv_chunk->data); } bool tox_event_file_recv_chunk_pack( @@ -162,7 +161,7 @@ Tox_Event_File_Recv_Chunk *tox_event_file_recv_chunk_new(const Memory *mem) void tox_event_file_recv_chunk_free(Tox_Event_File_Recv_Chunk *file_recv_chunk, const Memory *mem) { if (file_recv_chunk != nullptr) { - tox_event_file_recv_chunk_destruct(file_recv_chunk, mem); + tox_event_file_recv_chunk_destruct((Tox_Event_File_Recv_Chunk * _Nonnull)file_recv_chunk, mem); } mem_delete(mem, file_recv_chunk); } @@ -200,11 +199,8 @@ bool tox_event_file_recv_chunk_unpack( return tox_event_file_recv_chunk_unpack_into(*event, bu); } -static Tox_Event_File_Recv_Chunk *tox_event_file_recv_chunk_alloc(void *_Nonnull user_data) +static Tox_Event_File_Recv_Chunk *tox_event_file_recv_chunk_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -229,7 +225,8 @@ void tox_events_handle_file_recv_chunk( Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, void *user_data) { - Tox_Event_File_Recv_Chunk *file_recv_chunk = tox_event_file_recv_chunk_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_File_Recv_Chunk *file_recv_chunk = tox_event_file_recv_chunk_alloc(state); if (file_recv_chunk == nullptr) { return; @@ -238,5 +235,5 @@ void tox_events_handle_file_recv_chunk( tox_event_file_recv_chunk_set_friend_number(file_recv_chunk, friend_number); tox_event_file_recv_chunk_set_file_number(file_recv_chunk, file_number); tox_event_file_recv_chunk_set_position(file_recv_chunk, position); - tox_event_file_recv_chunk_set_data(file_recv_chunk, data, length); + tox_event_file_recv_chunk_set_data(file_recv_chunk, state->mem, data, length); } diff --git a/toxcore/events/file_recv_control.c b/toxcore/events/file_recv_control.c index e5a650e1..00c3c18e 100644 --- a/toxcore/events/file_recv_control.c +++ b/toxcore/events/file_recv_control.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -121,7 +121,7 @@ Tox_Event_File_Recv_Control *tox_event_file_recv_control_new(const Memory *mem) void tox_event_file_recv_control_free(Tox_Event_File_Recv_Control *file_recv_control, const Memory *mem) { if (file_recv_control != nullptr) { - tox_event_file_recv_control_destruct(file_recv_control, mem); + tox_event_file_recv_control_destruct((Tox_Event_File_Recv_Control * _Nonnull)file_recv_control, mem); } mem_delete(mem, file_recv_control); } @@ -159,11 +159,8 @@ bool tox_event_file_recv_control_unpack( return tox_event_file_recv_control_unpack_into(*event, bu); } -static Tox_Event_File_Recv_Control *tox_event_file_recv_control_alloc(void *_Nonnull user_data) +static Tox_Event_File_Recv_Control *tox_event_file_recv_control_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -188,7 +185,8 @@ void tox_events_handle_file_recv_control( Tox *tox, uint32_t friend_number, uint32_t file_number, Tox_File_Control control, void *user_data) { - Tox_Event_File_Recv_Control *file_recv_control = tox_event_file_recv_control_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_File_Recv_Control *file_recv_control = tox_event_file_recv_control_alloc(state); if (file_recv_control == nullptr) { return; diff --git a/toxcore/events/friend_connection_status.c b/toxcore/events/friend_connection_status.c index 64b04ab4..eb3bff48 100644 --- a/toxcore/events/friend_connection_status.c +++ b/toxcore/events/friend_connection_status.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -107,7 +107,7 @@ Tox_Event_Friend_Connection_Status *tox_event_friend_connection_status_new(const void tox_event_friend_connection_status_free(Tox_Event_Friend_Connection_Status *friend_connection_status, const Memory *mem) { if (friend_connection_status != nullptr) { - tox_event_friend_connection_status_destruct(friend_connection_status, mem); + tox_event_friend_connection_status_destruct((Tox_Event_Friend_Connection_Status * _Nonnull)friend_connection_status, mem); } mem_delete(mem, friend_connection_status); } @@ -145,11 +145,8 @@ bool tox_event_friend_connection_status_unpack( return tox_event_friend_connection_status_unpack_into(*event, bu); } -static Tox_Event_Friend_Connection_Status *tox_event_friend_connection_status_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Connection_Status *tox_event_friend_connection_status_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -174,7 +171,8 @@ void tox_events_handle_friend_connection_status( Tox *tox, uint32_t friend_number, Tox_Connection connection_status, void *user_data) { - Tox_Event_Friend_Connection_Status *friend_connection_status = tox_event_friend_connection_status_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Connection_Status *friend_connection_status = tox_event_friend_connection_status_alloc(state); if (friend_connection_status == nullptr) { return; diff --git a/toxcore/events/friend_lossless_packet.c b/toxcore/events/friend_lossless_packet.c index 7b9e6e91..0dba77b9 100644 --- a/toxcore/events/friend_lossless_packet.c +++ b/toxcore/events/friend_lossless_packet.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -41,11 +40,11 @@ uint32_t tox_event_friend_lossless_packet_get_friend_number(const Tox_Event_Frie } static bool tox_event_friend_lossless_packet_set_data(Tox_Event_Friend_Lossless_Packet *_Nonnull friend_lossless_packet, - const uint8_t *_Nullable data, uint32_t data_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable data, uint32_t data_length) { assert(friend_lossless_packet != nullptr); if (friend_lossless_packet->data != nullptr) { - free(friend_lossless_packet->data); + mem_delete(mem, friend_lossless_packet->data); friend_lossless_packet->data = nullptr; friend_lossless_packet->data_length = 0; } @@ -55,7 +54,7 @@ static bool tox_event_friend_lossless_packet_set_data(Tox_Event_Friend_Lossless_ return true; } - uint8_t *data_copy = (uint8_t *)malloc(data_length); + uint8_t *data_copy = (uint8_t *)mem_balloc(mem, data_length); if (data_copy == nullptr) { return false; @@ -85,7 +84,7 @@ static void tox_event_friend_lossless_packet_construct(Tox_Event_Friend_Lossless } static void tox_event_friend_lossless_packet_destruct(Tox_Event_Friend_Lossless_Packet *_Nonnull friend_lossless_packet, const Memory *_Nonnull mem) { - free(friend_lossless_packet->data); + mem_delete(mem, friend_lossless_packet->data); } bool tox_event_friend_lossless_packet_pack( @@ -134,7 +133,7 @@ Tox_Event_Friend_Lossless_Packet *tox_event_friend_lossless_packet_new(const Mem void tox_event_friend_lossless_packet_free(Tox_Event_Friend_Lossless_Packet *friend_lossless_packet, const Memory *mem) { if (friend_lossless_packet != nullptr) { - tox_event_friend_lossless_packet_destruct(friend_lossless_packet, mem); + tox_event_friend_lossless_packet_destruct((Tox_Event_Friend_Lossless_Packet * _Nonnull)friend_lossless_packet, mem); } mem_delete(mem, friend_lossless_packet); } @@ -172,11 +171,8 @@ bool tox_event_friend_lossless_packet_unpack( return tox_event_friend_lossless_packet_unpack_into(*event, bu); } -static Tox_Event_Friend_Lossless_Packet *tox_event_friend_lossless_packet_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Lossless_Packet *tox_event_friend_lossless_packet_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -201,12 +197,13 @@ void tox_events_handle_friend_lossless_packet( Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data) { - Tox_Event_Friend_Lossless_Packet *friend_lossless_packet = tox_event_friend_lossless_packet_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Lossless_Packet *friend_lossless_packet = tox_event_friend_lossless_packet_alloc(state); if (friend_lossless_packet == nullptr) { return; } tox_event_friend_lossless_packet_set_friend_number(friend_lossless_packet, friend_number); - tox_event_friend_lossless_packet_set_data(friend_lossless_packet, data, length); + tox_event_friend_lossless_packet_set_data(friend_lossless_packet, state->mem, data, length); } diff --git a/toxcore/events/friend_lossy_packet.c b/toxcore/events/friend_lossy_packet.c index 22096d4b..cb2bb0e7 100644 --- a/toxcore/events/friend_lossy_packet.c +++ b/toxcore/events/friend_lossy_packet.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -41,11 +40,11 @@ uint32_t tox_event_friend_lossy_packet_get_friend_number(const Tox_Event_Friend_ } static bool tox_event_friend_lossy_packet_set_data(Tox_Event_Friend_Lossy_Packet *_Nonnull friend_lossy_packet, - const uint8_t *_Nullable data, uint32_t data_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable data, uint32_t data_length) { assert(friend_lossy_packet != nullptr); if (friend_lossy_packet->data != nullptr) { - free(friend_lossy_packet->data); + mem_delete(mem, friend_lossy_packet->data); friend_lossy_packet->data = nullptr; friend_lossy_packet->data_length = 0; } @@ -55,7 +54,7 @@ static bool tox_event_friend_lossy_packet_set_data(Tox_Event_Friend_Lossy_Packet return true; } - uint8_t *data_copy = (uint8_t *)malloc(data_length); + uint8_t *data_copy = (uint8_t *)mem_balloc(mem, data_length); if (data_copy == nullptr) { return false; @@ -85,7 +84,7 @@ static void tox_event_friend_lossy_packet_construct(Tox_Event_Friend_Lossy_Packe } static void tox_event_friend_lossy_packet_destruct(Tox_Event_Friend_Lossy_Packet *_Nonnull friend_lossy_packet, const Memory *_Nonnull mem) { - free(friend_lossy_packet->data); + mem_delete(mem, friend_lossy_packet->data); } bool tox_event_friend_lossy_packet_pack( @@ -134,7 +133,7 @@ Tox_Event_Friend_Lossy_Packet *tox_event_friend_lossy_packet_new(const Memory *m void tox_event_friend_lossy_packet_free(Tox_Event_Friend_Lossy_Packet *friend_lossy_packet, const Memory *mem) { if (friend_lossy_packet != nullptr) { - tox_event_friend_lossy_packet_destruct(friend_lossy_packet, mem); + tox_event_friend_lossy_packet_destruct((Tox_Event_Friend_Lossy_Packet * _Nonnull)friend_lossy_packet, mem); } mem_delete(mem, friend_lossy_packet); } @@ -172,11 +171,8 @@ bool tox_event_friend_lossy_packet_unpack( return tox_event_friend_lossy_packet_unpack_into(*event, bu); } -static Tox_Event_Friend_Lossy_Packet *tox_event_friend_lossy_packet_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Lossy_Packet *tox_event_friend_lossy_packet_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -201,12 +197,13 @@ void tox_events_handle_friend_lossy_packet( Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data) { - Tox_Event_Friend_Lossy_Packet *friend_lossy_packet = tox_event_friend_lossy_packet_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Lossy_Packet *friend_lossy_packet = tox_event_friend_lossy_packet_alloc(state); if (friend_lossy_packet == nullptr) { return; } tox_event_friend_lossy_packet_set_friend_number(friend_lossy_packet, friend_number); - tox_event_friend_lossy_packet_set_data(friend_lossy_packet, data, length); + tox_event_friend_lossy_packet_set_data(friend_lossy_packet, state->mem, data, length); } diff --git a/toxcore/events/friend_message.c b/toxcore/events/friend_message.c index 0d59a6bc..b223a301 100644 --- a/toxcore/events/friend_message.c +++ b/toxcore/events/friend_message.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -55,11 +54,11 @@ Tox_Message_Type tox_event_friend_message_get_type(const Tox_Event_Friend_Messag } static bool tox_event_friend_message_set_message(Tox_Event_Friend_Message *_Nonnull friend_message, - const uint8_t *_Nullable message, uint32_t message_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable message, uint32_t message_length) { assert(friend_message != nullptr); if (friend_message->message != nullptr) { - free(friend_message->message); + mem_delete(mem, friend_message->message); friend_message->message = nullptr; friend_message->message_length = 0; } @@ -69,7 +68,7 @@ static bool tox_event_friend_message_set_message(Tox_Event_Friend_Message *_Nonn return true; } - uint8_t *message_copy = (uint8_t *)malloc(message_length); + uint8_t *message_copy = (uint8_t *)mem_balloc(mem, message_length); if (message_copy == nullptr) { return false; @@ -99,7 +98,7 @@ static void tox_event_friend_message_construct(Tox_Event_Friend_Message *_Nonnul } static void tox_event_friend_message_destruct(Tox_Event_Friend_Message *_Nonnull friend_message, const Memory *_Nonnull mem) { - free(friend_message->message); + mem_delete(mem, friend_message->message); } bool tox_event_friend_message_pack( @@ -150,7 +149,7 @@ Tox_Event_Friend_Message *tox_event_friend_message_new(const Memory *mem) void tox_event_friend_message_free(Tox_Event_Friend_Message *friend_message, const Memory *mem) { if (friend_message != nullptr) { - tox_event_friend_message_destruct(friend_message, mem); + tox_event_friend_message_destruct((Tox_Event_Friend_Message * _Nonnull)friend_message, mem); } mem_delete(mem, friend_message); } @@ -188,11 +187,8 @@ bool tox_event_friend_message_unpack( return tox_event_friend_message_unpack_into(*event, bu); } -static Tox_Event_Friend_Message *tox_event_friend_message_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Message *tox_event_friend_message_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -217,7 +213,8 @@ void tox_events_handle_friend_message( Tox *tox, uint32_t friend_number, Tox_Message_Type type, const uint8_t *message, size_t length, void *user_data) { - Tox_Event_Friend_Message *friend_message = tox_event_friend_message_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Message *friend_message = tox_event_friend_message_alloc(state); if (friend_message == nullptr) { return; @@ -225,5 +222,5 @@ void tox_events_handle_friend_message( tox_event_friend_message_set_friend_number(friend_message, friend_number); tox_event_friend_message_set_type(friend_message, type); - tox_event_friend_message_set_message(friend_message, message, length); + tox_event_friend_message_set_message(friend_message, state->mem, message, length); } diff --git a/toxcore/events/friend_name.c b/toxcore/events/friend_name.c index 4f1c6e22..5cc59051 100644 --- a/toxcore/events/friend_name.c +++ b/toxcore/events/friend_name.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -41,11 +40,11 @@ uint32_t tox_event_friend_name_get_friend_number(const Tox_Event_Friend_Name *fr } static bool tox_event_friend_name_set_name(Tox_Event_Friend_Name *_Nonnull friend_name, - const uint8_t *_Nullable name, uint32_t name_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable name, uint32_t name_length) { assert(friend_name != nullptr); if (friend_name->name != nullptr) { - free(friend_name->name); + mem_delete(mem, friend_name->name); friend_name->name = nullptr; friend_name->name_length = 0; } @@ -55,7 +54,7 @@ static bool tox_event_friend_name_set_name(Tox_Event_Friend_Name *_Nonnull frien return true; } - uint8_t *name_copy = (uint8_t *)malloc(name_length); + uint8_t *name_copy = (uint8_t *)mem_balloc(mem, name_length); if (name_copy == nullptr) { return false; @@ -85,7 +84,7 @@ static void tox_event_friend_name_construct(Tox_Event_Friend_Name *_Nonnull frie } static void tox_event_friend_name_destruct(Tox_Event_Friend_Name *_Nonnull friend_name, const Memory *_Nonnull mem) { - free(friend_name->name); + mem_delete(mem, friend_name->name); } bool tox_event_friend_name_pack( @@ -134,7 +133,7 @@ Tox_Event_Friend_Name *tox_event_friend_name_new(const Memory *mem) void tox_event_friend_name_free(Tox_Event_Friend_Name *friend_name, const Memory *mem) { if (friend_name != nullptr) { - tox_event_friend_name_destruct(friend_name, mem); + tox_event_friend_name_destruct((Tox_Event_Friend_Name * _Nonnull)friend_name, mem); } mem_delete(mem, friend_name); } @@ -172,11 +171,8 @@ bool tox_event_friend_name_unpack( return tox_event_friend_name_unpack_into(*event, bu); } -static Tox_Event_Friend_Name *tox_event_friend_name_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Name *tox_event_friend_name_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -201,12 +197,13 @@ void tox_events_handle_friend_name( Tox *tox, uint32_t friend_number, const uint8_t *name, size_t length, void *user_data) { - Tox_Event_Friend_Name *friend_name = tox_event_friend_name_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Name *friend_name = tox_event_friend_name_alloc(state); if (friend_name == nullptr) { return; } tox_event_friend_name_set_friend_number(friend_name, friend_number); - tox_event_friend_name_set_name(friend_name, name, length); + tox_event_friend_name_set_name(friend_name, state->mem, name, length); } diff --git a/toxcore/events/friend_read_receipt.c b/toxcore/events/friend_read_receipt.c index b4443bd3..f6bf6cf7 100644 --- a/toxcore/events/friend_read_receipt.c +++ b/toxcore/events/friend_read_receipt.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -105,7 +105,7 @@ Tox_Event_Friend_Read_Receipt *tox_event_friend_read_receipt_new(const Memory *m void tox_event_friend_read_receipt_free(Tox_Event_Friend_Read_Receipt *friend_read_receipt, const Memory *mem) { if (friend_read_receipt != nullptr) { - tox_event_friend_read_receipt_destruct(friend_read_receipt, mem); + tox_event_friend_read_receipt_destruct((Tox_Event_Friend_Read_Receipt * _Nonnull)friend_read_receipt, mem); } mem_delete(mem, friend_read_receipt); } @@ -143,11 +143,8 @@ bool tox_event_friend_read_receipt_unpack( return tox_event_friend_read_receipt_unpack_into(*event, bu); } -static Tox_Event_Friend_Read_Receipt *tox_event_friend_read_receipt_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Read_Receipt *tox_event_friend_read_receipt_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -172,7 +169,8 @@ void tox_events_handle_friend_read_receipt( Tox *tox, uint32_t friend_number, uint32_t message_id, void *user_data) { - Tox_Event_Friend_Read_Receipt *friend_read_receipt = tox_event_friend_read_receipt_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Read_Receipt *friend_read_receipt = tox_event_friend_read_receipt_alloc(state); if (friend_read_receipt == nullptr) { return; diff --git a/toxcore/events/friend_request.c b/toxcore/events/friend_request.c index b331d11e..098470aa 100644 --- a/toxcore/events/friend_request.c +++ b/toxcore/events/friend_request.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2022-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -15,7 +15,6 @@ #include "../tox.h" #include "../tox_event.h" #include "../tox_events.h" -#include "../tox_private.h" /***************************************************** * @@ -29,10 +28,9 @@ struct Tox_Event_Friend_Request { uint32_t message_length; }; -static bool tox_event_friend_request_set_public_key(Tox_Event_Friend_Request *_Nonnull friend_request, const uint8_t *_Nonnull public_key) +static bool tox_event_friend_request_set_public_key(Tox_Event_Friend_Request *_Nonnull friend_request, const uint8_t public_key[TOX_PUBLIC_KEY_SIZE]) { assert(friend_request != nullptr); - memcpy(friend_request->public_key, public_key, TOX_PUBLIC_KEY_SIZE); return true; } @@ -42,16 +40,21 @@ const uint8_t *tox_event_friend_request_get_public_key(const Tox_Event_Friend_Re return friend_request->public_key; } -static bool tox_event_friend_request_set_message(Tox_Event_Friend_Request *_Nonnull friend_request, const uint8_t *_Nonnull message, uint32_t message_length, const Memory *_Nonnull mem) +static bool tox_event_friend_request_set_message(Tox_Event_Friend_Request *_Nonnull friend_request, + const Memory *_Nonnull mem, const uint8_t *_Nullable message, uint32_t message_length) { assert(friend_request != nullptr); - if (friend_request->message != nullptr) { mem_delete(mem, friend_request->message); friend_request->message = nullptr; friend_request->message_length = 0; } + if (message == nullptr) { + assert(message_length == 0); + return true; + } + uint8_t *message_copy = (uint8_t *)mem_balloc(mem, message_length); if (message_copy == nullptr) { @@ -106,8 +109,13 @@ static bool tox_event_friend_request_unpack_into(Tox_Event_Friend_Request *_Nonn && bin_unpack_bin(bu, &event->message, &event->message_length); } -const Tox_Event_Friend_Request *tox_event_get_friend_request( - const Tox_Event *event) +/***************************************************** + * + * :: new/free/add/get/size/unpack + * + *****************************************************/ + +const Tox_Event_Friend_Request *tox_event_get_friend_request(const Tox_Event *event) { return event->type == TOX_EVENT_FRIEND_REQUEST ? event->data.friend_request : nullptr; } @@ -128,7 +136,7 @@ Tox_Event_Friend_Request *tox_event_friend_request_new(const Memory *mem) void tox_event_friend_request_free(Tox_Event_Friend_Request *friend_request, const Memory *mem) { if (friend_request != nullptr) { - tox_event_friend_request_destruct(friend_request, mem); + tox_event_friend_request_destruct((Tox_Event_Friend_Request * _Nonnull)friend_request, mem); } mem_delete(mem, friend_request); } @@ -166,11 +174,8 @@ bool tox_event_friend_request_unpack( return tox_event_friend_request_unpack_into(*event, bu); } -static Tox_Event_Friend_Request *tox_event_friend_request_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Request *tox_event_friend_request_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -191,17 +196,17 @@ static Tox_Event_Friend_Request *tox_event_friend_request_alloc(void *_Nonnull u * *****************************************************/ -void tox_events_handle_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, - void *user_data) +void tox_events_handle_friend_request( + Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data) { - Tox_Event_Friend_Request *friend_request = tox_event_friend_request_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Request *friend_request = tox_event_friend_request_alloc(state); if (friend_request == nullptr) { return; } - const Tox_System *sys = tox_get_system(tox); - tox_event_friend_request_set_public_key(friend_request, public_key); - tox_event_friend_request_set_message(friend_request, message, length, sys->mem); + tox_event_friend_request_set_message(friend_request, state->mem, message, length); } diff --git a/toxcore/events/friend_status.c b/toxcore/events/friend_status.c index 99d39bc8..ad3401dd 100644 --- a/toxcore/events/friend_status.c +++ b/toxcore/events/friend_status.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -107,7 +107,7 @@ Tox_Event_Friend_Status *tox_event_friend_status_new(const Memory *mem) void tox_event_friend_status_free(Tox_Event_Friend_Status *friend_status, const Memory *mem) { if (friend_status != nullptr) { - tox_event_friend_status_destruct(friend_status, mem); + tox_event_friend_status_destruct((Tox_Event_Friend_Status * _Nonnull)friend_status, mem); } mem_delete(mem, friend_status); } @@ -145,11 +145,8 @@ bool tox_event_friend_status_unpack( return tox_event_friend_status_unpack_into(*event, bu); } -static Tox_Event_Friend_Status *tox_event_friend_status_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Status *tox_event_friend_status_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -174,7 +171,8 @@ void tox_events_handle_friend_status( Tox *tox, uint32_t friend_number, Tox_User_Status status, void *user_data) { - Tox_Event_Friend_Status *friend_status = tox_event_friend_status_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Status *friend_status = tox_event_friend_status_alloc(state); if (friend_status == nullptr) { return; diff --git a/toxcore/events/friend_status_message.c b/toxcore/events/friend_status_message.c index c2e77d9c..cf3e4a3e 100644 --- a/toxcore/events/friend_status_message.c +++ b/toxcore/events/friend_status_message.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -41,11 +40,11 @@ uint32_t tox_event_friend_status_message_get_friend_number(const Tox_Event_Frien } static bool tox_event_friend_status_message_set_message(Tox_Event_Friend_Status_Message *_Nonnull friend_status_message, - const uint8_t *_Nullable message, uint32_t message_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable message, uint32_t message_length) { assert(friend_status_message != nullptr); if (friend_status_message->message != nullptr) { - free(friend_status_message->message); + mem_delete(mem, friend_status_message->message); friend_status_message->message = nullptr; friend_status_message->message_length = 0; } @@ -55,7 +54,7 @@ static bool tox_event_friend_status_message_set_message(Tox_Event_Friend_Status_ return true; } - uint8_t *message_copy = (uint8_t *)malloc(message_length); + uint8_t *message_copy = (uint8_t *)mem_balloc(mem, message_length); if (message_copy == nullptr) { return false; @@ -85,7 +84,7 @@ static void tox_event_friend_status_message_construct(Tox_Event_Friend_Status_Me } static void tox_event_friend_status_message_destruct(Tox_Event_Friend_Status_Message *_Nonnull friend_status_message, const Memory *_Nonnull mem) { - free(friend_status_message->message); + mem_delete(mem, friend_status_message->message); } bool tox_event_friend_status_message_pack( @@ -134,7 +133,7 @@ Tox_Event_Friend_Status_Message *tox_event_friend_status_message_new(const Memor void tox_event_friend_status_message_free(Tox_Event_Friend_Status_Message *friend_status_message, const Memory *mem) { if (friend_status_message != nullptr) { - tox_event_friend_status_message_destruct(friend_status_message, mem); + tox_event_friend_status_message_destruct((Tox_Event_Friend_Status_Message * _Nonnull)friend_status_message, mem); } mem_delete(mem, friend_status_message); } @@ -172,11 +171,8 @@ bool tox_event_friend_status_message_unpack( return tox_event_friend_status_message_unpack_into(*event, bu); } -static Tox_Event_Friend_Status_Message *tox_event_friend_status_message_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Status_Message *tox_event_friend_status_message_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -201,12 +197,13 @@ void tox_events_handle_friend_status_message( Tox *tox, uint32_t friend_number, const uint8_t *message, size_t length, void *user_data) { - Tox_Event_Friend_Status_Message *friend_status_message = tox_event_friend_status_message_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Status_Message *friend_status_message = tox_event_friend_status_message_alloc(state); if (friend_status_message == nullptr) { return; } tox_event_friend_status_message_set_friend_number(friend_status_message, friend_number); - tox_event_friend_status_message_set_message(friend_status_message, message, length); + tox_event_friend_status_message_set_message(friend_status_message, state->mem, message, length); } diff --git a/toxcore/events/friend_typing.c b/toxcore/events/friend_typing.c index fda20baf..c21faab7 100644 --- a/toxcore/events/friend_typing.c +++ b/toxcore/events/friend_typing.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -105,7 +105,7 @@ Tox_Event_Friend_Typing *tox_event_friend_typing_new(const Memory *mem) void tox_event_friend_typing_free(Tox_Event_Friend_Typing *friend_typing, const Memory *mem) { if (friend_typing != nullptr) { - tox_event_friend_typing_destruct(friend_typing, mem); + tox_event_friend_typing_destruct((Tox_Event_Friend_Typing * _Nonnull)friend_typing, mem); } mem_delete(mem, friend_typing); } @@ -143,11 +143,8 @@ bool tox_event_friend_typing_unpack( return tox_event_friend_typing_unpack_into(*event, bu); } -static Tox_Event_Friend_Typing *tox_event_friend_typing_alloc(void *_Nonnull user_data) +static Tox_Event_Friend_Typing *tox_event_friend_typing_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -172,7 +169,8 @@ void tox_events_handle_friend_typing( Tox *tox, uint32_t friend_number, bool typing, void *user_data) { - Tox_Event_Friend_Typing *friend_typing = tox_event_friend_typing_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Friend_Typing *friend_typing = tox_event_friend_typing_alloc(state); if (friend_typing == nullptr) { return; diff --git a/toxcore/events/group_custom_packet.c b/toxcore/events/group_custom_packet.c index 65af4d48..6d3cbae7 100644 --- a/toxcore/events/group_custom_packet.c +++ b/toxcore/events/group_custom_packet.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -53,11 +52,11 @@ uint32_t tox_event_group_custom_packet_get_peer_id(const Tox_Event_Group_Custom_ } static bool tox_event_group_custom_packet_set_data(Tox_Event_Group_Custom_Packet *_Nonnull group_custom_packet, - const uint8_t *_Nullable data, uint32_t data_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable data, uint32_t data_length) { assert(group_custom_packet != nullptr); if (group_custom_packet->data != nullptr) { - free(group_custom_packet->data); + mem_delete(mem, group_custom_packet->data); group_custom_packet->data = nullptr; group_custom_packet->data_length = 0; } @@ -67,7 +66,7 @@ static bool tox_event_group_custom_packet_set_data(Tox_Event_Group_Custom_Packet return true; } - uint8_t *data_copy = (uint8_t *)malloc(data_length); + uint8_t *data_copy = (uint8_t *)mem_balloc(mem, data_length); if (data_copy == nullptr) { return false; @@ -97,7 +96,7 @@ static void tox_event_group_custom_packet_construct(Tox_Event_Group_Custom_Packe } static void tox_event_group_custom_packet_destruct(Tox_Event_Group_Custom_Packet *_Nonnull group_custom_packet, const Memory *_Nonnull mem) { - free(group_custom_packet->data); + mem_delete(mem, group_custom_packet->data); } bool tox_event_group_custom_packet_pack( @@ -148,7 +147,7 @@ Tox_Event_Group_Custom_Packet *tox_event_group_custom_packet_new(const Memory *m void tox_event_group_custom_packet_free(Tox_Event_Group_Custom_Packet *group_custom_packet, const Memory *mem) { if (group_custom_packet != nullptr) { - tox_event_group_custom_packet_destruct(group_custom_packet, mem); + tox_event_group_custom_packet_destruct((Tox_Event_Group_Custom_Packet * _Nonnull)group_custom_packet, mem); } mem_delete(mem, group_custom_packet); } @@ -186,11 +185,8 @@ bool tox_event_group_custom_packet_unpack( return tox_event_group_custom_packet_unpack_into(*event, bu); } -static Tox_Event_Group_Custom_Packet *tox_event_group_custom_packet_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Custom_Packet *tox_event_group_custom_packet_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -215,7 +211,8 @@ void tox_events_handle_group_custom_packet( Tox *tox, uint32_t group_number, uint32_t peer_id, const uint8_t *data, size_t data_length, void *user_data) { - Tox_Event_Group_Custom_Packet *group_custom_packet = tox_event_group_custom_packet_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Custom_Packet *group_custom_packet = tox_event_group_custom_packet_alloc(state); if (group_custom_packet == nullptr) { return; @@ -223,5 +220,5 @@ void tox_events_handle_group_custom_packet( tox_event_group_custom_packet_set_group_number(group_custom_packet, group_number); tox_event_group_custom_packet_set_peer_id(group_custom_packet, peer_id); - tox_event_group_custom_packet_set_data(group_custom_packet, data, data_length); + tox_event_group_custom_packet_set_data(group_custom_packet, state->mem, data, data_length); } diff --git a/toxcore/events/group_custom_private_packet.c b/toxcore/events/group_custom_private_packet.c index eb581008..fd415ac9 100644 --- a/toxcore/events/group_custom_private_packet.c +++ b/toxcore/events/group_custom_private_packet.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -53,11 +52,11 @@ uint32_t tox_event_group_custom_private_packet_get_peer_id(const Tox_Event_Group } static bool tox_event_group_custom_private_packet_set_data(Tox_Event_Group_Custom_Private_Packet *_Nonnull group_custom_private_packet, - const uint8_t *_Nullable data, uint32_t data_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable data, uint32_t data_length) { assert(group_custom_private_packet != nullptr); if (group_custom_private_packet->data != nullptr) { - free(group_custom_private_packet->data); + mem_delete(mem, group_custom_private_packet->data); group_custom_private_packet->data = nullptr; group_custom_private_packet->data_length = 0; } @@ -67,7 +66,7 @@ static bool tox_event_group_custom_private_packet_set_data(Tox_Event_Group_Custo return true; } - uint8_t *data_copy = (uint8_t *)malloc(data_length); + uint8_t *data_copy = (uint8_t *)mem_balloc(mem, data_length); if (data_copy == nullptr) { return false; @@ -97,7 +96,7 @@ static void tox_event_group_custom_private_packet_construct(Tox_Event_Group_Cust } static void tox_event_group_custom_private_packet_destruct(Tox_Event_Group_Custom_Private_Packet *_Nonnull group_custom_private_packet, const Memory *_Nonnull mem) { - free(group_custom_private_packet->data); + mem_delete(mem, group_custom_private_packet->data); } bool tox_event_group_custom_private_packet_pack( @@ -148,7 +147,7 @@ Tox_Event_Group_Custom_Private_Packet *tox_event_group_custom_private_packet_new void tox_event_group_custom_private_packet_free(Tox_Event_Group_Custom_Private_Packet *group_custom_private_packet, const Memory *mem) { if (group_custom_private_packet != nullptr) { - tox_event_group_custom_private_packet_destruct(group_custom_private_packet, mem); + tox_event_group_custom_private_packet_destruct((Tox_Event_Group_Custom_Private_Packet * _Nonnull)group_custom_private_packet, mem); } mem_delete(mem, group_custom_private_packet); } @@ -186,11 +185,8 @@ bool tox_event_group_custom_private_packet_unpack( return tox_event_group_custom_private_packet_unpack_into(*event, bu); } -static Tox_Event_Group_Custom_Private_Packet *tox_event_group_custom_private_packet_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Custom_Private_Packet *tox_event_group_custom_private_packet_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -215,7 +211,8 @@ void tox_events_handle_group_custom_private_packet( Tox *tox, uint32_t group_number, uint32_t peer_id, const uint8_t *data, size_t data_length, void *user_data) { - Tox_Event_Group_Custom_Private_Packet *group_custom_private_packet = tox_event_group_custom_private_packet_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Custom_Private_Packet *group_custom_private_packet = tox_event_group_custom_private_packet_alloc(state); if (group_custom_private_packet == nullptr) { return; @@ -223,5 +220,5 @@ void tox_events_handle_group_custom_private_packet( tox_event_group_custom_private_packet_set_group_number(group_custom_private_packet, group_number); tox_event_group_custom_private_packet_set_peer_id(group_custom_private_packet, peer_id); - tox_event_group_custom_private_packet_set_data(group_custom_private_packet, data, data_length); + tox_event_group_custom_private_packet_set_data(group_custom_private_packet, state->mem, data, data_length); } diff --git a/toxcore/events/group_invite.c b/toxcore/events/group_invite.c index 47959e5d..89e4d19d 100644 --- a/toxcore/events/group_invite.c +++ b/toxcore/events/group_invite.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -43,11 +42,11 @@ uint32_t tox_event_group_invite_get_friend_number(const Tox_Event_Group_Invite * } static bool tox_event_group_invite_set_invite_data(Tox_Event_Group_Invite *_Nonnull group_invite, - const uint8_t *_Nullable invite_data, uint32_t invite_data_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable invite_data, uint32_t invite_data_length) { assert(group_invite != nullptr); if (group_invite->invite_data != nullptr) { - free(group_invite->invite_data); + mem_delete(mem, group_invite->invite_data); group_invite->invite_data = nullptr; group_invite->invite_data_length = 0; } @@ -57,7 +56,7 @@ static bool tox_event_group_invite_set_invite_data(Tox_Event_Group_Invite *_Nonn return true; } - uint8_t *invite_data_copy = (uint8_t *)malloc(invite_data_length); + uint8_t *invite_data_copy = (uint8_t *)mem_balloc(mem, invite_data_length); if (invite_data_copy == nullptr) { return false; @@ -80,11 +79,11 @@ const uint8_t *tox_event_group_invite_get_invite_data(const Tox_Event_Group_Invi } static bool tox_event_group_invite_set_group_name(Tox_Event_Group_Invite *_Nonnull group_invite, - const uint8_t *_Nullable group_name, uint32_t group_name_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable group_name, uint32_t group_name_length) { assert(group_invite != nullptr); if (group_invite->group_name != nullptr) { - free(group_invite->group_name); + mem_delete(mem, group_invite->group_name); group_invite->group_name = nullptr; group_invite->group_name_length = 0; } @@ -94,7 +93,7 @@ static bool tox_event_group_invite_set_group_name(Tox_Event_Group_Invite *_Nonnu return true; } - uint8_t *group_name_copy = (uint8_t *)malloc(group_name_length); + uint8_t *group_name_copy = (uint8_t *)mem_balloc(mem, group_name_length); if (group_name_copy == nullptr) { return false; @@ -124,8 +123,8 @@ static void tox_event_group_invite_construct(Tox_Event_Group_Invite *_Nonnull gr } static void tox_event_group_invite_destruct(Tox_Event_Group_Invite *_Nonnull group_invite, const Memory *_Nonnull mem) { - free(group_invite->invite_data); - free(group_invite->group_name); + mem_delete(mem, group_invite->invite_data); + mem_delete(mem, group_invite->group_name); } bool tox_event_group_invite_pack( @@ -176,7 +175,7 @@ Tox_Event_Group_Invite *tox_event_group_invite_new(const Memory *mem) void tox_event_group_invite_free(Tox_Event_Group_Invite *group_invite, const Memory *mem) { if (group_invite != nullptr) { - tox_event_group_invite_destruct(group_invite, mem); + tox_event_group_invite_destruct((Tox_Event_Group_Invite * _Nonnull)group_invite, mem); } mem_delete(mem, group_invite); } @@ -214,11 +213,8 @@ bool tox_event_group_invite_unpack( return tox_event_group_invite_unpack_into(*event, bu); } -static Tox_Event_Group_Invite *tox_event_group_invite_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Invite *tox_event_group_invite_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -243,13 +239,14 @@ void tox_events_handle_group_invite( Tox *tox, uint32_t friend_number, const uint8_t *invite_data, size_t invite_data_length, const uint8_t *group_name, size_t group_name_length, void *user_data) { - Tox_Event_Group_Invite *group_invite = tox_event_group_invite_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Invite *group_invite = tox_event_group_invite_alloc(state); if (group_invite == nullptr) { return; } tox_event_group_invite_set_friend_number(group_invite, friend_number); - tox_event_group_invite_set_invite_data(group_invite, invite_data, invite_data_length); - tox_event_group_invite_set_group_name(group_invite, group_name, group_name_length); + tox_event_group_invite_set_invite_data(group_invite, state->mem, invite_data, invite_data_length); + tox_event_group_invite_set_group_name(group_invite, state->mem, group_name, group_name_length); } diff --git a/toxcore/events/group_join_fail.c b/toxcore/events/group_join_fail.c index 8931f7b7..37294fa3 100644 --- a/toxcore/events/group_join_fail.c +++ b/toxcore/events/group_join_fail.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -107,7 +107,7 @@ Tox_Event_Group_Join_Fail *tox_event_group_join_fail_new(const Memory *mem) void tox_event_group_join_fail_free(Tox_Event_Group_Join_Fail *group_join_fail, const Memory *mem) { if (group_join_fail != nullptr) { - tox_event_group_join_fail_destruct(group_join_fail, mem); + tox_event_group_join_fail_destruct((Tox_Event_Group_Join_Fail * _Nonnull)group_join_fail, mem); } mem_delete(mem, group_join_fail); } @@ -145,11 +145,8 @@ bool tox_event_group_join_fail_unpack( return tox_event_group_join_fail_unpack_into(*event, bu); } -static Tox_Event_Group_Join_Fail *tox_event_group_join_fail_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Join_Fail *tox_event_group_join_fail_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -174,7 +171,8 @@ void tox_events_handle_group_join_fail( Tox *tox, uint32_t group_number, Tox_Group_Join_Fail fail_type, void *user_data) { - Tox_Event_Group_Join_Fail *group_join_fail = tox_event_group_join_fail_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Join_Fail *group_join_fail = tox_event_group_join_fail_alloc(state); if (group_join_fail == nullptr) { return; diff --git a/toxcore/events/group_message.c b/toxcore/events/group_message.c index b8da841f..d3d01fa7 100644 --- a/toxcore/events/group_message.c +++ b/toxcore/events/group_message.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -68,11 +67,11 @@ Tox_Message_Type tox_event_group_message_get_message_type(const Tox_Event_Group_ } static bool tox_event_group_message_set_message(Tox_Event_Group_Message *_Nonnull group_message, - const uint8_t *_Nullable message, uint32_t message_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable message, uint32_t message_length) { assert(group_message != nullptr); if (group_message->message != nullptr) { - free(group_message->message); + mem_delete(mem, group_message->message); group_message->message = nullptr; group_message->message_length = 0; } @@ -82,7 +81,7 @@ static bool tox_event_group_message_set_message(Tox_Event_Group_Message *_Nonnul return true; } - uint8_t *message_copy = (uint8_t *)malloc(message_length); + uint8_t *message_copy = (uint8_t *)mem_balloc(mem, message_length); if (message_copy == nullptr) { return false; @@ -123,7 +122,7 @@ static void tox_event_group_message_construct(Tox_Event_Group_Message *_Nonnull } static void tox_event_group_message_destruct(Tox_Event_Group_Message *_Nonnull group_message, const Memory *_Nonnull mem) { - free(group_message->message); + mem_delete(mem, group_message->message); } bool tox_event_group_message_pack( @@ -178,7 +177,7 @@ Tox_Event_Group_Message *tox_event_group_message_new(const Memory *mem) void tox_event_group_message_free(Tox_Event_Group_Message *group_message, const Memory *mem) { if (group_message != nullptr) { - tox_event_group_message_destruct(group_message, mem); + tox_event_group_message_destruct((Tox_Event_Group_Message * _Nonnull)group_message, mem); } mem_delete(mem, group_message); } @@ -216,11 +215,8 @@ bool tox_event_group_message_unpack( return tox_event_group_message_unpack_into(*event, bu); } -static Tox_Event_Group_Message *tox_event_group_message_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Message *tox_event_group_message_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -245,7 +241,8 @@ void tox_events_handle_group_message( Tox *tox, uint32_t group_number, uint32_t peer_id, Tox_Message_Type message_type, const uint8_t *message, size_t message_length, uint32_t message_id, void *user_data) { - Tox_Event_Group_Message *group_message = tox_event_group_message_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Message *group_message = tox_event_group_message_alloc(state); if (group_message == nullptr) { return; @@ -254,6 +251,6 @@ void tox_events_handle_group_message( tox_event_group_message_set_group_number(group_message, group_number); tox_event_group_message_set_peer_id(group_message, peer_id); tox_event_group_message_set_message_type(group_message, message_type); - tox_event_group_message_set_message(group_message, message, message_length); + tox_event_group_message_set_message(group_message, state->mem, message, message_length); tox_event_group_message_set_message_id(group_message, message_id); } diff --git a/toxcore/events/group_moderation.c b/toxcore/events/group_moderation.c index d6224796..df133e13 100644 --- a/toxcore/events/group_moderation.c +++ b/toxcore/events/group_moderation.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -135,7 +135,7 @@ Tox_Event_Group_Moderation *tox_event_group_moderation_new(const Memory *mem) void tox_event_group_moderation_free(Tox_Event_Group_Moderation *group_moderation, const Memory *mem) { if (group_moderation != nullptr) { - tox_event_group_moderation_destruct(group_moderation, mem); + tox_event_group_moderation_destruct((Tox_Event_Group_Moderation * _Nonnull)group_moderation, mem); } mem_delete(mem, group_moderation); } @@ -173,11 +173,8 @@ bool tox_event_group_moderation_unpack( return tox_event_group_moderation_unpack_into(*event, bu); } -static Tox_Event_Group_Moderation *tox_event_group_moderation_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Moderation *tox_event_group_moderation_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -202,7 +199,8 @@ void tox_events_handle_group_moderation( Tox *tox, uint32_t group_number, uint32_t source_peer_id, uint32_t target_peer_id, Tox_Group_Mod_Event mod_type, void *user_data) { - Tox_Event_Group_Moderation *group_moderation = tox_event_group_moderation_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Moderation *group_moderation = tox_event_group_moderation_alloc(state); if (group_moderation == nullptr) { return; diff --git a/toxcore/events/group_password.c b/toxcore/events/group_password.c index 5c692339..b1eac6ac 100644 --- a/toxcore/events/group_password.c +++ b/toxcore/events/group_password.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -41,11 +40,11 @@ uint32_t tox_event_group_password_get_group_number(const Tox_Event_Group_Passwor } static bool tox_event_group_password_set_password(Tox_Event_Group_Password *_Nonnull group_password, - const uint8_t *_Nullable password, uint32_t password_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable password, uint32_t password_length) { assert(group_password != nullptr); if (group_password->password != nullptr) { - free(group_password->password); + mem_delete(mem, group_password->password); group_password->password = nullptr; group_password->password_length = 0; } @@ -55,7 +54,7 @@ static bool tox_event_group_password_set_password(Tox_Event_Group_Password *_Non return true; } - uint8_t *password_copy = (uint8_t *)malloc(password_length); + uint8_t *password_copy = (uint8_t *)mem_balloc(mem, password_length); if (password_copy == nullptr) { return false; @@ -85,7 +84,7 @@ static void tox_event_group_password_construct(Tox_Event_Group_Password *_Nonnul } static void tox_event_group_password_destruct(Tox_Event_Group_Password *_Nonnull group_password, const Memory *_Nonnull mem) { - free(group_password->password); + mem_delete(mem, group_password->password); } bool tox_event_group_password_pack( @@ -134,7 +133,7 @@ Tox_Event_Group_Password *tox_event_group_password_new(const Memory *mem) void tox_event_group_password_free(Tox_Event_Group_Password *group_password, const Memory *mem) { if (group_password != nullptr) { - tox_event_group_password_destruct(group_password, mem); + tox_event_group_password_destruct((Tox_Event_Group_Password * _Nonnull)group_password, mem); } mem_delete(mem, group_password); } @@ -172,11 +171,8 @@ bool tox_event_group_password_unpack( return tox_event_group_password_unpack_into(*event, bu); } -static Tox_Event_Group_Password *tox_event_group_password_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Password *tox_event_group_password_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -201,12 +197,13 @@ void tox_events_handle_group_password( Tox *tox, uint32_t group_number, const uint8_t *password, size_t password_length, void *user_data) { - Tox_Event_Group_Password *group_password = tox_event_group_password_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Password *group_password = tox_event_group_password_alloc(state); if (group_password == nullptr) { return; } tox_event_group_password_set_group_number(group_password, group_number); - tox_event_group_password_set_password(group_password, password, password_length); + tox_event_group_password_set_password(group_password, state->mem, password, password_length); } diff --git a/toxcore/events/group_peer_exit.c b/toxcore/events/group_peer_exit.c index 76442666..457888e6 100644 --- a/toxcore/events/group_peer_exit.c +++ b/toxcore/events/group_peer_exit.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -69,11 +68,11 @@ Tox_Group_Exit_Type tox_event_group_peer_exit_get_exit_type(const Tox_Event_Grou } static bool tox_event_group_peer_exit_set_name(Tox_Event_Group_Peer_Exit *_Nonnull group_peer_exit, - const uint8_t *_Nullable name, uint32_t name_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable name, uint32_t name_length) { assert(group_peer_exit != nullptr); if (group_peer_exit->name != nullptr) { - free(group_peer_exit->name); + mem_delete(mem, group_peer_exit->name); group_peer_exit->name = nullptr; group_peer_exit->name_length = 0; } @@ -83,7 +82,7 @@ static bool tox_event_group_peer_exit_set_name(Tox_Event_Group_Peer_Exit *_Nonnu return true; } - uint8_t *name_copy = (uint8_t *)malloc(name_length); + uint8_t *name_copy = (uint8_t *)mem_balloc(mem, name_length); if (name_copy == nullptr) { return false; @@ -106,11 +105,11 @@ const uint8_t *tox_event_group_peer_exit_get_name(const Tox_Event_Group_Peer_Exi } static bool tox_event_group_peer_exit_set_part_message(Tox_Event_Group_Peer_Exit *_Nonnull group_peer_exit, - const uint8_t *_Nullable part_message, uint32_t part_message_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable part_message, uint32_t part_message_length) { assert(group_peer_exit != nullptr); if (group_peer_exit->part_message != nullptr) { - free(group_peer_exit->part_message); + mem_delete(mem, group_peer_exit->part_message); group_peer_exit->part_message = nullptr; group_peer_exit->part_message_length = 0; } @@ -120,7 +119,7 @@ static bool tox_event_group_peer_exit_set_part_message(Tox_Event_Group_Peer_Exit return true; } - uint8_t *part_message_copy = (uint8_t *)malloc(part_message_length); + uint8_t *part_message_copy = (uint8_t *)mem_balloc(mem, part_message_length); if (part_message_copy == nullptr) { return false; @@ -150,8 +149,8 @@ static void tox_event_group_peer_exit_construct(Tox_Event_Group_Peer_Exit *_Nonn } static void tox_event_group_peer_exit_destruct(Tox_Event_Group_Peer_Exit *_Nonnull group_peer_exit, const Memory *_Nonnull mem) { - free(group_peer_exit->name); - free(group_peer_exit->part_message); + mem_delete(mem, group_peer_exit->name); + mem_delete(mem, group_peer_exit->part_message); } bool tox_event_group_peer_exit_pack( @@ -206,7 +205,7 @@ Tox_Event_Group_Peer_Exit *tox_event_group_peer_exit_new(const Memory *mem) void tox_event_group_peer_exit_free(Tox_Event_Group_Peer_Exit *group_peer_exit, const Memory *mem) { if (group_peer_exit != nullptr) { - tox_event_group_peer_exit_destruct(group_peer_exit, mem); + tox_event_group_peer_exit_destruct((Tox_Event_Group_Peer_Exit * _Nonnull)group_peer_exit, mem); } mem_delete(mem, group_peer_exit); } @@ -244,11 +243,8 @@ bool tox_event_group_peer_exit_unpack( return tox_event_group_peer_exit_unpack_into(*event, bu); } -static Tox_Event_Group_Peer_Exit *tox_event_group_peer_exit_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Peer_Exit *tox_event_group_peer_exit_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -273,7 +269,8 @@ void tox_events_handle_group_peer_exit( Tox *tox, uint32_t group_number, uint32_t peer_id, Tox_Group_Exit_Type exit_type, const uint8_t *name, size_t name_length, const uint8_t *part_message, size_t part_message_length, void *user_data) { - Tox_Event_Group_Peer_Exit *group_peer_exit = tox_event_group_peer_exit_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Peer_Exit *group_peer_exit = tox_event_group_peer_exit_alloc(state); if (group_peer_exit == nullptr) { return; @@ -282,6 +279,6 @@ void tox_events_handle_group_peer_exit( tox_event_group_peer_exit_set_group_number(group_peer_exit, group_number); tox_event_group_peer_exit_set_peer_id(group_peer_exit, peer_id); tox_event_group_peer_exit_set_exit_type(group_peer_exit, exit_type); - tox_event_group_peer_exit_set_name(group_peer_exit, name, name_length); - tox_event_group_peer_exit_set_part_message(group_peer_exit, part_message, part_message_length); + tox_event_group_peer_exit_set_name(group_peer_exit, state->mem, name, name_length); + tox_event_group_peer_exit_set_part_message(group_peer_exit, state->mem, part_message, part_message_length); } diff --git a/toxcore/events/group_peer_join.c b/toxcore/events/group_peer_join.c index 4fd13f9a..a3a672a0 100644 --- a/toxcore/events/group_peer_join.c +++ b/toxcore/events/group_peer_join.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -105,7 +105,7 @@ Tox_Event_Group_Peer_Join *tox_event_group_peer_join_new(const Memory *mem) void tox_event_group_peer_join_free(Tox_Event_Group_Peer_Join *group_peer_join, const Memory *mem) { if (group_peer_join != nullptr) { - tox_event_group_peer_join_destruct(group_peer_join, mem); + tox_event_group_peer_join_destruct((Tox_Event_Group_Peer_Join * _Nonnull)group_peer_join, mem); } mem_delete(mem, group_peer_join); } @@ -143,11 +143,8 @@ bool tox_event_group_peer_join_unpack( return tox_event_group_peer_join_unpack_into(*event, bu); } -static Tox_Event_Group_Peer_Join *tox_event_group_peer_join_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Peer_Join *tox_event_group_peer_join_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -172,7 +169,8 @@ void tox_events_handle_group_peer_join( Tox *tox, uint32_t group_number, uint32_t peer_id, void *user_data) { - Tox_Event_Group_Peer_Join *group_peer_join = tox_event_group_peer_join_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Peer_Join *group_peer_join = tox_event_group_peer_join_alloc(state); if (group_peer_join == nullptr) { return; diff --git a/toxcore/events/group_peer_limit.c b/toxcore/events/group_peer_limit.c index e652f7a7..d53c2c5a 100644 --- a/toxcore/events/group_peer_limit.c +++ b/toxcore/events/group_peer_limit.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -105,7 +105,7 @@ Tox_Event_Group_Peer_Limit *tox_event_group_peer_limit_new(const Memory *mem) void tox_event_group_peer_limit_free(Tox_Event_Group_Peer_Limit *group_peer_limit, const Memory *mem) { if (group_peer_limit != nullptr) { - tox_event_group_peer_limit_destruct(group_peer_limit, mem); + tox_event_group_peer_limit_destruct((Tox_Event_Group_Peer_Limit * _Nonnull)group_peer_limit, mem); } mem_delete(mem, group_peer_limit); } @@ -143,11 +143,8 @@ bool tox_event_group_peer_limit_unpack( return tox_event_group_peer_limit_unpack_into(*event, bu); } -static Tox_Event_Group_Peer_Limit *tox_event_group_peer_limit_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Peer_Limit *tox_event_group_peer_limit_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -172,7 +169,8 @@ void tox_events_handle_group_peer_limit( Tox *tox, uint32_t group_number, uint32_t peer_limit, void *user_data) { - Tox_Event_Group_Peer_Limit *group_peer_limit = tox_event_group_peer_limit_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Peer_Limit *group_peer_limit = tox_event_group_peer_limit_alloc(state); if (group_peer_limit == nullptr) { return; diff --git a/toxcore/events/group_peer_name.c b/toxcore/events/group_peer_name.c index 6009fd75..2870aa85 100644 --- a/toxcore/events/group_peer_name.c +++ b/toxcore/events/group_peer_name.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -53,11 +52,11 @@ uint32_t tox_event_group_peer_name_get_peer_id(const Tox_Event_Group_Peer_Name * } static bool tox_event_group_peer_name_set_name(Tox_Event_Group_Peer_Name *_Nonnull group_peer_name, - const uint8_t *_Nullable name, uint32_t name_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable name, uint32_t name_length) { assert(group_peer_name != nullptr); if (group_peer_name->name != nullptr) { - free(group_peer_name->name); + mem_delete(mem, group_peer_name->name); group_peer_name->name = nullptr; group_peer_name->name_length = 0; } @@ -67,7 +66,7 @@ static bool tox_event_group_peer_name_set_name(Tox_Event_Group_Peer_Name *_Nonnu return true; } - uint8_t *name_copy = (uint8_t *)malloc(name_length); + uint8_t *name_copy = (uint8_t *)mem_balloc(mem, name_length); if (name_copy == nullptr) { return false; @@ -97,7 +96,7 @@ static void tox_event_group_peer_name_construct(Tox_Event_Group_Peer_Name *_Nonn } static void tox_event_group_peer_name_destruct(Tox_Event_Group_Peer_Name *_Nonnull group_peer_name, const Memory *_Nonnull mem) { - free(group_peer_name->name); + mem_delete(mem, group_peer_name->name); } bool tox_event_group_peer_name_pack( @@ -148,7 +147,7 @@ Tox_Event_Group_Peer_Name *tox_event_group_peer_name_new(const Memory *mem) void tox_event_group_peer_name_free(Tox_Event_Group_Peer_Name *group_peer_name, const Memory *mem) { if (group_peer_name != nullptr) { - tox_event_group_peer_name_destruct(group_peer_name, mem); + tox_event_group_peer_name_destruct((Tox_Event_Group_Peer_Name * _Nonnull)group_peer_name, mem); } mem_delete(mem, group_peer_name); } @@ -186,11 +185,8 @@ bool tox_event_group_peer_name_unpack( return tox_event_group_peer_name_unpack_into(*event, bu); } -static Tox_Event_Group_Peer_Name *tox_event_group_peer_name_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Peer_Name *tox_event_group_peer_name_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -215,7 +211,8 @@ void tox_events_handle_group_peer_name( Tox *tox, uint32_t group_number, uint32_t peer_id, const uint8_t *name, size_t name_length, void *user_data) { - Tox_Event_Group_Peer_Name *group_peer_name = tox_event_group_peer_name_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Peer_Name *group_peer_name = tox_event_group_peer_name_alloc(state); if (group_peer_name == nullptr) { return; @@ -223,5 +220,5 @@ void tox_events_handle_group_peer_name( tox_event_group_peer_name_set_group_number(group_peer_name, group_number); tox_event_group_peer_name_set_peer_id(group_peer_name, peer_id); - tox_event_group_peer_name_set_name(group_peer_name, name, name_length); + tox_event_group_peer_name_set_name(group_peer_name, state->mem, name, name_length); } diff --git a/toxcore/events/group_peer_status.c b/toxcore/events/group_peer_status.c index f193e2b7..16e754cc 100644 --- a/toxcore/events/group_peer_status.c +++ b/toxcore/events/group_peer_status.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -121,7 +121,7 @@ Tox_Event_Group_Peer_Status *tox_event_group_peer_status_new(const Memory *mem) void tox_event_group_peer_status_free(Tox_Event_Group_Peer_Status *group_peer_status, const Memory *mem) { if (group_peer_status != nullptr) { - tox_event_group_peer_status_destruct(group_peer_status, mem); + tox_event_group_peer_status_destruct((Tox_Event_Group_Peer_Status * _Nonnull)group_peer_status, mem); } mem_delete(mem, group_peer_status); } @@ -159,11 +159,8 @@ bool tox_event_group_peer_status_unpack( return tox_event_group_peer_status_unpack_into(*event, bu); } -static Tox_Event_Group_Peer_Status *tox_event_group_peer_status_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Peer_Status *tox_event_group_peer_status_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -188,7 +185,8 @@ void tox_events_handle_group_peer_status( Tox *tox, uint32_t group_number, uint32_t peer_id, Tox_User_Status status, void *user_data) { - Tox_Event_Group_Peer_Status *group_peer_status = tox_event_group_peer_status_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Peer_Status *group_peer_status = tox_event_group_peer_status_alloc(state); if (group_peer_status == nullptr) { return; diff --git a/toxcore/events/group_privacy_state.c b/toxcore/events/group_privacy_state.c index 9d50e505..27573add 100644 --- a/toxcore/events/group_privacy_state.c +++ b/toxcore/events/group_privacy_state.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -107,7 +107,7 @@ Tox_Event_Group_Privacy_State *tox_event_group_privacy_state_new(const Memory *m void tox_event_group_privacy_state_free(Tox_Event_Group_Privacy_State *group_privacy_state, const Memory *mem) { if (group_privacy_state != nullptr) { - tox_event_group_privacy_state_destruct(group_privacy_state, mem); + tox_event_group_privacy_state_destruct((Tox_Event_Group_Privacy_State * _Nonnull)group_privacy_state, mem); } mem_delete(mem, group_privacy_state); } @@ -145,11 +145,8 @@ bool tox_event_group_privacy_state_unpack( return tox_event_group_privacy_state_unpack_into(*event, bu); } -static Tox_Event_Group_Privacy_State *tox_event_group_privacy_state_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Privacy_State *tox_event_group_privacy_state_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -174,7 +171,8 @@ void tox_events_handle_group_privacy_state( Tox *tox, uint32_t group_number, Tox_Group_Privacy_State privacy_state, void *user_data) { - Tox_Event_Group_Privacy_State *group_privacy_state = tox_event_group_privacy_state_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Privacy_State *group_privacy_state = tox_event_group_privacy_state_alloc(state); if (group_privacy_state == nullptr) { return; diff --git a/toxcore/events/group_private_message.c b/toxcore/events/group_private_message.c index d36e4c01..4386b3ac 100644 --- a/toxcore/events/group_private_message.c +++ b/toxcore/events/group_private_message.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -68,11 +67,11 @@ Tox_Message_Type tox_event_group_private_message_get_message_type(const Tox_Even } static bool tox_event_group_private_message_set_message(Tox_Event_Group_Private_Message *_Nonnull group_private_message, - const uint8_t *_Nullable message, uint32_t message_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable message, uint32_t message_length) { assert(group_private_message != nullptr); if (group_private_message->message != nullptr) { - free(group_private_message->message); + mem_delete(mem, group_private_message->message); group_private_message->message = nullptr; group_private_message->message_length = 0; } @@ -82,7 +81,7 @@ static bool tox_event_group_private_message_set_message(Tox_Event_Group_Private_ return true; } - uint8_t *message_copy = (uint8_t *)malloc(message_length); + uint8_t *message_copy = (uint8_t *)mem_balloc(mem, message_length); if (message_copy == nullptr) { return false; @@ -123,7 +122,7 @@ static void tox_event_group_private_message_construct(Tox_Event_Group_Private_Me } static void tox_event_group_private_message_destruct(Tox_Event_Group_Private_Message *_Nonnull group_private_message, const Memory *_Nonnull mem) { - free(group_private_message->message); + mem_delete(mem, group_private_message->message); } bool tox_event_group_private_message_pack( @@ -178,7 +177,7 @@ Tox_Event_Group_Private_Message *tox_event_group_private_message_new(const Memor void tox_event_group_private_message_free(Tox_Event_Group_Private_Message *group_private_message, const Memory *mem) { if (group_private_message != nullptr) { - tox_event_group_private_message_destruct(group_private_message, mem); + tox_event_group_private_message_destruct((Tox_Event_Group_Private_Message * _Nonnull)group_private_message, mem); } mem_delete(mem, group_private_message); } @@ -216,11 +215,8 @@ bool tox_event_group_private_message_unpack( return tox_event_group_private_message_unpack_into(*event, bu); } -static Tox_Event_Group_Private_Message *tox_event_group_private_message_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Private_Message *tox_event_group_private_message_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -245,7 +241,8 @@ void tox_events_handle_group_private_message( Tox *tox, uint32_t group_number, uint32_t peer_id, Tox_Message_Type message_type, const uint8_t *message, size_t message_length, uint32_t message_id, void *user_data) { - Tox_Event_Group_Private_Message *group_private_message = tox_event_group_private_message_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Private_Message *group_private_message = tox_event_group_private_message_alloc(state); if (group_private_message == nullptr) { return; @@ -254,6 +251,6 @@ void tox_events_handle_group_private_message( tox_event_group_private_message_set_group_number(group_private_message, group_number); tox_event_group_private_message_set_peer_id(group_private_message, peer_id); tox_event_group_private_message_set_message_type(group_private_message, message_type); - tox_event_group_private_message_set_message(group_private_message, message, message_length); + tox_event_group_private_message_set_message(group_private_message, state->mem, message, message_length); tox_event_group_private_message_set_message_id(group_private_message, message_id); } diff --git a/toxcore/events/group_self_join.c b/toxcore/events/group_self_join.c index 1fc7b96d..06e1d417 100644 --- a/toxcore/events/group_self_join.c +++ b/toxcore/events/group_self_join.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -86,7 +86,7 @@ Tox_Event_Group_Self_Join *tox_event_group_self_join_new(const Memory *mem) void tox_event_group_self_join_free(Tox_Event_Group_Self_Join *group_self_join, const Memory *mem) { if (group_self_join != nullptr) { - tox_event_group_self_join_destruct(group_self_join, mem); + tox_event_group_self_join_destruct((Tox_Event_Group_Self_Join * _Nonnull)group_self_join, mem); } mem_delete(mem, group_self_join); } @@ -124,11 +124,8 @@ bool tox_event_group_self_join_unpack( return tox_event_group_self_join_unpack_into(*event, bu); } -static Tox_Event_Group_Self_Join *tox_event_group_self_join_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Self_Join *tox_event_group_self_join_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -153,7 +150,8 @@ void tox_events_handle_group_self_join( Tox *tox, uint32_t group_number, void *user_data) { - Tox_Event_Group_Self_Join *group_self_join = tox_event_group_self_join_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Self_Join *group_self_join = tox_event_group_self_join_alloc(state); if (group_self_join == nullptr) { return; diff --git a/toxcore/events/group_topic.c b/toxcore/events/group_topic.c index 396a423e..156b89c3 100644 --- a/toxcore/events/group_topic.c +++ b/toxcore/events/group_topic.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" #include -#include #include #include "../attributes.h" @@ -53,11 +52,11 @@ uint32_t tox_event_group_topic_get_peer_id(const Tox_Event_Group_Topic *group_to } static bool tox_event_group_topic_set_topic(Tox_Event_Group_Topic *_Nonnull group_topic, - const uint8_t *_Nullable topic, uint32_t topic_length) + const Memory *_Nonnull mem, const uint8_t *_Nullable topic, uint32_t topic_length) { assert(group_topic != nullptr); if (group_topic->topic != nullptr) { - free(group_topic->topic); + mem_delete(mem, group_topic->topic); group_topic->topic = nullptr; group_topic->topic_length = 0; } @@ -67,7 +66,7 @@ static bool tox_event_group_topic_set_topic(Tox_Event_Group_Topic *_Nonnull grou return true; } - uint8_t *topic_copy = (uint8_t *)malloc(topic_length); + uint8_t *topic_copy = (uint8_t *)mem_balloc(mem, topic_length); if (topic_copy == nullptr) { return false; @@ -97,7 +96,7 @@ static void tox_event_group_topic_construct(Tox_Event_Group_Topic *_Nonnull grou } static void tox_event_group_topic_destruct(Tox_Event_Group_Topic *_Nonnull group_topic, const Memory *_Nonnull mem) { - free(group_topic->topic); + mem_delete(mem, group_topic->topic); } bool tox_event_group_topic_pack( @@ -148,7 +147,7 @@ Tox_Event_Group_Topic *tox_event_group_topic_new(const Memory *mem) void tox_event_group_topic_free(Tox_Event_Group_Topic *group_topic, const Memory *mem) { if (group_topic != nullptr) { - tox_event_group_topic_destruct(group_topic, mem); + tox_event_group_topic_destruct((Tox_Event_Group_Topic * _Nonnull)group_topic, mem); } mem_delete(mem, group_topic); } @@ -186,11 +185,8 @@ bool tox_event_group_topic_unpack( return tox_event_group_topic_unpack_into(*event, bu); } -static Tox_Event_Group_Topic *tox_event_group_topic_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Topic *tox_event_group_topic_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -215,7 +211,8 @@ void tox_events_handle_group_topic( Tox *tox, uint32_t group_number, uint32_t peer_id, const uint8_t *topic, size_t topic_length, void *user_data) { - Tox_Event_Group_Topic *group_topic = tox_event_group_topic_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Topic *group_topic = tox_event_group_topic_alloc(state); if (group_topic == nullptr) { return; @@ -223,5 +220,5 @@ void tox_events_handle_group_topic( tox_event_group_topic_set_group_number(group_topic, group_number); tox_event_group_topic_set_peer_id(group_topic, peer_id); - tox_event_group_topic_set_topic(group_topic, topic, topic_length); + tox_event_group_topic_set_topic(group_topic, state->mem, topic, topic_length); } diff --git a/toxcore/events/group_topic_lock.c b/toxcore/events/group_topic_lock.c index 4c000317..cc1cf8ef 100644 --- a/toxcore/events/group_topic_lock.c +++ b/toxcore/events/group_topic_lock.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -107,7 +107,7 @@ Tox_Event_Group_Topic_Lock *tox_event_group_topic_lock_new(const Memory *mem) void tox_event_group_topic_lock_free(Tox_Event_Group_Topic_Lock *group_topic_lock, const Memory *mem) { if (group_topic_lock != nullptr) { - tox_event_group_topic_lock_destruct(group_topic_lock, mem); + tox_event_group_topic_lock_destruct((Tox_Event_Group_Topic_Lock * _Nonnull)group_topic_lock, mem); } mem_delete(mem, group_topic_lock); } @@ -145,11 +145,8 @@ bool tox_event_group_topic_lock_unpack( return tox_event_group_topic_lock_unpack_into(*event, bu); } -static Tox_Event_Group_Topic_Lock *tox_event_group_topic_lock_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Topic_Lock *tox_event_group_topic_lock_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -174,7 +171,8 @@ void tox_events_handle_group_topic_lock( Tox *tox, uint32_t group_number, Tox_Group_Topic_Lock topic_lock, void *user_data) { - Tox_Event_Group_Topic_Lock *group_topic_lock = tox_event_group_topic_lock_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Topic_Lock *group_topic_lock = tox_event_group_topic_lock_alloc(state); if (group_topic_lock == nullptr) { return; diff --git a/toxcore/events/group_voice_state.c b/toxcore/events/group_voice_state.c index 98602549..1d0e51d5 100644 --- a/toxcore/events/group_voice_state.c +++ b/toxcore/events/group_voice_state.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -107,7 +107,7 @@ Tox_Event_Group_Voice_State *tox_event_group_voice_state_new(const Memory *mem) void tox_event_group_voice_state_free(Tox_Event_Group_Voice_State *group_voice_state, const Memory *mem) { if (group_voice_state != nullptr) { - tox_event_group_voice_state_destruct(group_voice_state, mem); + tox_event_group_voice_state_destruct((Tox_Event_Group_Voice_State * _Nonnull)group_voice_state, mem); } mem_delete(mem, group_voice_state); } @@ -145,11 +145,8 @@ bool tox_event_group_voice_state_unpack( return tox_event_group_voice_state_unpack_into(*event, bu); } -static Tox_Event_Group_Voice_State *tox_event_group_voice_state_alloc(void *_Nonnull user_data) +static Tox_Event_Group_Voice_State *tox_event_group_voice_state_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -174,7 +171,8 @@ void tox_events_handle_group_voice_state( Tox *tox, uint32_t group_number, Tox_Group_Voice_State voice_state, void *user_data) { - Tox_Event_Group_Voice_State *group_voice_state = tox_event_group_voice_state_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Group_Voice_State *group_voice_state = tox_event_group_voice_state_alloc(state); if (group_voice_state == nullptr) { return; diff --git a/toxcore/events/self_connection_status.c b/toxcore/events/self_connection_status.c index 223aca58..692c5938 100644 --- a/toxcore/events/self_connection_status.c +++ b/toxcore/events/self_connection_status.c @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-3.0-or-later - * Copyright © 2023-2025 The TokTok team. + * Copyright © 2023-2026 The TokTok team. */ #include "events_alloc.h" @@ -88,7 +88,7 @@ Tox_Event_Self_Connection_Status *tox_event_self_connection_status_new(const Mem void tox_event_self_connection_status_free(Tox_Event_Self_Connection_Status *self_connection_status, const Memory *mem) { if (self_connection_status != nullptr) { - tox_event_self_connection_status_destruct(self_connection_status, mem); + tox_event_self_connection_status_destruct((Tox_Event_Self_Connection_Status * _Nonnull)self_connection_status, mem); } mem_delete(mem, self_connection_status); } @@ -126,11 +126,8 @@ bool tox_event_self_connection_status_unpack( return tox_event_self_connection_status_unpack_into(*event, bu); } -static Tox_Event_Self_Connection_Status *tox_event_self_connection_status_alloc(void *_Nonnull user_data) +static Tox_Event_Self_Connection_Status *tox_event_self_connection_status_alloc(Tox_Events_State *_Nonnull state) { - Tox_Events_State *state = tox_events_alloc(user_data); - assert(state != nullptr); - if (state->events == nullptr) { return nullptr; } @@ -155,7 +152,8 @@ void tox_events_handle_self_connection_status( Tox *tox, Tox_Connection connection_status, void *user_data) { - Tox_Event_Self_Connection_Status *self_connection_status = tox_event_self_connection_status_alloc(user_data); + Tox_Events_State *state = tox_events_alloc(user_data); + Tox_Event_Self_Connection_Status *self_connection_status = tox_event_self_connection_status_alloc(state); if (self_connection_status == nullptr) { return; diff --git a/toxcore/forwarding.c b/toxcore/forwarding.c index 5b29c8e7..599bc85a 100644 --- a/toxcore/forwarding.c +++ b/toxcore/forwarding.c @@ -18,12 +18,12 @@ #include "timed_auth.h" struct Forwarding { - const Logger *log; - const Memory *mem; - const Random *rng; - DHT *dht; - const Mono_Time *mono_time; - Networking_Core *net; + const Logger *_Nonnull log; + const Memory *_Nonnull mem; + const Random *_Nonnull rng; + DHT *_Nonnull dht; + const Mono_Time *_Nonnull mono_time; + Networking_Core *_Nonnull net; uint8_t hmac_key[CRYPTO_HMAC_KEY_SIZE]; diff --git a/toxcore/forwarding_fuzz_test.cc b/toxcore/forwarding_fuzz_test.cc index 829d6947..e37b8aad 100644 --- a/toxcore/forwarding_fuzz_test.cc +++ b/toxcore/forwarding_fuzz_test.cc @@ -5,11 +5,21 @@ #include #include -#include "../testing/fuzzing/fuzz_support.hh" -#include "../testing/fuzzing/fuzz_tox.hh" +#include "../testing/support/public/fuzz_data.hh" +#include "../testing/support/public/fuzz_helpers.hh" +#include "../testing/support/public/simulated_environment.hh" namespace { +using tox::test::configure_fuzz_memory_source; +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + +constexpr uint16_t SIZE_IP_PORT = SIZE_IP6 + sizeof(uint16_t); + +template +using Ptr = std::unique_ptr; + std::optional> prepare(Fuzz_Data &input) { CONSUME_OR_RETURN_VAL(const uint8_t *ipp_packed, input, SIZE_IP_PORT, std::nullopt); @@ -43,16 +53,18 @@ void TestSendForwardRequest(Fuzz_Data &input) } const auto [ipp, forwarder, data, data_size] = prep.value(); - // rest of the fuzz data is input for malloc and network - Fuzz_System sys(input); + SimulatedEnvironment env; + auto node = env.create_node(ipp.port); + configure_fuzz_memory_source(env.fake_memory(), input); - const Ptr logger(logger_new(sys.mem.get()), logger_kill); + const Ptr logger(logger_new(&node->c_memory), logger_kill); if (logger == nullptr) { return; } - const Ptr net(new_networking_ex(logger.get(), sys.mem.get(), sys.ns.get(), - &ipp.ip, ipp.port, ipp.port + 100, nullptr), + const Ptr net( + new_networking_ex(logger.get(), &node->c_memory, &node->c_network, &ipp.ip, ipp.port, + ipp.port + 100, nullptr), kill_networking); if (net == nullptr) { return; @@ -72,16 +84,18 @@ void TestForwardReply(Fuzz_Data &input) } const auto [ipp, forwarder, data, data_size] = prep.value(); - // rest of the fuzz data is input for malloc and network - Fuzz_System sys(input); + SimulatedEnvironment env; + auto node = env.create_node(ipp.port); + configure_fuzz_memory_source(env.fake_memory(), input); - const Ptr logger(logger_new(sys.mem.get()), logger_kill); + const Ptr logger(logger_new(&node->c_memory), logger_kill); if (logger == nullptr) { return; } - const Ptr net(new_networking_ex(logger.get(), sys.mem.get(), sys.ns.get(), - &ipp.ip, ipp.port, ipp.port + 100, nullptr), + const Ptr net( + new_networking_ex(logger.get(), &node->c_memory, &node->c_network, &ipp.ip, ipp.port, + ipp.port + 100, nullptr), kill_networking); if (net == nullptr) { return; @@ -95,6 +109,6 @@ void TestForwardReply(Fuzz_Data &input) extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - fuzz_select_target(data, size); + tox::test::fuzz_select_target(data, size); return 0; } diff --git a/toxcore/friend_connection.c b/toxcore/friend_connection.c index bab78f87..b65f2496 100644 --- a/toxcore/friend_connection.c +++ b/toxcore/friend_connection.c @@ -29,11 +29,11 @@ #define PORTS_PER_DISCOVERY 10 typedef struct Friend_Conn_Callbacks { - fc_status_cb *status_callback; - fc_data_cb *data_callback; - fc_lossy_data_cb *lossy_data_callback; + fc_status_cb *_Nullable status_callback; + fc_data_cb *_Nullable data_callback; + fc_lossy_data_cb *_Nullable lossy_data_callback; - void *callback_object; + void *_Nullable callback_object; int callback_id; } Friend_Conn_Callbacks; @@ -68,23 +68,23 @@ struct Friend_Conn { static const Friend_Conn empty_friend_conn = {0}; struct Friend_Connections { - const Mono_Time *mono_time; - const Memory *mem; - const Logger *logger; - Networking_Core *net; - Net_Crypto *net_crypto; - DHT *dht; - Broadcast_Info *broadcast; - Onion_Client *onion_c; + const Mono_Time *_Nonnull mono_time; + const Memory *_Nonnull mem; + const Logger *_Nonnull logger; + Networking_Core *_Nonnull net; + Net_Crypto *_Nonnull net_crypto; + DHT *_Nonnull dht; + Broadcast_Info *_Nullable broadcast; + Onion_Client *_Nonnull onion_c; - Friend_Conn *conns; + Friend_Conn *_Nullable conns; uint32_t num_cons; - fr_request_cb *fr_request_callback; - void *fr_request_object; + fr_request_cb *_Nullable fr_request_callback; + void *_Nullable fr_request_object; - global_status_cb *global_status_callback; - void *global_status_callback_object; + global_status_cb *_Nullable global_status_callback; + void *_Nullable global_status_callback_object; uint64_t last_lan_discovery; uint16_t next_lan_port; @@ -351,10 +351,6 @@ static void dht_ip_callback(void *_Nonnull object, int32_t number, const IP_Port return; } - if (friend_con->crypt_connection_id == -1) { - friend_new_connection(fr_c, number); - } - set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, ip_port, true); friend_con->dht_ip_port = *ip_port; friend_con->dht_ip_port_lastrecv = mono_time_get(fr_c->mono_time); @@ -387,7 +383,7 @@ static void change_dht_pk(Friend_Connections *_Nonnull fr_c, int friendcon_id, c memcpy(friend_con->dht_temp_pk, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); } -static int handle_status(void *_Nonnull object, int id, bool status, void *_Nonnull userdata) +static int handle_status(void *_Nonnull object, int id, bool status, void *_Nullable userdata) { Friend_Connections *const fr_c = (Friend_Connections *)object; Friend_Conn *const friend_con = get_conn(fr_c, id); @@ -434,7 +430,7 @@ static int handle_status(void *_Nonnull object, int id, bool status, void *_Nonn } /** Callback for dht public key changes. */ -static void dht_pk_callback(void *_Nonnull object, int32_t number, const uint8_t *_Nonnull dht_public_key, void *_Nonnull userdata) +static void dht_pk_callback(void *_Nonnull object, int32_t number, const uint8_t *_Nonnull dht_public_key, void *_Nullable userdata) { Friend_Connections *const fr_c = (Friend_Connections *)object; Friend_Conn *const friend_con = get_conn(fr_c, number); @@ -456,7 +452,6 @@ static void dht_pk_callback(void *_Nonnull object, int32_t number, const uint8_t handle_status(object, number, false, userdata); /* Going offline. */ } - friend_new_connection(fr_c, number); onion_set_friend_dht_pubkey(fr_c->onion_c, friend_con->onion_friendnum, dht_public_key); } @@ -940,6 +935,9 @@ Friend_Connections *new_friend_connections( static void lan_discovery(Friend_Connections *_Nonnull fr_c) { if (fr_c->last_lan_discovery + LAN_DISCOVERY_INTERVAL < mono_time_get(fr_c->mono_time)) { + if (fr_c->broadcast == nullptr) { + return; + } const uint16_t first = fr_c->next_lan_port; uint16_t last = first + PORTS_PER_DISCOVERY; last = last > TOX_PORTRANGE_TO ? TOX_PORTRANGE_TO : last; diff --git a/toxcore/friend_connection.h b/toxcore/friend_connection.h index a6d09f0e..9298f251 100644 --- a/toxcore/friend_connection.h +++ b/toxcore/friend_connection.h @@ -51,6 +51,10 @@ typedef enum Friendconn_Status { FRIENDCONN_STATUS_CONNECTED, } Friendconn_Status; +#ifdef __cplusplus +extern "C" { +#endif + typedef struct Friend_Connections Friend_Connections; Net_Crypto *_Nonnull friendconn_net_crypto(const Friend_Connections *_Nonnull fr_c); @@ -82,7 +86,7 @@ unsigned int friend_con_connected(const Friend_Connections *_Nonnull fr_c, int f */ int get_friendcon_public_keys(uint8_t *_Nullable real_pk, uint8_t *_Nullable dht_temp_pk, const Friend_Connections *_Nonnull fr_c, int friendcon_id); /** Set temp dht key for connection. */ -void set_dht_temp_pk(Friend_Connections *_Nonnull fr_c, int friendcon_id, const uint8_t *_Nonnull dht_temp_pk, void *_Nonnull userdata); +void set_dht_temp_pk(Friend_Connections *_Nonnull fr_c, int friendcon_id, const uint8_t *_Nonnull dht_temp_pk, void *_Nullable userdata); typedef int global_status_cb(void *_Nullable object, int friendcon_id, bool status, void *_Nullable userdata); @@ -141,7 +145,7 @@ typedef int fr_request_cb( * * This function will be called every time a friend request packet is received. */ -void set_friend_request_callback(Friend_Connections *_Nonnull fr_c, fr_request_cb *_Nonnull fr_request_callback, void *_Nonnull object); +void set_friend_request_callback(Friend_Connections *_Nonnull fr_c, fr_request_cb *_Nullable fr_request_callback, void *_Nullable object); /** Create new friend_connections instance. */ Friend_Connections *_Nullable new_friend_connections(const Logger *_Nonnull logger, const Memory *_Nonnull mem, const Mono_Time *_Nonnull mono_time, const Network *_Nonnull ns, @@ -149,7 +153,7 @@ Friend_Connections *_Nullable new_friend_connections(const Logger *_Nonnull logg bool local_discovery_enabled); /** main friend_connections loop. */ -void do_friend_connections(Friend_Connections *_Nonnull fr_c, void *_Nonnull userdata); +void do_friend_connections(Friend_Connections *_Nonnull fr_c, void *_Nullable userdata); /** Free everything related with friend_connections. */ void kill_friend_connections(Friend_Connections *_Nullable fr_c); @@ -159,4 +163,8 @@ Friend_Conn *_Nullable get_conn(const Friend_Connections *_Nonnull fr_c, int fri int friend_conn_get_onion_friendnum(const Friend_Conn *_Nonnull fc); const IP_Port *_Nullable friend_conn_get_dht_ip_port(const Friend_Conn *_Nonnull fc); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* C_TOXCORE_TOXCORE_FRIEND_CONNECTION_H */ diff --git a/toxcore/friend_connection_test.cc b/toxcore/friend_connection_test.cc new file mode 100644 index 00000000..fe1114b8 --- /dev/null +++ b/toxcore/friend_connection_test.cc @@ -0,0 +1,180 @@ +#include "friend_connection.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../testing/support/public/simulated_environment.hh" +#include "DHT_test_util.hh" +#include "crypto_core.h" +#include "logger.h" +#include "mono_time.h" +#include "net_crypto.h" +#include "net_profile.h" +#include "network.h" +#include "onion_client.h" + +namespace { + +using namespace tox::test; + +// --- Helper Class --- + +template +class FriendConnTestNode { +public: + FriendConnTestNode(SimulatedEnvironment &env, uint16_t port) + : dht_wrapper_(env, port) + , net_profile_(netprof_new(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory), + [mem = &dht_wrapper_.node().c_memory](Net_Profile *p) { netprof_kill(mem, p); }) + , net_crypto_(nullptr, [](Net_Crypto *c) { kill_net_crypto(c); }) + , onion_client_(nullptr, [](Onion_Client *c) { kill_onion_client(c); }) + , friend_connections_(nullptr, [](Friend_Connections *c) { kill_friend_connections(c); }) + { + logger_callback_log( + dht_wrapper_.logger(), + [](void *context, Logger_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *userdata) { + fprintf(stderr, "[%d] %s:%u: %s: %s\n", level, file, line, func, message); + }, + nullptr, nullptr); + + // Setup NetCrypto + TCP_Proxy_Info proxy_info = {{0}, TCP_PROXY_NONE}; + net_crypto_.reset(new_net_crypto(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory, + &dht_wrapper_.node().c_random, &dht_wrapper_.node().c_network, dht_wrapper_.mono_time(), + dht_wrapper_.networking(), dht_wrapper_.get_dht(), &DHTWrapper::funcs, &proxy_info, + net_profile_.get())); + + new_keys(net_crypto_.get()); + + // Setup Onion Client + onion_client_.reset(new_onion_client(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory, + &dht_wrapper_.node().c_random, dht_wrapper_.mono_time(), net_crypto_.get(), + dht_wrapper_.get_dht(), dht_wrapper_.networking())); + + // Setup Friend Connections + friend_connections_.reset( + new_friend_connections(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory, + dht_wrapper_.mono_time(), &dht_wrapper_.node().c_network, onion_client_.get(), + dht_wrapper_.get_dht(), net_crypto_.get(), dht_wrapper_.networking(), true)); + } + + Friend_Connections *get_friend_connections() { return friend_connections_.get(); } + Onion_Client *get_onion_client() { return onion_client_.get(); } + Net_Crypto *get_net_crypto() { return net_crypto_.get(); } + DHT *get_dht() { return dht_wrapper_.get_dht(); } + const uint8_t *dht_public_key() const { return dht_wrapper_.dht_public_key(); } + const uint8_t *real_public_key() const { return nc_get_self_public_key(net_crypto_.get()); } + const Random *get_random() { return &dht_wrapper_.node().c_random; } + + IP_Port get_ip_port() const { return dht_wrapper_.get_ip_port(); } + + void poll() + { + dht_wrapper_.poll(); + do_net_crypto(net_crypto_.get(), nullptr); + do_onion_client(onion_client_.get()); + do_friend_connections(friend_connections_.get(), nullptr); + } + + ~FriendConnTestNode(); + +private: + DHTWrapper dht_wrapper_; + std::unique_ptr> net_profile_; + std::unique_ptr net_crypto_; + std::unique_ptr onion_client_; + std::unique_ptr friend_connections_; +}; + +template +FriendConnTestNode::~FriendConnTestNode() = default; + +using FriendConnNode = FriendConnTestNode; + +class FriendConnectionTest : public ::testing::Test { +protected: + SimulatedEnvironment env; +}; + +TEST_F(FriendConnectionTest, CreationAndDestruction) +{ + FriendConnNode alice(env, 33445); + EXPECT_NE(alice.get_friend_connections(), nullptr); +} + +TEST_F(FriendConnectionTest, AddKillConnection) +{ + FriendConnNode alice(env, 33445); + uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), friend_pk, friend_sk); + + // Add Connection + int conn_id = new_friend_connection(alice.get_friend_connections(), friend_pk); + EXPECT_NE(conn_id, -1); + + // Verify status (should be connecting or none initially, but ID is valid) + EXPECT_NE( + friend_con_connected(alice.get_friend_connections(), conn_id), FRIENDCONN_STATUS_NONE); + + // Kill Connection + EXPECT_EQ(kill_friend_connection(alice.get_friend_connections(), conn_id), 0); +} + +TEST_F(FriendConnectionTest, ConnectTwoNodes) +{ + FriendConnNode alice(env, 33445); + FriendConnNode bob(env, 33446); + + // Alice adds Bob as friend + int alice_conn_id + = new_friend_connection(alice.get_friend_connections(), bob.real_public_key()); + ASSERT_NE(alice_conn_id, -1); + + // Bob adds Alice as friend + int bob_conn_id = new_friend_connection(bob.get_friend_connections(), alice.real_public_key()); + ASSERT_NE(bob_conn_id, -1); + + // Helper to inject peer info into DHT and trigger connection + auto inject_peer = [](FriendConnNode &self, const FriendConnNode &peer, int conn_id) { + // 1. Tell DHT where the peer is (IP/Port + DHT Public Key) + IP_Port peer_ip = peer.get_ip_port(); + addto_lists(self.get_dht(), &peer_ip, peer.dht_public_key()); + + // 2. Tell friend_connection the peer's DHT Public Key (normally found via Onion) + set_dht_temp_pk(self.get_friend_connections(), conn_id, peer.dht_public_key(), nullptr); + }; + + inject_peer(alice, bob, alice_conn_id); + inject_peer(bob, alice, bob_conn_id); + + auto start = env.clock().current_time_ms(); + bool connected = false; + + while ((env.clock().current_time_ms() - start) < 5000) { // 5 seconds timeout + alice.poll(); + bob.poll(); + env.advance_time(10); + + if (friend_con_connected(alice.get_friend_connections(), alice_conn_id) + == FRIENDCONN_STATUS_CONNECTED + && friend_con_connected(bob.get_friend_connections(), bob_conn_id) + == FRIENDCONN_STATUS_CONNECTED) { + connected = true; + break; + } + } + + EXPECT_TRUE(connected) << "Alice and Bob failed to connect"; +} + +} // namespace diff --git a/toxcore/friend_requests.c b/toxcore/friend_requests.c index 00373274..c42292f5 100644 --- a/toxcore/friend_requests.c +++ b/toxcore/friend_requests.c @@ -36,15 +36,15 @@ struct Received_Requests { }; struct Friend_Requests { - const Memory *mem; + const Memory *_Nonnull mem; uint32_t nospam; - fr_friend_request_cb *handle_friendrequest; + fr_friend_request_cb *_Nullable handle_friendrequest; uint8_t handle_friendrequest_isset; - void *handle_friendrequest_object; + void *_Nullable handle_friendrequest_object; - filter_function_cb *filter_function; - void *filter_function_userdata; + filter_function_cb *_Nullable filter_function; + void *_Nullable filter_function_userdata; struct Received_Requests received; }; diff --git a/toxcore/friend_requests.h b/toxcore/friend_requests.h index 0a31367c..b97640ac 100644 --- a/toxcore/friend_requests.h +++ b/toxcore/friend_requests.h @@ -35,14 +35,14 @@ typedef void fr_friend_request_cb(void *_Nonnull object, const uint8_t *_Nonnull void *_Nullable user_data); /** Set the function that will be executed when a friend request for us is received. */ -void callback_friendrequest(Friend_Requests *_Nonnull fr, fr_friend_request_cb *_Nonnull function, void *_Nonnull object); +void callback_friendrequest(Friend_Requests *_Nonnull fr, fr_friend_request_cb *_Nullable function, void *_Nullable object); typedef int filter_function_cb(void *_Nonnull object, const uint8_t *_Nonnull public_key); /** @brief Set the function used to check if a friend request should be displayed to the user or not. * It must return 0 if the request is ok (anything else if it is bad). */ -void set_filter_function(Friend_Requests *_Nonnull fr, filter_function_cb *_Nonnull function, void *_Nonnull userdata); +void set_filter_function(Friend_Requests *_Nonnull fr, filter_function_cb *_Nullable function, void *_Nullable userdata); /** Sets up friendreq packet handlers. */ void friendreq_init(Friend_Requests *_Nonnull fr, Friend_Connections *_Nonnull fr_c); diff --git a/toxcore/group.c b/toxcore/group.c index 375c31c5..ad60760c 100644 --- a/toxcore/group.c +++ b/toxcore/group.c @@ -87,7 +87,7 @@ typedef struct Group_Peer { uint16_t bottom_lossy_number; uint16_t top_lossy_number; - void *object; + void *_Nullable object; } Group_Peer; typedef struct Groupchat_Connection { @@ -112,10 +112,10 @@ typedef struct Group_c { bool need_send_name; bool title_fresh; - Group_Peer *group; + Group_Peer *_Nullable group; uint32_t numpeers; - Group_Peer *frozen; + Group_Peer *_Nullable frozen; uint32_t numfrozen; uint32_t maxfrozen; @@ -140,31 +140,31 @@ typedef struct Group_c { uint32_t num_introducer_connections; - void *object; + void *_Nullable object; - peer_on_join_cb *peer_on_join; - peer_on_leave_cb *peer_on_leave; - group_on_delete_cb *group_on_delete; + peer_on_join_cb *_Nullable peer_on_join; + peer_on_leave_cb *_Nullable peer_on_leave; + group_on_delete_cb *_Nullable group_on_delete; } Group_c; struct Group_Chats { - const Memory *mem; - const Mono_Time *mono_time; + const Memory *_Nonnull mem; + const Mono_Time *_Nonnull mono_time; - Messenger *m; - Friend_Connections *fr_c; + Messenger *_Nonnull m; + Friend_Connections *_Nonnull fr_c; - Group_c *chats; + Group_c *_Nullable chats; uint16_t num_chats; - g_conference_invite_cb *invite_callback; - g_conference_connected_cb *connected_callback; - g_conference_message_cb *message_callback; - peer_name_cb *peer_name_callback; - peer_list_changed_cb *peer_list_changed_callback; - title_cb *title_callback; + g_conference_invite_cb *_Nullable invite_callback; + g_conference_connected_cb *_Nullable connected_callback; + g_conference_message_cb *_Nullable message_callback; + peer_name_cb *_Nullable peer_name_callback; + peer_list_changed_cb *_Nullable peer_list_changed_callback; + title_cb *_Nullable title_callback; - lossy_packet_cb *lossy_packethandlers[256]; + lossy_packet_cb *_Nullable lossy_packethandlers[256]; }; static const Group_c empty_group_c = {0}; diff --git a/toxcore/group_announce_fuzz_test.cc b/toxcore/group_announce_fuzz_test.cc index 154e486d..7ed78227 100644 --- a/toxcore/group_announce_fuzz_test.cc +++ b/toxcore/group_announce_fuzz_test.cc @@ -5,11 +5,15 @@ #include #include -#include "../testing/fuzzing/fuzz_support.hh" -#include "mem_test_util.hh" +#include "../testing/support/public/fuzz_data.hh" +#include "../testing/support/public/simulated_environment.hh" namespace { +using tox::test::FakeClock; +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + void TestUnpackAnnouncesList(Fuzz_Data &input) { CONSUME1_OR_RETURN(const uint8_t, max_count, input); @@ -19,8 +23,9 @@ void TestUnpackAnnouncesList(Fuzz_Data &input) // TODO(iphydf): How do we know the packed size? CONSUME1_OR_RETURN(const uint16_t, packed_size, input); - Test_Memory mem; - Logger *logger = logger_new(mem); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Logger *logger = logger_new(&c_mem); if (gca_unpack_announces_list(logger, input.data(), input.size(), announces.data(), max_count) != -1) { // Always allocate at least something to avoid passing nullptr to functions below. @@ -39,8 +44,9 @@ void TestUnpackPublicAnnounce(Fuzz_Data &input) // TODO(iphydf): How do we know the packed size? CONSUME1_OR_RETURN(const uint16_t, packed_size, input); - Test_Memory mem; - Logger *logger = logger_new(mem); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Logger *logger = logger_new(&c_mem); if (gca_unpack_public_announce(logger, input.data(), input.size(), &public_announce) != -1) { // Always allocate at least something to avoid passing nullptr to functions below. std::vector packed(packed_size + 1); @@ -51,17 +57,22 @@ void TestUnpackPublicAnnounce(Fuzz_Data &input) void TestDoGca(Fuzz_Data &input) { - Test_Memory mem; - std::unique_ptr logger(logger_new(mem), logger_kill); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + std::unique_ptr logger(logger_new(&c_mem), logger_kill); - uint64_t clock = 1; std::unique_ptr> mono_time( mono_time_new( - mem, [](void *user_data) { return *static_cast(user_data); }, &clock), - [mem](Mono_Time *ptr) { mono_time_free(mem, ptr); }); + &c_mem, + [](void *user_data) -> uint64_t { + return static_cast(user_data)->current_time_ms(); + }, + &env.fake_clock()), + [c_mem](Mono_Time *ptr) { mono_time_free(&c_mem, ptr); }); assert(mono_time != nullptr); - std::unique_ptr gca( - new_gca_list(mem), kill_gca); + + std::unique_ptr> gca( + new_gca_list(&c_mem), [](GC_Announces_List *ptr) { kill_gca(ptr); }); assert(gca != nullptr); while (!input.empty()) { @@ -73,14 +84,14 @@ void TestDoGca(Fuzz_Data &input) CONSUME_OR_RETURN(const uint8_t *data, input, length); GC_Public_Announce public_announce; if (gca_unpack_public_announce(logger.get(), data, length, &public_announce) != -1) { - gca_add_announce(mem, mono_time.get(), gca.get(), &public_announce); + gca_add_announce(&c_mem, mono_time.get(), gca.get(), &public_announce); } break; } case 1: { // Advance the time by a number of tox_iteration_intervals. CONSUME1_OR_RETURN(const uint8_t, iterations, input); - clock += iterations * 20; + env.fake_clock().advance(iterations * 20); // Do an iteration. do_gca(mono_time.get(), gca.get()); break; @@ -111,6 +122,7 @@ void TestDoGca(Fuzz_Data &input) extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - fuzz_select_target(data, size); + tox::test::fuzz_select_target( + data, size); return 0; } diff --git a/toxcore/group_announce_test.cc b/toxcore/group_announce_test.cc index d78fccfd..c9ce9498 100644 --- a/toxcore/group_announce_test.cc +++ b/toxcore/group_announce_test.cc @@ -1,20 +1,26 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" #include "group_announce.h" +// clang-format on #include #include "DHT.h" #include "crypto_core.h" #include "logger.h" -#include "mem_test_util.hh" #include "mono_time.h" +#include "mono_time_test_util.hh" #include "network.h" namespace { +using tox::test::FakeClock; +using tox::test::SimulatedEnvironment; + struct Announces : ::testing::Test { protected: - Test_Memory mem_; - uint64_t clock_ = 1000; + SimulatedEnvironment env; + Tox_Memory c_mem_; Mono_Time *mono_time_ = nullptr; GC_Announces_List *gca_ = nullptr; GC_Announce _ann1; @@ -22,24 +28,24 @@ protected: void SetUp() override { - mono_time_ = mono_time_new(mem_, nullptr, nullptr); + c_mem_ = env.fake_memory().get_c_memory(); + mono_time_ = mono_time_new(&c_mem_, nullptr, nullptr); ASSERT_NE(mono_time_, nullptr); - mono_time_set_current_time_callback( - mono_time_, [](void *user_data) { return *static_cast(user_data); }, - &clock_); - gca_ = new_gca_list(mem_); + setup_fake_clock(mono_time_, env.fake_clock()); + + gca_ = new_gca_list(&c_mem_); ASSERT_NE(gca_, nullptr); } ~Announces() override { kill_gca(gca_); - mono_time_free(mem_, mono_time_); + mono_time_free(&c_mem_, mono_time_); } void advance_clock(uint64_t increment) { - clock_ += increment; + env.fake_clock().advance(increment); mono_time_update(mono_time_); } }; @@ -54,10 +60,10 @@ TEST_F(Announces, CanBeCreatedAndDeleted) { GC_Public_Announce ann{}; ann.chat_public_key[0] = 0x88; - ASSERT_NE(gca_add_announce(mem_, mono_time_, gca_, &ann), nullptr); + ASSERT_NE(gca_add_announce(&c_mem_, mono_time_, gca_, &ann), nullptr); #ifndef __clang__ - ASSERT_EQ(gca_add_announce(mem_, mono_time_, gca_, nullptr), nullptr); - ASSERT_EQ(gca_add_announce(mem_, mono_time_, nullptr, &ann), nullptr); + ASSERT_EQ(gca_add_announce(&c_mem_, mono_time_, gca_, nullptr), nullptr); + ASSERT_EQ(gca_add_announce(&c_mem_, mono_time_, nullptr, &ann), nullptr); #endif } @@ -67,7 +73,7 @@ TEST_F(Announces, AnnouncesCanTimeOut) ASSERT_EQ(gca_->root_announces, nullptr); GC_Public_Announce ann{}; ann.chat_public_key[0] = 0xae; - ASSERT_NE(gca_add_announce(mem_, mono_time_, gca_, &ann), nullptr); + ASSERT_NE(gca_add_announce(&c_mem_, mono_time_, gca_, &ann), nullptr); ASSERT_NE(gca_->root_announces, nullptr); ASSERT_EQ(gca_->root_announces->chat_id[0], 0xae); @@ -95,9 +101,9 @@ TEST_F(Announces, AnnouncesGetAndCleanup) ann2.chat_public_key[0] = 0x92; ann2.base_announce.peer_public_key[0] = 0x7c; - ASSERT_NE(gca_add_announce(mem_, mono_time_, gca_, &ann1), nullptr); - ASSERT_NE(gca_add_announce(mem_, mono_time_, gca_, &ann2), nullptr); - ASSERT_NE(gca_add_announce(mem_, mono_time_, gca_, &ann2), nullptr); + ASSERT_NE(gca_add_announce(&c_mem_, mono_time_, gca_, &ann1), nullptr); + ASSERT_NE(gca_add_announce(&c_mem_, mono_time_, gca_, &ann2), nullptr); + ASSERT_NE(gca_add_announce(&c_mem_, mono_time_, gca_, &ann2), nullptr); uint8_t empty_pk[ENC_PUBLIC_KEY_SIZE] = {0}; @@ -117,13 +123,15 @@ TEST_F(Announces, AnnouncesGetAndCleanup) struct AnnouncesPack : ::testing::Test { protected: + SimulatedEnvironment env; + Tox_Memory c_mem_; std::vector announces_; - Test_Memory mem_; Logger *logger_ = nullptr; void SetUp() override { - logger_ = logger_new(mem_); + c_mem_ = env.fake_memory().get_c_memory(); + logger_ = logger_new(&c_mem_); ASSERT_NE(logger_, nullptr); // Add an announce without TCP relay. diff --git a/toxcore/group_chats.c b/toxcore/group_chats.c index 5e228261..323b0ffa 100644 --- a/toxcore/group_chats.c +++ b/toxcore/group_chats.c @@ -1069,6 +1069,10 @@ static bool prune_gc_mod_list(GC_Chat *_Nonnull chat) bool pruned_mod = false; for (uint16_t i = 0; i < chat->moderation.num_mods; ++i) { + if (chat->moderation.mod_list[i] == nullptr) { + LOGGER_ERROR(chat->log, "Moderator list contains a null entry at index %u", i); + continue; + } if (get_peer_number_of_sig_pk(chat, chat->moderation.mod_list[i]) == -1) { memcpy(public_sig_key, chat->moderation.mod_list[i], SIG_PUBLIC_KEY_SIZE); @@ -1622,6 +1626,9 @@ static bool send_lossless_group_packet(const GC_Chat *_Nonnull chat, GC_Connecti } if (length > MAX_GC_PACKET_CHUNK_SIZE) { + if (data == nullptr) { + return false; + } return gcc_send_lossless_packet_fragments(chat, gconn, data, length, packet_type); } @@ -1719,6 +1726,9 @@ static bool unpack_gc_sync_announce(GC_Chat *_Nonnull chat, const uint8_t *_Nonn uint32_t added_tcp_relays = 0; for (uint8_t i = 0; i < announce.tcp_relays_count; ++i) { + if (chat->tcp_conn == nullptr) { + return false; + } const int add_tcp_result = add_tcp_relay_connection(chat->tcp_conn, new_gconn->tcp_connection_num, &announce.tcp_relays[i].ip_port, announce.tcp_relays[i].public_key); @@ -1733,6 +1743,9 @@ static bool unpack_gc_sync_announce(GC_Chat *_Nonnull chat, const uint8_t *_Nonn } if (!announce.ip_port_is_set && added_tcp_relays == 0) { + if (chat->tcp_conn == nullptr) { + return false; + } gcc_mark_for_deletion(new_gconn, chat->tcp_conn, GC_EXIT_TYPE_DISCONNECTED, nullptr, 0); LOGGER_ERROR(chat->log, "Sync error: Invalid peer connection info"); return false; @@ -1760,6 +1773,9 @@ static int handle_gc_sync_response(const GC_Session *_Nonnull c, GC_Chat *_Nonnu uint16_t length, void *_Nullable userdata) { if (length > 0) { + if (data == nullptr) { + return -1; + } if (!unpack_gc_sync_announce(chat, data, length)) { return -1; } @@ -2187,7 +2203,7 @@ static bool send_gc_invite_response_reject(const GC_Chat *_Nonnull chat, GC_Conn * Return -3 if we fail to send an invite response. * Return -4 if peer_number does not designate a valid peer. */ -static int handle_gc_invite_request(GC_Chat *_Nonnull chat, uint32_t peer_number, const uint8_t *_Nullable data, uint16_t length) +static int handle_gc_invite_request(GC_Chat *_Nonnull chat, uint32_t peer_number, const uint8_t *_Nonnull data, uint16_t length) { if (chat->shared_state.version == 0) { // we aren't synced yet; ignore request return 0; @@ -3825,10 +3841,12 @@ static bool handle_gc_topic_validate(const GC_Chat *_Nonnull chat, const GC_Peer } } - if (topic_info->version == chat->shared_state.topic_lock) { - // always accept topic on initial connection - if (!mono_time_is_timeout(chat->mono_time, chat->time_connected, GC_PING_TIMEOUT)) { - return true; + if (topic_info->version == chat->shared_state.topic_lock || + !mono_time_is_timeout(chat->mono_time, chat->time_connected, GC_PING_TIMEOUT)) { + if (chat->topic_prev_checksum == topic_info->checksum && + !mono_time_is_timeout(chat->mono_time, chat->topic_time_set, GC_CONFIRMED_PEER_TIMEOUT)) { + LOGGER_DEBUG(chat->log, "Topic reversion (probable sync error)"); + return false; } return true; @@ -7241,13 +7259,15 @@ static bool init_gc_tcp_connection(const GC_Session *_Nonnull c, GC_Chat *_Nonnu { const Messenger *m = c->messenger; - chat->tcp_conn = new_tcp_connections(chat->log, chat->mem, chat->rng, m->ns, chat->mono_time, chat->self_secret_key.enc, - &m->options.proxy_info, c->tcp_np); + TCP_Connections *tcp_conn = new_tcp_connections(chat->log, chat->mem, chat->rng, m->ns, chat->mono_time, chat->self_secret_key.enc, + &m->options.proxy_info, c->tcp_np); - if (chat->tcp_conn == nullptr) { + if (tcp_conn == nullptr) { return false; } + chat->tcp_conn = tcp_conn; + add_tcp_relays_to_chat(c, chat); set_packet_tcp_connection_callback(chat->tcp_conn, &handle_gc_tcp_packet, c->messenger); diff --git a/toxcore/group_chats.h b/toxcore/group_chats.h index ef051e75..83692934 100644 --- a/toxcore/group_chats.h +++ b/toxcore/group_chats.h @@ -541,7 +541,7 @@ void do_gc(GC_Session *_Nonnull c, void *_Nullable userdata); * Make sure that DHT is initialized before calling this. * Returns a NULL pointer on failure. */ -GC_Session *_Nullable new_dht_groupchats(Messenger *_Nullable m); +GC_Session *_Nullable new_dht_groupchats(Messenger *_Nonnull m); /** @brief Cleans up groupchat structures and calls `gc_group_exit()` for every group chat */ void kill_dht_groupchats(GC_Session *_Nullable c); /** @brief Loads a previously saved group and attempts to join it. @@ -680,7 +680,7 @@ bool gc_send_message_ack(const GC_Chat *_Nonnull chat, GC_Connection *_Nonnull g * * @retval true if packet is successfully handled. */ -bool handle_gc_lossless_helper(const GC_Session *_Nonnull c, GC_Chat *_Nonnull chat, uint32_t peer_number, const uint8_t *_Nullable data, +bool handle_gc_lossless_helper(const GC_Session *_Nonnull c, GC_Chat *_Nonnull chat, uint32_t peer_number, const uint8_t *_Nonnull data, uint16_t length, uint8_t packet_type, void *_Nullable userdata); /** @brief Handles an invite accept packet. * diff --git a/toxcore/group_common.h b/toxcore/group_common.h index fea6b413..cee126df 100644 --- a/toxcore/group_common.h +++ b/toxcore/group_common.h @@ -276,7 +276,7 @@ typedef struct GC_Chat { IP_Port self_ip_port; Networking_Core *_Nonnull net; - TCP_Connections *_Nullable tcp_conn; + TCP_Connections *_Nonnull tcp_conn; uint64_t last_checked_tcp_relays; Group_Handshake_Join_Type join_type; @@ -380,8 +380,8 @@ typedef void gc_rejected_cb(const Messenger *_Nonnull m, uint32_t group_number, typedef struct GC_Session { Messenger *_Nonnull messenger; GC_Chat *_Nullable chats; - Net_Profile *_Nullable tcp_np; - struct GC_Announces_List *_Nullable announces_list; + Net_Profile *_Nonnull tcp_np; + struct GC_Announces_List *_Nonnull announces_list; uint32_t chats_index; diff --git a/toxcore/group_connection.c b/toxcore/group_connection.c index 94ae529b..be94784e 100644 --- a/toxcore/group_connection.c +++ b/toxcore/group_connection.c @@ -452,13 +452,15 @@ int gcc_handle_packet_fragment(const GC_Session *c, GC_Chat *chat, uint32_t peer /* peer number can change from peer add operations in packet handlers */ peer_number = get_peer_number_of_enc_pk(chat, sender_pk, false); - gconn = get_gc_connection(chat, peer_number); + GC_Connection *new_gconn = get_gc_connection(chat, peer_number); - if (gconn == nullptr) { + if (new_gconn == nullptr) { mem_delete(chat->mem, payload); return 0; } + gconn = new_gconn; + gcc_set_recv_message_id(gconn, gconn->received_message_id + 1); gconn->last_chunk_id = 0; @@ -510,7 +512,14 @@ static bool process_recv_array_entry(const GC_Session *_Nonnull c, GC_Chat *_Non uint8_t sender_pk[ENC_PUBLIC_KEY_SIZE]; memcpy(sender_pk, get_enc_key(&gconn->addr.public_key), ENC_PUBLIC_KEY_SIZE); - const bool ret = handle_gc_lossless_helper(c, chat, peer_number, array_entry->data, array_entry->data_length, + const uint8_t *data = array_entry->data; + // TODO(iphydf): This is ugly. We should probably change all the handlers to accept nullable. + const uint8_t empty_data[1] = { 0 }; + if (data == nullptr) { + data = empty_data; + } + + const bool ret = handle_gc_lossless_helper(c, chat, peer_number, data, array_entry->data_length, array_entry->packet_type, userdata); /* peer number can change from peer add operations in packet handlers */ @@ -612,6 +621,9 @@ bool gcc_send_packet(const GC_Chat *chat, GC_Connection *gconn, const uint8_t *p } } + if (chat->tcp_conn == nullptr) { + return false; + } const int ret = send_packet_tcp_connection(chat->tcp_conn, gconn->tcp_connection_num, packet, length); return ret == 0 || direct_send_attempt; } @@ -682,7 +694,9 @@ void gcc_mark_for_deletion(GC_Connection *gconn, TCP_Connections *tcp_conn, Grou gconn->pending_delete = true; gconn->exit_info.exit_type = type; - kill_tcp_connection_to(tcp_conn, gconn->tcp_connection_num); + if (tcp_conn != nullptr) { + kill_tcp_connection_to(tcp_conn, gconn->tcp_connection_num); + } if (length > 0 && length <= MAX_GC_PART_MESSAGE_SIZE && part_message != nullptr) { memcpy(gconn->exit_info.part_message, part_message, length); diff --git a/toxcore/group_moderation.c b/toxcore/group_moderation.c index c73f4f03..c4f86622 100644 --- a/toxcore/group_moderation.c +++ b/toxcore/group_moderation.c @@ -439,7 +439,7 @@ int sanctions_list_unpack(Mod_Sanction *_Nonnull sanctions, Mod_Sanction_Creds * * Return true on success. */ static bool sanctions_list_make_hash(const Memory *_Nonnull mem, const Mod_Sanction *_Nullable sanctions, uint32_t new_version, uint16_t num_sanctions, - uint8_t *_Nonnull hash) + bool sort, uint8_t *_Nonnull hash) { if (num_sanctions == 0 || sanctions == nullptr) { memzero(hash, MOD_SANCTION_HASH_SIZE); @@ -464,7 +464,9 @@ static bool sanctions_list_make_hash(const Memory *_Nonnull mem, const Mod_Sanct memcpy(&data[i * SIGNATURE_SIZE], sanctions[i].signature, SIGNATURE_SIZE); } - qsort(data, num_sanctions, SIGNATURE_SIZE, compare_signatures); + if (sort) { + qsort(data, num_sanctions, SIGNATURE_SIZE, compare_signatures); + } memcpy(&data[sig_data_size], &new_version, sizeof(uint32_t)); crypto_sha256(hash, data, data_buf_size); @@ -524,7 +526,7 @@ bool sanctions_list_make_creds(Moderation *_Nonnull moderation) uint8_t hash[MOD_SANCTION_HASH_SIZE]; if (!sanctions_list_make_hash(moderation->mem, moderation->sanctions, moderation->sanctions_creds.version, - moderation->num_sanctions, hash)) { + moderation->num_sanctions, true, hash)) { moderation->sanctions_creds = old_creds; return false; } @@ -563,15 +565,23 @@ static bool sanctions_creds_validate(const Moderation *_Nonnull moderation, cons uint8_t hash[MOD_SANCTION_HASH_SIZE]; - if (!sanctions_list_make_hash(moderation->mem, sanctions, creds->version, num_sanctions, hash)) { + if (!sanctions_list_make_hash(moderation->mem, sanctions, creds->version, num_sanctions, true, hash)) { return false; } if (memcmp(hash, creds->hash, MOD_SANCTION_HASH_SIZE) != 0) { - LOGGER_WARNING(moderation->log, "Invalid credentials hash"); - return false; - } + /* Some older/other clients might not sort the signatures. + * Try calculating the hash without sorting. + */ + if (!sanctions_list_make_hash(moderation->mem, sanctions, creds->version, num_sanctions, false, hash)) { + return false; + } + if (memcmp(hash, creds->hash, MOD_SANCTION_HASH_SIZE) != 0) { + LOGGER_WARNING(moderation->log, "Invalid credentials hash"); + return false; + } + } if (creds->checksum != sanctions_creds_get_checksum(creds)) { LOGGER_WARNING(moderation->log, "Invalid credentials checksum"); return false; @@ -684,6 +694,9 @@ static bool sanctions_list_remove_index(Moderation *_Nonnull moderation, uint16_ } /* Operate on a copy of the list in case something goes wrong. */ + if (moderation->sanctions == nullptr) { + return false; + } Mod_Sanction *sanctions_copy = sanctions_list_copy(moderation->mem, moderation->sanctions, moderation->num_sanctions); if (sanctions_copy == nullptr) { @@ -782,6 +795,9 @@ bool sanctions_list_add_entry(Moderation *_Nonnull moderation, const Mod_Sanctio Mod_Sanction *sanctions_copy = nullptr; if (moderation->num_sanctions > 0) { + if (moderation->sanctions == nullptr) { + return false; + } sanctions_copy = sanctions_list_copy(moderation->mem, moderation->sanctions, moderation->num_sanctions); if (sanctions_copy == nullptr) { diff --git a/toxcore/group_moderation_fuzz_test.cc b/toxcore/group_moderation_fuzz_test.cc index dde78ba1..e0943534 100644 --- a/toxcore/group_moderation_fuzz_test.cc +++ b/toxcore/group_moderation_fuzz_test.cc @@ -1,15 +1,19 @@ #include "group_moderation.h" -#include "../testing/fuzzing/fuzz_support.hh" -#include "mem_test_util.hh" +#include "../testing/support/public/fuzz_data.hh" +#include "../testing/support/public/simulated_environment.hh" namespace { +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + void TestModListUnpack(Fuzz_Data &input) { CONSUME1_OR_RETURN(const uint16_t, num_mods, input); - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; mod_list_unpack(&mods, input.data(), input.size(), num_mods); mod_list_cleanup(&mods); } @@ -34,7 +38,7 @@ void TestSanctionCredsUnpack(Fuzz_Data &input) extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - fuzz_select_target( - data, size); + tox::test::fuzz_select_target(data, size); return 0; } diff --git a/toxcore/group_moderation_test.cc b/toxcore/group_moderation_test.cc index 89555b9a..633b7085 100644 --- a/toxcore/group_moderation_test.cc +++ b/toxcore/group_moderation_test.cc @@ -1,4 +1,7 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" #include "group_moderation.h" +// clang-format on #include @@ -10,17 +13,18 @@ #include "crypto_core.h" #include "crypto_core_test_util.hh" #include "logger.h" -#include "mem_test_util.hh" #include "util.h" namespace { +using tox::test::SimulatedEnvironment; using ModerationHash = std::array; TEST(ModList, PackedSizeOfEmptyModListIsZero) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; EXPECT_EQ(mod_list_packed_size(&mods), 0); uint8_t byte = 1; @@ -30,16 +34,18 @@ TEST(ModList, PackedSizeOfEmptyModListIsZero) TEST(ModList, UnpackingZeroSizeArrayIsNoop) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; const uint8_t byte = 1; EXPECT_EQ(mod_list_unpack(&mods, &byte, 0, 0), 0); } TEST(ModList, AddRemoveMultipleMods) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; uint8_t sig_pk1[32] = {1}; uint8_t sig_pk2[32] = {2}; EXPECT_TRUE(mod_list_add_entry(&mods, sig_pk1)); @@ -51,8 +57,9 @@ TEST(ModList, AddRemoveMultipleMods) TEST(ModList, PackingAndUnpackingList) { using ModListEntry = std::array; - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; EXPECT_TRUE(mod_list_add_entry(&mods, ModListEntry{}.data())); std::vector packed(mod_list_packed_size(&mods)); @@ -60,7 +67,7 @@ TEST(ModList, PackingAndUnpackingList) EXPECT_TRUE(mod_list_remove_entry(&mods, ModListEntry{}.data())); - Moderation mods2{mem}; + Moderation mods2{&c_mem}; EXPECT_EQ(mod_list_unpack(&mods2, packed.data(), packed.size(), 1), packed.size()); EXPECT_TRUE(mod_list_remove_entry(&mods2, ModListEntry{}.data())); } @@ -68,14 +75,15 @@ TEST(ModList, PackingAndUnpackingList) TEST(ModList, UnpackingTooManyModsFails) { using ModListEntry = std::array; - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; EXPECT_TRUE(mod_list_add_entry(&mods, ModListEntry{}.data())); std::vector packed(mod_list_packed_size(&mods)); mod_list_pack(&mods, packed.data()); - Moderation mods2{mem}; + Moderation mods2{&c_mem}; EXPECT_EQ(mod_list_unpack(&mods2, packed.data(), packed.size(), 2), -1); EXPECT_TRUE(mod_list_remove_entry(&mods, ModListEntry{}.data())); } @@ -84,45 +92,50 @@ TEST(ModList, UnpackingFromEmptyBufferFails) { std::vector packed(1); - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; EXPECT_EQ(mod_list_unpack(&mods, packed.data(), 0, 1), -1); } TEST(ModList, HashOfEmptyModListZeroesOutBuffer) { - Test_Memory mem; - Test_Random rng; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); - Moderation mods{mem}; + Moderation mods{&c_mem}; // Fill with random data, check that it's zeroed. ModerationHash hash; - random_bytes(rng, hash.data(), hash.size()); + random_bytes(&c_rng, hash.data(), hash.size()); EXPECT_TRUE(mod_list_make_hash(&mods, hash.data())); EXPECT_EQ(hash, ModerationHash{}); } TEST(ModList, RemoveIndexFromEmptyModListFails) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; EXPECT_FALSE(mod_list_remove_index(&mods, 0)); EXPECT_FALSE(mod_list_remove_index(&mods, UINT16_MAX)); } TEST(ModList, RemoveEntryFromEmptyModListFails) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; uint8_t sig_pk[32] = {0}; EXPECT_FALSE(mod_list_remove_entry(&mods, sig_pk)); } TEST(ModList, ModListRemoveIndex) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; uint8_t sig_pk[32] = {1}; EXPECT_TRUE(mod_list_add_entry(&mods, sig_pk)); EXPECT_TRUE(mod_list_remove_index(&mods, 0)); @@ -130,23 +143,26 @@ TEST(ModList, ModListRemoveIndex) TEST(ModList, CleanupOnEmptyModsIsNoop) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; mod_list_cleanup(&mods); } TEST(ModList, EmptyModListCannotVerifyAnySigPk) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; uint8_t sig_pk[32] = {1}; EXPECT_FALSE(mod_list_verify_sig_pk(&mods, sig_pk)); } TEST(ModList, ModListAddVerifyRemoveSigPK) { - Test_Memory mem; - Moderation mods{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods{&c_mem}; uint8_t sig_pk[32] = {1}; EXPECT_TRUE(mod_list_add_entry(&mods, sig_pk)); EXPECT_TRUE(mod_list_verify_sig_pk(&mods, sig_pk)); @@ -156,8 +172,9 @@ TEST(ModList, ModListAddVerifyRemoveSigPK) TEST(ModList, ModListHashCheck) { - Test_Memory mem; - Moderation mods1{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mods1{&c_mem}; uint8_t sig_pk1[32] = {1}; std::array hash1; @@ -179,8 +196,9 @@ TEST(SanctionsList, PackingIntoUndersizedBufferFails) TEST(SanctionsList, PackUnpackSanctionsCreds) { - Test_Memory mem; - Moderation mod{mem}; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Moderation mod{&c_mem}; std::array packed; EXPECT_EQ(sanctions_creds_pack(&mod.sanctions_creds, packed.data()), MOD_SANCTIONS_CREDS_SIZE); EXPECT_EQ( @@ -189,20 +207,30 @@ TEST(SanctionsList, PackUnpackSanctionsCreds) struct SanctionsListMod : ::testing::Test { protected: + SimulatedEnvironment env; + Tox_Memory c_mem_; + Tox_Random c_rng_; + Extended_Public_Key pk; Extended_Secret_Key sk; - Test_Random rng; - Test_Memory mem; - Logger *log = logger_new(mem); - Moderation mod{mem}; + Logger *log = nullptr; + Moderation mod; Mod_Sanction sanctions[2] = {}; const uint8_t sanctioned_pk1[32] = {1}; const uint8_t sanctioned_pk2[32] = {2}; + SanctionsListMod() + : c_mem_(env.fake_memory().get_c_memory()) + , c_rng_(env.fake_random().get_c_random()) + , mod{&c_mem_} + { + } + void SetUp() override { - ASSERT_TRUE(create_extended_keypair(&pk, &sk, rng)); + log = logger_new(&c_mem_); + ASSERT_TRUE(create_extended_keypair(&pk, &sk, &c_rng_)); mod.log = log; diff --git a/toxcore/list.c b/toxcore/list.c index 66330b0c..574890bd 100644 --- a/toxcore/list.c +++ b/toxcore/list.c @@ -59,7 +59,7 @@ static int find(const BS_List *_Nonnull list, const uint8_t *_Nonnull data) // closest match is found if we move back to where we have already been while (true) { - const int r = list->cmp_callback(data, (const void *_Nonnull)(list->data + list->element_size * i), list->element_size); + const int r = list->cmp_callback(data, list->data + list->element_size * i, list->element_size); if (r == 0) { return i; diff --git a/toxcore/logger.c b/toxcore/logger.c index b97ef8e1..89d4af6b 100644 --- a/toxcore/logger.c +++ b/toxcore/logger.c @@ -18,11 +18,11 @@ #include "mem.h" struct Logger { - const Memory *mem; + const Memory *_Nonnull mem; - logger_cb *callback; - void *context; - void *userdata; + logger_cb *_Nullable callback; + void *_Nullable context; + void *_Nullable userdata; }; /* diff --git a/toxcore/mem_test_util.cc b/toxcore/mem_test_util.cc deleted file mode 100644 index e6288710..00000000 --- a/toxcore/mem_test_util.cc +++ /dev/null @@ -1,29 +0,0 @@ -#include "mem_test_util.hh" - -#include - -#include "test_util.hh" -#include "tox_memory_impl.h" - -Tox_Memory_Funcs const Memory_Class::vtable = { - Method::invoke<&Memory_Class::malloc>, - Method::invoke<&Memory_Class::realloc>, - Method::invoke<&Memory_Class::dealloc>, -}; - -Memory_Class::~Memory_Class() = default; - -void *Test_Memory::malloc(void *obj, uint32_t size) -{ - return mem->funcs->malloc_callback(mem->user_data, size); -} - -void *Test_Memory::realloc(void *obj, void *ptr, uint32_t size) -{ - return mem->funcs->realloc_callback(mem->user_data, ptr, size); -} - -void Test_Memory::dealloc(void *obj, void *ptr) -{ - return mem->funcs->dealloc_callback(mem->user_data, ptr); -} diff --git a/toxcore/mem_test_util.hh b/toxcore/mem_test_util.hh deleted file mode 100644 index 7a02a22e..00000000 --- a/toxcore/mem_test_util.hh +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef C_TOXCORE_TOXCORE_MEM_TEST_UTIL_H -#define C_TOXCORE_TOXCORE_MEM_TEST_UTIL_H - -#include "mem.h" -#include "os_memory.h" -#include "test_util.hh" -#include "tox_memory_impl.h" - -struct Memory_Class { - static Tox_Memory_Funcs const vtable; - Tox_Memory const self; - - operator Tox_Memory const *() const { return &self; } - - Memory_Class(Memory_Class const &) = default; - Memory_Class() - : self{&vtable, this} - { - } - - virtual ~Memory_Class(); - virtual tox_memory_malloc_cb malloc = 0; - virtual tox_memory_realloc_cb realloc = 0; - virtual tox_memory_dealloc_cb dealloc = 0; -}; - -/** - * Base test Memory class that just forwards to os_memory. Can be - * subclassed to override individual (or all) functions. - */ -class Test_Memory : public Memory_Class { - const Tox_Memory *mem = REQUIRE_NOT_NULL(os_memory()); - - void *malloc(void *obj, uint32_t size) override; - void *realloc(void *obj, void *ptr, uint32_t size) override; - void dealloc(void *obj, void *ptr) override; -}; - -#endif // C_TOXCORE_TOXCORE_MEM_TEST_UTIL_H diff --git a/toxcore/mono_time.c b/toxcore/mono_time.c index 6e1ea375..6ee6d7d7 100644 --- a/toxcore/mono_time.c +++ b/toxcore/mono_time.c @@ -43,12 +43,12 @@ struct Mono_Time { uint64_t base_time; #ifndef ESP_PLATFORM - /* protect `time` from concurrent access */ - pthread_rwlock_t *time_update_lock; + /** protect @ref cur_time from concurrent access */ + pthread_rwlock_t *_Nonnull time_update_lock; #endif /* ESP_PLATFORM */ - mono_time_current_time_cb *current_time_callback; - void *user_data; + mono_time_current_time_cb *_Nonnull current_time_callback; + void *_Nullable user_data; }; static uint64_t timespec_to_u64(struct timespec clock_mono) @@ -116,7 +116,7 @@ Mono_Time *mono_time_new(const Memory *mem, mono_time_current_time_cb *current_t } #ifndef ESP_PLATFORM - pthread_rwlock_t *rwlock = (pthread_rwlock_t *)mem_alloc(mem, sizeof(pthread_rwlock_t)); + pthread_rwlock_t *const rwlock = (pthread_rwlock_t *)mem_alloc(mem, sizeof(pthread_rwlock_t)); if (rwlock == nullptr) { mem_delete(mem, mono_time); diff --git a/toxcore/mono_time_test.cc b/toxcore/mono_time_test.cc index 0334f323..8844f78b 100644 --- a/toxcore/mono_time_test.cc +++ b/toxcore/mono_time_test.cc @@ -1,80 +1,92 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" #include "mono_time.h" +// clang-format on #include -#include -#include - -#include "mem_test_util.hh" +#include "mono_time_test_util.hh" namespace { -TEST(MonoTime, UnixTimeIncreasesOverTime) +using tox::test::FakeClock; +using tox::test::SimulatedEnvironment; + +TEST(MonoTime, TimeIncreasesWhenAdvanced) { - Test_Memory mem; - Mono_Time *mono_time = mono_time_new(mem, nullptr, nullptr); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Mono_Time *mono_time = mono_time_new(&c_mem, nullptr, nullptr); ASSERT_NE(mono_time, nullptr); + setup_fake_clock(mono_time, env.fake_clock()); mono_time_update(mono_time); uint64_t const start = mono_time_get(mono_time); - while (start == mono_time_get(mono_time)) { - mono_time_update(mono_time); - } + // Advance 10 seconds to ensure we definitely cross second boundaries and see an increase + env.fake_clock().advance(10000); + mono_time_update(mono_time); uint64_t const end = mono_time_get(mono_time); EXPECT_GT(end, start); + EXPECT_EQ(end, start + 10); - mono_time_free(mem, mono_time); + mono_time_free(&c_mem, mono_time); } TEST(MonoTime, IsTimeout) { - Test_Memory mem; - Mono_Time *mono_time = mono_time_new(mem, nullptr, nullptr); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Mono_Time *mono_time = mono_time_new(&c_mem, nullptr, nullptr); ASSERT_NE(mono_time, nullptr); + setup_fake_clock(mono_time, env.fake_clock()); + mono_time_update(mono_time); // Ensure start is consistent with fake clock uint64_t const start = mono_time_get(mono_time); EXPECT_FALSE(mono_time_is_timeout(mono_time, start, 1)); - while (start == mono_time_get(mono_time)) { - mono_time_update(mono_time); - } + env.fake_clock().advance(2000); // 2 seconds + mono_time_update(mono_time); EXPECT_TRUE(mono_time_is_timeout(mono_time, start, 1)); - mono_time_free(mem, mono_time); + mono_time_free(&c_mem, mono_time); } -TEST(MonoTime, IsTimeoutReal) +TEST(MonoTime, IsTimeoutWithIntermediateUpdates) { - Test_Memory mem; - Mono_Time *mono_time = mono_time_new(mem, nullptr, nullptr); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Mono_Time *mono_time = mono_time_new(&c_mem, nullptr, nullptr); ASSERT_NE(mono_time, nullptr); + setup_fake_clock(mono_time, env.fake_clock()); + mono_time_update(mono_time); uint64_t const start = mono_time_get(mono_time); EXPECT_FALSE(mono_time_is_timeout(mono_time, start, 5)); - const uint64_t before_sleep = mono_time_get(mono_time); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + env.fake_clock().advance(100); mono_time_update(mono_time); - const uint64_t after_sleep = mono_time_get(mono_time); - // should still not have timed out (5sec) after sleeping ~100ms - EXPECT_FALSE(mono_time_is_timeout(mono_time, start, 5)) - << "before sleep: " << before_sleep << ", after sleep: " << after_sleep; + // Should not have timed out (5sec) after 100ms + EXPECT_FALSE(mono_time_is_timeout(mono_time, start, 5)); - mono_time_free(mem, mono_time); + env.fake_clock().advance(5000); + mono_time_update(mono_time); + EXPECT_TRUE(mono_time_is_timeout(mono_time, start, 5)); + + mono_time_free(&c_mem, mono_time); } TEST(MonoTime, CustomTime) { - Test_Memory mem; - Mono_Time *mono_time = mono_time_new(mem, nullptr, nullptr); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + Mono_Time *mono_time = mono_time_new(&c_mem, nullptr, nullptr); ASSERT_NE(mono_time, nullptr); - uint64_t test_time = current_time_monotonic(mono_time) + 42137; - + uint64_t test_time = 123456; mono_time_set_current_time_callback( mono_time, [](void *user_data) { return *static_cast(user_data); }, &test_time); mono_time_update(mono_time); @@ -84,13 +96,12 @@ TEST(MonoTime, CustomTime) uint64_t const start = mono_time_get(mono_time); test_time += 7000; - mono_time_update(mono_time); - EXPECT_EQ(mono_time_get(mono_time) - start, 7); + EXPECT_EQ(mono_time_get(mono_time) - start, 7); EXPECT_EQ(current_time_monotonic(mono_time), test_time); - mono_time_free(mem, mono_time); + mono_time_free(&c_mem, mono_time); } } // namespace diff --git a/toxcore/mono_time_test_util.cc b/toxcore/mono_time_test_util.cc new file mode 100644 index 00000000..b46d386b --- /dev/null +++ b/toxcore/mono_time_test_util.cc @@ -0,0 +1,13 @@ +#include "mono_time_test_util.hh" + +using tox::test::FakeClock; + +void setup_fake_clock(Mono_Time *mono_time, FakeClock &clock) +{ + mono_time_set_current_time_callback( + mono_time, + [](void *user_data) -> uint64_t { + return static_cast(user_data)->current_time_ms(); + }, + &clock); +} diff --git a/toxcore/mono_time_test_util.hh b/toxcore/mono_time_test_util.hh new file mode 100644 index 00000000..357b345a --- /dev/null +++ b/toxcore/mono_time_test_util.hh @@ -0,0 +1,9 @@ +#ifndef C_TOXCORE_TOXCORE_MONO_TIME_TEST_UTIL_HH +#define C_TOXCORE_TOXCORE_MONO_TIME_TEST_UTIL_HH + +#include "../testing/support/doubles/fake_clock.hh" +#include "mono_time.h" + +void setup_fake_clock(Mono_Time *mono_time, tox::test::FakeClock &clock); + +#endif // C_TOXCORE_TOXCORE_MONO_TIME_TEST_UTIL_HH diff --git a/toxcore/net_crypto.c b/toxcore/net_crypto.c index 59ca4601..2841f961 100644 --- a/toxcore/net_crypto.c +++ b/toxcore/net_crypto.c @@ -13,7 +13,7 @@ #include #include -#include "DHT.h" +#include "DHT.h" // Node_format #include "LAN_discovery.h" #include "TCP_client.h" #include "TCP_connection.h" @@ -35,7 +35,7 @@ typedef struct Packet_Data { } Packet_Data; typedef struct Packets_Array { - Packet_Data *buffer[CRYPTO_PACKET_BUFFER_SIZE]; + Packet_Data *_Nullable buffer[CRYPTO_PACKET_BUFFER_SIZE]; uint32_t buffer_start; uint32_t buffer_end; /* packet numbers in array: `{buffer_start, buffer_end)` */ } Packets_Array; @@ -66,7 +66,7 @@ typedef struct Crypto_Connection { uint64_t cookie_request_number; /* number used in the cookie request packets for this connection */ uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer */ - uint8_t *temp_packet; /* Where the cookie request/handshake packet is stored while it is being sent. */ + uint8_t *_Nullable temp_packet; /* Where the cookie request/handshake packet is stored while it is being sent. */ uint16_t temp_packet_length; uint64_t temp_packet_sent_time; /* The time at which the last temp_packet was sent in ms. */ uint32_t temp_packet_num_sent; @@ -81,16 +81,16 @@ typedef struct Crypto_Connection { Packets_Array send_array; Packets_Array recv_array; - connection_status_cb *connection_status_callback; - void *connection_status_callback_object; + connection_status_cb *_Nullable connection_status_callback; + void *_Nullable connection_status_callback_object; int connection_status_callback_id; - connection_data_cb *connection_data_callback; - void *connection_data_callback_object; + connection_data_cb *_Nullable connection_data_callback; + void *_Nullable connection_data_callback_object; int connection_data_callback_id; - connection_lossy_data_cb *connection_lossy_data_callback; - void *connection_lossy_data_callback_object; + connection_lossy_data_cb *_Nullable connection_lossy_data_callback; + void *_Nullable connection_lossy_data_callback_object; int connection_lossy_data_callback_id; uint64_t last_request_packet_sent; @@ -124,25 +124,27 @@ typedef struct Crypto_Connection { bool maximum_speed_reached; - dht_pk_cb *dht_pk_callback; - void *dht_pk_callback_object; + dht_pk_cb *_Nullable dht_pk_callback; + void *_Nullable dht_pk_callback_object; uint32_t dht_pk_callback_number; } Crypto_Connection; static const Crypto_Connection empty_crypto_connection = {{0}}; struct Net_Crypto { - const Logger *log; - const Memory *mem; - const Random *rng; - Mono_Time *mono_time; - const Network *ns; + const Logger *_Nonnull log; + const Memory *_Nonnull mem; + const Random *_Nonnull rng; + Mono_Time *_Nonnull mono_time; + const Network *_Nonnull ns; Networking_Core *net; - DHT *dht; - TCP_Connections *tcp_c; + void *_Nonnull dht; + const Net_Crypto_DHT_Funcs *_Nonnull dht_funcs; - Crypto_Connection *crypto_connections; + TCP_Connections *_Nonnull tcp_c; + + Crypto_Connection *_Nullable crypto_connections; uint32_t crypto_connections_length; /* Length of connections array. */ @@ -153,13 +155,17 @@ struct Net_Crypto { /* The secret key used for cookies */ uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; - new_connection_cb *new_connection_callback; - void *new_connection_callback_object; + new_connection_cb *_Nullable new_connection_callback; + void *_Nullable new_connection_callback_object; /* The current optimal sleep time */ uint32_t current_sleep_time; BS_List ip_port_list; + + /* Rate limiter for cookie requests */ + uint64_t cookie_request_last_time; + uint32_t cookie_request_tokens; }; const uint8_t *nc_get_self_public_key(const Net_Crypto *c) @@ -202,6 +208,11 @@ static bool crypt_connection_id_is_valid(const Net_Crypto *_Nonnull c, int crypt #define COOKIE_REQUEST_LENGTH (uint16_t)(1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE) #define COOKIE_RESPONSE_LENGTH (uint16_t)(1 + CRYPTO_NONCE_SIZE + COOKIE_LENGTH + sizeof(uint64_t) + CRYPTO_MAC_SIZE) +/** Maximum number of tokens in the bucket for cookie request rate limiting. */ +#define COOKIE_REQUEST_MAX_TOKENS 10 +/** Interval in milliseconds to generate one token for cookie request rate limiting. */ +#define COOKIE_REQUEST_TOKEN_INTERVAL 100 + /** @brief Create a cookie request packet and put it in packet. * * dht_public_key is the dht public key of the other @@ -218,12 +229,16 @@ static int create_cookie_request(const Net_Crypto *_Nonnull c, uint8_t *_Nonnull memcpy(plain, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); memzero(plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); memcpy(plain + (CRYPTO_PUBLIC_KEY_SIZE * 2), &number, sizeof(uint64_t)); - const uint8_t *tmp_shared_key = dht_get_shared_key_sent(c->dht, dht_public_key); + const uint8_t *tmp_shared_key = c->dht_funcs->get_shared_key_sent(c->dht, dht_public_key); + if (tmp_shared_key == nullptr) { + LOGGER_ERROR(c->log, "Failed to get shared key for cookie request"); + return -1; + } memcpy(shared_key, tmp_shared_key, CRYPTO_SHARED_KEY_SIZE); uint8_t nonce[CRYPTO_NONCE_SIZE]; random_nonce(c->rng, nonce); packet[0] = NET_PACKET_COOKIE_REQUEST; - memcpy(packet + 1, dht_get_self_public_key(c->dht), CRYPTO_PUBLIC_KEY_SIZE); + memcpy(packet + 1, c->dht_funcs->get_self_public_key(c->dht), CRYPTO_PUBLIC_KEY_SIZE); memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); const int len = encrypt_data_symmetric(c->mem, shared_key, nonce, plain, sizeof(plain), packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); @@ -330,12 +345,17 @@ static int handle_cookie_request(const Net_Crypto *_Nonnull c, uint8_t *_Nonnull } memcpy(dht_public_key, packet + 1, CRYPTO_PUBLIC_KEY_SIZE); - const uint8_t *tmp_shared_key = dht_get_shared_key_sent(c->dht, dht_public_key); + const uint8_t *tmp_shared_key = c->dht_funcs->get_shared_key_sent(c->dht, dht_public_key); + if (tmp_shared_key == nullptr) { + LOGGER_ERROR(c->log, "Failed to get shared key for cookie response"); + return -1; + } memcpy(shared_key, tmp_shared_key, CRYPTO_SHARED_KEY_SIZE); const int len = decrypt_data_symmetric(c->mem, shared_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE, request_plain); + if (len != COOKIE_REQUEST_PLAIN_LENGTH) { return -1; } @@ -347,7 +367,32 @@ static int handle_cookie_request(const Net_Crypto *_Nonnull c, uint8_t *_Nonnull static int udp_handle_cookie_request(void *_Nonnull object, const IP_Port *_Nonnull source, const uint8_t *_Nonnull packet, uint16_t length, void *_Nullable userdata) { - const Net_Crypto *c = (const Net_Crypto *)object; + Net_Crypto *c = (Net_Crypto *)object; + + const uint64_t current_time = mono_time_get_ms(c->mono_time); + + // Add 1 token every 100ms + const uint64_t new_tokens = (current_time - c->cookie_request_last_time) / COOKIE_REQUEST_TOKEN_INTERVAL; + + if (new_tokens > 0) { + c->cookie_request_tokens += new_tokens; + if (c->cookie_request_tokens > COOKIE_REQUEST_MAX_TOKENS) { + c->cookie_request_tokens = COOKIE_REQUEST_MAX_TOKENS; + // Full bucket, reset time anchor to now so we stop accumulating until consumed + c->cookie_request_last_time = current_time; + } else { + // Advance time anchor by the exact time consumed to generate these tokens + // to preserve the remainder (precision) + c->cookie_request_last_time += new_tokens * COOKIE_REQUEST_TOKEN_INTERVAL; + } + } + + if (c->cookie_request_tokens == 0) { + return 0; + } + + --c->cookie_request_tokens; + uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; @@ -505,16 +550,19 @@ static bool handle_crypto_handshake(const Net_Crypto *_Nonnull c, uint8_t *_Nonn uint8_t *_Nonnull dht_public_key, uint8_t *_Nonnull cookie, const uint8_t *_Nonnull packet, uint16_t length, const uint8_t *_Nullable expected_real_pk) { if (length != HANDSHAKE_PACKET_LENGTH) { + LOGGER_WARNING(c->log, "Handshake length mismatch: %u != %u", length, (unsigned int)HANDSHAKE_PACKET_LENGTH); return false; } uint8_t cookie_plain[COOKIE_DATA_LENGTH]; if (open_cookie(c->mem, c->mono_time, cookie_plain, packet + 1, c->secret_symmetric_key) != 0) { + LOGGER_WARNING(c->log, "Failed to open cookie"); return false; } if (expected_real_pk != nullptr && !pk_equal(cookie_plain, expected_real_pk)) { + LOGGER_WARNING(c->log, "Expected real pk mismatch"); return false; } @@ -527,10 +575,12 @@ static bool handle_crypto_handshake(const Net_Crypto *_Nonnull c, uint8_t *_Nonn HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE), plain); if (len != sizeof(plain)) { + LOGGER_WARNING(c->log, "Failed to decrypt handshake data"); return false; } if (!crypto_sha512_eq(cookie_hash, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE)) { + LOGGER_WARNING(c->log, "Cookie hash mismatch"); return false; } @@ -2240,7 +2290,7 @@ uint32_t copy_connected_tcp_relays_index(const Net_Crypto *c, Node_format *tcp_r return tcp_copy_connected_relays_index(c->tcp_c, tcp_relays, num, idx); } -static void do_tcp(Net_Crypto *_Nonnull c, void *_Nonnull userdata) +static void do_tcp(Net_Crypto *_Nonnull c, void *_Nullable userdata) { do_tcp_connections(c->log, c->tcp_c, userdata); @@ -2911,9 +2961,9 @@ void load_secret_key(Net_Crypto *c, const uint8_t *sk) * Sets all the global connection variables to their default values. */ Net_Crypto *new_net_crypto(const Logger *log, const Memory *mem, const Random *rng, const Network *ns, - Mono_Time *mono_time, Networking_Core *net, DHT *dht, const TCP_Proxy_Info *proxy_info, Net_Profile *tcp_np) + Mono_Time *mono_time, Networking_Core *net, void *dht, const Net_Crypto_DHT_Funcs *dht_funcs, const TCP_Proxy_Info *proxy_info, Net_Profile *tcp_np) { - if (dht == nullptr) { + if (dht == nullptr || dht_funcs == nullptr || dht_funcs->get_shared_key_sent == nullptr || dht_funcs->get_self_public_key == nullptr || dht_funcs->get_self_secret_key == nullptr) { return nullptr; } @@ -2930,18 +2980,21 @@ Net_Crypto *new_net_crypto(const Logger *log, const Memory *mem, const Random *r temp->ns = ns; temp->net = net; - temp->tcp_c = new_tcp_connections(log, mem, rng, ns, mono_time, dht_get_self_secret_key(dht), proxy_info, tcp_np); + temp->dht = dht; + temp->dht_funcs = dht_funcs; - if (temp->tcp_c == nullptr) { + TCP_Connections *const tcp_c = new_tcp_connections(log, mem, rng, ns, mono_time, dht_funcs->get_self_secret_key(dht), proxy_info, tcp_np); + + if (tcp_c == nullptr) { mem_delete(mem, temp); return nullptr; } + temp->tcp_c = tcp_c; + set_packet_tcp_connection_callback(temp->tcp_c, &tcp_data_callback, temp); set_oob_packet_tcp_connection_callback(temp->tcp_c, &tcp_oob_callback, temp); - temp->dht = dht; - new_keys(temp); new_symmetric_key(rng, temp->secret_symmetric_key); @@ -2954,6 +3007,9 @@ Net_Crypto *new_net_crypto(const Logger *log, const Memory *mem, const Random *r bs_list_init(&temp->ip_port_list, mem, sizeof(IP_Port), 8, ipport_cmp_handler); + temp->cookie_request_tokens = COOKIE_REQUEST_MAX_TOKENS; + temp->cookie_request_last_time = mono_time_get_ms(mono_time); + return temp; } @@ -3020,3 +3076,13 @@ void kill_net_crypto(Net_Crypto *c) crypto_memzero(c, sizeof(Net_Crypto)); mem_delete(mem, c); } + +void nc_testonly_get_secrets(const Net_Crypto *c, int conn_id, uint8_t *shared_key, uint8_t *sent_nonce, uint8_t *recv_nonce) +{ + const Crypto_Connection *conn = get_crypto_connection(c, conn_id); + if (conn != nullptr) { + memcpy(shared_key, conn->shared_key, CRYPTO_SHARED_KEY_SIZE); + memcpy(sent_nonce, conn->sent_nonce, CRYPTO_NONCE_SIZE); + memcpy(recv_nonce, conn->recv_nonce, CRYPTO_NONCE_SIZE); + } +} diff --git a/toxcore/net_crypto.h b/toxcore/net_crypto.h index 6a1e0af7..1a462660 100644 --- a/toxcore/net_crypto.h +++ b/toxcore/net_crypto.h @@ -9,7 +9,7 @@ #ifndef C_TOXCORE_TOXCORE_NET_CRYPTO_H #define C_TOXCORE_TOXCORE_NET_CRYPTO_H -#include "DHT.h" +#include "DHT.h" // Node_format #include "TCP_client.h" #include "TCP_connection.h" #include "attributes.h" @@ -24,6 +24,16 @@ extern "C" { #endif +typedef const uint8_t *_Nullable nc_dht_get_shared_key_sent_cb(void *_Nonnull obj, const uint8_t *_Nonnull public_key); +typedef const uint8_t *_Nonnull nc_dht_get_self_public_key_cb(const void *_Nonnull obj); +typedef const uint8_t *_Nonnull nc_dht_get_self_secret_key_cb(const void *_Nonnull obj); + +typedef struct Net_Crypto_DHT_Funcs { + nc_dht_get_shared_key_sent_cb *_Nonnull get_shared_key_sent; + nc_dht_get_self_public_key_cb *_Nonnull get_self_public_key; + nc_dht_get_self_secret_key_cb *_Nonnull get_self_secret_key; +} Net_Crypto_DHT_Funcs; + /*** Crypto payloads. */ /*** Ranges. */ @@ -133,7 +143,7 @@ typedef struct New_Connection { uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer. */ uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ - uint8_t *_Nullable cookie; + uint8_t *_Nonnull cookie; uint8_t cookie_length; } New_Connection; @@ -369,7 +379,7 @@ void load_secret_key(Net_Crypto *_Nonnull c, const uint8_t *_Nonnull sk); * Sets all the global connection variables to their default values. */ Net_Crypto *_Nullable new_net_crypto(const Logger *_Nonnull log, const Memory *_Nonnull mem, const Random *_Nonnull rng, const Network *_Nonnull ns, Mono_Time *_Nonnull mono_time, - Networking_Core *_Nonnull net, DHT *_Nonnull dht, + Networking_Core *_Nonnull net, void *_Nonnull dht, const Net_Crypto_DHT_Funcs *_Nonnull dht_funcs, const TCP_Proxy_Info *_Nonnull proxy_info, Net_Profile *_Nonnull tcp_np); /** return the optimal interval in ms for running do_net_crypto. */ @@ -378,6 +388,10 @@ uint32_t crypto_run_interval(const Net_Crypto *_Nonnull c); /** Main loop. */ void do_net_crypto(Net_Crypto *_Nonnull c, void *_Nullable userdata); void kill_net_crypto(Net_Crypto *_Nullable c); + +/** Unit test support functions. Do not use outside tests. */ +void nc_testonly_get_secrets(const Net_Crypto *_Nonnull c, int conn_id, uint8_t *_Nonnull shared_key, uint8_t *_Nonnull sent_nonce, uint8_t *_Nonnull recv_nonce); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/toxcore/net_crypto_fuzz_test.cc b/toxcore/net_crypto_fuzz_test.cc index 01ff12c2..73451ae5 100644 --- a/toxcore/net_crypto_fuzz_test.cc +++ b/toxcore/net_crypto_fuzz_test.cc @@ -6,8 +6,9 @@ #include #include -#include "../testing/fuzzing/fuzz_support.hh" -#include "../testing/fuzzing/fuzz_tox.hh" +#include "../testing/support/public/fuzz_data.hh" +#include "../testing/support/public/fuzz_helpers.hh" +#include "../testing/support/public/simulated_environment.hh" #include "DHT.h" #include "TCP_client.h" #include "net_profile.h" @@ -15,11 +16,19 @@ namespace { +using tox::test::configure_fuzz_memory_source; +using tox::test::FakeClock; +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + +template +using Ptr = std::unique_ptr; + std::optional> prepare(Fuzz_Data &input) { IP_Port ipp; ip_init(&ipp.ip, true); - ipp.port = 33445; + ipp.port = net_htons(33445); CONSUME_OR_RETURN_VAL(const uint8_t *iterations_packed, input, 1, std::nullopt); uint8_t iterations = *iterations_packed; @@ -27,6 +36,14 @@ std::optional> prepare(Fuzz_Data &input) return {{ipp, iterations}}; } +static constexpr Net_Crypto_DHT_Funcs dht_funcs = { + [](void *dht, const uint8_t *public_key) { + return dht_get_shared_key_sent(static_cast(dht), public_key); + }, + [](const void *dht) { return dht_get_self_public_key(static_cast(dht)); }, + [](const void *dht) { return dht_get_self_secret_key(static_cast(dht)); }, +}; + void TestNetCrypto(Fuzz_Data &input) { const auto prep = prepare(input); @@ -35,16 +52,19 @@ void TestNetCrypto(Fuzz_Data &input) } const auto [ipp, iterations] = prep.value(); - // rest of the fuzz data is input for malloc and network - Fuzz_System sys(input); + SimulatedEnvironment env; + env.fake_clock().advance(1000000000); // Start clock high to match legacy behavior + auto node = env.create_node(ipp.port); + configure_fuzz_memory_source(env.fake_memory(), input); - const Ptr logger(logger_new(sys.mem.get()), logger_kill); + const Ptr logger(logger_new(&node->c_memory), logger_kill); if (logger == nullptr) { return; } - const Ptr net(new_networking_ex(logger.get(), sys.mem.get(), sys.ns.get(), - &ipp.ip, ipp.port, ipp.port + 100, nullptr), + const Ptr net( + new_networking_ex(logger.get(), &node->c_memory, &node->c_network, &ipp.ip, ipp.port, + ipp.port + 100, nullptr), kill_networking); if (net == nullptr) { return; @@ -52,21 +72,23 @@ void TestNetCrypto(Fuzz_Data &input) const std::unique_ptr> mono_time( mono_time_new( - sys.mem.get(), [](void *user_data) { return *static_cast(user_data); }, - &sys.clock), - [mem = sys.mem.get()](Mono_Time *ptr) { mono_time_free(mem, ptr); }); + &node->c_memory, + [](void *user_data) { return static_cast(user_data)->current_time_ms(); }, + &env.fake_clock()), + [&node](Mono_Time *ptr) { mono_time_free(&node->c_memory, ptr); }); + if (mono_time == nullptr) { return; } - const Ptr dht(new_dht(logger.get(), sys.mem.get(), sys.rng.get(), sys.ns.get(), + const Ptr dht(new_dht(logger.get(), &node->c_memory, &node->c_random, &node->c_network, mono_time.get(), net.get(), false, false), kill_dht); if (dht == nullptr) { return; } - Net_Profile *tcp_np = netprof_new(logger.get(), sys.mem.get()); + Net_Profile *tcp_np = netprof_new(logger.get(), &node->c_memory); if (tcp_np == nullptr) { return; @@ -75,11 +97,11 @@ void TestNetCrypto(Fuzz_Data &input) const TCP_Proxy_Info proxy_info = {0}; const Ptr net_crypto( - new_net_crypto(logger.get(), sys.mem.get(), sys.rng.get(), sys.ns.get(), mono_time.get(), - net.get(), dht.get(), &proxy_info, tcp_np), + new_net_crypto(logger.get(), &node->c_memory, &node->c_random, &node->c_network, + mono_time.get(), net.get(), dht.get(), &dht_funcs, &proxy_info, tcp_np), kill_net_crypto); if (net_crypto == nullptr) { - netprof_kill(sys.mem.get(), tcp_np); + netprof_kill(&node->c_memory, tcp_np); return; } @@ -87,11 +109,11 @@ void TestNetCrypto(Fuzz_Data &input) networking_poll(net.get(), nullptr); do_dht(dht.get()); do_net_crypto(net_crypto.get(), nullptr); - // "Sleep" - sys.clock += System::BOOTSTRAP_ITERATION_INTERVAL; + + env.advance_time(200); } - netprof_kill(sys.mem.get(), tcp_np); + netprof_kill(&node->c_memory, tcp_np); } } // namespace @@ -99,6 +121,6 @@ void TestNetCrypto(Fuzz_Data &input) extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - fuzz_select_target(data, size); + tox::test::fuzz_select_target(data, size); return 0; } diff --git a/toxcore/net_crypto_test.cc b/toxcore/net_crypto_test.cc new file mode 100644 index 00000000..c28e11e2 --- /dev/null +++ b/toxcore/net_crypto_test.cc @@ -0,0 +1,577 @@ +#include "net_crypto.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../testing/support/public/simulated_environment.hh" +#include "DHT_test_util.hh" +#include "crypto_core.h" +#include "logger.h" +#include "mono_time.h" +#include "net_profile.h" +#include "network.h" + +namespace { + +using namespace tox::test; + +// --- Helper Class --- + +template +class TestNode { +public: + TestNode(SimulatedEnvironment &env, uint16_t port, bool enable_trace = false) + : dht_wrapper_(env, port) + , net_profile_(netprof_new(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory), + [mem = &dht_wrapper_.node().c_memory](Net_Profile *p) { netprof_kill(mem, p); }) + , net_crypto_(nullptr, [](Net_Crypto *c) { kill_net_crypto(c); }) + , trace_enabled_(enable_trace) + { + // Setup Logger to stderr + logger_callback_log( + dht_wrapper_.logger(), + [](void *context, Logger_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *) { + auto *self = static_cast(context); + if (self->trace_enabled_ || level >= LOGGER_LEVEL_DEBUG) { + fprintf(stderr, "[%d] %s:%u %s: %s\n", level, file, line, func, message); + } + }, + this, nullptr); + + // 3. Setup NetCrypto + TCP_Proxy_Info proxy_info = {{0}, TCP_PROXY_NONE}; + net_crypto_.reset(new_net_crypto(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory, + &dht_wrapper_.node().c_random, &dht_wrapper_.node().c_network, dht_wrapper_.mono_time(), + dht_wrapper_.networking(), dht_wrapper_.get_dht(), &DHTWrapper::funcs, &proxy_info, + net_profile_.get())); + + // 4. Register Callbacks + new_connection_handler(net_crypto_.get(), &TestNode::static_new_connection_cb, this); + } + + Net_Crypto *get_net_crypto() { return net_crypto_.get(); } + const uint8_t *dht_public_key() const { return dht_wrapper_.dht_public_key(); } + const uint8_t *real_public_key() const { return nc_get_self_public_key(net_crypto_.get()); } + int dht_computation_count() const { return dht_wrapper_.dht_computation_count(); } + const Memory *get_memory() const { return &dht_wrapper_.node().c_memory; } + + IP_Port get_ip_port() const { return dht_wrapper_.get_ip_port(); } + + void poll() + { + dht_wrapper_.poll(); + do_net_crypto(net_crypto_.get(), nullptr); + } + + // -- High Level Operations -- + + // Initiates a connection to 'other'. Returns the connection ID. + template + int connect_to(TestNode &other) + { + int id = new_crypto_connection( + net_crypto_.get(), other.real_public_key(), other.dht_public_key()); + if (id == -1) + return -1; + + // "Cheating" by telling net_crypto the direct IP immediately + IP_Port addr = other.get_ip_port(); + set_direct_ip_port(net_crypto_.get(), id, &addr, true); + + // Setup monitoring for this connection + setup_connection_callbacks(id); + return id; + } + + // Sends data to the connected peer (assuming only 1 for simplicity or last connected) + bool send_data(int conn_id, const std::vector &data) + { + if (data.empty()) + return false; + + return write_cryptpacket(net_crypto_.get(), conn_id, data.data(), data.size(), false) != -1; + } + + void send_direct_packet(const IP_Port &dest, const std::vector &data) + { + if (data.empty()) + return; + sendpacket(dht_wrapper_.networking(), &dest, data.data(), data.size()); + } + + // -- Observability -- + + bool is_connected(int conn_id) const + { + if (conn_id < 0 || conn_id >= static_cast(connections_.size())) + return false; + return connections_[conn_id].connected; + } + + const std::vector &get_last_received_data(int conn_id) const + { + if (conn_id < 0 || conn_id >= static_cast(connections_.size())) + return empty_vector_; + return connections_[conn_id].received_data; + } + + // Helper to get the ID assigned to a peer by Public Key (for the acceptor side) + int get_connection_id_by_pk(const uint8_t *pk) { return last_accepted_id_; } + + ~TestNode(); + +private: + DHTWrapper dht_wrapper_; + + struct ConnectionState { + bool connected = false; + std::vector received_data; + }; + + // We map connection IDs to state. connection IDs are small ints. + std::vector connections_{128}; + int last_accepted_id_ = -1; + std::vector empty_vector_; + + void setup_connection_callbacks(int id) + { + if (id >= static_cast(connections_.size())) + connections_.resize(id + 1); + + connection_status_handler( + net_crypto_.get(), id, &TestNode::static_connection_status_cb, this, id); + connection_data_handler( + net_crypto_.get(), id, &TestNode::static_connection_data_cb, this, id); + } + + // -- Static Callbacks -- + + static int static_new_connection_cb(void *object, const New_Connection *n_c) + { + auto *self = static_cast(object); + int id = accept_crypto_connection(self->net_crypto_.get(), n_c); + if (id != -1) { + self->last_accepted_id_ = id; + self->setup_connection_callbacks(id); + } + return id; // Return ID on success + } + + static int static_connection_status_cb(void *object, int id, bool status, void *userdata) + { + auto *self = static_cast(object); + if (id < static_cast(self->connections_.size())) { + self->connections_[id].connected = status; + } + return 0; + } + + static int static_connection_data_cb( + void *object, int id, const uint8_t *data, uint16_t length, void *userdata) + { + auto *self = static_cast(object); + if (id < static_cast(self->connections_.size())) { + self->connections_[id].received_data.assign(data, data + length); + } + return 0; + } + + // Use std::function for the deleter to allow capturing memory pointer + std::unique_ptr> net_profile_; + std::unique_ptr net_crypto_; + bool trace_enabled_ = false; +}; + +template +TestNode::~TestNode() = default; + +using NetCryptoNode = TestNode; +using RealDHTNode = TestNode; + +class NetCryptoTest : public ::testing::Test { +protected: + SimulatedEnvironment env; +}; + +TEST_F(NetCryptoTest, EndToEndDataExchange) +{ + NetCryptoNode alice(env, 33445); + NetCryptoNode bob(env, 33446); + + // 1. Alice initiates connection to Bob + int alice_conn_id = alice.connect_to(bob); + ASSERT_NE(alice_conn_id, -1); + + // 2. Run simulation until connected + auto start = env.clock().current_time_ms(); + int bob_conn_id = -1; + bool connected = false; + + while ((env.clock().current_time_ms() - start) < 5000) { + alice.poll(); + bob.poll(); + env.advance_time(10); // 10ms steps + + bob_conn_id = bob.get_connection_id_by_pk(alice.real_public_key()); + if (alice.is_connected(alice_conn_id) && bob_conn_id != -1 + && bob.is_connected(bob_conn_id)) { + connected = true; + break; + } + } + + ASSERT_TRUE(connected) << "Failed to establish connection within timeout"; + + // 3. Exchange Data + // Packet ID must be in custom range (160+) + std::vector message = {160, 'H', 'e', 'l', 'l', 'o'}; + + EXPECT_TRUE(alice.send_data(alice_conn_id, message)); + + start = env.clock().current_time_ms(); + bool data_received = false; + while ((env.clock().current_time_ms() - start) < 1000) { + alice.poll(); + bob.poll(); + env.advance_time(10); + + if (bob.get_last_received_data(bob_conn_id) == message) { + data_received = true; + break; + } + } + + EXPECT_TRUE(data_received) << "Bob did not receive the correct data"; +} + +TEST_F(NetCryptoTest, ConnectionTimeout) +{ + NetCryptoNode alice(env, 33445); + NetCryptoNode bob(env, 33446); + + // Alice tries to connect to Bob + int alice_conn_id = alice.connect_to(bob); + ASSERT_NE(alice_conn_id, -1); + + // Filter: Drop ALL packets from Bob to Alice + env.simulation().net().add_filter([&](tox::test::Packet &p) { + // Drop if destination is Alice (33445) + if (net_ntohs(p.to.port) == 33445) { + return false; + } + return true; + }); + + // Run simulation for longer than timeout (approx 8-10s) + auto start = env.clock().current_time_ms(); + bool timeout_detected = false; + + // expect Alice to kill the connection after MAX_NUM_SENDPACKET_TRIES * INTERVAL (8*1s=8s). + // + // Run for 15 seconds to be safe + while ((env.clock().current_time_ms() - start) < 15000) { + alice.poll(); + bob.poll(); + env.advance_time(100); + + bool direct; + if (!crypto_connection_status(alice.get_net_crypto(), alice_conn_id, &direct, nullptr)) { + timeout_detected = true; + break; + } + } + + EXPECT_TRUE(timeout_detected) << "Alice should have killed the timed-out connection"; +} + +TEST_F(NetCryptoTest, DataLossAndRetransmission) +{ + bool dropped = false; + NetCryptoNode alice(env, 33445); + NetCryptoNode bob(env, 33446); + + int alice_conn_id = alice.connect_to(bob); + ASSERT_NE(alice_conn_id, -1); + + // Establish connection + auto start = env.clock().current_time_ms(); + int bob_conn_id = -1; + bool connected = false; + + while ((env.clock().current_time_ms() - start) < 5000) { + alice.poll(); + bob.poll(); + env.advance_time(10); + + bob_conn_id = bob.get_connection_id_by_pk(alice.real_public_key()); + if (alice.is_connected(alice_conn_id) && bob_conn_id != -1 + && bob.is_connected(bob_conn_id)) { + connected = true; + break; + } + } + ASSERT_TRUE(connected); + + // Configure network to drop the next packet from Alice + // NET_PACKET_CRYPTO_DATA is 0x1b + // We want to drop the *first* data packet sent. + env.simulation().net().add_filter([&](tox::test::Packet &p) { + if (!dropped && net_ntohs(p.to.port) == 33446 && p.data.size() > 0 + && p.data[0] == NET_PACKET_CRYPTO_DATA) { + dropped = true; + return false; // Drop it + } + return true; + }); + + std::vector message = {161, 'R', 'e', 't', 'r', 'y'}; + alice.send_data(alice_conn_id, message); + + // Alice needs to detect packet loss and retransmit. + // Timeout for retransmission is tricky, it depends on RTT estimation. + // Default RTT is 1s. + + start = env.clock().current_time_ms(); + bool data_received = false; + while ((env.clock().current_time_ms() - start) < 5000) { + alice.poll(); + bob.poll(); + env.advance_time(50); // coarser steps + + if (bob.get_last_received_data(bob_conn_id) == message) { + data_received = true; + break; + } + } + + EXPECT_TRUE(dropped) << "Packet filter failed to target the data packet"; + EXPECT_TRUE(data_received) << "Bob failed to receive data after retransmission"; +} + +TEST_F(NetCryptoTest, CookieRequestCPUExhaustion) +{ + NetCryptoNode victim(env, 33445); + NetCryptoNode attacker(env, 33446); + + // Cookie Request Packet Length + // From net_crypto.c: + // #define COOKIE_REQUEST_LENGTH (uint16_t)(1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + + // COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE) 1 + 32 + 24 + (32 * 2 + 8) + 16 = 145 + const int TEST_COOKIE_REQUEST_LENGTH = 145; + + // Send enough packets to trigger rate limiting + const int NUM_PACKETS = 50; + + std::minstd_rand rng(42); + std::uniform_int_distribution dist; + auto gen = [&]() { return dist(rng); }; + + for (int i = 0; i < NUM_PACKETS; ++i) { + std::vector packet(TEST_COOKIE_REQUEST_LENGTH); + packet[0] = NET_PACKET_COOKIE_REQUEST; + + // Random public key at offset 1 (size 32) + std::generate(packet.begin() + 1, packet.begin() + 1 + CRYPTO_PUBLIC_KEY_SIZE, gen); + + // Fill the rest with random data just to be safe + std::generate(packet.begin() + 1 + CRYPTO_PUBLIC_KEY_SIZE, packet.end(), gen); + + attacker.send_direct_packet(victim.get_ip_port(), packet); + + // Advance time to allow network delivery + env.advance_time(1); + victim.poll(); + } + + // Verify that the victim performed some computations (as it must for the first few packets) + // but filtered out the majority of the flood due to rate limiting. + int computations = victim.dht_computation_count(); + EXPECT_GT(computations, 0) << "Should handle at least some packets"; + EXPECT_LT(computations, NUM_PACKETS) << "Victim performed expensive shared key computations " + "for ALL packets! CPU exhaustion mitigation failed."; +} + +TEST_F(NetCryptoTest, CookieRequestRateLimiting) +{ + NetCryptoNode victim(env, 33445); + NetCryptoNode attacker(env, 33446); + + const int TEST_COOKIE_REQUEST_LENGTH = 145; + std::minstd_rand rng(42); + std::uniform_int_distribution dist; + auto gen = [&]() { return dist(rng); }; + + auto send_packet = [&]() { + std::vector packet(TEST_COOKIE_REQUEST_LENGTH); + packet[0] = NET_PACKET_COOKIE_REQUEST; + std::generate(packet.begin() + 1, packet.begin() + 1 + CRYPTO_PUBLIC_KEY_SIZE, gen); + std::generate(packet.begin() + 1 + CRYPTO_PUBLIC_KEY_SIZE, packet.end(), gen); + attacker.send_direct_packet(victim.get_ip_port(), packet); + env.advance_time(1); // Network delivery + victim.poll(); + }; + + // 1. Initial Burst: Consume all 10 tokens + int initial_computations = victim.dht_computation_count(); + for (int i = 0; i < 10; ++i) { + send_packet(); + } + int burst_computations = victim.dht_computation_count(); + EXPECT_EQ(burst_computations - initial_computations, 10) + << "Should accept initial burst of 10 packets"; + + // 2. Verify Limit Reached: 11th packet should be dropped + send_packet(); + EXPECT_EQ(victim.dht_computation_count(), burst_computations) << "Should drop 11th packet"; + + // 3. Partial Refill Check: Advance 80ms (total < 100ms since empty) + env.advance_time(80); + send_packet(); + EXPECT_EQ(victim.dht_computation_count(), burst_computations) + << "Should drop packet before 100ms refill"; + + // 4. Full Refill Check: Advance to > 100ms + env.advance_time(20); + send_packet(); + EXPECT_EQ(victim.dht_computation_count(), burst_computations + 1) + << "Should accept packet after 100ms refill"; +} + +TEST_F(NetCryptoTest, HandleRequestPacketOOB) +{ + NetCryptoNode alice(env, 33445); + NetCryptoNode bob(env, 33446); + + // 1. Establish connection + int alice_conn_id = alice.connect_to(bob); + ASSERT_NE(alice_conn_id, -1); + + auto start = env.clock().current_time_ms(); + int bob_conn_id = -1; + bool connected = false; + + while ((env.clock().current_time_ms() - start) < 5000) { + alice.poll(); + bob.poll(); + env.advance_time(10); + + bob_conn_id = bob.get_connection_id_by_pk(alice.real_public_key()); + if (alice.is_connected(alice_conn_id) && bob_conn_id != -1 + && bob.is_connected(bob_conn_id)) { + connected = true; + break; + } + } + ASSERT_TRUE(connected); + + // 2. Alice sends many packets to populate her send_array. + std::vector dummy_data(50, 'A'); + for (int i = 0; i < 300; ++i) { + dummy_data[0] = 160 + (i % 30); // Valid packet ID range + alice.send_data(alice_conn_id, dummy_data); + } + alice.poll(); // Process sends + + // 3. Construct the malicious packet. + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t alice_sent_nonce[CRYPTO_NONCE_SIZE]; + uint8_t alice_recv_nonce[CRYPTO_NONCE_SIZE]; + + // Retrieve secrets + nc_testonly_get_secrets( + alice.get_net_crypto(), alice_conn_id, shared_key, alice_sent_nonce, alice_recv_nonce); + + // Use Alice's recv_nonce (which Bob uses to encrypt) + uint8_t nonce[CRYPTO_NONCE_SIZE]; + memcpy(nonce, alice_recv_nonce, CRYPTO_NONCE_SIZE); + + // Payload: [PACKET_ID_REQUEST (1), 255] + // The length of 2 will trigger the OOB read when n wraps around. + uint8_t plaintext[] = {PACKET_ID_REQUEST, 255}; + uint16_t plaintext_len = sizeof(plaintext); + + uint16_t packet_size = 1 + sizeof(uint16_t) + plaintext_len + CRYPTO_MAC_SIZE; + std::vector malicious_packet(packet_size); + + malicious_packet[0] = NET_PACKET_CRYPTO_DATA; + memcpy(&malicious_packet[1], nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); + + int len = encrypt_data_symmetric( + alice.get_memory(), shared_key, nonce, plaintext, plaintext_len, &malicious_packet[3]); + ASSERT_EQ(len, plaintext_len + CRYPTO_MAC_SIZE); + + // 4. Inject the packet + tox::test::Packet p; + p.to = alice.get_ip_port(); + p.data = malicious_packet; + p.from = bob.get_ip_port(); + + env.simulation().net().send_packet(p); + + // 5. Trigger processing - Expect ASAN/UBSAN failure if vulnerable + alice.poll(); +} + +// Test with Real DHT (but fake network) to ensure integration works +TEST_F(NetCryptoTest, EndToEndDataExchange_RealDHT) +{ + RealDHTNode alice(env, 33445); + RealDHTNode bob(env, 33446); + + // 1. Alice initiates connection to Bob + int alice_conn_id = alice.connect_to(bob); + ASSERT_NE(alice_conn_id, -1); + + // 2. Run simulation until connected + auto start = env.clock().current_time_ms(); + int bob_conn_id = -1; + bool connected = false; + + while ((env.clock().current_time_ms() - start) < 5000) { + alice.poll(); + bob.poll(); + env.advance_time(10); // 10ms steps + + bob_conn_id = bob.get_connection_id_by_pk(alice.real_public_key()); + if (alice.is_connected(alice_conn_id) && bob_conn_id != -1 + && bob.is_connected(bob_conn_id)) { + connected = true; + break; + } + } + + ASSERT_TRUE(connected) << "Failed to establish connection within timeout"; + + // 3. Exchange Data + // Packet ID must be in custom range (160+) + std::vector message = {160, 'H', 'e', 'l', 'l', 'o'}; + + EXPECT_TRUE(alice.send_data(alice_conn_id, message)); + + start = env.clock().current_time_ms(); + bool data_received = false; + while ((env.clock().current_time_ms() - start) < 1000) { + alice.poll(); + bob.poll(); + env.advance_time(10); + + if (bob.get_last_received_data(bob_conn_id) == message) { + data_received = true; + break; + } + } + + EXPECT_TRUE(data_received) << "Bob did not receive the correct data"; +} + +} // namespace diff --git a/toxcore/network.c b/toxcore/network.c index e3852954..ac91dc23 100644 --- a/toxcore/network.c +++ b/toxcore/network.c @@ -282,6 +282,7 @@ static const Family family_tcp_ipv6 = {TCP_INET6}; static const Family family_tox_tcp_ipv4 = {TOX_TCP_INET}; static const Family family_tox_tcp_ipv6 = {TOX_TCP_INET6}; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION static const Family *make_tox_family(int family) { switch (family) { @@ -298,6 +299,7 @@ static const Family *make_tox_family(int family) return nullptr; } } +#endif /* FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ static void get_ip4(IP4 *_Nonnull result, const struct in_addr *_Nonnull addr) { @@ -472,10 +474,48 @@ bool sock_valid(Socket sock) return sock.value != invalid_socket.value; } -struct Network_Addr { +typedef struct Network_Addr { struct sockaddr_storage addr; size_t size; -}; +} Network_Addr; + +static void ip_port_to_network_addr(const IP_Port *ip_port, Network_Addr *addr) +{ + addr->size = 0; + if (net_family_is_ipv4(ip_port->ip.family)) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr->addr; + addr->size = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + fill_addr4(&ip_port->ip.ip.v4, &addr4->sin_addr); + addr4->sin_port = ip_port->port; + } else if (net_family_is_ipv6(ip_port->ip.family)) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr->addr; + addr->size = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + fill_addr6(&ip_port->ip.ip.v6, &addr6->sin6_addr); + addr6->sin6_port = ip_port->port; + addr6->sin6_flowinfo = 0; + addr6->sin6_scope_id = 0; + } +} + +static bool network_addr_to_ip_port(const Network_Addr *addr, IP_Port *ip_port) +{ + if (addr->addr.ss_family == AF_INET) { + const struct sockaddr_in *addr_in = (const struct sockaddr_in *)&addr->addr; + ip_port->ip.family = net_family_ipv4(); + get_ip4(&ip_port->ip.ip.v4, &addr_in->sin_addr); + ip_port->port = addr_in->sin_port; + return true; + } else if (addr->addr.ss_family == AF_INET6) { + const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)&addr->addr; + ip_port->ip.family = net_family_ipv6(); + get_ip6(&ip_port->ip.ip.v6, &addr_in6->sin6_addr); + ip_port->port = addr_in6->sin6_port; + return true; + } + return false; +} static int sys_close(void *_Nonnull obj, Socket sock) { @@ -491,9 +531,14 @@ static Socket sys_accept(void *_Nonnull obj, Socket sock) return net_socket_from_native(accept(net_socket_to_native(sock), nullptr, nullptr)); } -static int sys_bind(void *_Nonnull obj, Socket sock, const Network_Addr *_Nonnull addr) +static int sys_bind(void *_Nonnull obj, Socket sock, const IP_Port *_Nonnull addr) { - return bind(net_socket_to_native(sock), (const struct sockaddr *)&addr->addr, addr->size); + Network_Addr naddr; + ip_port_to_network_addr(addr, &naddr); + if (naddr.size == 0) { + return -1; + } + return bind(net_socket_to_native(sock), (const struct sockaddr *)&naddr.addr, naddr.size); } static int sys_listen(void *_Nonnull obj, Socket sock, int backlog) @@ -501,9 +546,14 @@ static int sys_listen(void *_Nonnull obj, Socket sock, int backlog) return listen(net_socket_to_native(sock), backlog); } -static int sys_connect(void *_Nonnull obj, Socket sock, const Network_Addr *_Nonnull addr) +static int sys_connect(void *_Nonnull obj, Socket sock, const IP_Port *_Nonnull addr) { - return connect(net_socket_to_native(sock), (const struct sockaddr *)&addr->addr, addr->size); + Network_Addr naddr; + ip_port_to_network_addr(addr, &naddr); + if (naddr.size == 0) { + return -1; + } + return connect(net_socket_to_native(sock), (const struct sockaddr *)&naddr.addr, naddr.size); } static int sys_recvbuf(void *_Nonnull obj, Socket sock) @@ -529,16 +579,28 @@ static int sys_send(void *_Nonnull obj, Socket sock, const uint8_t *_Nonnull buf return send(net_socket_to_native(sock), (const char *)buf, len, MSG_NOSIGNAL); } -static int sys_sendto(void *_Nonnull obj, Socket sock, const uint8_t *_Nonnull buf, size_t len, const Network_Addr *_Nonnull addr) +static int sys_sendto(void *_Nonnull obj, Socket sock, const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr) { - return sendto(net_socket_to_native(sock), (const char *)buf, len, 0, (const struct sockaddr *)&addr->addr, addr->size); + Network_Addr naddr; + ip_port_to_network_addr(addr, &naddr); + if (naddr.size == 0) { + return -1; + } + return sendto(net_socket_to_native(sock), (const char *)buf, len, 0, (const struct sockaddr *)&naddr.addr, naddr.size); } -static int sys_recvfrom(void *_Nonnull obj, Socket sock, uint8_t *_Nonnull buf, size_t len, Network_Addr *_Nonnull addr) +static int sys_recvfrom(void *_Nonnull obj, Socket sock, uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr) { - socklen_t size = addr->size; - const int ret = recvfrom(net_socket_to_native(sock), (char *)buf, len, 0, (struct sockaddr *)&addr->addr, &size); - addr->size = size; + Network_Addr naddr = {{0}}; + socklen_t addrlen = sizeof(naddr.addr); + const int ret = recvfrom(net_socket_to_native(sock), (char *)buf, len, 0, (struct sockaddr *)&naddr.addr, &addrlen); + naddr.size = addrlen; + if (ret >= 0) { + if (!network_addr_to_ip_port(&naddr, addr)) { + // Ignore packets from unknown families + return -1; + } + } return ret; } @@ -576,14 +638,13 @@ static int sys_setsockopt(void *_Nonnull obj, Socket sock, int level, int optnam // sets and fills an array of addrs for address // returns the number of entries in addrs -static int sys_getaddrinfo(void *_Nonnull obj, const Memory *_Nonnull mem, const char *_Nonnull address, int family, int sock_type, Network_Addr **_Nonnull addrs) +static int sys_getaddrinfo(void *_Nonnull obj, const Memory *_Nonnull mem, const char *_Nonnull address, int family, int sock_type, IP_Port *_Nullable *_Nonnull addrs) { assert(addrs != nullptr); struct addrinfo hints = {0}; hints.ai_family = family; - // different platforms favour a different field // hints.ai_socktype = SOCK_DGRAM; // type of socket Tox uses. hints.ai_socktype = sock_type; @@ -599,7 +660,7 @@ static int sys_getaddrinfo(void *_Nonnull obj, const Memory *_Nonnull mem, const return 0; } - const int32_t max_count = INT32_MAX / sizeof(Network_Addr); + const int32_t max_count = INT32_MAX / sizeof(IP_Port); // we count number of "valid" results int result = 0; @@ -613,7 +674,7 @@ static int sys_getaddrinfo(void *_Nonnull obj, const Memory *_Nonnull mem, const assert(max_count >= result); - Network_Addr *tmp_addrs = (Network_Addr *)mem_valloc(mem, result, sizeof(Network_Addr)); + IP_Port *tmp_addrs = (IP_Port *)mem_valloc(mem, result, sizeof(IP_Port)); if (tmp_addrs == nullptr) { freeaddrinfo(infos); return 0; @@ -623,21 +684,18 @@ static int sys_getaddrinfo(void *_Nonnull obj, const Memory *_Nonnull mem, const int i = 0; for (struct addrinfo *walker = infos; walker != nullptr; walker = walker->ai_next) { if (walker->ai_family == family || family == AF_UNSPEC) { - tmp_addrs[i].size = sizeof(struct sockaddr_storage); - tmp_addrs[i].addr.ss_family = walker->ai_family; + Network_Addr naddr; + naddr.size = walker->ai_addrlen; + memcpy(&naddr.addr, walker->ai_addr, walker->ai_addrlen); - // according to spec, storage is supposed to be large enough (and source shows they are) - // storage is 128 bytes - assert(walker->ai_addrlen <= tmp_addrs[i].size); - - memcpy(&tmp_addrs[i].addr, walker->ai_addr, walker->ai_addrlen); - tmp_addrs[i].size = walker->ai_addrlen; - - ++i; + if (network_addr_to_ip_port(&naddr, &tmp_addrs[i])) { + ++i; + } } } - assert(i == result); + // Correct the count if conversion failed for some reason + result = i; freeaddrinfo(infos); @@ -647,7 +705,7 @@ static int sys_getaddrinfo(void *_Nonnull obj, const Memory *_Nonnull mem, const return result; } -static int sys_freeaddrinfo(void *_Nonnull obj, const Memory *_Nonnull mem, Network_Addr *_Nonnull addrs) +static int sys_freeaddrinfo(void *_Nonnull obj, const Memory *_Nonnull mem, IP_Port *_Nonnull addrs) { if (addrs == nullptr) { return 0; @@ -728,9 +786,9 @@ int net_send(const Network *ns, const Logger *log, return res; } -static int net_sendto(const Network *_Nonnull ns, Socket sock, const uint8_t *_Nonnull buf, size_t len, const Network_Addr *_Nonnull addr, const IP_Port *_Nonnull ip_port) +static int net_sendto(const Network *_Nonnull ns, Socket sock, const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull ip_port) { - return ns->funcs->sendto(ns->obj, sock, buf, len, addr); + return ns->funcs->sendto(ns->obj, sock, buf, len, ip_port); } int net_recv(const Network *ns, const Logger *log, @@ -741,7 +799,7 @@ int net_recv(const Network *ns, const Logger *log, return res; } -static int net_recvfrom(const Network *_Nonnull ns, Socket sock, uint8_t *_Nonnull buf, size_t len, Network_Addr *_Nonnull addr) +static int net_recvfrom(const Network *_Nonnull ns, Socket sock, uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr) { return ns->funcs->recvfrom(ns->obj, sock, buf, len, addr); } @@ -751,7 +809,7 @@ int net_listen(const Network *ns, Socket sock, int backlog) return ns->funcs->listen(ns->obj, sock, backlog); } -static int net_bind(const Network *_Nonnull ns, Socket sock, const Network_Addr *_Nonnull addr) +static int net_bind(const Network *_Nonnull ns, Socket sock, const IP_Port *_Nonnull addr) { return ns->funcs->bind(ns->obj, sock, addr); } @@ -807,22 +865,22 @@ bool set_socket_dualstack(const Network *ns, Socket sock) } typedef struct Packet_Handler { - packet_handler_cb *function; - void *object; + packet_handler_cb *_Nullable function; + void *_Nullable object; } Packet_Handler; struct Networking_Core { - const Logger *log; - const Memory *mem; + const Logger *_Nonnull log; + const Memory *_Nonnull mem; Packet_Handler packethandlers[256]; - const Network *ns; + const Network *_Nonnull ns; Family family; uint16_t port; /* Our UDP socket. */ Socket sock; - Net_Profile *udp_net_profile; + Net_Profile *_Nullable udp_net_profile; }; Family net_family(const Networking_Core *net) @@ -838,7 +896,7 @@ uint16_t net_port(const Networking_Core *net) /* Basic network functions: */ -int net_send_packet(const Networking_Core *net, const IP_Port *ip_port, Packet packet) +int net_send_packet(const Networking_Core *net, const IP_Port *ip_port, Net_Packet packet) { IP_Port ipp_copy = *ip_port; @@ -880,31 +938,7 @@ int net_send_packet(const Networking_Core *net, const IP_Port *ip_port, Packet p ipp_copy.ip.ip.v6 = ip6; } - Network_Addr addr; - - if (net_family_is_ipv4(ipp_copy.ip.family)) { - struct sockaddr_in *const addr4 = (struct sockaddr_in *)&addr.addr; - - addr.size = sizeof(struct sockaddr_in); - addr4->sin_family = AF_INET; - addr4->sin_port = ipp_copy.port; - fill_addr4(&ipp_copy.ip.ip.v4, &addr4->sin_addr); - } else if (net_family_is_ipv6(ipp_copy.ip.family)) { - struct sockaddr_in6 *const addr6 = (struct sockaddr_in6 *)&addr.addr; - - addr.size = sizeof(struct sockaddr_in6); - addr6->sin6_family = AF_INET6; - addr6->sin6_port = ipp_copy.port; - fill_addr6(&ipp_copy.ip.ip.v6, &addr6->sin6_addr); - - addr6->sin6_flowinfo = 0; - addr6->sin6_scope_id = 0; - } else { - LOGGER_ERROR(net->log, "unknown address type: %d", ipp_copy.ip.family.value); - return -1; - } - - const long res = net_sendto(net->ns, net->sock, packet.data, packet.length, &addr, &ipp_copy); + const long res = net_sendto(net->ns, net->sock, packet.data, packet.length, &ipp_copy); net_log_data(net->log, "O=>", packet.data, packet.length, ip_port, res); assert(res <= INT_MAX); @@ -923,7 +957,7 @@ int net_send_packet(const Networking_Core *net, const IP_Port *ip_port, Packet p */ int sendpacket(const Networking_Core *net, const IP_Port *ip_port, const uint8_t *data, uint16_t length) { - const Packet packet = {data, length}; + const Net_Packet packet = {data, length}; return net_send_packet(net, ip_port, packet); } @@ -935,11 +969,9 @@ int sendpacket(const Networking_Core *net, const IP_Port *ip_port, const uint8_t static int receivepacket(const Network *_Nonnull ns, const Logger *_Nonnull log, Socket sock, IP_Port *_Nonnull ip_port, uint8_t *_Nonnull data, uint32_t *_Nonnull length) { memset(ip_port, 0, sizeof(IP_Port)); - Network_Addr addr = {{0}}; - addr.size = sizeof(addr.addr); *length = 0; - const int fail_or_len = net_recvfrom(ns, sock, data, MAX_UDP_PACKET_SIZE, &addr); + const int fail_or_len = net_recvfrom(ns, sock, data, MAX_UDP_PACKET_SIZE, ip_port); if (fail_or_len < 0) { const int error = net_error(); @@ -954,38 +986,9 @@ static int receivepacket(const Network *_Nonnull ns, const Logger *_Nonnull log, *length = (uint32_t)fail_or_len; - if (addr.addr.ss_family == AF_INET) { - const struct sockaddr_in *addr_in = (const struct sockaddr_in *)&addr.addr; - - const Family *const family = make_tox_family(addr_in->sin_family); - assert(family != nullptr); - - if (family == nullptr) { - return -1; - } - - ip_port->ip.family = *family; - get_ip4(&ip_port->ip.ip.v4, &addr_in->sin_addr); - ip_port->port = addr_in->sin_port; - } else if (addr.addr.ss_family == AF_INET6) { - const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)&addr.addr; - const Family *const family = make_tox_family(addr_in6->sin6_family); - assert(family != nullptr); - - if (family == nullptr) { - return -1; - } - - ip_port->ip.family = *family; - get_ip6(&ip_port->ip.ip.v6, &addr_in6->sin6_addr); - ip_port->port = addr_in6->sin6_port; - - if (ipv6_ipv4_in_v6(&ip_port->ip.ip.v6)) { - ip_port->ip.family = net_family_ipv4(); - ip_port->ip.ip.v4.uint32 = ip_port->ip.ip.v6.uint32[3]; - } - } else { - return -1; + if (net_family_is_ipv6(ip_port->ip.family) && ipv6_ipv4_in_v6(&ip_port->ip.ip.v6)) { + ip_port->ip.family = net_family_ipv4(); + ip_port->ip.ip.v4.uint32 = ip_port->ip.ip.v6.uint32[3]; } net_log_data(log, "=>O", data, MAX_UDP_PACKET_SIZE, ip_port, *length); @@ -1080,7 +1083,7 @@ Networking_Core *new_networking_ex( Net_Profile *np = netprof_new(log, mem); if (np == nullptr) { - free(temp); + mem_delete(mem, temp); return nullptr; } @@ -1153,29 +1156,13 @@ Networking_Core *new_networking_ex( /* Bind our socket to port PORT and the given IP address (usually 0.0.0.0 or ::) */ uint16_t *portptr = nullptr; - Network_Addr addr = {{0}}; + IP_Port addr; + ip_init(&addr.ip, net_family_is_ipv6(temp->family)); - if (net_family_is_ipv4(temp->family)) { - struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr.addr; - - addr.size = sizeof(struct sockaddr_in); - addr4->sin_family = AF_INET; - addr4->sin_port = 0; - fill_addr4(&ip->ip.v4, &addr4->sin_addr); - - portptr = &addr4->sin_port; - } else if (net_family_is_ipv6(temp->family)) { - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr.addr; - - addr.size = sizeof(struct sockaddr_in6); - addr6->sin6_family = AF_INET6; - addr6->sin6_port = 0; - fill_addr6(&ip->ip.v6, &addr6->sin6_addr); - - addr6->sin6_flowinfo = 0; - addr6->sin6_scope_id = 0; - - portptr = &addr6->sin6_port; + if (net_family_is_ipv4(temp->family) || net_family_is_ipv6(temp->family)) { + ip_copy(&addr.ip, ip); + addr.port = 0; + portptr = &addr.port; } else { mem_delete(mem, temp); return nullptr; @@ -1747,7 +1734,7 @@ static bool addr_resolve(const Network *_Nonnull ns, const Memory *_Nonnull mem, const Family tox_family = to->family; const int family = make_family(tox_family); - Network_Addr *addrs = nullptr; + IP_Port *addrs = nullptr; const int rc = ns->funcs->getaddrinfo(ns->obj, mem, address, family, 0, &addrs); // Lookup failed / empty. @@ -1766,39 +1753,23 @@ static bool addr_resolve(const Network *_Nonnull ns, const Memory *_Nonnull mem, bool done = false; for (int i = 0; i < rc && !done; ++i) { - switch (addrs[i].addr.ss_family) { - case AF_INET: { - if (addrs[i].addr.ss_family == family) { /* AF_INET requested, done */ - const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)&addrs[i].addr; - get_ip4(&to->ip.v4, &addr->sin_addr); - result = TOX_ADDR_RESOLVE_INET; - done = true; - } else if ((result & TOX_ADDR_RESOLVE_INET) == 0) { /* AF_UNSPEC requested, store away */ - const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)&addrs[i].addr; - get_ip4(&ip4.ip.v4, &addr->sin_addr); - result |= TOX_ADDR_RESOLVE_INET; - } - - break; /* switch */ + if (net_family_is_ipv4(addrs[i].ip.family)) { + if (addrs[i].ip.family.value == to->family.value) { /* AF_INET requested, done */ + ip_copy(to, &addrs[i].ip); + result = TOX_ADDR_RESOLVE_INET; + done = true; + } else if ((result & TOX_ADDR_RESOLVE_INET) == 0) { /* AF_UNSPEC requested, store away */ + ip_copy(&ip4, &addrs[i].ip); + result |= TOX_ADDR_RESOLVE_INET; } - - case AF_INET6: { - if (addrs[i].addr.ss_family == family) { /* AF_INET6 requested, done */ - if (addrs[i].size == sizeof(struct sockaddr_in6)) { - const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(void *)&addrs[i].addr; - get_ip6(&to->ip.v6, &addr->sin6_addr); - result = TOX_ADDR_RESOLVE_INET6; - done = true; - } - } else if ((result & TOX_ADDR_RESOLVE_INET6) == 0) { /* AF_UNSPEC requested, store away */ - if (addrs[i].size == sizeof(struct sockaddr_in6)) { - const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(void *)&addrs[i].addr; - get_ip6(&ip6.ip.v6, &addr->sin6_addr); - result |= TOX_ADDR_RESOLVE_INET6; - } - } - - break; /* switch */ + } else if (net_family_is_ipv6(addrs[i].ip.family)) { + if (addrs[i].ip.family.value == to->family.value) { /* AF_INET6 requested, done */ + ip_copy(to, &addrs[i].ip); + result = TOX_ADDR_RESOLVE_INET6; + done = true; + } else if ((result & TOX_ADDR_RESOLVE_INET6) == 0) { /* AF_UNSPEC requested, store away */ + ip_copy(&ip6, &addrs[i].ip); + result |= TOX_ADDR_RESOLVE_INET6; } } } @@ -1846,23 +1817,7 @@ const char *net_err_connect_to_string(Net_Err_Connect err) bool net_connect(const Network *ns, const Memory *mem, const Logger *log, Socket sock, const IP_Port *ip_port, Net_Err_Connect *err) { - Network_Addr addr = {{0}}; - - if (net_family_is_ipv4(ip_port->ip.family)) { - struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr.addr; - - addr.size = sizeof(struct sockaddr_in); - addr4->sin_family = AF_INET; - fill_addr4(&ip_port->ip.ip.v4, &addr4->sin_addr); - addr4->sin_port = ip_port->port; - } else if (net_family_is_ipv6(ip_port->ip.family)) { - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr.addr; - - addr.size = sizeof(struct sockaddr_in6); - addr6->sin6_family = AF_INET6; - fill_addr6(&ip_port->ip.ip.v6, &addr6->sin6_addr); - addr6->sin6_port = ip_port->port; - } else { + if (!(net_family_is_ipv4(ip_port->ip.family) || net_family_is_ipv6(ip_port->ip.family))) { Ip_Ntoa ip_str; LOGGER_ERROR(log, "cannot connect to %s:%d which is neither IPv4 nor IPv6", net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port)); @@ -1882,7 +1837,7 @@ bool net_connect(const Network *ns, const Memory *mem, const Logger *log, Socket net_socket_to_native(sock), net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port)); errno = 0; - if (ns->funcs->connect(ns->obj, sock, &addr) == -1) { + if (ns->funcs->connect(ns->obj, sock, ip_port) == -1) { const int error = net_error(); // Non-blocking socket: "Operation in progress" means it's connecting. @@ -1946,7 +1901,7 @@ int32_t net_getipport(const Network *ns, const Memory *mem, const char *node, IP } // It's not an IP address, so now we try doing a DNS lookup. - Network_Addr *addrs = nullptr; + IP_Port *addrs = nullptr; const int rc = ns->funcs->getaddrinfo(ns->obj, mem, node, AF_UNSPEC, type, &addrs); // Lookup failed / empty. @@ -1956,62 +1911,8 @@ int32_t net_getipport(const Network *ns, const Memory *mem, const char *node, IP assert(addrs != nullptr); - // Used to avoid calloc parameter overflow - const size_t max_count = min_u64(SIZE_MAX, INT32_MAX) / sizeof(IP_Port); - size_t count = 0; - - for (int i = 0; i < rc && count < max_count; ++i) { - if (addrs[i].addr.ss_family != AF_INET && addrs[i].addr.ss_family != AF_INET6) { - continue; - } - - ++count; - } - - assert(count <= max_count); - - if (count == 0) { - ns->funcs->freeaddrinfo(ns->obj, mem, addrs); - return 0; - } - - IP_Port *ip_port = (IP_Port *)mem_valloc(mem, count, sizeof(IP_Port)); - - if (ip_port == nullptr) { - ns->funcs->freeaddrinfo(ns->obj, mem, addrs); - *res = nullptr; - return -1; - } - - *res = ip_port; - - for (int i = 0; i < rc && count < max_count; ++i) { - if (addrs[i].addr.ss_family == AF_INET) { - const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)&addrs[i].addr; - ip_port->ip.ip.v4.uint32 = addr->sin_addr.s_addr; - } else if (addrs[i].addr.ss_family == AF_INET6) { - const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)&addrs[i].addr; - memcpy(ip_port->ip.ip.v6.uint8, addr->sin6_addr.s6_addr, sizeof(IP6)); - } else { - continue; - } - - const Family *const family = make_tox_family(addrs[i].addr.ss_family); - assert(family != nullptr); - - if (family == nullptr) { - ns->funcs->freeaddrinfo(ns->obj, mem, addrs); - return -1; - } - - ip_port->ip.family = *family; - - ++ip_port; - } - - ns->funcs->freeaddrinfo(ns->obj, mem, addrs); - - return count; + *res = addrs; + return rc; } void net_freeipport(const Memory *mem, IP_Port *ip_ports) @@ -2021,24 +1922,18 @@ void net_freeipport(const Memory *mem, IP_Port *ip_ports) bool bind_to_port(const Network *ns, Socket sock, Family family, uint16_t port) { - Network_Addr addr = {{0}}; + IP_Port addr; + ip_init(&addr.ip, net_family_is_ipv6(family)); + addr.ip.family = family; if (net_family_is_ipv4(family)) { - struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr.addr; - - addr.size = sizeof(struct sockaddr_in); - addr4->sin_family = AF_INET; - addr4->sin_port = net_htons(port); - } else if (net_family_is_ipv6(family)) { - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr.addr; - - addr.size = sizeof(struct sockaddr_in6); - addr6->sin6_family = AF_INET6; - addr6->sin6_port = net_htons(port); + addr.ip.ip.v4.uint32 = 0; } else { - return false; + memset(addr.ip.ip.v6.uint8, 0, 16); } + addr.port = net_htons(port); + return net_bind(ns, sock, &addr) == 0; } diff --git a/toxcore/network.h b/toxcore/network.h index b8e14ea7..37c5cdfc 100644 --- a/toxcore/network.h +++ b/toxcore/network.h @@ -23,35 +23,68 @@ extern "C" { #endif -/** - * @brief Wrapper for sockaddr_storage and size. - */ -typedef struct Network_Addr Network_Addr; - typedef bitwise int Socket_Value; typedef struct Socket { Socket_Value value; } Socket; +#define SIZE_IP4 4 +#define SIZE_IP6 16 +#define SIZE_IP (1 + SIZE_IP6) +#define SIZE_PORT 2 +#define SIZE_IPPORT (SIZE_IP + SIZE_PORT) + +typedef struct Family { + uint8_t value; +} Family; + +typedef union IP4 { + uint32_t uint32; + uint16_t uint16[2]; + uint8_t uint8[4]; +} IP4; + +typedef union IP6 { + uint8_t uint8[16]; + uint16_t uint16[8]; + uint32_t uint32[4]; + uint64_t uint64[2]; +} IP6; + +typedef union IP_Union { + IP4 v4; + IP6 v6; +} IP_Union; + +typedef struct IP { + Family family; + IP_Union ip; +} IP; + +typedef struct IP_Port { + IP ip; + uint16_t port; +} IP_Port; + int net_socket_to_native(Socket sock); Socket net_socket_from_native(int sock); typedef int net_close_cb(void *_Nullable obj, Socket sock); typedef Socket net_accept_cb(void *_Nullable obj, Socket sock); -typedef int net_bind_cb(void *_Nullable obj, Socket sock, const Network_Addr *_Nonnull addr); +typedef int net_bind_cb(void *_Nullable obj, Socket sock, const IP_Port *_Nonnull addr); typedef int net_listen_cb(void *_Nullable obj, Socket sock, int backlog); -typedef int net_connect_cb(void *_Nullable obj, Socket sock, const Network_Addr *_Nonnull addr); +typedef int net_connect_cb(void *_Nullable obj, Socket sock, const IP_Port *_Nonnull addr); typedef int net_recvbuf_cb(void *_Nullable obj, Socket sock); typedef int net_recv_cb(void *_Nullable obj, Socket sock, uint8_t *_Nonnull buf, size_t len); -typedef int net_recvfrom_cb(void *_Nullable obj, Socket sock, uint8_t *_Nonnull buf, size_t len, Network_Addr *_Nonnull addr); +typedef int net_recvfrom_cb(void *_Nullable obj, Socket sock, uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr); typedef int net_send_cb(void *_Nullable obj, Socket sock, const uint8_t *_Nonnull buf, size_t len); -typedef int net_sendto_cb(void *_Nullable obj, Socket sock, const uint8_t *_Nonnull buf, size_t len, const Network_Addr *_Nonnull addr); +typedef int net_sendto_cb(void *_Nullable obj, Socket sock, const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr); typedef Socket net_socket_cb(void *_Nullable obj, int domain, int type, int proto); typedef int net_socket_nonblock_cb(void *_Nullable obj, Socket sock, bool nonblock); typedef int net_getsockopt_cb(void *_Nullable obj, Socket sock, int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen); typedef int net_setsockopt_cb(void *_Nullable obj, Socket sock, int level, int optname, const void *_Nonnull optval, size_t optlen); -typedef int net_getaddrinfo_cb(void *_Nullable obj, const Memory *_Nonnull mem, const char *_Nonnull address, int family, int protocol, Network_Addr *_Nullable *_Nonnull addrs); -typedef int net_freeaddrinfo_cb(void *_Nullable obj, const Memory *_Nonnull mem, Network_Addr *_Nullable addrs); +typedef int net_getaddrinfo_cb(void *_Nullable obj, const Memory *_Nonnull mem, const char *_Nonnull address, int family, int protocol, IP_Port *_Nullable *_Nonnull addrs); +typedef int net_freeaddrinfo_cb(void *_Nullable obj, const Memory *_Nonnull mem, IP_Port *_Nullable addrs); /** @brief Functions wrapping POSIX network functions. * @@ -84,10 +117,6 @@ typedef struct Network { const Network *_Nullable os_network(void); -typedef struct Family { - uint8_t value; -} Family; - bool net_family_is_unspec(Family family); bool net_family_is_ipv4(Family family); bool net_family_is_ipv6(Family family); @@ -182,46 +211,12 @@ typedef enum Net_Packet_Type { #define TCP_INET6 (TOX_AF_INET6 + 3) #define TCP_SERVER_FAMILY (TOX_AF_INET6 + 4) -#define SIZE_IP4 4 -#define SIZE_IP6 16 -#define SIZE_IP (1 + SIZE_IP6) -#define SIZE_PORT 2 -#define SIZE_IPPORT (SIZE_IP + SIZE_PORT) - -typedef union IP4 { - uint32_t uint32; - uint16_t uint16[2]; - uint8_t uint8[4]; -} IP4; - IP4 get_ip4_loopback(void); IP4 get_ip4_broadcast(void); -typedef union IP6 { - uint8_t uint8[16]; - uint16_t uint16[8]; - uint32_t uint32[4]; - uint64_t uint64[2]; -} IP6; - IP6 get_ip6_loopback(void); IP6 get_ip6_broadcast(void); -typedef union IP_Union { - IP4 v4; - IP6 v6; -} IP_Union; - -typedef struct IP { - Family family; - IP_Union ip; -} IP; - -typedef struct IP_Port { - IP ip; - uint16_t port; -} IP_Port; - Socket net_socket(const Network *_Nonnull ns, Family domain, int type, int protocol); /** @@ -450,15 +445,15 @@ bool set_socket_dualstack(const Network *_Nonnull ns, Socket sock); * * Use `net_send_packet` to send it to an IP/port endpoint. */ -typedef struct Packet { +typedef struct Net_Packet { const uint8_t *_Nonnull data; uint16_t length; -} Packet; +} Net_Packet; /** * Function to send a network packet to a given IP/port. */ -int net_send_packet(const Networking_Core *_Nonnull net, const IP_Port *_Nonnull ip_port, Packet packet); +int net_send_packet(const Networking_Core *_Nonnull net, const IP_Port *_Nonnull ip_port, Net_Packet packet); /** * Function to send packet(data) of length length to ip_port. diff --git a/toxcore/network_test.cc b/toxcore/network_test.cc index 8969c7d8..da88fb6c 100644 --- a/toxcore/network_test.cc +++ b/toxcore/network_test.cc @@ -1,4 +1,7 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" #include "network.h" +// clang-format on #include @@ -6,11 +9,13 @@ namespace { -TEST(TestUtil, ProducesNonNullNetwork) +TEST(SimulatedEnvironment, ProducesNonNullNetwork) { - Test_Network net; - const Network *ns = net; - EXPECT_NE(ns, nullptr); + tox::test::SimulatedEnvironment env; + auto node = env.create_node(0); + struct Network net = node->c_network; + EXPECT_NE(net.funcs, nullptr); + EXPECT_NE(net.obj, nullptr); } TEST(IpNtoa, DoesntWriteOutOfBounds) diff --git a/toxcore/network_test_util.cc b/toxcore/network_test_util.cc index c8d2ff3e..6f74f35b 100644 --- a/toxcore/network_test_util.cc +++ b/toxcore/network_test_util.cc @@ -7,87 +7,6 @@ #include "network.h" #include "test_util.hh" -Network_Funcs const Network_Class::vtable = { - Method::invoke<&Network_Class::close>, - Method::invoke<&Network_Class::accept>, - Method::invoke<&Network_Class::bind>, - Method::invoke<&Network_Class::listen>, - Method::invoke<&Network_Class::connect>, - Method::invoke<&Network_Class::recvbuf>, - Method::invoke<&Network_Class::recv>, - Method::invoke<&Network_Class::recvfrom>, - Method::invoke<&Network_Class::send>, - Method::invoke<&Network_Class::sendto>, - Method::invoke<&Network_Class::socket>, - Method::invoke<&Network_Class::socket_nonblock>, - Method::invoke<&Network_Class::getsockopt>, - Method::invoke<&Network_Class::setsockopt>, - Method::invoke<&Network_Class::getaddrinfo>, - Method::invoke<&Network_Class::freeaddrinfo>, -}; - -int Test_Network::close(void *obj, Socket sock) { return net->funcs->close(net->obj, sock); } -Socket Test_Network::accept(void *obj, Socket sock) { return net->funcs->accept(net->obj, sock); } -int Test_Network::bind(void *obj, Socket sock, const Network_Addr *addr) -{ - return net->funcs->bind(net->obj, sock, addr); -} -int Test_Network::listen(void *obj, Socket sock, int backlog) -{ - return net->funcs->listen(net->obj, sock, backlog); -} -int Test_Network::connect(void *obj, Socket sock, const Network_Addr *addr) -{ - return net->funcs->connect(net->obj, sock, addr); -} -int Test_Network::recvbuf(void *obj, Socket sock) { return net->funcs->recvbuf(net->obj, sock); } -int Test_Network::recv(void *obj, Socket sock, uint8_t *buf, size_t len) -{ - return net->funcs->recv(net->obj, sock, buf, len); -} -int Test_Network::recvfrom(void *obj, Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) -{ - return net->funcs->recvfrom(net->obj, sock, buf, len, addr); -} -int Test_Network::send(void *obj, Socket sock, const uint8_t *buf, size_t len) -{ - return net->funcs->send(net->obj, sock, buf, len); -} -int Test_Network::sendto( - void *obj, Socket sock, const uint8_t *buf, size_t len, const Network_Addr *addr) -{ - return net->funcs->sendto(net->obj, sock, buf, len, addr); -} -Socket Test_Network::socket(void *obj, int domain, int type, int proto) -{ - return net->funcs->socket(net->obj, domain, type, proto); -} -int Test_Network::socket_nonblock(void *obj, Socket sock, bool nonblock) -{ - return net->funcs->socket_nonblock(net->obj, sock, nonblock); -} -int Test_Network::getsockopt( - void *obj, Socket sock, int level, int optname, void *optval, size_t *optlen) -{ - return net->funcs->getsockopt(net->obj, sock, level, optname, optval, optlen); -} -int Test_Network::setsockopt( - void *obj, Socket sock, int level, int optname, const void *optval, size_t optlen) -{ - return net->funcs->setsockopt(net->obj, sock, level, optname, optval, optlen); -} -int Test_Network::getaddrinfo(void *obj, const Memory *mem, const char *address, int family, - int protocol, Network_Addr **addrs) -{ - return net->funcs->getaddrinfo(net->obj, mem, address, family, protocol, addrs); -} -int Test_Network::freeaddrinfo(void *obj, const Memory *mem, Network_Addr *addrs) -{ - return net->funcs->freeaddrinfo(net->obj, mem, addrs); -} - -Network_Class::~Network_Class() = default; - IP_Port increasing_ip_port::operator()() { IP_Port ip_port; diff --git a/toxcore/network_test_util.hh b/toxcore/network_test_util.hh index be967d3d..620cc285 100644 --- a/toxcore/network_test_util.hh +++ b/toxcore/network_test_util.hh @@ -8,66 +8,6 @@ #include "network.h" #include "test_util.hh" -struct Network_Class { - static Network_Funcs const vtable; - Network const self; - - operator Network const *() const { return &self; } - - Network_Class(Network_Class const &) = default; - Network_Class() - : self{&vtable, this} - { - } - - virtual ~Network_Class(); - virtual net_close_cb close = 0; - virtual net_accept_cb accept = 0; - virtual net_bind_cb bind = 0; - virtual net_listen_cb listen = 0; - virtual net_connect_cb connect = 0; - virtual net_recvbuf_cb recvbuf = 0; - virtual net_recv_cb recv = 0; - virtual net_recvfrom_cb recvfrom = 0; - virtual net_send_cb send = 0; - virtual net_sendto_cb sendto = 0; - virtual net_socket_cb socket = 0; - virtual net_socket_nonblock_cb socket_nonblock = 0; - virtual net_getsockopt_cb getsockopt = 0; - virtual net_setsockopt_cb setsockopt = 0; - virtual net_getaddrinfo_cb getaddrinfo = 0; - virtual net_freeaddrinfo_cb freeaddrinfo = 0; -}; - -/** - * Base test Network class that just forwards to os_network. Can be - * subclassed to override individual (or all) functions. - */ -class Test_Network : public Network_Class { - const Network *net = REQUIRE_NOT_NULL(os_network()); - - int close(void *obj, Socket sock) override; - Socket accept(void *obj, Socket sock) override; - int bind(void *obj, Socket sock, const Network_Addr *addr) override; - int listen(void *obj, Socket sock, int backlog) override; - int connect(void *obj, Socket sock, const Network_Addr *addr) override; - int recvbuf(void *obj, Socket sock) override; - int recv(void *obj, Socket sock, uint8_t *buf, size_t len) override; - int recvfrom(void *obj, Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) override; - int send(void *obj, Socket sock, const uint8_t *buf, size_t len) override; - int sendto( - void *obj, Socket sock, const uint8_t *buf, size_t len, const Network_Addr *addr) override; - Socket socket(void *obj, int domain, int type, int proto) override; - int socket_nonblock(void *obj, Socket sock, bool nonblock) override; - int getsockopt( - void *obj, Socket sock, int level, int optname, void *optval, size_t *optlen) override; - int setsockopt( - void *obj, Socket sock, int level, int optname, const void *optval, size_t optlen) override; - int getaddrinfo(void *obj, const Memory *mem, const char *address, int family, int protocol, - Network_Addr **addrs) override; - int freeaddrinfo(void *obj, const Memory *mem, Network_Addr *addrs) override; -}; - template <> struct Deleter : Function_Deleter { }; diff --git a/toxcore/onion.c b/toxcore/onion.c index 9452bd2c..dc39160c 100644 --- a/toxcore/onion.c +++ b/toxcore/onion.c @@ -724,18 +724,23 @@ Onion *new_onion(const Logger *log, const Memory *mem, const Mono_Time *mono_tim onion->timestamp = mono_time_get(onion->mono_time); const uint8_t *secret_key = dht_get_self_secret_key(dht); - onion->shared_keys_1 = shared_key_cache_new(log, mono_time, mem, secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); - onion->shared_keys_2 = shared_key_cache_new(log, mono_time, mem, secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); - onion->shared_keys_3 = shared_key_cache_new(log, mono_time, mem, secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); + Shared_Key_Cache *const temp_shared_keys_1 = shared_key_cache_new(log, mono_time, mem, secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); + Shared_Key_Cache *const temp_shared_keys_2 = shared_key_cache_new(log, mono_time, mem, secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); + Shared_Key_Cache *const temp_shared_keys_3 = shared_key_cache_new(log, mono_time, mem, secret_key, KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); - if (onion->shared_keys_1 == nullptr || - onion->shared_keys_2 == nullptr || - onion->shared_keys_3 == nullptr) { + if (temp_shared_keys_1 == nullptr || temp_shared_keys_2 == nullptr || temp_shared_keys_3 == nullptr) { + shared_key_cache_free(temp_shared_keys_3); + shared_key_cache_free(temp_shared_keys_2); + shared_key_cache_free(temp_shared_keys_1); // cppcheck-suppress mismatchAllocDealloc kill_onion(onion); return nullptr; } + onion->shared_keys_1 = temp_shared_keys_1; + onion->shared_keys_2 = temp_shared_keys_2; + onion->shared_keys_3 = temp_shared_keys_3; + networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, &handle_send_initial, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, &handle_send_1, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, &handle_send_2, onion); diff --git a/toxcore/onion_announce.c b/toxcore/onion_announce.c index f752506c..0fe8c9cc 100644 --- a/toxcore/onion_announce.c +++ b/toxcore/onion_announce.c @@ -55,20 +55,20 @@ typedef struct Onion_Announce_Entry { } Onion_Announce_Entry; struct Onion_Announce { - const Logger *log; - const Mono_Time *mono_time; - const Random *rng; - const Memory *mem; - DHT *dht; - Networking_Core *net; + const Logger *_Nonnull log; + const Mono_Time *_Nonnull mono_time; + const Random *_Nonnull rng; + const Memory *_Nonnull mem; + DHT *_Nonnull dht; + Networking_Core *_Nonnull net; Onion_Announce_Entry entries[ONION_ANNOUNCE_MAX_ENTRIES]; uint8_t hmac_key[CRYPTO_HMAC_KEY_SIZE]; - Shared_Key_Cache *shared_keys_recv; + Shared_Key_Cache *_Nonnull shared_keys_recv; uint16_t extra_data_max_size; - pack_extra_data_cb *extra_data_callback; - void *extra_data_object; + pack_extra_data_cb *_Nullable extra_data_callback; + void *_Nullable extra_data_object; }; void onion_announce_extra_data_callback(Onion_Announce *onion_a, uint16_t extra_data_max_size, @@ -693,6 +693,13 @@ Onion_Announce *new_onion_announce(const Logger *log, const Memory *mem, const R return nullptr; } + Shared_Key_Cache *const shared_keys_recv = shared_key_cache_new(log, mono_time, mem, dht_get_self_secret_key(dht), KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); + if (shared_keys_recv == nullptr) { + mem_delete(mem, onion_a); + return nullptr; + } + onion_a->shared_keys_recv = shared_keys_recv; + onion_a->log = log; onion_a->rng = rng; onion_a->mem = mem; @@ -704,13 +711,6 @@ Onion_Announce *new_onion_announce(const Logger *log, const Memory *mem, const R onion_a->extra_data_object = nullptr; new_hmac_key(rng, onion_a->hmac_key); - onion_a->shared_keys_recv = shared_key_cache_new(log, mono_time, mem, dht_get_self_secret_key(dht), KEYS_TIMEOUT, MAX_KEYS_PER_SLOT); - if (onion_a->shared_keys_recv == nullptr) { - // cppcheck-suppress mismatchAllocDealloc - kill_onion_announce(onion_a); - return nullptr; - } - networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, &handle_announce_request, onion_a); networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST_OLD, &handle_announce_request_old, onion_a); networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, &handle_data_request, onion_a); diff --git a/toxcore/onion_client.c b/toxcore/onion_client.c index b47a6522..1aa42d25 100644 --- a/toxcore/onion_client.c +++ b/toxcore/onion_client.c @@ -20,6 +20,7 @@ #include "crypto_core.h" #include "group_announce.h" #include "group_onion_announce.h" +#include "list.h" #include "logger.h" #include "mem.h" #include "mono_time.h" @@ -94,12 +95,12 @@ struct Onion_Friend { Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; uint8_t last_pinged_index; - recv_tcp_relay_cb *tcp_relay_node_callback; - void *tcp_relay_node_callback_object; + recv_tcp_relay_cb *_Nullable tcp_relay_node_callback; + void *_Nullable tcp_relay_node_callback_object; uint32_t tcp_relay_node_callback_number; - onion_dht_pk_cb *dht_pk_callback; - void *dht_pk_callback_object; + onion_dht_pk_cb *_Nullable dht_pk_callback; + void *_Nullable dht_pk_callback_object; uint32_t dht_pk_callback_number; uint8_t gc_data[GCA_MAX_DATA_LENGTH]; @@ -111,21 +112,24 @@ struct Onion_Friend { static const Onion_Friend empty_onion_friend = {false}; typedef struct Onion_Data_Handler { - oniondata_handler_cb *function; - void *object; + oniondata_handler_cb *_Nullable function; + void *_Nullable object; } Onion_Data_Handler; struct Onion_Client { - const Mono_Time *mono_time; - const Logger *logger; - const Random *rng; - const Memory *mem; + const Mono_Time *_Nonnull mono_time; + const Logger *_Nonnull logger; + const Random *_Nonnull rng; + const Memory *_Nonnull mem; - DHT *dht; - Net_Crypto *c; - Networking_Core *net; - Onion_Friend *friends_list; - uint16_t num_friends; + DHT *_Nonnull dht; + Net_Crypto *_Nonnull c; + Networking_Core *_Nonnull net; + Onion_Friend *_Nullable friends_list; + uint32_t friends_list_capacity; + uint32_t num_friends; + + BS_List friends_lookup; Onion_Node clients_announce_list[MAX_ONION_CLIENTS_ANNOUNCE]; uint64_t last_announce; @@ -149,7 +153,7 @@ struct Onion_Client { Node_format path_nodes_bs[MAX_PATH_NODES]; uint16_t path_nodes_index_bs; - Ping_Array *announce_ping_array; + Ping_Array *_Nonnull announce_ping_array; uint8_t last_pinged_index; Onion_Data_Handler onion_data_handlers[256]; @@ -159,16 +163,16 @@ struct Onion_Client { unsigned int onion_connected; bool udp_connected; - onion_group_announce_cb *group_announce_response; - void *group_announce_response_user_data; + onion_group_announce_cb *_Nullable group_announce_response; + void *_Nullable group_announce_response_user_data; }; -uint16_t onion_get_friend_count(const Onion_Client *const onion_c) +uint32_t onion_get_friend_count(const Onion_Client *const onion_c) { return onion_c->num_friends; } -Onion_Friend *onion_get_friend(const Onion_Client *const onion_c, uint16_t friend_num) +Onion_Friend *onion_get_friend(const Onion_Client *const onion_c, uint32_t friend_num) { return &onion_c->friends_list[friend_num]; } @@ -954,9 +958,9 @@ static int handle_announce_response(void *_Nonnull object, const IP_Port *_Nonnu return 1; } - uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; - IP_Port ip_port; - uint32_t path_num; + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE] = {0}; + IP_Port ip_port = {0}; + uint32_t path_num = 0; const uint32_t num = check_sendback(onion_c, packet + 1, public_key, &ip_port, &path_num); if (num > onion_c->num_friends) { @@ -1001,6 +1005,11 @@ static int handle_announce_response(void *_Nonnull object, const IP_Port *_Nonnu } uint16_t len_nodes = 0; + + if (plain_size <= 1 + ONION_PING_ID_SIZE) { + return 1; + } + const uint8_t nodes_count = plain[1 + ONION_PING_ID_SIZE]; if (nodes_count > 0) { @@ -1056,9 +1065,9 @@ static int handle_announce_response_old(void *_Nonnull object, const IP_Port *_N const uint16_t len_nodes = length - ONION_ANNOUNCE_RESPONSE_MIN_SIZE; - uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; - IP_Port ip_port; - uint32_t path_num; + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE] = {0}; + IP_Port ip_port = {0}; + uint32_t path_num = 0; const uint32_t num = check_sendback(onion_c, packet + 1, public_key, &ip_port, &path_num); if (num > onion_c->num_friends) { @@ -1378,7 +1387,7 @@ static int send_dht_dhtpk(const Onion_Client *_Nonnull onion_c, int friend_num, onion_c->mem, onion_c->rng, dht_get_self_public_key(onion_c->dht), dht_get_self_secret_key(onion_c->dht), packet_data, onion_c->friends_list[friend_num].dht_public_key, temp, temp_size, CRYPTO_PACKET_DHTPK); assert(len <= UINT16_MAX); - const Packet packet = {packet_data, (uint16_t)len}; + const Net_Packet packet = {packet_data, (uint16_t)len}; if (len == -1) { return -1; @@ -1426,7 +1435,7 @@ static int handle_dht_dhtpk(void *_Nonnull object, const IP_Port *_Nonnull sourc * return the number of packets sent on success * return -1 on failure. */ -static int send_dhtpk_announce(Onion_Client *_Nonnull onion_c, uint16_t friend_num, uint8_t onion_dht_both) +static int send_dhtpk_announce(Onion_Client *_Nonnull onion_c, uint32_t friend_num, uint8_t onion_dht_both) { if (friend_num >= onion_c->num_friends) { return -1; @@ -1481,17 +1490,7 @@ static int send_dhtpk_announce(Onion_Client *_Nonnull onion_c, uint16_t friend_n */ int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key) { - for (unsigned int i = 0; i < onion_c->num_friends; ++i) { - if (!onion_c->friends_list[i].is_valid) { - continue; - } - - if (pk_equal(public_key, onion_c->friends_list[i].real_public_key)) { - return i; - } - } - - return -1; + return bs_list_find(&onion_c->friends_lookup, public_key); } /** @brief Set the size of the friend list to num. @@ -1504,16 +1503,28 @@ static int realloc_onion_friends(Onion_Client *_Nonnull onion_c, uint32_t num) if (num == 0) { mem_delete(onion_c->mem, onion_c->friends_list); onion_c->friends_list = nullptr; + onion_c->friends_list_capacity = 0; return 0; } - Onion_Friend *newonion_friends = (Onion_Friend *)mem_vrealloc(onion_c->mem, onion_c->friends_list, num, sizeof(Onion_Friend)); + if (num <= onion_c->friends_list_capacity) { + return 0; + } + + // Geometric growth: Double the capacity or set to num if starting. + uint32_t new_capacity = onion_c->friends_list_capacity == 0 ? num : onion_c->friends_list_capacity * 2; + if (new_capacity < num) { + new_capacity = num; + } + + Onion_Friend *newonion_friends = (Onion_Friend *)mem_vrealloc(onion_c->mem, onion_c->friends_list, new_capacity, sizeof(Onion_Friend)); if (newonion_friends == nullptr) { return -1; } onion_c->friends_list = newonion_friends; + onion_c->friends_list_capacity = new_capacity; return 0; } @@ -1530,9 +1541,9 @@ int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key) return num; } - unsigned int index = -1; + uint32_t index = (uint32_t) -1; - for (unsigned int i = 0; i < onion_c->num_friends; ++i) { + for (uint32_t i = 0; i < onion_c->num_friends; ++i) { if (!onion_c->friends_list[i].is_valid) { index = i; break; @@ -1553,6 +1564,12 @@ int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key) memcpy(onion_c->friends_list[index].real_public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); crypto_new_keypair(onion_c->rng, onion_c->friends_list[index].temp_public_key, onion_c->friends_list[index].temp_secret_key); + + if (!bs_list_add(&onion_c->friends_lookup, public_key, index)) { + LOGGER_ERROR(onion_c->logger, "Failed to add friend to lookup list (index: %u)", index); + return -1; + } + return index; } @@ -1575,8 +1592,12 @@ int onion_delfriend(Onion_Client *onion_c, int friend_num) #endif /* 0 */ + if (!bs_list_remove(&onion_c->friends_lookup, onion_c->friends_list[friend_num].real_public_key, friend_num)) { + LOGGER_ERROR(onion_c->logger, "Failed to remove friend from lookup list (index: %d)", friend_num); + } + crypto_memzero(&onion_c->friends_list[friend_num], sizeof(Onion_Friend)); - unsigned int i; + uint32_t i; for (i = onion_c->num_friends; i != 0; --i) { if (onion_c->friends_list[i - 1].is_valid) { @@ -1584,10 +1605,7 @@ int onion_delfriend(Onion_Client *onion_c, int friend_num) } } - if (onion_c->num_friends != i) { - onion_c->num_friends = i; - realloc_onion_friends(onion_c, onion_c->num_friends); - } + onion_c->num_friends = i; return friend_num; } @@ -1752,7 +1770,7 @@ static void populate_path_nodes(Onion_Client *_Nonnull onion_c) /* Max exponent when calculating the announce request interval */ #define MAX_RUN_COUNT_EXPONENT 12 -static void do_friend(Onion_Client *_Nonnull onion_c, uint16_t friendnum) +static void do_friend(Onion_Client *_Nonnull onion_c, uint32_t friendnum) { if (friendnum >= onion_c->num_friends) { return; @@ -2088,7 +2106,7 @@ static bool onion_isconnected(Onion_Client *_Nonnull onion_c) static void reset_friend_run_counts(Onion_Client *_Nonnull onion_c) { - for (uint16_t i = 0; i < onion_c->num_friends; ++i) { + for (uint32_t i = 0; i < onion_c->num_friends; ++i) { Onion_Friend *o_friend = &onion_c->friends_list[i]; if (o_friend->is_valid) { @@ -2147,7 +2165,7 @@ void do_onion_client(Onion_Client *onion_c) } if (onion_connection_status(onion_c) != ONION_CONNECTION_STATUS_NONE) { - for (unsigned i = 0; i < onion_c->num_friends; ++i) { + for (uint32_t i = 0; i < onion_c->num_friends; ++i) { do_friend(onion_c, i); } } @@ -2172,12 +2190,13 @@ Onion_Client *new_onion_client(const Logger *logger, const Memory *mem, const Ra return nullptr; } - onion_c->announce_ping_array = ping_array_new(mem, ANNOUNCE_ARRAY_SIZE, ANNOUNCE_TIMEOUT); + Ping_Array *const temp_ping_array = ping_array_new(mem, ANNOUNCE_ARRAY_SIZE, ANNOUNCE_TIMEOUT); - if (onion_c->announce_ping_array == nullptr) { + if (temp_ping_array == nullptr) { mem_delete(mem, onion_c); return nullptr; } + onion_c->announce_ping_array = temp_ping_array; onion_c->mono_time = mono_time; onion_c->logger = logger; @@ -2186,6 +2205,9 @@ Onion_Client *new_onion_client(const Logger *logger, const Memory *mem, const Ra onion_c->dht = dht; onion_c->net = net; onion_c->c = c; + onion_c->friends_list_capacity = 0; + bs_list_init(&onion_c->friends_lookup, mem, CRYPTO_PUBLIC_KEY_SIZE, 0, memcmp); + new_symmetric_key(rng, onion_c->secret_symmetric_key); crypto_new_keypair(rng, onion_c->temp_public_key, onion_c->temp_secret_key); networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_announce_response, onion_c); @@ -2208,6 +2230,7 @@ void kill_onion_client(Onion_Client *onion_c) ping_array_kill(onion_c->announce_ping_array); realloc_onion_friends(onion_c, 0); + bs_list_free(&onion_c->friends_lookup); networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, nullptr, nullptr); networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE_OLD, nullptr, nullptr); networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, nullptr, nullptr); @@ -2217,3 +2240,18 @@ void kill_onion_client(Onion_Client *onion_c) crypto_memzero(onion_c, sizeof(Onion_Client)); mem_delete(mem, onion_c); } + +uint64_t onion_testonly_get_last_packet_recv(const Onion_Client *onion_c) +{ + return onion_c->last_packet_recv; +} + +void onion_testonly_get_temp_public_key(const Onion_Client *onion_c, uint8_t *public_key) +{ + memcpy(public_key, onion_c->temp_public_key, CRYPTO_PUBLIC_KEY_SIZE); +} + +void onion_testonly_get_secret_symmetric_key(const Onion_Client *onion_c, uint8_t *secret_key) +{ + memcpy(secret_key, onion_c->secret_symmetric_key, CRYPTO_SYMMETRIC_KEY_SIZE); +} diff --git a/toxcore/onion_client.h b/toxcore/onion_client.h index a61e9ac7..5b7a090b 100644 --- a/toxcore/onion_client.h +++ b/toxcore/onion_client.h @@ -63,6 +63,10 @@ #define ONION_DATA_FRIEND_REQ CRYPTO_PACKET_FRIEND_REQ #define ONION_DATA_DHTPK CRYPTO_PACKET_DHTPK +#ifdef __cplusplus +extern "C" { +#endif + typedef struct Onion_Client Onion_Client; /** @brief Add a node to the path_nodes bootstrap array. @@ -208,12 +212,21 @@ Onion_Connection_Status onion_connection_status(const Onion_Client *_Nonnull oni typedef struct Onion_Friend Onion_Friend; -uint16_t onion_get_friend_count(const Onion_Client *_Nonnull onion_c); -Onion_Friend *_Nullable onion_get_friend(const Onion_Client *_Nonnull onion_c, uint16_t friend_num); -const uint8_t *_Nullable onion_friend_get_gc_public_key(const Onion_Friend *_Nonnull onion_friend); -const uint8_t *_Nullable onion_friend_get_gc_public_key_num(const Onion_Client *_Nonnull onion_c, uint32_t num); +uint32_t onion_get_friend_count(const Onion_Client *_Nonnull onion_c); +Onion_Friend *_Nonnull onion_get_friend(const Onion_Client *_Nonnull onion_c, uint32_t friend_num); +const uint8_t *_Nonnull onion_friend_get_gc_public_key(const Onion_Friend *_Nonnull onion_friend); +const uint8_t *_Nonnull onion_friend_get_gc_public_key_num(const Onion_Client *_Nonnull onion_c, uint32_t num); void onion_friend_set_gc_public_key(Onion_Friend *_Nonnull onion_friend, const uint8_t *_Nonnull public_key); void onion_friend_set_gc_data(Onion_Friend *_Nonnull onion_friend, const uint8_t *_Nullable gc_data, uint16_t gc_data_length); bool onion_friend_is_groupchat(const Onion_Friend *_Nonnull onion_friend); +/** Unit test support functions. Do not use outside tests. */ +uint64_t onion_testonly_get_last_packet_recv(const Onion_Client *_Nonnull onion_c); +void onion_testonly_get_temp_public_key(const Onion_Client *_Nonnull onion_c, uint8_t *_Nonnull public_key); +void onion_testonly_get_secret_symmetric_key(const Onion_Client *_Nonnull onion_c, uint8_t *_Nonnull secret_key); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* C_TOXCORE_TOXCORE_ONION_CLIENT_H */ diff --git a/toxcore/onion_client_fuzz_test.cc b/toxcore/onion_client_fuzz_test.cc new file mode 100644 index 00000000..d0a5b815 --- /dev/null +++ b/toxcore/onion_client_fuzz_test.cc @@ -0,0 +1,390 @@ +#include "onion_client.h" + +#include +#include +#include +#include +#include + +#include "../testing/support/doubles/fake_sockets.hh" +#include "../testing/support/public/fuzz_data.hh" +#include "../testing/support/public/simulated_environment.hh" +#include "DHT.h" +#include "net_crypto.h" +#include "net_profile.h" +#include "network.h" + +namespace { + +using tox::test::FakeUdpSocket; +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + +template +T consume_range(Fuzz_Data &input, T min, T max) +{ + T val = input.consume_integral(); + if (max <= min) + return min; + return min + (val % (max - min + 1)); +} + +// Minimal DHT wrapper for fuzzing +class FuzzDHT { +public: + FuzzDHT(SimulatedEnvironment &env, uint16_t port) + : node_(env.create_node(port)) + , logger_(logger_new(&node_->c_memory), [](Logger *l) { logger_kill(l); }) + , mono_time_(mono_time_new( + &node_->c_memory, + [](void *ud) -> uint64_t { + return static_cast(ud)->current_time_ms(); + }, + &env.fake_clock()), + [mem = &node_->c_memory](Mono_Time *t) { mono_time_free(mem, t); }) + , networking_(nullptr, [](Networking_Core *n) { kill_networking(n); }) + , dht_(nullptr, [](DHT *d) { kill_dht(d); }) + { + IP ip; + ip_init(&ip, true); + unsigned int error = 0; + networking_.reset(new_networking_ex( + logger_.get(), &node_->c_memory, &node_->c_network, &ip, port, port + 1, &error)); + // In fuzzing we might ignore assert, but setup should succeed + node_->endpoint = node_->node->get_primary_socket(); + + dht_.reset(new_dht(logger_.get(), &node_->c_memory, &node_->c_random, &node_->c_network, + mono_time_.get(), networking_.get(), true, true)); + } + + DHT *get_dht() { return dht_.get(); } + Networking_Core *networking() { return networking_.get(); } + Mono_Time *mono_time() { return mono_time_.get(); } + Logger *logger() { return logger_.get(); } + tox::test::ScopedToxSystem &node() { return *node_; } + FakeUdpSocket *endpoint() { return node_->endpoint; } + + static const Net_Crypto_DHT_Funcs funcs; + +private: + std::unique_ptr node_; + std::unique_ptr logger_; + std::unique_ptr> mono_time_; + std::unique_ptr networking_; + std::unique_ptr dht_; +}; + +const Net_Crypto_DHT_Funcs FuzzDHT::funcs = { + [](void *obj, const uint8_t *public_key) { + return dht_get_shared_key_sent(static_cast(obj), public_key); + }, + [](const void *obj) { return dht_get_self_public_key(static_cast(obj)); }, + [](const void *obj) { return dht_get_self_secret_key(static_cast(obj)); }, +}; + +class OnionClientFuzzer { +public: + OnionClientFuzzer(SimulatedEnvironment &env) + : env_(env) + , dht_(env, 33445) + , net_profile_(netprof_new(dht_.logger(), &dht_.node().c_memory), + [mem = &dht_.node().c_memory](Net_Profile *p) { netprof_kill(mem, p); }) + , net_crypto_(nullptr, [](Net_Crypto *c) { kill_net_crypto(c); }) + , onion_client_(nullptr, [](Onion_Client *c) { kill_onion_client(c); }) + { + TCP_Proxy_Info proxy_info = {{0}, TCP_PROXY_NONE}; + net_crypto_.reset(new_net_crypto(dht_.logger(), &dht_.node().c_memory, + &dht_.node().c_random, &dht_.node().c_network, dht_.mono_time(), dht_.networking(), + dht_.get_dht(), &FuzzDHT::funcs, &proxy_info, net_profile_.get())); + + onion_client_.reset( + new_onion_client(dht_.logger(), &dht_.node().c_memory, &dht_.node().c_random, + dht_.mono_time(), net_crypto_.get(), dht_.get_dht(), dht_.networking())); + + // Register a handler for onion data to verify reception + oniondata_registerhandler( + onion_client_.get(), 0, + [](void *, const uint8_t *, const uint8_t *, uint16_t, void *) { + // Callback hit + return 0; + }, + nullptr); + } + + void Run(Fuzz_Data &input) + { + while (!input.empty()) { + Action(input); + // Always pump the loop + do_onion_client(onion_client_.get()); + networking_poll(dht_.networking(), nullptr); + } + } + +private: + void Action(Fuzz_Data &input) + { + uint8_t op = input.consume_integral(); + switch (op % 12) { + case 0: + AddFriend(input); + break; + case 1: + DelFriend(input); + break; + case 2: + SetOnline(input); + break; + case 3: + SetDHTKey(input); + break; + case 4: + AddBSNode(input); + break; + case 5: + ReceivePacket(input); + break; + case 6: + AdvanceTime(input); + break; + case 7: + SendData(input); + break; + case 8: + GetFriendIP(input); + break; + case 9: + BackupNodes(input); + break; + case 10: + CheckStatus(); + break; + case 11: + SetFriendTCPRelay(input); + break; + } + } + + void AddFriend(Fuzz_Data &input) + { + uint8_t pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(&dht_.node().c_random, pk, sk); + + int friend_num = onion_addfriend(onion_client_.get(), pk); + if (friend_num != -1) { + friends_.push_back(friend_num); + friend_keys_[friend_num] = {std::vector(pk, pk + CRYPTO_PUBLIC_KEY_SIZE), + std::vector(sk, sk + CRYPTO_SECRET_KEY_SIZE)}; + } + } + + void DelFriend(Fuzz_Data &input) + { + if (friends_.empty()) + return; + size_t idx = consume_range(input, 0, friends_.size() - 1); + int friend_num = friends_[idx]; + onion_delfriend(onion_client_.get(), friend_num); + friends_.erase(friends_.begin() + idx); + friend_keys_.erase(friend_num); + } + + void SetOnline(Fuzz_Data &input) + { + if (friends_.empty()) + return; + int friend_num = friends_[consume_range(input, 0, friends_.size() - 1)]; + bool online = input.consume_integral(); + onion_set_friend_online(onion_client_.get(), friend_num, online); + } + + void SetDHTKey(Fuzz_Data &input) + { + if (friends_.empty()) + return; + int friend_num = friends_[consume_range(input, 0, friends_.size() - 1)]; + CONSUME_OR_RETURN(const uint8_t *pk, input, CRYPTO_PUBLIC_KEY_SIZE); + onion_set_friend_dht_pubkey(onion_client_.get(), friend_num, pk); + } + + void AddBSNode(Fuzz_Data &input) + { + IP_Port ip_port; + ip_init(&ip_port.ip, 1); + ip_port.port = input.consume_integral(); + CONSUME_OR_RETURN(const uint8_t *pk, input, CRYPTO_PUBLIC_KEY_SIZE); + onion_add_bs_path_node(onion_client_.get(), &ip_port, pk); + } + + void ReceivePacket(Fuzz_Data &input) + { + if (input.remaining_bytes() < 1) + return; + + std::vector packet; + uint8_t type = input.consume_integral(); + + if (type < 50) { + size_t size = consume_range(input, 10, 500); + if (input.remaining_bytes() >= size) { + const uint8_t *ptr = input.consume("ReceivePacket", size); + if (ptr) + packet.assign(ptr, ptr + size); + } + } else if (type >= 50 && type < 150) { + // Generate valid NET_PACKET_ANNOUNCE_RESPONSE + uint8_t secret_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + onion_testonly_get_secret_symmetric_key(onion_client_.get(), secret_key); + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_bytes(&dht_.node().c_random, nonce, sizeof(nonce)); + + size_t data_len = consume_range(input, 1, 100); + std::vector plaintext = input.consume_bytes(data_len); + if (plaintext.empty()) + plaintext = {1, 2, 3}; // fallback + + std::vector ciphertext(plaintext.size() + CRYPTO_MAC_SIZE); + int len = encrypt_data_symmetric(&dht_.node().c_memory, secret_key, nonce, + plaintext.data(), plaintext.size(), ciphertext.data()); + + if (len != -1) { + packet.push_back(NET_PACKET_ANNOUNCE_RESPONSE); + packet.insert(packet.end(), nonce, nonce + CRYPTO_NONCE_SIZE); + packet.insert(packet.end(), ciphertext.begin(), ciphertext.end()); + } + + } else if (type >= 150 && !friends_.empty()) { + // Valid onion data response injection + int friend_num = friends_[consume_range(input, 0, friends_.size() - 1)]; + const auto &keys = friend_keys_[friend_num]; + + uint8_t sender_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t sender_temp_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(&dht_.node().c_random, sender_temp_pk, sender_temp_sk); + + uint8_t nonce[CRYPTO_NONCE_SIZE]; + random_bytes(&dht_.node().c_random, nonce, sizeof(nonce)); + + // Inner packet - Let fuzzer choose type + uint8_t inner_type = input.consume_integral(); + std::vector inner_data = {inner_type}; + + size_t data_len = consume_range(input, 1, 100); + std::vector rand_data = input.consume_bytes(data_len); + inner_data.insert(inner_data.end(), rand_data.begin(), rand_data.end()); + + std::vector inner_ciphertext(inner_data.size() + CRYPTO_MAC_SIZE); + int len = encrypt_data(&dht_.node().c_memory, dht_get_self_public_key(dht_.get_dht()), + keys.second.data(), nonce, inner_data.data(), inner_data.size(), + inner_ciphertext.data()); + if (len == -1) + return; + + // Outer packet content: Sender Real PK + Inner Ciphertext + std::vector outer_plaintext(CRYPTO_PUBLIC_KEY_SIZE + inner_ciphertext.size()); + memcpy(outer_plaintext.data(), keys.first.data(), CRYPTO_PUBLIC_KEY_SIZE); + memcpy(outer_plaintext.data() + CRYPTO_PUBLIC_KEY_SIZE, inner_ciphertext.data(), + inner_ciphertext.size()); + + uint8_t receiver_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; + onion_testonly_get_temp_public_key(onion_client_.get(), receiver_temp_pk); + + std::vector outer_ciphertext(outer_plaintext.size() + CRYPTO_MAC_SIZE); + len = encrypt_data(&dht_.node().c_memory, receiver_temp_pk, sender_temp_sk, nonce, + outer_plaintext.data(), outer_plaintext.size(), outer_ciphertext.data()); + if (len == -1) + return; + + // Final packet: Type + Nonce + Sender Temp PK + Outer Ciphertext + packet.push_back(NET_PACKET_ONION_DATA_RESPONSE); + packet.insert(packet.end(), nonce, nonce + CRYPTO_NONCE_SIZE); + packet.insert(packet.end(), sender_temp_pk, sender_temp_pk + CRYPTO_PUBLIC_KEY_SIZE); + packet.insert(packet.end(), outer_ciphertext.begin(), outer_ciphertext.end()); + } else { + packet = input.consume_remaining_bytes(); + } + + if (packet.empty()) + return; + + IP_Port from; + ip_init(&from.ip, 1); // loopback + from.port = 12345; // arbitrary + + dht_.endpoint()->push_packet(packet, from); + } + + void AdvanceTime(Fuzz_Data &input) + { + uint32_t ms = input.consume_integral_in_range(1, 10000); + env_.fake_clock().advance(ms); + } + + void SendData(Fuzz_Data &input) + { + if (friends_.empty()) + return; + int friend_num = friends_[consume_range(input, 0, friends_.size() - 1)]; + + uint16_t length = consume_range(input, 1, 1024); + if (input.remaining_bytes() >= length) { + const uint8_t *ptr = input.consume("SendData", length); + if (ptr) + send_onion_data(onion_client_.get(), friend_num, ptr, length); + } + } + + void GetFriendIP(Fuzz_Data &input) + { + if (friends_.empty()) + return; + int friend_num = friends_[consume_range(input, 0, friends_.size() - 1)]; + IP_Port ip_port; + onion_getfriendip(onion_client_.get(), friend_num, &ip_port); + } + + void BackupNodes(Fuzz_Data &input) + { + Node_format nodes[10]; + onion_backup_nodes(onion_client_.get(), nodes, 10); + } + + void CheckStatus() { onion_connection_status(onion_client_.get()); } + + void SetFriendTCPRelay(Fuzz_Data &input) + { + if (friends_.empty()) + return; + int friend_num = friends_[input.consume_integral_in_range(0, friends_.size() - 1)]; + // Just setting a dummy callback + recv_tcp_relay_handler( + onion_client_.get(), friend_num, + [](void *, uint32_t, const IP_Port *, const uint8_t *) { return 0; }, this, 0); + } + + SimulatedEnvironment &env_; + FuzzDHT dht_; + std::unique_ptr> net_profile_; + std::unique_ptr net_crypto_; + std::unique_ptr onion_client_; + std::vector friends_; + std::map, std::vector>> friend_keys_; +}; + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size == 0) + return 0; + + SimulatedEnvironment env; + OnionClientFuzzer fuzzer(env); + Fuzz_Data input(data, size); + fuzzer.Run(input); + + return 0; +} diff --git a/toxcore/onion_client_test.cc b/toxcore/onion_client_test.cc new file mode 100644 index 00000000..3aa09fd1 --- /dev/null +++ b/toxcore/onion_client_test.cc @@ -0,0 +1,579 @@ +#include "onion_client.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../testing/support/public/simulated_environment.hh" +#include "DHT_test_util.hh" +#include "crypto_core.h" +#include "logger.h" +#include "mono_time.h" +#include "net_crypto.h" +#include "net_profile.h" +#include "network.h" +#include "onion.h" +#include "onion_announce.h" + +namespace { + +using namespace tox::test; + +// --- Helper Class --- + +template +class OnionTestNode { +public: + OnionTestNode(SimulatedEnvironment &env, uint16_t port) + : dht_wrapper_(env, port) + , net_profile_(netprof_new(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory), + [mem = &dht_wrapper_.node().c_memory](Net_Profile *p) { netprof_kill(mem, p); }) + , net_crypto_(nullptr, [](Net_Crypto *c) { kill_net_crypto(c); }) + , onion_client_(nullptr, [](Onion_Client *c) { kill_onion_client(c); }) + { + // Setup NetCrypto + TCP_Proxy_Info proxy_info = {{0}, TCP_PROXY_NONE}; + net_crypto_.reset(new_net_crypto(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory, + &dht_wrapper_.node().c_random, &dht_wrapper_.node().c_network, dht_wrapper_.mono_time(), + dht_wrapper_.networking(), dht_wrapper_.get_dht(), &DHTWrapper::funcs, &proxy_info, + net_profile_.get())); + + // Setup Onion Client + onion_client_.reset(new_onion_client(dht_wrapper_.logger(), &dht_wrapper_.node().c_memory, + &dht_wrapper_.node().c_random, dht_wrapper_.mono_time(), net_crypto_.get(), + dht_wrapper_.get_dht(), dht_wrapper_.networking())); + } + + Onion_Client *get_onion_client() { return onion_client_.get(); } + Net_Crypto *get_net_crypto() { return net_crypto_.get(); } + DHT *get_dht() { return dht_wrapper_.get_dht(); } + Logger *get_logger() { return dht_wrapper_.logger(); } + const uint8_t *dht_public_key() const { return dht_wrapper_.dht_public_key(); } + const uint8_t *real_public_key() const { return nc_get_self_public_key(net_crypto_.get()); } + const Random *get_random() { return &dht_wrapper_.node().c_random; } + + IP_Port get_ip_port() const { return dht_wrapper_.get_ip_port(); } + + void poll() + { + dht_wrapper_.poll(); + do_net_crypto(net_crypto_.get(), nullptr); + do_onion_client(onion_client_.get()); + } + + ~OnionTestNode(); + +private: + DHTWrapper dht_wrapper_; + std::unique_ptr> net_profile_; + std::unique_ptr net_crypto_; + std::unique_ptr onion_client_; +}; + +template +OnionTestNode::~OnionTestNode() = default; + +using OnionNode = OnionTestNode; + +class OnionClientTest : public ::testing::Test { +public: + static void print_log(void *context, Logger_Level level, const char *file, uint32_t line, + const char *func, const char *message, void *userdata) + { + fprintf(stderr, "[%d] %s:%u %s: %s\n", level, file, line, func, message); + } + +protected: + SimulatedEnvironment env; +}; + +TEST_F(OnionClientTest, CreationAndDestruction) +{ + OnionNode alice(env, 33445); + EXPECT_NE(alice.get_onion_client(), nullptr); +} + +TEST_F(OnionClientTest, FriendManagement) +{ + OnionNode alice(env, 33445); + uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), friend_pk, friend_sk); + + // Add Friend + int friend_num = onion_addfriend(alice.get_onion_client(), friend_pk); + ASSERT_NE(friend_num, -1); + + // Check Friend Num + EXPECT_EQ(onion_friend_num(alice.get_onion_client(), friend_pk), friend_num); + + // Add Same Friend Again + EXPECT_EQ(onion_addfriend(alice.get_onion_client(), friend_pk), friend_num); + + // Check Friend Count + EXPECT_EQ(onion_get_friend_count(alice.get_onion_client()), 1); + + // Delete Friend + EXPECT_NE(onion_delfriend(alice.get_onion_client(), friend_num), -1); + EXPECT_EQ(onion_get_friend_count(alice.get_onion_client()), 0); + + // Check Friend Num after deletion + EXPECT_EQ(onion_friend_num(alice.get_onion_client(), friend_pk), -1); + + // Delete Invalid Friend + EXPECT_EQ(onion_delfriend(alice.get_onion_client(), friend_num), -1); +} + +TEST_F(OnionClientTest, FriendStatus) +{ + OnionNode alice(env, 33445); + uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), friend_pk, friend_sk); + + int friend_num = onion_addfriend(alice.get_onion_client(), friend_pk); + ASSERT_NE(friend_num, -1); + + // Set DHT Key so we can get IP + uint8_t dht_key[CRYPTO_PUBLIC_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), dht_key, friend_sk); + EXPECT_EQ(onion_set_friend_dht_pubkey(alice.get_onion_client(), friend_num, dht_key), 0); + + uint32_t lock_token; + EXPECT_EQ( + dht_addfriend(alice.get_dht(), dht_key, nullptr, nullptr, friend_num, &lock_token), 0); + + // Set Online + EXPECT_EQ(onion_set_friend_online(alice.get_onion_client(), friend_num, true), 0); + + // Get Friend IP (should be 0 as not connected) + IP_Port ip; + EXPECT_EQ(onion_getfriendip(alice.get_onion_client(), friend_num, &ip), 0); + + // Set Offline + EXPECT_EQ(onion_set_friend_online(alice.get_onion_client(), friend_num, false), 0); + + // Invalid friend num + EXPECT_EQ(onion_set_friend_online(alice.get_onion_client(), 12345, true), -1); +} + +TEST_F(OnionClientTest, DHTKey) +{ + OnionNode alice(env, 33445); + uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), friend_pk, friend_sk); + + int friend_num = onion_addfriend(alice.get_onion_client(), friend_pk); + ASSERT_NE(friend_num, -1); + + uint8_t dht_key[CRYPTO_PUBLIC_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), dht_key, friend_sk); + + // Set DHT Key + EXPECT_EQ(onion_set_friend_dht_pubkey(alice.get_onion_client(), friend_num, dht_key), 0); + + // Get DHT Key + uint8_t retrieved_key[CRYPTO_PUBLIC_KEY_SIZE]; + EXPECT_EQ(onion_getfriend_dht_pubkey(alice.get_onion_client(), friend_num, retrieved_key), 1); + EXPECT_EQ(std::memcmp(dht_key, retrieved_key, CRYPTO_PUBLIC_KEY_SIZE), 0); + + // Invalid friend + EXPECT_EQ(onion_set_friend_dht_pubkey(alice.get_onion_client(), 12345, dht_key), -1); + EXPECT_EQ(onion_getfriend_dht_pubkey(alice.get_onion_client(), 12345, retrieved_key), 0); +} + +TEST_F(OnionClientTest, BootstrapNodes) +{ + OnionNode alice(env, 33445); + IP_Port ip_port; + ip_init(&ip_port.ip, 1); + ip_port.port = 1234; + uint8_t pk[CRYPTO_PUBLIC_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), pk, pk); + + EXPECT_TRUE(onion_add_bs_path_node(alice.get_onion_client(), &ip_port, pk)); + + Node_format nodes[MAX_ONION_CLIENTS]; + uint16_t count = onion_backup_nodes(alice.get_onion_client(), nodes, MAX_ONION_CLIENTS); + EXPECT_GE(count, 0); +} + +TEST_F(OnionClientTest, ConnectionStatus) +{ + OnionNode alice(env, 33445); + Onion_Connection_Status status = onion_connection_status(alice.get_onion_client()); + EXPECT_GE(status, ONION_CONNECTION_STATUS_NONE); + EXPECT_LE(status, ONION_CONNECTION_STATUS_UDP); +} + +TEST_F(OnionClientTest, GroupChatHelpers) +{ + OnionNode alice(env, 33445); + uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t friend_sk[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), friend_pk, friend_sk); + + int friend_num = onion_addfriend(alice.get_onion_client(), friend_pk); + ASSERT_NE(friend_num, -1); + + Onion_Friend *friend_obj = onion_get_friend(alice.get_onion_client(), friend_num); + EXPECT_NE(friend_obj, nullptr); + + // Test Group Chat Public Key + uint8_t gc_pk[CRYPTO_PUBLIC_KEY_SIZE]; + crypto_new_keypair(alice.get_random(), gc_pk, friend_sk); + + onion_friend_set_gc_public_key(friend_obj, gc_pk); + const uint8_t *retrieved_gc_pk = onion_friend_get_gc_public_key(friend_obj); + EXPECT_EQ(std::memcmp(gc_pk, retrieved_gc_pk, CRYPTO_PUBLIC_KEY_SIZE), 0); + + // Test Group Chat Flag + EXPECT_FALSE(onion_friend_is_groupchat(friend_obj)); + uint8_t data[] = {1, 2, 3}; + onion_friend_set_gc_data(friend_obj, data, sizeof(data)); + EXPECT_TRUE(onion_friend_is_groupchat(friend_obj)); +} + +TEST_F(OnionClientTest, OOBReadInHandleAnnounceResponse) +{ + OnionNode alice(env, 33445); + logger_callback_log(alice.get_logger(), OnionClientTest::print_log, nullptr, nullptr); + WrappedDHT bob(env, 12345); + FakeUdpSocket *bob_socket = bob.node().endpoint; + + IP_Port bob_ip = bob.get_ip_port(); + const uint8_t *bob_pk = bob.dht_public_key(); + const uint8_t *bob_sk = bob.dht_secret_key(); + + // Bootstrap Alice to Bob + dht_bootstrap(alice.get_dht(), &bob_ip, bob_pk); + + // Add Bob as Onion Bootstrap node + onion_add_bs_path_node(alice.get_onion_client(), &bob_ip, bob_pk); + + // Get internal state + uint64_t initial_recv_time = onion_testonly_get_last_packet_recv(alice.get_onion_client()); + + // Setup Memory + Tox_Memory mem_struct = env.fake_memory().get_c_memory(); + const Memory *mem = &mem_struct; + + // Observer + bool triggered = false; + bob_socket->set_recv_observer([&](const std::vector &data, const IP_Port &from) { + if (triggered) + return; + if (data.size() < 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) + return; + + // Layer 1 + if (data[0] != NET_PACKET_ONION_SEND_INITIAL) + return; + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t nonce[CRYPTO_NONCE_SIZE]; + memcpy(nonce, data.data() + 1, CRYPTO_NONCE_SIZE); + const uint8_t *ephem_pk = data.data() + 1 + CRYPTO_NONCE_SIZE; + + encrypt_precompute(ephem_pk, bob_sk, shared_key); + + std::vector decrypted1( + data.size() - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE)); + int dlen = decrypt_data_symmetric(mem, shared_key, nonce, + data.data() + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + data.size() - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), decrypted1.data()); + if (dlen <= 0) + return; + + // Decrypted 1: [IP] [PK] [Encrypted 2] + size_t offset = SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE; + if (static_cast(dlen) <= offset + CRYPTO_MAC_SIZE) + return; + ephem_pk = decrypted1.data() + SIZE_IPPORT; + + encrypt_precompute(ephem_pk, bob_sk, shared_key); + + std::vector decrypted2(dlen - offset - CRYPTO_MAC_SIZE); + dlen = decrypt_data_symmetric( + mem, shared_key, nonce, decrypted1.data() + offset, dlen - offset, decrypted2.data()); + if (dlen <= 0) + return; + + // Decrypted 2: [IP] [PK] [Encrypted 3] + if (static_cast(dlen) <= offset + CRYPTO_MAC_SIZE) + return; + ephem_pk = decrypted2.data() + SIZE_IPPORT; + encrypt_precompute(ephem_pk, bob_sk, shared_key); + + std::vector decrypted3(dlen - offset - CRYPTO_MAC_SIZE); + dlen = decrypt_data_symmetric( + mem, shared_key, nonce, decrypted2.data() + offset, dlen - offset, decrypted3.data()); + if (dlen <= 0) + return; + + // Decrypted 3: [IP] [Data] + size_t data_offset = SIZE_IPPORT; + if (static_cast(dlen) <= data_offset) + return; + uint8_t *req = decrypted3.data() + data_offset; + size_t req_len = dlen - data_offset; + + // Announce Request: [131] [Nonce] [Alice PK] [Encrypted] + if (req[0] != 0x87 && req[0] != 0x83) + return; + + uint8_t req_nonce[CRYPTO_NONCE_SIZE]; + memcpy(req_nonce, req + 1, CRYPTO_NONCE_SIZE); + uint8_t alice_pk[CRYPTO_PUBLIC_KEY_SIZE]; + memcpy(alice_pk, req + 1 + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + + uint8_t *req_enc = req + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE; + size_t req_enc_len = req_len - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); + + std::vector req_plain(req_enc_len - CRYPTO_MAC_SIZE); + int plen = decrypt_data( + mem, alice_pk, bob_sk, req_nonce, req_enc, req_enc_len, req_plain.data()); + + if (plen <= 0) + return; + + // Payload: [Ping ID (32)] [Search ID (32)] [Data PK (32)] [Sendback (Rest)] + size_t sendback_offset = 32 + 32 + 32; + if (static_cast(plen) < sendback_offset + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH) + return; + uint8_t *sendback = req_plain.data() + sendback_offset; + size_t sendback_len = ONION_ANNOUNCE_SENDBACK_DATA_LENGTH; + + // Construct Malicious Response + std::vector resp; + resp.push_back(NET_PACKET_ANNOUNCE_RESPONSE); + resp.insert(resp.end(), sendback, sendback + sendback_len); + + uint8_t resp_nonce[CRYPTO_NONCE_SIZE]; + random_nonce(alice.get_random(), resp_nonce); + resp.insert(resp.end(), resp_nonce, resp_nonce + CRYPTO_NONCE_SIZE); + + // Encrypted Payload: [is_stored (1)] [ping_id (32)] + // Total 33 bytes. OMIT count. + std::vector payload(33, 0); + + std::vector ciphertext(33 + CRYPTO_MAC_SIZE); + encrypt_data(mem, alice_pk, bob_sk, resp_nonce, payload.data(), 33, ciphertext.data()); + + resp.insert(resp.end(), ciphertext.begin(), ciphertext.end()); + + // Send to Alice + bob_socket->sendto(resp.data(), resp.size(), &from); + triggered = true; + }); + + // Run simulation + for (int i = 0; i < 200; ++i) { + env.advance_time(50); + alice.poll(); + bob.poll(); + if (triggered) { + // Give Alice time to process the malicious packet + env.advance_time(50); + alice.poll(); + break; + } + } + + ASSERT_TRUE(triggered) << "Failed to trigger vulnerability (Alice didn't send announce request " + "or we failed to parse it)"; + + // Check if the packet was accepted + // If accepted, last_packet_recv should be updated + uint64_t final_recv_time = onion_testonly_get_last_packet_recv(alice.get_onion_client()); + + // IF the vulnerability is present, the code accepts the packet and updates last_packet_recv. + // We want the test to FAIL if vulnerability is present. + // So we Assert that the packet was NOT accepted. + EXPECT_EQ(initial_recv_time, final_recv_time) + << "Vulnerability Present: Malformed packet was accepted!"; +} + +TEST_F(OnionClientTest, DISABLED_IntegerOverflowNumFriends) +{ + OnionNode alice(env, 33445); + uint8_t friend_pk[CRYPTO_PUBLIC_KEY_SIZE]; + std::memset(friend_pk, 0, sizeof(friend_pk)); + + // Add 65536 friends to trigger integer overflow of uint16_t num_friends + // This loop demonstrates that we can add enough friends to wrap the counter. + for (int i = 0; i < 65536; ++i) { + // Ensure unique public key + std::memcpy(friend_pk, &i, sizeof(int)); + + int res = onion_addfriend(alice.get_onion_client(), friend_pk); + if (res == -1) { + FAIL() << "Failed to add friend " << i << ". May be out of memory."; + } + } + + // After 65536 adds, num_friends should be 65536 (no overflow with uint32_t) + EXPECT_EQ(onion_get_friend_count(alice.get_onion_client()), 65536); + + // Add one more friend with a GUARANTEED unique key. + // Previous keys only modified first 4 bytes. + // Setting the last byte ensures uniqueness against all 65536 previous keys. + std::memset(friend_pk, 0, sizeof(friend_pk)); + friend_pk[CRYPTO_PUBLIC_KEY_SIZE - 1] = 1; + + int res = onion_addfriend(alice.get_onion_client(), friend_pk); + EXPECT_EQ(res, 65536) << "Failed to add extra friend. Got index: " << res; + EXPECT_EQ(onion_get_friend_count(alice.get_onion_client()), 65537); + + // Now demonstrate access is safe. + Onion_Friend *f = onion_get_friend(alice.get_onion_client(), 65536); + // Dereferencing 'f' triggers ASAN error if OOB. + onion_friend_get_gc_public_key(f); +} + +TEST_F(OnionClientTest, OnionAnnounceResponse_TooShort) +{ + OnionNode alice(env, 33445); + logger_callback_log(alice.get_logger(), OnionClientTest::print_log, nullptr, nullptr); + WrappedDHT bob(env, 12345); + logger_callback_log(bob.logger(), OnionClientTest::print_log, nullptr, nullptr); + FakeUdpSocket *bob_socket = bob.node().endpoint; + + IP_Port bob_ip = bob.get_ip_port(); + const uint8_t *bob_pk = bob.dht_public_key(); + const uint8_t *bob_sk = bob.dht_secret_key(); + + dht_bootstrap(alice.get_dht(), &bob_ip, bob_pk); + onion_add_bs_path_node(alice.get_onion_client(), &bob_ip, bob_pk); + + uint64_t initial_recv_time = onion_testonly_get_last_packet_recv(alice.get_onion_client()); + bool triggered = false; + + // Setup Memory + Tox_Memory mem_struct = env.fake_memory().get_c_memory(); + const Memory *mem = &mem_struct; + + bob_socket->set_recv_observer([&](const std::vector &data, const IP_Port &from) { + if (triggered) + return; + if (data.size() < 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) + return; + + if (data[0] != NET_PACKET_ONION_SEND_INITIAL) + return; + + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t nonce[CRYPTO_NONCE_SIZE]; + memcpy(nonce, data.data() + 1, CRYPTO_NONCE_SIZE); + const uint8_t *ephem_pk = data.data() + 1 + CRYPTO_NONCE_SIZE; + + encrypt_precompute(ephem_pk, bob_sk, shared_key); + + std::vector decrypted1( + data.size() - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE)); + int dlen = decrypt_data_symmetric(mem, shared_key, nonce, + data.data() + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, + data.size() - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), decrypted1.data()); + if (dlen <= 0) + return; + + size_t offset = SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE; + if (static_cast(dlen) <= offset + CRYPTO_MAC_SIZE) + return; + ephem_pk = decrypted1.data() + SIZE_IPPORT; + + encrypt_precompute(ephem_pk, bob_sk, shared_key); + + std::vector decrypted2(dlen - offset - CRYPTO_MAC_SIZE); + dlen = decrypt_data_symmetric( + mem, shared_key, nonce, decrypted1.data() + offset, dlen - offset, decrypted2.data()); + if (dlen <= 0) + return; + + if (static_cast(dlen) <= offset + CRYPTO_MAC_SIZE) + return; + ephem_pk = decrypted2.data() + SIZE_IPPORT; + encrypt_precompute(ephem_pk, bob_sk, shared_key); + + std::vector decrypted3(dlen - offset - CRYPTO_MAC_SIZE); + dlen = decrypt_data_symmetric( + mem, shared_key, nonce, decrypted2.data() + offset, dlen - offset, decrypted3.data()); + if (dlen <= 0) + return; + + size_t data_offset = SIZE_IPPORT; + if (static_cast(dlen) <= data_offset) + return; + uint8_t *req = decrypted3.data() + data_offset; + size_t req_len = dlen - data_offset; + + if (req[0] != 0x87 && req[0] != 0x83) + return; + + uint8_t req_nonce[CRYPTO_NONCE_SIZE]; + memcpy(req_nonce, req + 1, CRYPTO_NONCE_SIZE); + uint8_t alice_pk[CRYPTO_PUBLIC_KEY_SIZE]; + memcpy(alice_pk, req + 1 + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + + uint8_t *req_enc = req + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE; + size_t req_enc_len = req_len - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); + + std::vector req_plain(req_enc_len - CRYPTO_MAC_SIZE); + int plen = decrypt_data( + mem, alice_pk, bob_sk, req_nonce, req_enc, req_enc_len, req_plain.data()); + + if (plen <= 0) + return; + + size_t sendback_offset = 32 + 32 + 32; + if (static_cast(plen) < sendback_offset + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH) + return; + uint8_t *sendback = req_plain.data() + sendback_offset; + size_t sendback_len = ONION_ANNOUNCE_SENDBACK_DATA_LENGTH; + + std::vector resp; + resp.push_back(NET_PACKET_ANNOUNCE_RESPONSE); + resp.insert(resp.end(), sendback, sendback + sendback_len); + + uint8_t resp_nonce[CRYPTO_NONCE_SIZE]; + random_nonce(alice.get_random(), resp_nonce); + resp.insert(resp.end(), resp_nonce, resp_nonce + CRYPTO_NONCE_SIZE); + + // PAYLOAD SIZE 33 (1 + 32) + // This is exactly what triggers the missing byte read for nodes_count + std::vector payload(33, 0); + + std::vector ciphertext(payload.size() + CRYPTO_MAC_SIZE); + encrypt_data( + mem, alice_pk, bob_sk, resp_nonce, payload.data(), payload.size(), ciphertext.data()); + + resp.insert(resp.end(), ciphertext.begin(), ciphertext.end()); + + bob_socket->sendto(resp.data(), resp.size(), &from); + triggered = true; + }); + + for (int i = 0; i < 200; ++i) { + env.advance_time(50); + alice.poll(); + bob.poll(); + if (triggered) { + env.advance_time(50); + alice.poll(); + break; + } + } + + ASSERT_TRUE(triggered); + EXPECT_EQ(onion_testonly_get_last_packet_recv(alice.get_onion_client()), initial_recv_time); +} + +} // namespace diff --git a/toxcore/ping.c b/toxcore/ping.c index 910a0a6f..a70bf584 100644 --- a/toxcore/ping.c +++ b/toxcore/ping.c @@ -29,13 +29,13 @@ #define TIME_TO_PING 2 struct Ping { - const Mono_Time *mono_time; - const Random *rng; - const Memory *mem; - DHT *dht; - Networking_Core *net; + const Mono_Time *_Nonnull mono_time; + const Random *_Nonnull rng; + const Memory *_Nonnull mem; + DHT *_Nonnull dht; + Networking_Core *_Nonnull net; - Ping_Array *ping_array; + Ping_Array *_Nonnull ping_array; Node_format to_ping[MAX_TO_PING]; uint64_t last_to_ping; }; @@ -333,12 +333,13 @@ Ping *ping_new(const Memory *mem, const Mono_Time *mono_time, const Random *rng, return nullptr; } - ping->ping_array = ping_array_new(mem, PING_NUM_MAX, PING_TIMEOUT); + Ping_Array *const ping_array = ping_array_new(mem, PING_NUM_MAX, PING_TIMEOUT); - if (ping->ping_array == nullptr) { + if (ping_array == nullptr) { mem_delete(mem, ping); return nullptr; } + ping->ping_array = ping_array; ping->mono_time = mono_time; ping->rng = rng; diff --git a/toxcore/ping_array.c b/toxcore/ping_array.c index 61e607cc..2b312ce5 100644 --- a/toxcore/ping_array.c +++ b/toxcore/ping_array.c @@ -17,15 +17,15 @@ #include "mono_time.h" typedef struct Ping_Array_Entry { - uint8_t *data; + uint8_t *_Nullable data; uint32_t length; uint64_t ping_time; uint64_t ping_id; } Ping_Array_Entry; struct Ping_Array { - const Memory *mem; - Ping_Array_Entry *entries; + const Memory *_Nonnull mem; + Ping_Array_Entry *_Nonnull entries; uint32_t last_deleted; /* number representing the next entry to be deleted. */ uint32_t last_added; /* number representing the last entry to be added. */ @@ -50,15 +50,15 @@ Ping_Array *ping_array_new(const Memory *mem, uint32_t size, uint32_t timeout) return nullptr; } - Ping_Array_Entry *entries = (Ping_Array_Entry *)mem_valloc(mem, size, sizeof(Ping_Array_Entry)); + Ping_Array_Entry *const entries = (Ping_Array_Entry *)mem_valloc(mem, size, sizeof(Ping_Array_Entry)); if (entries == nullptr) { mem_delete(mem, empty_array); return nullptr; } + empty_array->entries = entries; empty_array->mem = mem; - empty_array->entries = entries; empty_array->last_deleted = 0; empty_array->last_added = 0; empty_array->total_size = size; diff --git a/toxcore/ping_array_test.cc b/toxcore/ping_array_test.cc index cc9d0bfc..11bc61a4 100644 --- a/toxcore/ping_array_test.cc +++ b/toxcore/ping_array_test.cc @@ -1,15 +1,20 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" #include "ping_array.h" +// clang-format on #include #include +#include #include "crypto_core_test_util.hh" -#include "mem_test_util.hh" #include "mono_time.h" namespace { +using tox::test::SimulatedEnvironment; + struct Ping_Array_Deleter { void operator()(Ping_Array *arr) { ping_array_kill(arr); } }; @@ -17,59 +22,63 @@ struct Ping_Array_Deleter { using Ping_Array_Ptr = std::unique_ptr; struct Mono_Time_Deleter { - Mono_Time_Deleter(const Test_Memory &mem) + Mono_Time_Deleter(Tox_Memory mem) : mem_(mem) { } - void operator()(Mono_Time *arr) { mono_time_free(mem_, arr); } + void operator()(Mono_Time *arr) { mono_time_free(&mem_, arr); } private: - const Test_Memory &mem_; + Tox_Memory mem_; }; using Mono_Time_Ptr = std::unique_ptr; TEST(PingArray, MinimumTimeoutIsOne) { - Test_Memory mem; - EXPECT_EQ(ping_array_new(mem, 1, 0), nullptr); - EXPECT_NE(Ping_Array_Ptr(ping_array_new(mem, 1, 1)), nullptr); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + EXPECT_EQ(ping_array_new(&c_mem, 1, 0), nullptr); + EXPECT_NE(Ping_Array_Ptr(ping_array_new(&c_mem, 1, 1)), nullptr); } TEST(PingArray, MinimumArraySizeIsOne) { - Test_Memory mem; - EXPECT_EQ(ping_array_new(mem, 0, 1), nullptr); - EXPECT_NE(Ping_Array_Ptr(ping_array_new(mem, 1, 1)), nullptr); + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + EXPECT_EQ(ping_array_new(&c_mem, 0, 1), nullptr); + EXPECT_NE(Ping_Array_Ptr(ping_array_new(&c_mem, 1, 1)), nullptr); } TEST(PingArray, ArraySizeMustBePowerOfTwo) { - Test_Memory mem; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); Ping_Array_Ptr arr; - arr.reset(ping_array_new(mem, 2, 1)); + arr.reset(ping_array_new(&c_mem, 2, 1)); EXPECT_NE(arr, nullptr); - arr.reset(ping_array_new(mem, 4, 1)); + arr.reset(ping_array_new(&c_mem, 4, 1)); EXPECT_NE(arr, nullptr); - arr.reset(ping_array_new(mem, 1024, 1)); + arr.reset(ping_array_new(&c_mem, 1024, 1)); EXPECT_NE(arr, nullptr); - EXPECT_EQ(ping_array_new(mem, 1023, 1), nullptr); - EXPECT_EQ(ping_array_new(mem, 1234, 1), nullptr); + EXPECT_EQ(ping_array_new(&c_mem, 1023, 1), nullptr); + EXPECT_EQ(ping_array_new(&c_mem, 1234, 1), nullptr); } TEST(PingArray, StoredDataCanBeRetrieved) { - Test_Memory mem; - Test_Random rng; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); - Ping_Array_Ptr const arr(ping_array_new(mem, 2, 1)); - Mono_Time_Ptr const mono_time(mono_time_new(mem, nullptr, nullptr), mem); + Ping_Array_Ptr const arr(ping_array_new(&c_mem, 2, 1)); + Mono_Time_Ptr const mono_time(mono_time_new(&c_mem, nullptr, nullptr), c_mem); ASSERT_NE(mono_time, nullptr); uint64_t const ping_id = ping_array_add( - arr.get(), mono_time.get(), rng, std::vector{1, 2, 3, 4}.data(), 4); + arr.get(), mono_time.get(), &c_rng, std::vector{1, 2, 3, 4}.data(), 4); EXPECT_NE(ping_id, 0); std::vector data(4); @@ -79,15 +88,16 @@ TEST(PingArray, StoredDataCanBeRetrieved) TEST(PingArray, RetrievingDataWithTooSmallOutputBufferHasNoEffect) { - Test_Memory mem; - Test_Random rng; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); - Ping_Array_Ptr const arr(ping_array_new(mem, 2, 1)); - Mono_Time_Ptr const mono_time(mono_time_new(mem, nullptr, nullptr), mem); + Ping_Array_Ptr const arr(ping_array_new(&c_mem, 2, 1)); + Mono_Time_Ptr const mono_time(mono_time_new(&c_mem, nullptr, nullptr), c_mem); ASSERT_NE(mono_time, nullptr); uint64_t const ping_id = ping_array_add( - arr.get(), mono_time.get(), rng, (std::vector{1, 2, 3, 4}).data(), 4); + arr.get(), mono_time.get(), &c_rng, (std::vector{1, 2, 3, 4}).data(), 4); EXPECT_NE(ping_id, 0); std::vector data(4); @@ -101,15 +111,16 @@ TEST(PingArray, RetrievingDataWithTooSmallOutputBufferHasNoEffect) TEST(PingArray, ZeroLengthDataCanBeAdded) { - Test_Memory mem; - Test_Random rng; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); - Ping_Array_Ptr const arr(ping_array_new(mem, 2, 1)); - Mono_Time_Ptr const mono_time(mono_time_new(mem, nullptr, nullptr), mem); + Ping_Array_Ptr const arr(ping_array_new(&c_mem, 2, 1)); + Mono_Time_Ptr const mono_time(mono_time_new(&c_mem, nullptr, nullptr), c_mem); ASSERT_NE(mono_time, nullptr); uint8_t c = 0; - uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), rng, &c, sizeof(c)); + uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), &c_rng, &c, sizeof(c)); EXPECT_NE(ping_id, 0); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), &c, sizeof(c), ping_id), 1); @@ -117,10 +128,11 @@ TEST(PingArray, ZeroLengthDataCanBeAdded) TEST(PingArray, PingId0IsInvalid) { - Test_Memory mem; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); - Ping_Array_Ptr const arr(ping_array_new(mem, 2, 1)); - Mono_Time_Ptr const mono_time(mono_time_new(mem, nullptr, nullptr), mem); + Ping_Array_Ptr const arr(ping_array_new(&c_mem, 2, 1)); + Mono_Time_Ptr const mono_time(mono_time_new(&c_mem, nullptr, nullptr), c_mem); ASSERT_NE(mono_time, nullptr); uint8_t c = 0; @@ -130,15 +142,16 @@ TEST(PingArray, PingId0IsInvalid) // Protection against replay attacks. TEST(PingArray, DataCanOnlyBeRetrievedOnce) { - Test_Memory mem; - Test_Random rng; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); - Ping_Array_Ptr const arr(ping_array_new(mem, 2, 1)); - Mono_Time_Ptr const mono_time(mono_time_new(mem, nullptr, nullptr), mem); + Ping_Array_Ptr const arr(ping_array_new(&c_mem, 2, 1)); + Mono_Time_Ptr const mono_time(mono_time_new(&c_mem, nullptr, nullptr), c_mem); ASSERT_NE(mono_time, nullptr); uint8_t c = 0; - uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), rng, &c, sizeof(c)); + uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), &c_rng, &c, sizeof(c)); EXPECT_NE(ping_id, 0); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), &c, sizeof(c), ping_id), 1); @@ -147,15 +160,16 @@ TEST(PingArray, DataCanOnlyBeRetrievedOnce) TEST(PingArray, PingIdMustMatchOnCheck) { - Test_Memory mem; - Test_Random rng; + SimulatedEnvironment env; + auto c_mem = env.fake_memory().get_c_memory(); + auto c_rng = env.fake_random().get_c_random(); - Ping_Array_Ptr const arr(ping_array_new(mem, 1, 1)); - Mono_Time_Ptr const mono_time(mono_time_new(mem, nullptr, nullptr), mem); + Ping_Array_Ptr const arr(ping_array_new(&c_mem, 1, 1)); + Mono_Time_Ptr const mono_time(mono_time_new(&c_mem, nullptr, nullptr), c_mem); ASSERT_NE(mono_time, nullptr); uint8_t c = 0; - uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), rng, &c, sizeof(c)); + uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), &c_rng, &c, sizeof(c)); EXPECT_NE(ping_id, 0); uint64_t const bad_ping_id = ping_id == 1 ? 2 : 1; diff --git a/toxcore/shared_key_cache.c b/toxcore/shared_key_cache.c index 1e0a82b1..dc442fff 100644 --- a/toxcore/shared_key_cache.c +++ b/toxcore/shared_key_cache.c @@ -21,12 +21,12 @@ typedef struct Shared_Key { } Shared_Key; struct Shared_Key_Cache { - Shared_Key *keys; - const uint8_t *self_secret_key; + Shared_Key *_Nonnull keys; + const uint8_t *_Nonnull self_secret_key; uint64_t timeout; /** After this time (in seconds), a key is erased on the next housekeeping cycle */ - const Mono_Time *mono_time; - const Memory *mem; - const Logger *log; + const Mono_Time *_Nonnull mono_time; + const Memory *_Nonnull mem; + const Logger *_Nonnull log; uint8_t keys_per_slot; }; diff --git a/toxcore/sort_bench.cc b/toxcore/sort_bench.cc index dbb872e6..df4036a3 100644 --- a/toxcore/sort_bench.cc +++ b/toxcore/sort_bench.cc @@ -15,9 +15,9 @@ namespace { -std::pair, std::mt19937> random_vec(benchmark::State &state) +std::pair, std::minstd_rand> random_vec(benchmark::State &state) { - std::mt19937 rng; + std::minstd_rand rng; // INT_MAX-1 so later we have room to add 1 larger element if needed. std::uniform_int_distribution dist{ std::numeric_limits::min(), std::numeric_limits::max() - 1}; diff --git a/toxcore/sort_test.cc b/toxcore/sort_test.cc index 2a08e7ed..74d118c9 100644 --- a/toxcore/sort_test.cc +++ b/toxcore/sort_test.cc @@ -16,7 +16,7 @@ namespace { TEST(MergeSort, BehavesLikeStdSort) { - std::mt19937 rng; + std::minstd_rand rng; // INT_MAX-1 so later we have room to add 1 larger element if needed. std::uniform_int_distribution dist{ std::numeric_limits::min(), std::numeric_limits::max() - 1}; @@ -48,7 +48,7 @@ TEST(MergeSort, BehavesLikeStdSort) TEST(MergeSort, WorksWithNonTrivialTypes) { - std::mt19937 rng; + std::minstd_rand rng; std::uniform_int_distribution dist{ std::numeric_limits::min(), std::numeric_limits::max()}; diff --git a/toxcore/test_util_test.cc b/toxcore/test_util_test.cc index 8dc97ab5..1dc4527a 100644 --- a/toxcore/test_util_test.cc +++ b/toxcore/test_util_test.cc @@ -5,27 +5,32 @@ #include +#include "../testing/support/public/simulated_environment.hh" #include "crypto_core.h" -#include "crypto_core_test_util.hh" namespace { using ::testing::Each; using ::testing::Eq; +using tox::test::SimulatedEnvironment; TEST(CryptoCoreTestUtil, RandomBytesDoesNotTouchZeroSizeArray) { - const Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); + std::array bytes{}; for (uint32_t i = 0; i < 100; ++i) { - random_bytes(rng, bytes.data(), 0); + random_bytes(&c_rng, bytes.data(), 0); ASSERT_THAT(bytes, Each(Eq(0x00))); } } TEST(CryptoCoreTestUtil, RandomBytesFillsEntireArray) { - const Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); + std::array bytes{}; for (uint32_t size = 1; size < bytes.size(); ++size) { @@ -33,7 +38,7 @@ TEST(CryptoCoreTestUtil, RandomBytesFillsEntireArray) // Try a few times. There ought to be a non-zero byte in our randomness at // some point. for (uint32_t i = 0; i < 100; ++i) { - random_bytes(rng, bytes.data(), bytes.size()); + random_bytes(&c_rng, bytes.data(), bytes.size()); if (bytes[size - 1] != 0x00) { return true; } @@ -46,7 +51,8 @@ TEST(CryptoCoreTestUtil, RandomBytesFillsEntireArray) TEST(CryptoCoreTestUtil, RandomBytesDoesNotBufferOverrun) { - const Test_Random rng; + SimulatedEnvironment env; + auto c_rng = env.fake_random().get_c_random(); std::array bytes{}; @@ -54,7 +60,7 @@ TEST(CryptoCoreTestUtil, RandomBytesDoesNotBufferOverrun) for (uint32_t i = 0; i < 100; ++i) { for (uint32_t diff = 1; diff < sizeof(uint64_t); ++diff) { bytes = {}; - random_bytes(rng, bytes.data(), bytes.size() - diff); + random_bytes(&c_rng, bytes.data(), bytes.size() - diff); // All bytes not in the range we want to write should be 0. ASSERT_THAT(std::vector(bytes.begin() + (bytes.size() - diff), bytes.end()), Each(Eq(0x00))); diff --git a/toxcore/tox.c b/toxcore/tox.c index c81741a6..e830a25a 100644 --- a/toxcore/tox.c +++ b/toxcore/tox.c @@ -669,6 +669,10 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ return nullptr; } + const Random *const rng = sys->rng; + const Network *const ns = sys->ns; + const Memory *const mem = sys->mem; + Messenger_Options m_options = {false}; m_options.dns_enabled = !tox_options_get_experimental_disable_dns(opts); @@ -722,7 +726,7 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ m_options.local_discovery_enabled = false; } - Tox *tox = (Tox *)mem_alloc(sys->mem, sizeof(Tox)); + Tox *tox = (Tox *)mem_alloc(mem, sizeof(Tox)); if (tox == nullptr) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); @@ -753,7 +757,7 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ default: { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_TYPE); - mem_delete(sys->mem, tox); + mem_delete(mem, tox); tox_options_free(default_options); return nullptr; } @@ -764,7 +768,7 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ if (m_options.proxy_info.proxy_type != TCP_PROXY_NONE) { if (tox_options_get_proxy_port(opts) == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_PORT); - mem_delete(sys->mem, tox); + mem_delete(mem, tox); tox_options_free(default_options); return nullptr; } @@ -779,10 +783,10 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ const bool dns_enabled = !tox_options_get_experimental_disable_dns(opts); if (proxy_host == nullptr - || !addr_resolve_or_parse_ip(tox->sys.ns, tox->sys.mem, proxy_host, &m_options.proxy_info.ip_port.ip, nullptr, dns_enabled)) { + || !addr_resolve_or_parse_ip(ns, mem, proxy_host, &m_options.proxy_info.ip_port.ip, nullptr, dns_enabled)) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_HOST); // TODO(irungentoo): TOX_ERR_NEW_PROXY_NOT_FOUND if domain. - mem_delete(sys->mem, tox); + mem_delete(mem, tox); tox_options_free(default_options); return nullptr; } @@ -790,21 +794,22 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ m_options.proxy_info.ip_port.port = net_htons(tox_options_get_proxy_port(opts)); } - tox->mono_time = mono_time_new(tox->sys.mem, sys->mono_time_callback, sys->mono_time_user_data); + Mono_Time *temp_mono_time = mono_time_new(mem, sys->mono_time_callback, sys->mono_time_user_data); - if (tox->mono_time == nullptr) { + if (temp_mono_time == nullptr) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); - mem_delete(sys->mem, tox); + mem_delete(mem, tox); tox_options_free(default_options); return nullptr; } + tox->mono_time = temp_mono_time; if (tox_options_get_experimental_thread_safety(opts)) { - pthread_mutex_t *mutex = (pthread_mutex_t *)mem_alloc(sys->mem, sizeof(pthread_mutex_t)); + pthread_mutex_t *mutex = (pthread_mutex_t *)mem_alloc(mem, sizeof(pthread_mutex_t)); if (mutex == nullptr) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); - mem_delete(sys->mem, tox); + mem_delete(mem, tox); tox_options_free(default_options); return nullptr; } @@ -819,9 +824,9 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ tox_lock(tox); Messenger_Error m_error; - tox->m = new_messenger(tox->mono_time, tox->sys.mem, tox->sys.rng, tox->sys.ns, &m_options, &m_error); + Messenger *temp_m = new_messenger(tox->mono_time, mem, rng, ns, &m_options, &m_error); - if (tox->m == nullptr) { + if (temp_m == nullptr) { switch (m_error) { case MESSENGER_ERROR_PORT: case MESSENGER_ERROR_TCP_SERVER: { @@ -835,33 +840,34 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ } } - mono_time_free(tox->sys.mem, tox->mono_time); + mono_time_free(mem, tox->mono_time); tox_unlock(tox); if (tox->mutex != nullptr) { pthread_mutex_destroy(tox->mutex); } - mem_delete(sys->mem, tox->mutex); - mem_delete(sys->mem, tox); + mem_delete(mem, tox->mutex); + mem_delete(mem, tox); tox_options_free(default_options); return nullptr; } + tox->m = temp_m; - tox->m->conferences_object = new_groupchats(tox->mono_time, sys->mem, tox->m); + tox->m->conferences_object = new_groupchats(tox->mono_time, mem, tox->m); if (tox->m->conferences_object == nullptr) { kill_messenger(tox->m); - mono_time_free(tox->sys.mem, tox->mono_time); + mono_time_free(mem, tox->mono_time); tox_unlock(tox); if (tox->mutex != nullptr) { pthread_mutex_destroy(tox->mutex); } - mem_delete(sys->mem, tox->mutex); - mem_delete(sys->mem, tox); + mem_delete(mem, tox->mutex); + mem_delete(mem, tox); SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); tox_options_free(default_options); @@ -873,15 +879,15 @@ static Tox *tox_new_system(const struct Tox_Options *_Nullable options, Tox_Err_ kill_groupchats(tox->m->conferences_object); kill_messenger(tox->m); - mono_time_free(tox->sys.mem, tox->mono_time); + mono_time_free(mem, tox->mono_time); tox_unlock(tox); if (tox->mutex != nullptr) { pthread_mutex_destroy(tox->mutex); } - mem_delete(sys->mem, tox->mutex); - mem_delete(sys->mem, tox); + mem_delete(mem, tox->mutex); + mem_delete(mem, tox); SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); tox_options_free(default_options); @@ -4641,7 +4647,7 @@ bool tox_group_kick_peer(const Tox *tox, uint32_t group_number, uint32_t peer_id return false; } -const Tox_System *tox_get_system(Tox *tox) +const Tox_System *tox_get_system(const Tox *tox) { assert(tox != nullptr); return &tox->sys; diff --git a/toxcore/tox_dispatch.c b/toxcore/tox_dispatch.c index 04e0d934..0015bfcf 100644 --- a/toxcore/tox_dispatch.c +++ b/toxcore/tox_dispatch.c @@ -14,46 +14,46 @@ #include "tox_events.h" struct Tox_Dispatch { - tox_events_conference_connected_cb *conference_connected_callback; - tox_events_conference_invite_cb *conference_invite_callback; - tox_events_conference_message_cb *conference_message_callback; - tox_events_conference_peer_list_changed_cb *conference_peer_list_changed_callback; - tox_events_conference_peer_name_cb *conference_peer_name_callback; - tox_events_conference_title_cb *conference_title_callback; - tox_events_file_chunk_request_cb *file_chunk_request_callback; - tox_events_file_recv_cb *file_recv_callback; - tox_events_file_recv_chunk_cb *file_recv_chunk_callback; - tox_events_file_recv_control_cb *file_recv_control_callback; - tox_events_friend_connection_status_cb *friend_connection_status_callback; - tox_events_friend_lossless_packet_cb *friend_lossless_packet_callback; - tox_events_friend_lossy_packet_cb *friend_lossy_packet_callback; - tox_events_friend_message_cb *friend_message_callback; - tox_events_friend_name_cb *friend_name_callback; - tox_events_friend_read_receipt_cb *friend_read_receipt_callback; - tox_events_friend_request_cb *friend_request_callback; - tox_events_friend_status_cb *friend_status_callback; - tox_events_friend_status_message_cb *friend_status_message_callback; - tox_events_friend_typing_cb *friend_typing_callback; - tox_events_self_connection_status_cb *self_connection_status_callback; - tox_events_group_peer_name_cb *group_peer_name_callback; - tox_events_group_peer_status_cb *group_peer_status_callback; - tox_events_group_topic_cb *group_topic_callback; - tox_events_group_privacy_state_cb *group_privacy_state_callback; - tox_events_group_voice_state_cb *group_voice_state_callback; - tox_events_group_topic_lock_cb *group_topic_lock_callback; - tox_events_group_peer_limit_cb *group_peer_limit_callback; - tox_events_group_password_cb *group_password_callback; - tox_events_group_message_cb *group_message_callback; - tox_events_group_private_message_cb *group_private_message_callback; - tox_events_group_custom_packet_cb *group_custom_packet_callback; - tox_events_group_custom_private_packet_cb *group_custom_private_packet_callback; - tox_events_group_invite_cb *group_invite_callback; - tox_events_group_peer_join_cb *group_peer_join_callback; - tox_events_group_peer_exit_cb *group_peer_exit_callback; - tox_events_group_self_join_cb *group_self_join_callback; - tox_events_group_join_fail_cb *group_join_fail_callback; - tox_events_group_moderation_cb *group_moderation_callback; - tox_events_dht_nodes_response_cb *dht_nodes_response_callback; + tox_events_conference_connected_cb *_Nullable conference_connected_callback; + tox_events_conference_invite_cb *_Nullable conference_invite_callback; + tox_events_conference_message_cb *_Nullable conference_message_callback; + tox_events_conference_peer_list_changed_cb *_Nullable conference_peer_list_changed_callback; + tox_events_conference_peer_name_cb *_Nullable conference_peer_name_callback; + tox_events_conference_title_cb *_Nullable conference_title_callback; + tox_events_file_chunk_request_cb *_Nullable file_chunk_request_callback; + tox_events_file_recv_cb *_Nullable file_recv_callback; + tox_events_file_recv_chunk_cb *_Nullable file_recv_chunk_callback; + tox_events_file_recv_control_cb *_Nullable file_recv_control_callback; + tox_events_friend_connection_status_cb *_Nullable friend_connection_status_callback; + tox_events_friend_lossless_packet_cb *_Nullable friend_lossless_packet_callback; + tox_events_friend_lossy_packet_cb *_Nullable friend_lossy_packet_callback; + tox_events_friend_message_cb *_Nullable friend_message_callback; + tox_events_friend_name_cb *_Nullable friend_name_callback; + tox_events_friend_read_receipt_cb *_Nullable friend_read_receipt_callback; + tox_events_friend_request_cb *_Nullable friend_request_callback; + tox_events_friend_status_cb *_Nullable friend_status_callback; + tox_events_friend_status_message_cb *_Nullable friend_status_message_callback; + tox_events_friend_typing_cb *_Nullable friend_typing_callback; + tox_events_self_connection_status_cb *_Nullable self_connection_status_callback; + tox_events_group_peer_name_cb *_Nullable group_peer_name_callback; + tox_events_group_peer_status_cb *_Nullable group_peer_status_callback; + tox_events_group_topic_cb *_Nullable group_topic_callback; + tox_events_group_privacy_state_cb *_Nullable group_privacy_state_callback; + tox_events_group_voice_state_cb *_Nullable group_voice_state_callback; + tox_events_group_topic_lock_cb *_Nullable group_topic_lock_callback; + tox_events_group_peer_limit_cb *_Nullable group_peer_limit_callback; + tox_events_group_password_cb *_Nullable group_password_callback; + tox_events_group_message_cb *_Nullable group_message_callback; + tox_events_group_private_message_cb *_Nullable group_private_message_callback; + tox_events_group_custom_packet_cb *_Nullable group_custom_packet_callback; + tox_events_group_custom_private_packet_cb *_Nullable group_custom_private_packet_callback; + tox_events_group_invite_cb *_Nullable group_invite_callback; + tox_events_group_peer_join_cb *_Nullable group_peer_join_callback; + tox_events_group_peer_exit_cb *_Nullable group_peer_exit_callback; + tox_events_group_self_join_cb *_Nullable group_self_join_callback; + tox_events_group_join_fail_cb *_Nullable group_join_fail_callback; + tox_events_group_moderation_cb *_Nullable group_moderation_callback; + tox_events_dht_nodes_response_cb *_Nullable dht_nodes_response_callback; }; Tox_Dispatch *tox_dispatch_new(Tox_Err_Dispatch_New *error) diff --git a/toxcore/tox_events_fuzz_test.cc b/toxcore/tox_events_fuzz_test.cc index 556d78ae..87c89903 100644 --- a/toxcore/tox_events_fuzz_test.cc +++ b/toxcore/tox_events_fuzz_test.cc @@ -5,11 +5,17 @@ #include #include -#include "../testing/fuzzing/fuzz_support.hh" +#include "../testing/support/public/fuzz_data.hh" +#include "../testing/support/public/fuzz_helpers.hh" +#include "../testing/support/public/simulated_environment.hh" #include "tox_dispatch.h" namespace { +using tox::test::configure_fuzz_memory_source; +using tox::test::Fuzz_Data; +using tox::test::SimulatedEnvironment; + void TestUnpack(Fuzz_Data data) { // 2 bytes: size of the events data @@ -27,7 +33,9 @@ void TestUnpack(Fuzz_Data data) } // rest of the fuzz data is input for malloc - Fuzz_System sys{data}; + SimulatedEnvironment env; + auto node = env.create_node(33445); + configure_fuzz_memory_source(env.fake_memory(), data); Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); assert(dispatch != nullptr); @@ -73,7 +81,7 @@ void TestUnpack(Fuzz_Data data) tox_events_callback_group_join_fail(dispatch, ignore); tox_events_callback_group_moderation(dispatch, ignore); - Tox_Events *events = tox_events_load(sys.sys.get(), events_data, events_size); + Tox_Events *events = tox_events_load(&node->system, events_data, events_size); if (events) { std::vector packed(tox_events_bytes_size(events)); tox_events_get_bytes(events, packed.data()); diff --git a/toxcore/tox_events_test.cc b/toxcore/tox_events_test.cc index 749d6768..b2854ffd 100644 --- a/toxcore/tox_events_test.cc +++ b/toxcore/tox_events_test.cc @@ -1,4 +1,7 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" #include "tox_events.h" +// clang-format on #include @@ -10,28 +13,33 @@ namespace { +using tox::test::SimulatedEnvironment; + TEST(ToxEvents, UnpackRandomDataDoesntCrash) { - const Tox_System sys = tox_default_system(); - ASSERT_NE(sys.rng, nullptr); + SimulatedEnvironment env; + auto node = env.create_node(33445); + ASSERT_NE(node->system.rng, nullptr); std::array data; - random_bytes(sys.rng, data.data(), data.size()); - tox_events_free(tox_events_load(&sys, data.data(), data.size())); + random_bytes(node->system.rng, data.data(), data.size()); + tox_events_free(tox_events_load(&node->system, data.data(), data.size())); } TEST(ToxEvents, UnpackEmptyDataFails) { - const Tox_System sys = tox_default_system(); + SimulatedEnvironment env; + auto node = env.create_node(33445); std::array data; - Tox_Events *events = tox_events_load(&sys, data.end(), 0); + Tox_Events *events = tox_events_load(&node->system, data.end(), 0); EXPECT_EQ(events, nullptr); } TEST(ToxEvents, UnpackEmptyArrayCreatesEmptyEvents) { - const Tox_System sys = tox_default_system(); + SimulatedEnvironment env; + auto node = env.create_node(33445); std::array data{0x90}; // empty msgpack array - Tox_Events *events = tox_events_load(&sys, data.data(), data.size()); + Tox_Events *events = tox_events_load(&node->system, data.data(), data.size()); ASSERT_NE(events, nullptr); EXPECT_EQ(tox_events_get_size(events), 0); tox_events_free(events); @@ -47,10 +55,11 @@ TEST(ToxEvents, NullEventsPacksToEmptyArray) TEST(ToxEvents, PackedEventsCanBeUnpacked) { - const Tox_System sys = tox_default_system(); + SimulatedEnvironment env; + auto node = env.create_node(33445); // [[0, 1]] == Tox_Self_Connection_Status { .connection_status = TOX_CONNECTION_TCP } std::array packed{0x91, 0x92, 0xcc, 0x00, 0xcc, 0x01}; - Tox_Events *events = tox_events_load(&sys, packed.data(), packed.size()); + Tox_Events *events = tox_events_load(&node->system, packed.data(), packed.size()); ASSERT_NE(events, nullptr); std::array bytes; ASSERT_EQ(tox_events_bytes_size(events), bytes.size()); @@ -61,9 +70,10 @@ TEST(ToxEvents, PackedEventsCanBeUnpacked) TEST(ToxEvents, DealsWithHugeMsgpackArrays) { - const Tox_System sys = tox_default_system(); + SimulatedEnvironment env; + auto node = env.create_node(33445); std::vector data{0xdd, 0xff, 0xff, 0xff, 0xff}; - EXPECT_EQ(tox_events_load(&sys, data.data(), data.size()), nullptr); + EXPECT_EQ(tox_events_load(&node->system, data.data(), data.size()), nullptr); } } // namespace diff --git a/toxcore/tox_options.c b/toxcore/tox_options.c index d67a8aeb..58c7b25d 100644 --- a/toxcore/tox_options.c +++ b/toxcore/tox_options.c @@ -260,6 +260,26 @@ void tox_options_default(Tox_Options *options) } } +void tox_options_copy(Tox_Options *dest, const Tox_Options *src) +{ + if (dest != nullptr && src != nullptr) { + // Clear dest's owned pointers before copying src's members + dest->owned_proxy_host = nullptr; + dest->owned_savedata_data = nullptr; + + // Copy all non-owned members + *dest = *src; + + // Clear these again as they now point to src's memory + dest->owned_proxy_host = nullptr; + dest->owned_savedata_data = nullptr; + + // Use setters to correctly re-allocate and copy if experimental_owned_data is true + tox_options_set_proxy_host(dest, src->proxy_host); + tox_options_set_savedata_data(dest, src->savedata_data, src->savedata_length); + } +} + Tox_Options *tox_options_new(Tox_Err_Options_New *error) { Tox_Options *options = (Tox_Options *)calloc(1, sizeof(Tox_Options)); diff --git a/toxcore/tox_options.h b/toxcore/tox_options.h index f90e186f..13936c8b 100644 --- a/toxcore/tox_options.h +++ b/toxcore/tox_options.h @@ -411,6 +411,18 @@ void tox_options_set_experimental_disable_dns(Tox_Options *options, bool experim */ void tox_options_default(Tox_Options *options); +/** + * @brief Copy all options from one object to another. + * + * The result of this function is independent of the original options. + * + * If either options object is NULL, this function has no effect. + * + * @param dest The options object to be written to. + * @param src The options object to be read from. + */ +void tox_options_copy(Tox_Options *dest, const Tox_Options *src); + typedef enum Tox_Err_Options_New { /** * The function returned successfully. diff --git a/toxcore/tox_private.h b/toxcore/tox_private.h index 89a292ef..a23674ec 100644 --- a/toxcore/tox_private.h +++ b/toxcore/tox_private.h @@ -10,6 +10,7 @@ #include #include +#include "attributes.h" #include "tox.h" #include "tox_options.h" @@ -17,22 +18,22 @@ extern "C" { #endif -typedef uint64_t tox_mono_time_cb(void *user_data); +typedef uint64_t tox_mono_time_cb(void *_Nullable user_data); typedef struct Tox_System { - tox_mono_time_cb *mono_time_callback; - void *mono_time_user_data; - const struct Tox_Random *rng; - const struct Network *ns; - const struct Tox_Memory *mem; + tox_mono_time_cb *_Nullable mono_time_callback; + void *_Nullable mono_time_user_data; + const struct Tox_Random *_Nullable rng; + const struct Network *_Nullable ns; + const struct Tox_Memory *_Nullable mem; } Tox_System; Tox_System tox_default_system(void); -const Tox_System *tox_get_system(Tox *tox); +const Tox_System *_Nonnull tox_get_system(const Tox *_Nonnull tox); typedef struct Tox_Options_Testing { - const struct Tox_System *operating_system; + const struct Tox_System *_Nullable operating_system; } Tox_Options_Testing; typedef enum Tox_Err_New_Testing { @@ -40,10 +41,10 @@ typedef enum Tox_Err_New_Testing { TOX_ERR_NEW_TESTING_NULL, } Tox_Err_New_Testing; -Tox *tox_new_testing(const Tox_Options *options, Tox_Err_New *error, const Tox_Options_Testing *testing, Tox_Err_New_Testing *testing_error); +Tox *_Nullable tox_new_testing(const Tox_Options *_Nonnull options, Tox_Err_New *_Nullable error, const Tox_Options_Testing *_Nonnull testing, Tox_Err_New_Testing *_Nullable testing_error); -void tox_lock(const Tox *tox); -void tox_unlock(const Tox *tox); +void tox_lock(const Tox *_Nonnull tox); +void tox_unlock(const Tox *_Nonnull tox); /** * Set the callback for the `friend_lossy_packet` event for a specific packet @@ -53,7 +54,7 @@ void tox_unlock(const Tox *tox); * from `PACKET_ID_RANGE_LOSSY_START` to `PACKET_ID_RANGE_LOSSY_END` (both * inclusive) */ -void tox_callback_friend_lossy_packet_per_pktid(Tox *tox, tox_friend_lossy_packet_cb *callback, uint8_t pktid); +void tox_callback_friend_lossy_packet_per_pktid(Tox *_Nonnull tox, tox_friend_lossy_packet_cb *_Nullable callback, uint8_t pktid); /** * Set the callback for the `friend_lossless_packet` event for a specific packet @@ -63,10 +64,10 @@ void tox_callback_friend_lossy_packet_per_pktid(Tox *tox, tox_friend_lossy_packe * from `PACKET_ID_RANGE_LOSSLESS_CUSTOM_START` to * `PACKET_ID_RANGE_LOSSLESS_CUSTOM_END` (both inclusive) and `PACKET_ID_MSI` */ -void tox_callback_friend_lossless_packet_per_pktid(Tox *tox, tox_friend_lossless_packet_cb *callback, uint8_t pktid); +void tox_callback_friend_lossless_packet_per_pktid(Tox *_Nonnull tox, tox_friend_lossless_packet_cb *_Nullable callback, uint8_t pktid); -void tox_set_av_object(Tox *tox, void *object); -void *tox_get_av_object(const Tox *tox); +void tox_set_av_object(Tox *_Nonnull tox, void *_Nullable object); +void *_Nullable tox_get_av_object(const Tox *_Nonnull tox); /******************************************************************************* * @@ -94,15 +95,15 @@ uint32_t tox_dht_node_public_key_size(void); * @param port The node's port. */ typedef void tox_dht_nodes_response_cb( - Tox *tox, const uint8_t *public_key, const char *ip, uint32_t ip_length, - uint16_t port, void *user_data); + Tox *_Nonnull tox, const uint8_t *_Nonnull public_key, const char *_Nonnull ip, uint32_t ip_length, + uint16_t port, void *_Nullable user_data); /** * Set the callback for the `dht_nodes_response` event. Pass NULL to unset. * * This event is triggered when a nodes response is received from a DHT peer. */ -void tox_callback_dht_nodes_response(Tox *tox, tox_dht_nodes_response_cb *callback); +void tox_callback_dht_nodes_response(Tox *_Nonnull tox, tox_dht_nodes_response_cb *_Nullable callback); typedef enum Tox_Err_Dht_Send_Nodes_Request { /** @@ -153,15 +154,15 @@ typedef enum Tox_Err_Dht_Send_Nodes_Request { * * @return true on success. */ -bool tox_dht_send_nodes_request(const Tox *tox, const uint8_t *public_key, const char *ip, uint16_t port, - const uint8_t *target_public_key, Tox_Err_Dht_Send_Nodes_Request *error); +bool tox_dht_send_nodes_request(const Tox *_Nonnull tox, const uint8_t *_Nonnull public_key, const char *_Nonnull ip, uint16_t port, + const uint8_t *_Nonnull target_public_key, Tox_Err_Dht_Send_Nodes_Request *_Nullable error); /** * This function returns the number of DHT nodes in the closelist. * * @return number */ -uint16_t tox_dht_get_num_closelist(const Tox *tox); +uint16_t tox_dht_get_num_closelist(const Tox *_Nonnull tox); /** * This function returns the number of DHT nodes in the closelist @@ -169,7 +170,7 @@ uint16_t tox_dht_get_num_closelist(const Tox *tox); * * @return number */ -uint16_t tox_dht_get_num_closelist_announce_capable(const Tox *tox); +uint16_t tox_dht_get_num_closelist_announce_capable(const Tox *_Nonnull tox); /******************************************************************************* * @@ -347,7 +348,7 @@ typedef enum Tox_Netprof_Packet_Id { TOX_NETPROF_PACKET_ID_BOOTSTRAP_INFO = 0xf0, } Tox_Netprof_Packet_Id; -const char *tox_netprof_packet_id_to_string(Tox_Netprof_Packet_Id value); +const char *_Nonnull tox_netprof_packet_id_to_string(Tox_Netprof_Packet_Id value); /** * Specifies the packet type for a given query. @@ -374,7 +375,7 @@ typedef enum Tox_Netprof_Packet_Type { TOX_NETPROF_PACKET_TYPE_UDP, } Tox_Netprof_Packet_Type; -const char *tox_netprof_packet_type_to_string(Tox_Netprof_Packet_Type value); +const char *_Nonnull tox_netprof_packet_type_to_string(Tox_Netprof_Packet_Type value); /** * Specifies the packet direction for a given query. @@ -391,7 +392,7 @@ typedef enum Tox_Netprof_Direction { TOX_NETPROF_DIRECTION_RECV, } Tox_Netprof_Direction; -const char *tox_netprof_direction_to_string(Tox_Netprof_Direction value); +const char *_Nonnull tox_netprof_direction_to_string(Tox_Netprof_Direction value); /** * Return the number of packets sent or received for a specific packet ID. @@ -400,7 +401,7 @@ const char *tox_netprof_direction_to_string(Tox_Netprof_Direction value); * @param id The packet ID being queried. * @param direction The packet direction. */ -uint64_t tox_netprof_get_packet_id_count(const Tox *tox, Tox_Netprof_Packet_Type type, uint8_t id, +uint64_t tox_netprof_get_packet_id_count(const Tox *_Nonnull tox, Tox_Netprof_Packet_Type type, uint8_t id, Tox_Netprof_Direction direction); /** @@ -409,7 +410,7 @@ uint64_t tox_netprof_get_packet_id_count(const Tox *tox, Tox_Netprof_Packet_Type * @param type The types of packets being queried. * @param direction The packet direction. */ -uint64_t tox_netprof_get_packet_total_count(const Tox *tox, Tox_Netprof_Packet_Type type, +uint64_t tox_netprof_get_packet_total_count(const Tox *_Nonnull tox, Tox_Netprof_Packet_Type type, Tox_Netprof_Direction direction); /** @@ -419,7 +420,7 @@ uint64_t tox_netprof_get_packet_total_count(const Tox *tox, Tox_Netprof_Packet_T * @param id The packet ID being queried. * @param direction The packet direction. */ -uint64_t tox_netprof_get_packet_id_bytes(const Tox *tox, Tox_Netprof_Packet_Type type, uint8_t id, +uint64_t tox_netprof_get_packet_id_bytes(const Tox *_Nonnull tox, Tox_Netprof_Packet_Type type, uint8_t id, Tox_Netprof_Direction direction); /** @@ -428,7 +429,7 @@ uint64_t tox_netprof_get_packet_id_bytes(const Tox *tox, Tox_Netprof_Packet_Type * @param type The types of packets being queried. * @param direction The packet direction. */ -uint64_t tox_netprof_get_packet_total_bytes(const Tox *tox, Tox_Netprof_Packet_Type type, +uint64_t tox_netprof_get_packet_total_bytes(const Tox *_Nonnull tox, Tox_Netprof_Packet_Type type, Tox_Netprof_Direction direction); @@ -453,8 +454,8 @@ uint32_t tox_group_peer_ip_string_max_length(void); * @param peer_id The ID of the peer whose IP address length we want to * retrieve. */ -size_t tox_group_peer_get_ip_address_size(const Tox *tox, uint32_t group_number, uint32_t peer_id, - Tox_Err_Group_Peer_Query *error); +size_t tox_group_peer_get_ip_address_size(const Tox *_Nonnull tox, uint32_t group_number, uint32_t peer_id, + Tox_Err_Group_Peer_Query *_Nullable error); /** * Write the IP address associated with the designated peer_id for the * designated group number to ip_addr. @@ -475,8 +476,8 @@ size_t tox_group_peer_get_ip_address_size(const Tox *tox, uint32_t group_number, * * @return true on success. */ -bool tox_group_peer_get_ip_address(const Tox *tox, uint32_t group_number, uint32_t peer_id, uint8_t *ip_addr, - Tox_Err_Group_Peer_Query *error); +bool tox_group_peer_get_ip_address(const Tox *_Nonnull tox, uint32_t group_number, uint32_t peer_id, uint8_t *_Nonnull ip_addr, + Tox_Err_Group_Peer_Query *_Nullable error); #ifdef __cplusplus } /* extern "C" */ diff --git a/toxcore/tox_struct.h b/toxcore/tox_struct.h index 9383c2a5..078a9b37 100644 --- a/toxcore/tox_struct.h +++ b/toxcore/tox_struct.h @@ -18,54 +18,54 @@ extern "C" { #endif struct Tox { - struct Messenger *m; - Mono_Time *mono_time; + struct Messenger *_Nonnull m; + Mono_Time *_Nonnull mono_time; Tox_System sys; - pthread_mutex_t *mutex; + pthread_mutex_t *_Nullable mutex; - tox_log_cb *log_callback; - tox_self_connection_status_cb *self_connection_status_callback; - tox_friend_name_cb *friend_name_callback; - tox_friend_status_message_cb *friend_status_message_callback; - tox_friend_status_cb *friend_status_callback; - tox_friend_connection_status_cb *friend_connection_status_callback; - tox_friend_typing_cb *friend_typing_callback; - tox_friend_read_receipt_cb *friend_read_receipt_callback; - tox_friend_request_cb *friend_request_callback; - tox_friend_message_cb *friend_message_callback; - tox_file_recv_control_cb *file_recv_control_callback; - tox_file_chunk_request_cb *file_chunk_request_callback; - tox_file_recv_cb *file_recv_callback; - tox_file_recv_chunk_cb *file_recv_chunk_callback; - tox_conference_invite_cb *conference_invite_callback; - tox_conference_connected_cb *conference_connected_callback; - tox_conference_message_cb *conference_message_callback; - tox_conference_title_cb *conference_title_callback; - tox_conference_peer_name_cb *conference_peer_name_callback; - tox_conference_peer_list_changed_cb *conference_peer_list_changed_callback; - tox_dht_nodes_response_cb *dht_nodes_response_callback; - tox_friend_lossy_packet_cb *friend_lossy_packet_callback_per_pktid[UINT8_MAX + 1]; - tox_friend_lossless_packet_cb *friend_lossless_packet_callback_per_pktid[UINT8_MAX + 1]; - tox_group_peer_name_cb *group_peer_name_callback; - tox_group_peer_status_cb *group_peer_status_callback; - tox_group_topic_cb *group_topic_callback; - tox_group_privacy_state_cb *group_privacy_state_callback; - tox_group_topic_lock_cb *group_topic_lock_callback; - tox_group_voice_state_cb *group_voice_state_callback; - tox_group_peer_limit_cb *group_peer_limit_callback; - tox_group_password_cb *group_password_callback; - tox_group_message_cb *group_message_callback; - tox_group_private_message_cb *group_private_message_callback; - tox_group_custom_packet_cb *group_custom_packet_callback; - tox_group_custom_private_packet_cb *group_custom_private_packet_callback; - tox_group_invite_cb *group_invite_callback; - tox_group_peer_join_cb *group_peer_join_callback; - tox_group_peer_exit_cb *group_peer_exit_callback; - tox_group_self_join_cb *group_self_join_callback; - tox_group_join_fail_cb *group_join_fail_callback; - tox_group_moderation_cb *group_moderation_callback; + tox_log_cb *_Nullable log_callback; + tox_self_connection_status_cb *_Nullable self_connection_status_callback; + tox_friend_name_cb *_Nullable friend_name_callback; + tox_friend_status_message_cb *_Nullable friend_status_message_callback; + tox_friend_status_cb *_Nullable friend_status_callback; + tox_friend_connection_status_cb *_Nullable friend_connection_status_callback; + tox_friend_typing_cb *_Nullable friend_typing_callback; + tox_friend_read_receipt_cb *_Nullable friend_read_receipt_callback; + tox_friend_request_cb *_Nullable friend_request_callback; + tox_friend_message_cb *_Nullable friend_message_callback; + tox_file_recv_control_cb *_Nullable file_recv_control_callback; + tox_file_chunk_request_cb *_Nullable file_chunk_request_callback; + tox_file_recv_cb *_Nullable file_recv_callback; + tox_file_recv_chunk_cb *_Nullable file_recv_chunk_callback; + tox_conference_invite_cb *_Nullable conference_invite_callback; + tox_conference_connected_cb *_Nullable conference_connected_callback; + tox_conference_message_cb *_Nullable conference_message_callback; + tox_conference_title_cb *_Nullable conference_title_callback; + tox_conference_peer_name_cb *_Nullable conference_peer_name_callback; + tox_conference_peer_list_changed_cb *_Nullable conference_peer_list_changed_callback; + tox_dht_nodes_response_cb *_Nullable dht_nodes_response_callback; + tox_friend_lossy_packet_cb *_Nullable friend_lossy_packet_callback_per_pktid[UINT8_MAX + 1]; + tox_friend_lossless_packet_cb *_Nullable friend_lossless_packet_callback_per_pktid[UINT8_MAX + 1]; + tox_group_peer_name_cb *_Nullable group_peer_name_callback; + tox_group_peer_status_cb *_Nullable group_peer_status_callback; + tox_group_topic_cb *_Nullable group_topic_callback; + tox_group_privacy_state_cb *_Nullable group_privacy_state_callback; + tox_group_topic_lock_cb *_Nullable group_topic_lock_callback; + tox_group_voice_state_cb *_Nullable group_voice_state_callback; + tox_group_peer_limit_cb *_Nullable group_peer_limit_callback; + tox_group_password_cb *_Nullable group_password_callback; + tox_group_message_cb *_Nullable group_message_callback; + tox_group_private_message_cb *_Nullable group_private_message_callback; + tox_group_custom_packet_cb *_Nullable group_custom_packet_callback; + tox_group_custom_private_packet_cb *_Nullable group_custom_private_packet_callback; + tox_group_invite_cb *_Nullable group_invite_callback; + tox_group_peer_join_cb *_Nullable group_peer_join_callback; + tox_group_peer_exit_cb *_Nullable group_peer_exit_callback; + tox_group_self_join_cb *_Nullable group_self_join_callback; + tox_group_join_fail_cb *_Nullable group_join_fail_callback; + tox_group_moderation_cb *_Nullable group_moderation_callback; - void *toxav_object; // workaround to store a ToxAV object (setter and getter functions are available) + void *_Nullable toxav_object; // workaround to store a ToxAV object (setter and getter functions are available) }; #ifdef __cplusplus diff --git a/toxcore/tox_test.cc b/toxcore/tox_test.cc index 39482962..6bc5a753 100644 --- a/toxcore/tox_test.cc +++ b/toxcore/tox_test.cc @@ -1,4 +1,7 @@ +// clang-format off +#include "../testing/support/public/simulated_environment.hh" #include "tox.h" +// clang-format on #include @@ -13,6 +16,8 @@ namespace { +using tox::test::SimulatedEnvironment; + static void set_random_name_and_status_message( Tox *tox, const Random *rng, uint8_t *name, uint8_t *status_message) { @@ -82,6 +87,7 @@ TEST(Tox, BootstrapErrorCodes) TEST(Tox, OneTest) { + SimulatedEnvironment env; struct Tox_Options *options = tox_options_new(nullptr); ASSERT_NE(options, nullptr); @@ -102,12 +108,21 @@ TEST(Tox, OneTest) std::vector name2(tox_max_name_length()); std::vector status_message2(tox_max_status_message_length()); - Tox *tox1 = tox_new(options, nullptr); + auto node1 = env.create_node(33545); + Tox_Options_Testing testing_opts1 = {}; + testing_opts1.operating_system = &node1->system; + + Tox *tox1 = tox_new_testing(options, nullptr, &testing_opts1, nullptr); ASSERT_NE(tox1, nullptr); const Random *rng = os_random(); ASSERT_NE(rng, nullptr); set_random_name_and_status_message(tox1, rng, name.data(), status_message.data()); - Tox *tox2 = tox_new(options, nullptr); + + auto node2 = env.create_node(33546); + Tox_Options_Testing testing_opts2 = {}; + testing_opts2.operating_system = &node2->system; + + Tox *tox2 = tox_new_testing(options, nullptr, &testing_opts2, nullptr); ASSERT_NE(tox2, nullptr); set_random_name_and_status_message(tox2, rng, name2.data(), status_message2.data()); @@ -163,7 +178,7 @@ TEST(Tox, OneTest) tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); tox_options_set_savedata_data(options, data.data(), data.size()); - tox2 = tox_new(options, &err_n); + tox2 = tox_new_testing(options, &err_n, &testing_opts2, nullptr); EXPECT_EQ(err_n, TOX_ERR_NEW_OK) << "Load failed"; EXPECT_EQ(tox_self_get_name_size(tox2), name.size()) << "Wrong name size."; @@ -192,7 +207,7 @@ TEST(Tox, OneTest) tox_options_default(options); tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_SECRET_KEY); tox_options_set_savedata_data(options, sk.data(), sk.size()); - tox2 = tox_new(options, &err_n); + tox2 = tox_new_testing(options, &err_n, &testing_opts2, nullptr); ASSERT_EQ(err_n, TOX_ERR_NEW_OK) << "Load failed"; tox_self_set_nospam(tox2, tox_self_get_nospam(tox1)); std::array address3; diff --git a/toxencryptsave/toxencryptsave.c b/toxencryptsave/toxencryptsave.c index 5efc642d..e79e0f24 100644 --- a/toxencryptsave/toxencryptsave.c +++ b/toxencryptsave/toxencryptsave.c @@ -242,7 +242,7 @@ bool tox_pass_key_encrypt(const Tox_Pass_Key *key, const uint8_t plaintext[], si ciphertext += crypto_box_NONCEBYTES; /* now encrypt */ - const int32_t encrypted_len = encrypt_data_symmetric(mem, key->key, nonce, plaintext, plaintext_len, ciphertext); + const int32_t encrypted_len = encrypt_data_symmetric((const Memory * _Nonnull)mem, key->key, nonce, plaintext, plaintext_len, ciphertext); if (encrypted_len < 0 || (size_t)encrypted_len != plaintext_len + crypto_box_MACBYTES) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_FAILED); return false; @@ -334,7 +334,7 @@ bool tox_pass_key_decrypt(const Tox_Pass_Key *key, const uint8_t ciphertext[], s ciphertext += crypto_box_NONCEBYTES; /* decrypt the ciphertext */ - const int32_t decrypted_len = decrypt_data_symmetric(mem, key->key, nonce, ciphertext, decrypt_length + crypto_box_MACBYTES, plaintext); + const int32_t decrypted_len = decrypt_data_symmetric((const Memory * _Nonnull)mem, key->key, nonce, ciphertext, decrypt_length + crypto_box_MACBYTES, plaintext); if (decrypted_len < 0 || (size_t)decrypted_len != decrypt_length) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_FAILED); return false;