Merge commit '565efa4f39650d09c05f3895f9a2b16d0f5e7bad'

This commit is contained in:
Green Sky
2026-01-11 14:42:31 +01:00
328 changed files with 19057 additions and 13982 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 \

View File

@@ -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],

View File

@@ -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()

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -1,34 +0,0 @@
#include <stdio.h>
#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;
}

View File

@@ -6,6 +6,7 @@
#include <stdio.h>
#include <stdlib.h>
#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

View File

@@ -1,480 +0,0 @@
/* Auto Tests: Conferences AV.
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#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;
}

View File

@@ -1,94 +0,0 @@
#include <stdbool.h>
#include <stdint.h>
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;
}

View File

@@ -1,182 +0,0 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
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;
}

View File

@@ -1,142 +0,0 @@
#include <stdbool.h>
#include <stdint.h>
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;
}

View File

@@ -1,268 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#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;
}

View File

@@ -1,441 +0,0 @@
/* Auto Tests: Conferences.
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#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;
}

View File

@@ -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;
}

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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

View File

@@ -1,272 +0,0 @@
/* File transfer test: streaming version (no known size).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@@ -1,375 +0,0 @@
/* File transfer test.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@@ -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);

View File

@@ -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 <stdint.h>
#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;
}

View File

@@ -1,77 +0,0 @@
/* Tests what happens when spamming friend requests from lots of temporary toxes.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@@ -1,147 +0,0 @@
/* Tests that we can add friends.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@@ -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 <stdbool.h>
#include <stdint.h>
#include <string.h>
#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

View File

@@ -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 <string.h>
#include <stdio.h>
#include <stdint.h>
#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

View File

@@ -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 <stdbool.h>
#include <stdint.h>
#include <string.h>
#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

View File

@@ -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 <stdbool.h>
#include <stdint.h>
#include <string.h>
#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

View File

@@ -1,306 +0,0 @@
/*
* Tests that we can save a groupchat and load a groupchat with the saved data.
*/
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#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

View File

@@ -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 <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#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

View File

@@ -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 <stdbool.h>
#include <stdint.h>
#include <string.h>
#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

View File

@@ -1,259 +0,0 @@
/*
* Does a basic functionality test for TCP connections.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#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

View File

@@ -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 <string.h>
#include <stdio.h>
#include <stdint.h>
#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

View File

@@ -1,53 +0,0 @@
#include <stdio.h>
#include <string.h>
#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;
}

View File

@@ -1,71 +0,0 @@
/* Tests that we can send lossless packets.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@@ -1,71 +0,0 @@
/* Tests that we can send lossy packets.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@@ -1,134 +0,0 @@
/** Auto Tests: basic network profile functionality test (UDP only)
*/
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#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

View File

@@ -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) {

View File

@@ -1,68 +0,0 @@
/* Try to overflow the net_crypto packet buffer.
*/
#include <stdint.h>
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;
}

View File

@@ -1,46 +0,0 @@
/* Try to overflow the net_crypto packet buffer.
*/
#include <stdint.h>
#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;
}

View File

@@ -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 <stdlib.h>
#include <time.h>
#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;
}

View File

@@ -1,183 +0,0 @@
/* Auto Tests: Save and load friends.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@@ -1,315 +0,0 @@
/* Tests that we can save and load Tox data.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@@ -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"])]

View File

@@ -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()

View File

@@ -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.

View File

@@ -0,0 +1,800 @@
#include "framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <stdarg.h>
#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]);
}
}
}

View File

@@ -0,0 +1,215 @@
#ifndef TOX_TEST_FRAMEWORK_H
#define TOX_TEST_FRAMEWORK_H
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,144 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@@ -0,0 +1,38 @@
#include "framework/framework.h"
#include <stdio.h>
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;
}

View File

@@ -0,0 +1,132 @@
#include "framework/framework.h"
#include "../../toxav/toxav.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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;
}

View File

@@ -0,0 +1,97 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,300 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}

View File

@@ -0,0 +1,132 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,111 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}

View File

@@ -0,0 +1,131 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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, &gt_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;
}

View File

@@ -0,0 +1,155 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,148 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,38 @@
#include "framework/framework.h"
#include <stdio.h>
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;
}

View File

@@ -0,0 +1,195 @@
#include "framework/framework.h"
#include "../../toxcore/tox_private.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,46 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,81 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,134 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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

View File

@@ -0,0 +1,150 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,46 @@
#include "framework/framework.h"
#include <stdio.h>
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;
}

View File

@@ -0,0 +1,103 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,72 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,83 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,102 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,78 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,231 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,303 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,214 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,367 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,172 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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

View File

@@ -0,0 +1,186 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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

View File

@@ -0,0 +1,263 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,234 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,241 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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

View File

@@ -0,0 +1,310 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,51 @@
#include "framework/framework.h"
#include <stdio.h>
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;
}

View File

@@ -0,0 +1,68 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@@ -0,0 +1,52 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@@ -0,0 +1,65 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,61 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#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;
}

View File

@@ -0,0 +1,105 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,125 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#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

View File

@@ -0,0 +1,63 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#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

View File

@@ -0,0 +1,61 @@
#include "framework/framework.h"
#include <stdio.h>
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;
}

View File

@@ -0,0 +1,93 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}

View File

@@ -0,0 +1,90 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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;
}

View File

@@ -0,0 +1,92 @@
#include "framework/framework.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@@ -0,0 +1,98 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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;
}

View File

@@ -0,0 +1,45 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#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;
}

View File

@@ -0,0 +1,45 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#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;
}

View File

@@ -0,0 +1,160 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,102 @@
#include "framework/framework.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@@ -0,0 +1,205 @@
#include "framework/framework.h"
#include "../../toxav/toxav.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}

View File

@@ -0,0 +1,184 @@
#include "framework/framework.h"
#include "../../toxav/toxav.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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;
}

View File

@@ -0,0 +1,54 @@
#include "framework/framework.h"
#include <stdio.h>
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;
}

Some files were not shown because too many files have changed in this diff Show More