Merge commit '9b36dd9d9952851d842c2f3bc6fadb0f9e4d8fa7'

This commit is contained in:
Green Sky
2026-02-01 14:26:52 +01:00
274 changed files with 11891 additions and 4292 deletions

View File

@@ -42,7 +42,7 @@ CheckOptions:
- key: concurrency-mt-unsafe.FunctionSet - key: concurrency-mt-unsafe.FunctionSet
value: posix value: posix
- key: readability-function-cognitive-complexity.Threshold - key: readability-function-cognitive-complexity.Threshold
value: 153 # TODO(iphydf): Decrease. tox_new is the highest at the moment. value: 159 # TODO(iphydf): Decrease. tox_new_system is the highest at the moment.
- key: cppcoreguidelines-avoid-do-while.IgnoreMacros - key: cppcoreguidelines-avoid-do-while.IgnoreMacros
value: true value: true
- key: readability-simplify-boolean-expr.SimplifyDeMorgan - key: readability-simplify-boolean-expr.SimplifyDeMorgan

View File

@@ -22,7 +22,6 @@ add_flag -Wframe-larger-than=9000
add_flag -Wignored-attributes add_flag -Wignored-attributes
add_flag -Wignored-qualifiers add_flag -Wignored-qualifiers
add_flag -Winit-self add_flag -Winit-self
add_flag -Winline
add_flag -Wlarger-than=530000 add_flag -Wlarger-than=530000
add_flag -Wmaybe-uninitialized add_flag -Wmaybe-uninitialized
add_flag -Wmemset-transposed-args add_flag -Wmemset-transposed-args
@@ -45,6 +44,8 @@ add_flag -Wunused-value
# Disable specific warning flags for both C and C++. # Disable specific warning flags for both C and C++.
# Not important, and bothersome with lambdas in C++.
add_flag -Wno-inline
# struct Foo foo = {0}; is a common idiom. # struct Foo foo = {0}; is a common idiom.
add_flag -Wno-missing-field-initializers add_flag -Wno-missing-field-initializers
# Checked by clang, but gcc is warning when it's not necessary. # Checked by clang, but gcc is warning when it's not necessary.

View File

@@ -17,7 +17,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
tool: [autotools, clang-tidy, compcert, cppcheck, doxygen, goblint, infer, misra, modules, pkgsrc, rpm, slimcc, sparse, tcc, tokstyle] tool: [autotools, clang-tidy, compcert, cppcheck, doxygen, infer, misra, modules, pkgsrc, rpm, slimcc, sparse, tcc, tokstyle]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Set up Docker Buildx - name: Set up Docker Buildx
@@ -178,10 +178,10 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set up Python 3.9 - name: Set up Python 3.12
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: 3.9 python-version: 3.12
- name: Install mypy - name: Install mypy
run: pip install mypy run: pip install mypy
- name: Run mypy - name: Run mypy

View File

@@ -0,0 +1,57 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '39 10 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libconfig-dev \
libopus-dev \
libsodium-dev \
libvpx-dev \
ninja-build \
pkg-config
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- name: Build
run: |
cmake -GNinja -B _build -S .
cmake --build _build --parallel $(nproc)
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"

View File

@@ -138,7 +138,7 @@ jobs:
- name: Build and store to local Docker daemon - name: Build and store to local Docker daemon
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: other/docker/windows file: other/docker/windows/windows.Dockerfile
load: true load: true
tags: toxchat/windows:win${{ matrix.bits }} tags: toxchat/windows:win${{ matrix.bits }}
cache-from: type=registry,ref=toxchat/windows:win${{ matrix.bits }} cache-from: type=registry,ref=toxchat/windows:win${{ matrix.bits }}
@@ -150,7 +150,7 @@ jobs:
if: ${{ github.event_name == 'push' }} if: ${{ github.event_name == 'push' }}
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: other/docker/windows file: other/docker/windows/windows.Dockerfile
push: ${{ github.event_name == 'push' }} push: ${{ github.event_name == 'push' }}
tags: toxchat/windows:win${{ matrix.bits }} tags: toxchat/windows:win${{ matrix.bits }}
build-args: | build-args: |

View File

@@ -98,5 +98,10 @@ tox.spec
.cache/ .cache/
compile_commands.json compile_commands.json
# gtags
/GPATH
/GRTAGS
/GTAGS
/infer /infer
.idea/ .idea/

View File

@@ -8,6 +8,7 @@ genrule(
srcs = [ srcs = [
"//c-toxcore/toxav:toxav.h", "//c-toxcore/toxav:toxav.h",
"//c-toxcore/toxcore:tox.h", "//c-toxcore/toxcore:tox.h",
"//c-toxcore/toxcore:tox_attributes.h",
"//c-toxcore/toxcore:tox_dispatch.h", "//c-toxcore/toxcore:tox_dispatch.h",
"//c-toxcore/toxcore:tox_events.h", "//c-toxcore/toxcore:tox_events.h",
"//c-toxcore/toxcore:tox_log_level.h", "//c-toxcore/toxcore:tox_log_level.h",
@@ -18,6 +19,7 @@ genrule(
outs = [ outs = [
"tox/toxav.h", "tox/toxav.h",
"tox/tox.h", "tox/tox.h",
"tox/tox_attributes.h",
"tox/tox_dispatch.h", "tox/tox_dispatch.h",
"tox/tox_events.h", "tox/tox_events.h",
"tox/tox_log_level.h", "tox/tox_log_level.h",
@@ -28,6 +30,7 @@ genrule(
cmd = """ cmd = """
cp $(location //c-toxcore/toxav:toxav.h) $(GENDIR)/c-toxcore/tox/toxav.h cp $(location //c-toxcore/toxav:toxav.h) $(GENDIR)/c-toxcore/tox/toxav.h
cp $(location //c-toxcore/toxcore:tox.h) $(GENDIR)/c-toxcore/tox/tox.h cp $(location //c-toxcore/toxcore:tox.h) $(GENDIR)/c-toxcore/tox/tox.h
cp $(location //c-toxcore/toxcore:tox_attributes.h) $(GENDIR)/c-toxcore/tox/tox_attributes.h
cp $(location //c-toxcore/toxcore:tox_dispatch.h) $(GENDIR)/c-toxcore/tox/tox_dispatch.h cp $(location //c-toxcore/toxcore:tox_dispatch.h) $(GENDIR)/c-toxcore/tox/tox_dispatch.h
cp $(location //c-toxcore/toxcore:tox_events.h) $(GENDIR)/c-toxcore/tox/tox_events.h cp $(location //c-toxcore/toxcore:tox_events.h) $(GENDIR)/c-toxcore/tox/tox_events.h
cp $(location //c-toxcore/toxcore:tox_log_level.h) $(GENDIR)/c-toxcore/tox/tox_log_level.h cp $(location //c-toxcore/toxcore:tox_log_level.h) $(GENDIR)/c-toxcore/tox/tox_log_level.h

View File

@@ -29,7 +29,8 @@ endif()
set_source_files_properties( set_source_files_properties(
toxcore/mono_time.c toxcore/mono_time.c
toxcore/network.c toxcore/os_event.c
toxcore/os_network.c
toxcore/tox.c toxcore/tox.c
toxcore/util.c toxcore/util.c
PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE) PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE)
@@ -70,13 +71,27 @@ include(CTest)
include(ModulePackage) include(ModulePackage)
include(StrictAbi) include(StrictAbi)
include(GNUInstallDirs) include(GNUInstallDirs)
include(CheckIncludeFile)
include(CheckSymbolExists)
if(APPLE) if(APPLE)
include(MacRpath) include(MacRpath)
endif() endif()
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
check_include_file(sys/epoll.h HAVE_SYS_EPOLL_H)
if(HAVE_SYS_EPOLL_H)
check_symbol_exists(epoll_create "sys/epoll.h" HAVE_EPOLL_CREATE)
if(HAVE_EPOLL_CREATE)
add_definitions(-DEV_USE_EPOLL=1)
add_definitions(-DTCP_SERVER_USE_EPOLL=1)
endif()
endif()
endif()
enable_testing() enable_testing()
find_package(GTest) find_package(GTest)
find_package(benchmark QUIET)
set(CMAKE_MACOSX_RPATH ON) set(CMAKE_MACOSX_RPATH ON)
@@ -232,6 +247,8 @@ set(toxcore_SOURCES
toxcore/crypto_core_pack.h toxcore/crypto_core_pack.h
toxcore/DHT.c toxcore/DHT.c
toxcore/DHT.h toxcore/DHT.h
toxcore/ev.c
toxcore/ev.h
toxcore/events/conference_connected.c toxcore/events/conference_connected.c
toxcore/events/conference_invite.c toxcore/events/conference_invite.c
toxcore/events/conference_message.c toxcore/events/conference_message.c
@@ -307,6 +324,8 @@ set(toxcore_SOURCES
toxcore/mem.h toxcore/mem.h
toxcore/mono_time.c toxcore/mono_time.c
toxcore/mono_time.h toxcore/mono_time.h
toxcore/net.c
toxcore/net.h
toxcore/net_crypto.c toxcore/net_crypto.c
toxcore/net_crypto.h toxcore/net_crypto.h
toxcore/net_log.c toxcore/net_log.c
@@ -321,14 +340,20 @@ set(toxcore_SOURCES
toxcore/onion_client.c toxcore/onion_client.c
toxcore/onion_client.h toxcore/onion_client.h
toxcore/onion.h toxcore/onion.h
toxcore/os_event.c
toxcore/os_event.h
toxcore/os_memory.c toxcore/os_memory.c
toxcore/os_memory.h toxcore/os_memory.h
toxcore/os_network.c
toxcore/os_network.h
toxcore/os_random.c toxcore/os_random.c
toxcore/os_random.h toxcore/os_random.h
toxcore/ping_array.c toxcore/ping_array.c
toxcore/ping_array.h toxcore/ping_array.h
toxcore/ping.c toxcore/ping.c
toxcore/ping.h toxcore/ping.h
toxcore/rng.c
toxcore/rng.h
toxcore/shared_key_cache.c toxcore/shared_key_cache.c
toxcore/shared_key_cache.h toxcore/shared_key_cache.h
toxcore/sort.c toxcore/sort.c
@@ -357,18 +382,12 @@ set(toxcore_SOURCES
toxcore/tox_events.h toxcore/tox_events.h
toxcore/tox_log_level.c toxcore/tox_log_level.c
toxcore/tox_log_level.h toxcore/tox_log_level.h
toxcore/tox_memory.c
toxcore/tox_memory.h
toxcore/tox_memory_impl.h
toxcore/tox_options.c toxcore/tox_options.c
toxcore/tox_options.h toxcore/tox_options.h
toxcore/tox_private.c toxcore/tox_private.c
toxcore/tox_private.h toxcore/tox_private.h
toxcore/tox_pack.c toxcore/tox_pack.c
toxcore/tox_pack.h toxcore/tox_pack.h
toxcore/tox_random.c
toxcore/tox_random.h
toxcore/tox_random_impl.h
toxcore/tox_unpack.c toxcore/tox_unpack.c
toxcore/tox_unpack.h toxcore/tox_unpack.h
toxcore/util.c toxcore/util.c
@@ -390,10 +409,10 @@ set(toxcore_API_HEADERS
${toxcore_SOURCE_DIR}/toxcore/tox_options.h^tox) ${toxcore_SOURCE_DIR}/toxcore/tox_options.h^tox)
if(EXPERIMENTAL_API) if(EXPERIMENTAL_API)
set(toxcore_API_HEADERS ${toxcore_API_HEADERS} set(toxcore_API_HEADERS ${toxcore_API_HEADERS}
${toxcore_SOURCE_DIR}/toxcore/tox_attributes.h^tox
${toxcore_SOURCE_DIR}/toxcore/tox_dispatch.h^tox ${toxcore_SOURCE_DIR}/toxcore/tox_dispatch.h^tox
${toxcore_SOURCE_DIR}/toxcore/tox_events.h^tox ${toxcore_SOURCE_DIR}/toxcore/tox_events.h^tox
${toxcore_SOURCE_DIR}/toxcore/tox_private.h^tox ${toxcore_SOURCE_DIR}/toxcore/tox_private.h^tox)
${toxcore_SOURCE_DIR}/toxcore/tox_random.h^tox)
endif() endif()
################################################################################ ################################################################################
@@ -547,12 +566,22 @@ if(UNITTEST)
toxcore/DHT_test_util.hh toxcore/DHT_test_util.hh
toxcore/crypto_core_test_util.cc toxcore/crypto_core_test_util.cc
toxcore/crypto_core_test_util.hh toxcore/crypto_core_test_util.hh
toxcore/ev_test_util.cc
toxcore/ev_test_util.hh
toxcore/mono_time_test_util.cc toxcore/mono_time_test_util.cc
toxcore/mono_time_test_util.hh toxcore/mono_time_test_util.hh
toxcore/network_test_util.cc toxcore/network_test_util.cc
toxcore/network_test_util.hh toxcore/network_test_util.hh
toxcore/sort_test_util.cc
toxcore/sort_test_util.hh
toxcore/test_util.cc toxcore/test_util.cc
toxcore/test_util.hh) toxcore/test_util.hh)
target_link_libraries(test_util PUBLIC support)
if(TARGET toxcore_static)
target_link_libraries(test_util PRIVATE toxcore_static)
else()
target_link_libraries(test_util PRIVATE toxcore_shared)
endif()
endif() endif()
function(unit_test subdir target) function(unit_test subdir target)
@@ -563,12 +592,8 @@ function(unit_test subdir target)
else() else()
target_link_libraries(unit_${target}_test PRIVATE toxcore_shared) target_link_libraries(unit_${target}_test PRIVATE toxcore_shared)
endif() endif()
if(TARGET pthreads4w::pthreads4w) if(WIN32)
target_link_libraries(unit_${target}_test PRIVATE pthreads4w::pthreads4w) target_link_libraries(unit_${target}_test PRIVATE ws2_32)
elseif(TARGET PThreads4W::PThreads4W)
target_link_libraries(unit_${target}_test PRIVATE PThreads4W::PThreads4W)
elseif(TARGET Threads::Threads)
target_link_libraries(unit_${target}_test PRIVATE Threads::Threads)
endif() endif()
target_link_libraries(unit_${target}_test PRIVATE GTest::gtest GTest::gtest_main GTest::gmock) 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}") set_target_properties(unit_${target}_test PROPERTIES COMPILE_FLAGS "${TEST_CXX_FLAGS}")
@@ -588,7 +613,7 @@ if(UNITTEST AND TARGET GTest::gtest AND TARGET GTest::gmock)
else() else()
target_link_libraries(av_test_support PRIVATE toxcore_shared) target_link_libraries(av_test_support PRIVATE toxcore_shared)
endif() endif()
target_link_libraries(av_test_support PRIVATE GTest::gtest) target_link_libraries(av_test_support PUBLIC support GTest::gtest)
unit_test(toxav audio) unit_test(toxav audio)
target_link_libraries(unit_audio_test PRIVATE av_test_support) target_link_libraries(unit_audio_test PRIVATE av_test_support)
@@ -601,16 +626,27 @@ if(UNITTEST AND TARGET GTest::gtest AND TARGET GTest::gmock)
endif() endif()
unit_test(toxcore DHT) unit_test(toxcore DHT)
unit_test(toxcore TCP_client)
unit_test(toxcore TCP_common)
unit_test(toxcore TCP_connection)
unit_test(toxcore bin_pack) unit_test(toxcore bin_pack)
unit_test(toxcore crypto_core) unit_test(toxcore crypto_core)
unit_test(toxcore ev)
unit_test(toxcore friend_connection)
unit_test(toxcore group_announce) unit_test(toxcore group_announce)
unit_test(toxcore group_moderation) unit_test(toxcore group_moderation)
unit_test(toxcore list) unit_test(toxcore list)
unit_test(toxcore mem) unit_test(toxcore mem)
unit_test(toxcore mono_time) unit_test(toxcore mono_time)
unit_test(toxcore net_crypto)
unit_test(toxcore network)
unit_test(toxcore onion_client)
unit_test(toxcore ping_array) unit_test(toxcore ping_array)
unit_test(toxcore shared_key_cache)
unit_test(toxcore sort)
unit_test(toxcore test_util) unit_test(toxcore test_util)
unit_test(toxcore tox) unit_test(toxcore tox)
unit_test(toxcore tox_events)
unit_test(toxcore util) unit_test(toxcore util)
endif() endif()
@@ -645,13 +681,6 @@ if(DHT_BOOTSTRAP)
elseif(TARGET unofficial-sodium::sodium) elseif(TARGET unofficial-sodium::sodium)
target_link_libraries(DHT_bootstrap PRIVATE unofficial-sodium::sodium) target_link_libraries(DHT_bootstrap PRIVATE unofficial-sodium::sodium)
endif() endif()
if(TARGET pthreads4w::pthreads4w)
target_link_libraries(DHT_bootstrap PRIVATE pthreads4w::pthreads4w)
elseif(TARGET PThreads4W::PThreads4W)
target_link_libraries(DHT_bootstrap PRIVATE PThreads4W::PThreads4W)
elseif(TARGET Threads::Threads)
target_link_libraries(DHT_bootstrap PRIVATE Threads::Threads)
endif()
install(TARGETS DHT_bootstrap RUNTIME DESTINATION bin) install(TARGETS DHT_bootstrap RUNTIME DESTINATION bin)
endif() endif()
@@ -671,3 +700,53 @@ endif()
if (BUILD_FUZZ_TESTS) if (BUILD_FUZZ_TESTS)
add_subdirectory(testing/fuzzing) add_subdirectory(testing/fuzzing)
endif() endif()
################################################################################
#
# :: Benchmarks
#
################################################################################
if(UNITTEST AND benchmark_FOUND)
if(BUILD_TOXAV AND TARGET av_test_support)
add_executable(rtp_bench toxav/rtp_bench.cc)
target_link_libraries(rtp_bench PRIVATE
toxcore_static
av_test_support
benchmark::benchmark
)
add_executable(audio_bench toxav/audio_bench.cc)
target_link_libraries(audio_bench PRIVATE
toxcore_static
av_test_support
benchmark::benchmark
)
add_executable(video_bench toxav/video_bench.cc)
target_link_libraries(video_bench PRIVATE
toxcore_static
av_test_support
benchmark::benchmark
)
endif()
add_executable(sort_bench
toxcore/sort_bench.cc
toxcore/sort_test_util.cc
toxcore/sort_test_util.hh
)
target_link_libraries(sort_bench PRIVATE
toxcore_static
benchmark::benchmark
)
add_executable(ev_bench
toxcore/ev_bench.cc
)
target_link_libraries(ev_bench PRIVATE
test_util
toxcore_static
benchmark::benchmark
)
endif()

View File

@@ -286,7 +286,7 @@ requirements are that you have Docker version of >= 1.9.0 and you are running
64-bit system. 64-bit system.
The cross-compilation is fully automated by a parameterized The cross-compilation is fully automated by a parameterized
[Dockerfile](/other/docker/windows/Dockerfile). [Dockerfile](/other/docker/windows/windows.Dockerfile).
Install Docker Install Docker
@@ -313,10 +313,10 @@ available to customize the building of the container image.
Example of building a container image with options Example of building a container image with options
```sh ```sh
cd other/docker/windows
docker build \ docker build \
--build-arg SUPPORT_TEST=true \ --build-arg SUPPORT_TEST=true \
-t toxcore \ -t toxcore \
-f other/docker/windows/windows.Dockerfile \
. .
``` ```

View File

@@ -195,8 +195,6 @@ This project uses various tools supporting Static Application Security Testing:
- [cppcheck](https://cppcheck.sourceforge.io/): A static analyzer for C/C++ - [cppcheck](https://cppcheck.sourceforge.io/): A static analyzer for C/C++
code. code.
- [cpplint](https://github.com/cpplint/cpplint): Static code checker for C++ - [cpplint](https://github.com/cpplint/cpplint): Static code checker for C++
- [goblint](https://goblint.in.tum.de/): A static analyzer for multi-threaded C
programs, specializing in finding concurrency bugs.
- [infer](https://github.com/facebook/infer): A static analyzer for Java, C, - [infer](https://github.com/facebook/infer): A static analyzer for Java, C,
C++, and Objective-C. C++, and Objective-C.
- [PVS-Studio](https://pvs-studio.com/en/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source): - [PVS-Studio](https://pvs-studio.com/en/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source):

View File

@@ -4,6 +4,8 @@ cc_library(
name = "check_compat", name = "check_compat",
testonly = True, testonly = True,
hdrs = ["check_compat.h"], hdrs = ["check_compat.h"],
visibility = ["//c-toxcore/auto_tests:__subpackages__"],
deps = ["//c-toxcore/toxcore:ccompat"],
) )
cc_library( cc_library(
@@ -14,11 +16,14 @@ cc_library(
deps = [ deps = [
":check_compat", ":check_compat",
"//c-toxcore/testing:misc_tools", "//c-toxcore/testing:misc_tools",
"//c-toxcore/toxcore:DHT",
"//c-toxcore/toxcore:Messenger", "//c-toxcore/toxcore:Messenger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:net_crypto",
"//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_dispatch", "//c-toxcore/toxcore:tox_dispatch",
"//c-toxcore/toxcore:tox_events", "//c-toxcore/toxcore:tox_events",
"//c-toxcore/toxcore:tox_log_level",
], ],
) )
@@ -72,6 +77,8 @@ extra_data = {
"//c-toxcore/toxcore:onion", "//c-toxcore/toxcore:onion",
"//c-toxcore/toxcore:onion_announce", "//c-toxcore/toxcore:onion_announce",
"//c-toxcore/toxcore:onion_client", "//c-toxcore/toxcore:onion_client",
"//c-toxcore/toxcore:os_memory",
"//c-toxcore/toxcore:os_random",
"//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_dispatch", "//c-toxcore/toxcore:tox_dispatch",
"//c-toxcore/toxcore:tox_events", "//c-toxcore/toxcore:tox_events",

View File

@@ -43,6 +43,7 @@ TESTS = \
scenario_friend_read_receipt_test \ scenario_friend_read_receipt_test \
scenario_friend_request_spam_test \ scenario_friend_request_spam_test \
scenario_friend_request_test \ scenario_friend_request_test \
scenario_group_by_id_test \
scenario_group_general_test \ scenario_group_general_test \
scenario_group_invite_test \ scenario_group_invite_test \
scenario_group_message_test \ scenario_group_message_test \
@@ -232,6 +233,10 @@ scenario_friend_request_spam_test_SOURCES = ../auto_tests/scenarios/scenario_fri
scenario_friend_request_spam_test_CFLAGS = $(AUTOTEST_CFLAGS) scenario_friend_request_spam_test_CFLAGS = $(AUTOTEST_CFLAGS)
scenario_friend_request_spam_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la scenario_friend_request_spam_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la
scenario_group_by_id_test_SOURCES = ../auto_tests/scenarios/scenario_group_by_id_test.c
scenario_group_by_id_test_CFLAGS = $(AUTOTEST_CFLAGS)
scenario_group_by_id_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la
scenario_group_general_test_SOURCES = ../auto_tests/scenarios/scenario_group_general_test.c scenario_group_general_test_SOURCES = ../auto_tests/scenarios/scenario_group_general_test.c
scenario_group_general_test_CFLAGS = $(AUTOTEST_CFLAGS) scenario_group_general_test_CFLAGS = $(AUTOTEST_CFLAGS)
scenario_group_general_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la scenario_group_general_test_LDADD = $(AUTOTEST_LDADD) libscenario_framework.la

View File

@@ -7,28 +7,53 @@
#include <stdlib.h> #include <stdlib.h>
#ifndef ck_assert #ifndef ck_assert
#define ck_assert(ok) do { \ #define ck_assert(ok) \
if (!(ok)) { \ do { \
fprintf(stderr, "%s:%d: failed `%s'\n", __FILE__, __LINE__, #ok); \ if (!(ok)) { \
exit(7); \ fprintf(stderr, "%s:%d: failed `%s'\n", __FILE__, __LINE__, #ok); \
} \ exit(7); \
} while (0) } \
} while (0)
#define ck_assert_msg(ok, ...) do { \ #define ck_assert_msg(ok, ...) \
if (!(ok)) { \ do { \
fprintf(stderr, "%s:%d: failed `%s': ", __FILE__, __LINE__, #ok); \ if (!(ok)) { \
fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "%s:%d: failed `%s': ", __FILE__, __LINE__, #ok); \
fprintf(stderr, "\n"); \ fprintf(stderr, __VA_ARGS__); \
exit(7); \ fprintf(stderr, "\n"); \
} \ exit(7); \
} while (0) } \
} while (0)
#define ck_abort_msg(...) do { \ #define ck_assert_int_eq(a, b) \
fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \ do { \
fprintf(stderr, __VA_ARGS__); \ const int32_t _a = (a); \
fprintf(stderr, "\n"); \ const int32_t _b = (b); \
exit(7); \ if (_a != _b) { \
} while (0) fprintf(stderr, "%s:%d: failed `%s == %s` (%d != %d)\n", __FILE__, __LINE__, #a, #b, \
_a, _b); \
exit(7); \
} \
} while (0)
#define ck_assert_uint_eq(a, b) \
do { \
const uint32_t _a = (a); \
const uint32_t _b = (b); \
if (_a != _b) { \
fprintf(stderr, "%s:%d: failed `%s == %s` (%u != %u)\n", __FILE__, __LINE__, #a, #b, \
_a, _b); \
exit(7); \
} \
} while (0)
#define ck_abort_msg(...) \
do { \
fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
exit(7); \
} while (0)
#endif #endif
#endif // C_TOXCORE_AUTO_TESTS_CHECK_COMPAT_H #endif // C_TOXCORE_AUTO_TESTS_CHECK_COMPAT_H

View File

@@ -7,7 +7,10 @@ cc_library(
hdrs = ["framework/framework.h"], hdrs = ["framework/framework.h"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//c-toxcore/auto_tests:check_compat",
"//c-toxcore/testing:misc_tools", "//c-toxcore/testing:misc_tools",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:ccompat",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network", "//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox",

View File

@@ -45,6 +45,7 @@ scenario_test(scenario_friend_query)
scenario_test(scenario_friend_read_receipt) scenario_test(scenario_friend_read_receipt)
scenario_test(scenario_friend_request) scenario_test(scenario_friend_request)
scenario_test(scenario_friend_request_spam) scenario_test(scenario_friend_request_spam)
scenario_test(scenario_group_by_id)
scenario_test(scenario_group_general) scenario_test(scenario_group_general)
scenario_test(scenario_group_invite) scenario_test(scenario_group_invite)
scenario_test(scenario_group_message) scenario_test(scenario_group_message)

View File

@@ -159,15 +159,16 @@ ToxScenario *tox_scenario_new(int argc, char *const argv[], uint64_t timeout_ms)
ToxScenario *s = (ToxScenario *)calloc(1, sizeof(ToxScenario)); ToxScenario *s = (ToxScenario *)calloc(1, sizeof(ToxScenario));
ck_assert(s != nullptr); 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->mutex, nullptr);
pthread_mutex_init(&s->clock_mutex, nullptr); pthread_mutex_init(&s->clock_mutex, nullptr);
pthread_cond_init(&s->cond_runner, nullptr); pthread_cond_init(&s->cond_runner, nullptr);
pthread_cond_init(&s->cond_nodes, nullptr); pthread_cond_init(&s->cond_nodes, 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);
return s; return s;
} }
@@ -660,10 +661,10 @@ ToxScenarioStatus tox_scenario_run(ToxScenario *s)
pthread_mutex_lock(&s->mutex); pthread_mutex_lock(&s->mutex);
uint64_t start_clock = s->virtual_clock; uint64_t start_clock = tox_scenario_get_time(s);
uint64_t deadline = start_clock + s->timeout_ms; uint64_t deadline = start_clock + s->timeout_ms;
while (s->num_active > 0 && s->virtual_clock < deadline) { while (s->num_active > 0 && tox_scenario_get_time(s) < deadline) {
// 1. Wait until all nodes (including finished ones) have reached the barrier // 1. Wait until all nodes (including finished ones) have reached the barrier
while (s->num_ready < s->num_nodes) { while (s->num_ready < s->num_nodes) {
pthread_cond_wait(&s->cond_runner, &s->mutex); pthread_cond_wait(&s->cond_runner, &s->mutex);
@@ -700,7 +701,7 @@ ToxScenarioStatus tox_scenario_run(ToxScenario *s)
ToxScenarioStatus result = (s->num_active == 0) ? TOX_SCENARIO_DONE : TOX_SCENARIO_TIMEOUT; ToxScenarioStatus result = (s->num_active == 0) ? TOX_SCENARIO_DONE : TOX_SCENARIO_TIMEOUT;
if (result == 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)); tox_scenario_log(s, "Scenario TIMEOUT after %lu ms (virtual time)", (unsigned long)(tox_scenario_get_time(s) - start_clock));
} }
// Stop nodes // Stop nodes

View File

@@ -6,6 +6,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "../../check_compat.h"
#include "../../../toxcore/attributes.h" #include "../../../toxcore/attributes.h"
#include "../../../toxcore/ccompat.h" #include "../../../toxcore/ccompat.h"
#include "../../../toxcore/tox.h" #include "../../../toxcore/tox.h"
@@ -194,22 +195,4 @@ void tox_node_friend_add(ToxNode *a, ToxNode *b);
*/ */
void tox_node_reload(ToxNode *node); 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 #endif // TOX_TEST_FRAMEWORK_H

View File

@@ -106,6 +106,34 @@ static void sender_script(ToxNode *self, void *ctx)
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); 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); tox_node_log(self, "Started sending file %u", fnum);
Tox_File_Id file_id;
if (!tox_file_get_file_id(tox_node_get_tox(self), 0, fnum, file_id, nullptr)) {
tox_node_log(self, "Failed to get file id for file %u", fnum);
abort();
}
Tox_Err_File_By_Id err;
Tox_File_Number found_fnum = tox_file_by_id(tox_node_get_tox(self), 0, file_id, &err);
if (found_fnum != fnum) {
tox_node_log(self, "tox_file_by_id failed: expected %u, got %u, error %u", fnum, found_fnum, err);
abort();
}
/* Test with invalid friend number */
found_fnum = tox_file_by_id(tox_node_get_tox(self), 1234, file_id, &err);
if (found_fnum != UINT32_MAX || err != TOX_ERR_FILE_BY_ID_FRIEND_NOT_FOUND) {
tox_node_log(self, "tox_file_by_id with invalid friend failed: expected UINT32_MAX and FRIEND_NOT_FOUND, got %u and error %u", found_fnum, err);
abort();
}
/* Test with invalid file id */
Tox_File_Id invalid_id = {0};
found_fnum = tox_file_by_id(tox_node_get_tox(self), 0, invalid_id, &err);
if (found_fnum != UINT32_MAX || err != TOX_ERR_FILE_BY_ID_NOT_FOUND) {
tox_node_log(self, "tox_file_by_id with invalid id failed: expected UINT32_MAX and NOT_FOUND, got %u and error %u", found_fnum, err);
abort();
}
WAIT_UNTIL(state->file_sending_done); WAIT_UNTIL(state->file_sending_done);
tox_node_log(self, "Done"); tox_node_log(self, "Done");
} }

View File

@@ -0,0 +1,96 @@
#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 PEER_NICK "Test Nick"
#define PEER_NICK_LEN (sizeof(PEER_NICK) - 1)
typedef struct {
uint32_t group_number;
uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE];
} GroupState;
static void script(ToxNode *self, void *ctx)
{
GroupState *state = (GroupState *)ctx;
Tox *tox = tox_node_get_tox(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 *)PEER_NICK, PEER_NICK_LEN, &err_new);
ck_assert_int_eq(err_new, TOX_ERR_GROUP_NEW_OK);
tox_node_log(self, "Group created: %u", state->group_number);
Tox_Err_Group_State_Query err_query;
ck_assert(tox_group_get_chat_id(tox, state->group_number, state->chat_id, &err_query));
ck_assert_int_eq(err_query, TOX_ERR_GROUP_STATE_QUERY_OK);
{
// Test tox_group_by_id
Tox_Err_Group_By_Id err_by_id;
uint32_t group_number = tox_group_by_id(tox, state->chat_id, &err_by_id);
ck_assert_int_eq(err_by_id, TOX_ERR_GROUP_BY_ID_OK);
ck_assert_uint_eq(group_number, state->group_number);
// Test tox_group_by_id with invalid ID
uint8_t invalid_chat_id[TOX_GROUP_CHAT_ID_SIZE];
memset(invalid_chat_id, 0xAA, TOX_GROUP_CHAT_ID_SIZE);
group_number = tox_group_by_id(tox, invalid_chat_id, &err_by_id);
ck_assert_int_eq(err_by_id, TOX_ERR_GROUP_BY_ID_NOT_FOUND);
ck_assert_uint_eq(group_number, UINT32_MAX);
// Test tox_group_by_id with NULL ID
group_number = tox_group_by_id(tox, NULL, &err_by_id);
ck_assert_int_eq(err_by_id, TOX_ERR_GROUP_BY_ID_NULL);
ck_assert_uint_eq(group_number, UINT32_MAX);
}
// Create another group to ensure it works with multiple groups
Tox_Group_Number group2 = tox_group_new(tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"Group 2", 7, (const uint8_t *)PEER_NICK, PEER_NICK_LEN, &err_new);
ck_assert_int_eq(err_new, TOX_ERR_GROUP_NEW_OK);
uint8_t chat_id2[TOX_GROUP_CHAT_ID_SIZE];
ck_assert(tox_group_get_chat_id(tox, group2, chat_id2, &err_query));
ck_assert_int_eq(err_query, TOX_ERR_GROUP_STATE_QUERY_OK);
{
Tox_Err_Group_By_Id err_by_id;
ck_assert_uint_eq(tox_group_by_id(tox, state->chat_id, &err_by_id), state->group_number);
ck_assert_int_eq(err_by_id, TOX_ERR_GROUP_BY_ID_OK);
ck_assert_uint_eq(tox_group_by_id(tox, chat_id2, &err_by_id), group2);
ck_assert_int_eq(err_by_id, TOX_ERR_GROUP_BY_ID_OK);
}
Tox_Err_Group_Leave err_leave;
ck_assert(tox_group_leave(tox, state->group_number, nullptr, 0, &err_leave));
ck_assert_int_eq(err_leave, TOX_ERR_GROUP_LEAVE_OK);
// Yield to allow the group to be actually deleted
tox_scenario_yield(self);
{
// After leaving, it should not be found
Tox_Err_Group_By_Id err_by_id;
uint32_t group_number = tox_group_by_id(tox, state->chat_id, &err_by_id);
ck_assert_int_eq(err_by_id, TOX_ERR_GROUP_BY_ID_NOT_FOUND);
ck_assert_uint_eq(group_number, UINT32_MAX);
}
}
int main(int argc, char *argv[])
{
ToxScenario *s = tox_scenario_new(argc, argv, 60000);
GroupState state = {0};
tox_scenario_add_node(s, "Node", script, &state, sizeof(GroupState));
ToxScenarioStatus res = tox_scenario_run(s);
if (res != TOX_SCENARIO_DONE) {
return 1;
}
tox_scenario_free(s);
return 0;
}

View File

@@ -305,6 +305,8 @@ int main(int argc, char *argv[])
return 0; return 0;
} }
#undef GROUP_NAME #undef TOPIC2
#undef TOPIC1
#undef GROUP_NAME_LEN #undef GROUP_NAME_LEN
#undef GROUP_NAME
#undef NUM_PEERS #undef NUM_PEERS

View File

@@ -158,6 +158,7 @@ AX_HAVE_EPOLL
if test "$enable_epoll" != "no"; then if test "$enable_epoll" != "no"; then
if test "${ax_cv_have_epoll}" = "yes"; then if test "${ax_cv_have_epoll}" = "yes"; then
AC_DEFINE([TCP_SERVER_USE_EPOLL],[1],[define to 1 to enable epoll support]) AC_DEFINE([TCP_SERVER_USE_EPOLL],[1],[define to 1 to enable epoll support])
AC_DEFINE([EV_USE_EPOLL],[1],[define to 1 to enable epoll support])
enable_epoll='yes' enable_epoll='yes'
else else
if test "$enable_epoll" = "yes"; then if test "$enable_epoll" = "yes"; then

View File

@@ -31,6 +31,8 @@ cc_binary(
"//c-toxcore/toxcore:network", "//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:onion", "//c-toxcore/toxcore:onion",
"//c-toxcore/toxcore:onion_announce", "//c-toxcore/toxcore:onion_announce",
"//c-toxcore/toxcore:os_memory",
"//c-toxcore/toxcore:os_random",
"//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox",
], ],
) )

View File

@@ -10,7 +10,7 @@ run() {
"${CPPFLAGS[@]}" \ "${CPPFLAGS[@]}" \
"${LDFLAGS[@]}" \ "${LDFLAGS[@]}" \
"$@" \ "$@" \
-std=c++17 \ -std=c++20 \
-Werror \ -Werror \
-Weverything \ -Weverything \
-Wno-alloca \ -Wno-alloca \

View File

@@ -11,7 +11,7 @@ run() {
"${CPPFLAGS[@]}" \ "${CPPFLAGS[@]}" \
"${LDFLAGS[@]}" \ "${LDFLAGS[@]}" \
"$@" \ "$@" \
-std=c++17 \ -std=c++20 \
-fdiagnostics-color=always \ -fdiagnostics-color=always \
-Wall \ -Wall \
-Wextra \ -Wextra \
@@ -20,6 +20,7 @@ run() {
-Wno-aggressive-loop-optimizations \ -Wno-aggressive-loop-optimizations \
-Wno-float-conversion \ -Wno-float-conversion \
-Wno-format-signedness \ -Wno-format-signedness \
-Wno-inline \
-Wno-missing-field-initializers \ -Wno-missing-field-initializers \
-Wno-nonnull-compare \ -Wno-nonnull-compare \
-Wno-padded \ -Wno-padded \
@@ -46,7 +47,6 @@ run() {
-Wignored-attributes \ -Wignored-attributes \
-Wignored-qualifiers \ -Wignored-qualifiers \
-Winit-self \ -Winit-self \
-Winline \
-Wlarger-than=530000 \ -Wlarger-than=530000 \
-Wmaybe-uninitialized \ -Wmaybe-uninitialized \
-Wmemset-transposed-args \ -Wmemset-transposed-args \

View File

@@ -25,6 +25,7 @@ cc_binary(
"//c-toxcore/toxcore:network", "//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:onion", "//c-toxcore/toxcore:onion",
"//c-toxcore/toxcore:onion_announce", "//c-toxcore/toxcore:onion_announce",
"//c-toxcore/toxcore:os_memory",
"//c-toxcore/toxcore:os_random", "//c-toxcore/toxcore:os_random",
"//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox",
"@libconfig", "@libconfig",

View File

@@ -290,8 +290,8 @@ int main(int argc, char *argv[])
IP ip; IP ip;
ip_init(&ip, enable_ipv6); ip_init(&ip, enable_ipv6);
const Tox_Memory *mem = os_memory(); const Memory *mem = os_memory();
const Tox_Random *rng = os_random(); const Random *rng = os_random();
const Network *ns = os_network(); const Network *ns = os_network();
Logger *logger = logger_new(mem); Logger *logger = logger_new(mem);

View File

@@ -0,0 +1,4 @@
#!/bin/bash
set -e
cmake -GNinja -B build -S .
cmake --build build --parallel "$(nproc)"

View File

@@ -0,0 +1,53 @@
# other/docker/codeql/codeql.Dockerfile
FROM toxchat/c-toxcore:sources AS sources
FROM ubuntu:22.04
RUN apt-get update && \
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
cmake \
curl \
git \
libconfig-dev \
libopus-dev \
libsodium-dev \
libvpx-dev \
ninja-build \
pkg-config \
unzip \
wget \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install CodeQL
ARG CODEQL_VERSION=v2.23.9
RUN curl -L -o /tmp/codeql.zip https://github.com/github/codeql-cli-binaries/releases/download/${CODEQL_VERSION}/codeql-linux64.zip && \
unzip -q /tmp/codeql.zip -d /opt && \
rm /tmp/codeql.zip
ENV PATH="/opt/codeql:$PATH"
RUN groupadd -r -g 1000 builder \
&& useradd -m --no-log-init -r -g builder -u 1000 builder
WORKDIR /home/builder/c-toxcore
# Copy sources
COPY --chown=builder:builder --from=sources /src/ /home/builder/c-toxcore/
# Pre-create build directory
RUN mkdir -p build codeql-db && chown builder:builder codeql-db build
# Copy scripts
COPY --chown=builder:builder other/docker/codeql/build.sh .
COPY --chown=builder:builder other/docker/codeql/run-analysis.sh .
RUN chmod +x build.sh run-analysis.sh
USER builder
# Download standard queries as builder
RUN codeql pack download codeql/cpp-queries
CMD ["./run-analysis.sh"]

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -eux
BUILD=codeql
# Ensure the sources image is built
other/docker/sources/build.sh
# Build the codeql image
docker build -t "toxchat/c-toxcore:$BUILD" -f "other/docker/$BUILD/$BUILD.Dockerfile" .
# Run the container
echo "Running CodeQL analysis..."
docker run --rm "toxchat/c-toxcore:$BUILD"

View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -e
echo "Creating CodeQL Database..."
codeql database create codeql-db --language=cpp --overwrite --command="./build.sh"
echo "Analyzing..."
codeql database analyze codeql-db codeql/cpp-queries:codeql-suites/cpp-security-and-quality.qls --format=csv --output=codeql-db/results.csv
echo "Analysis complete. Results in codeql-db/results.csv"
cat codeql-db/results.csv

View File

@@ -1,8 +0,0 @@
load("@rules_cc//cc:defs.bzl", "cc_library")
cc_library(
name = "sodium",
testonly = True,
srcs = ["sodium.c"],
deps = ["@libsodium"],
)

View File

@@ -1,43 +0,0 @@
{
"ana": {
"activated": [
"base","mallocWrapper","escape","mutex","mutexEvents","access","assert","expRelation"
],
"arrayoob": true,
"wp": true,
"apron": {
"strengthening": true
},
"base": {
"structs" : {
"domain" : "combined-sk"
},
"arrays": {
"domain": "partitioned"
}
},
"malloc": {
"wrappers": [
"mem_balloc",
"mem_alloc",
"mem_valloc",
"mem_vrealloc"
]
}
},
"warn": {
"behavior": false,
"call": false,
"integer": true,
"float": false,
"race": false,
"deadcode": false,
"unsound": false,
"imprecise": false,
"success": false,
"unknown": false
},
"exp": {
"earlyglobs": true
}
}

View File

@@ -1,21 +0,0 @@
FROM toxchat/c-toxcore:sources AS sources
FROM ghcr.io/goblint/analyzer:2.5.0
RUN apt-get update && \
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
libsodium-dev \
tcc \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /work
COPY --from=sources /src/ /work/
COPY other/deploy/single-file/make_single_file /work/
RUN ./make_single_file -core auto_tests/tox_new_test.c other/docker/goblint/sodium.c > analysis.c
# Try compiling+linking just to make sure we have all the fake functions.
RUN tcc analysis.c
COPY other/docker/goblint/analysis.json /work/other/docker/goblint/
RUN /opt/goblint/analyzer/bin/goblint --conf /work/other/docker/goblint/analysis.json analysis.c

View File

@@ -1,6 +0,0 @@
#!/bin/sh
set -eux
BUILD=goblint
other/docker/sources/build.sh
docker build -t "toxchat/c-toxcore:$BUILD" -f "other/docker/$BUILD/$BUILD.Dockerfile" .

View File

@@ -1,123 +0,0 @@
#include <sodium.h>
#include <string.h>
int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, const unsigned char *seed)
{
memset(pk, 0, 32);
memset(sk, 0, 32);
return 0;
}
int crypto_sign_ed25519_pk_to_curve25519(unsigned char *curve25519_pk,
const unsigned char *ed25519_pk)
{
memset(curve25519_pk, 0, 32);
return 0;
}
int crypto_sign_ed25519_sk_to_curve25519(unsigned char *curve25519_sk,
const unsigned char *ed25519_sk)
{
memset(curve25519_sk, 0, 32);
return 0;
}
void sodium_memzero(void *const pnt, const size_t len)
{
memset(pnt, 0, len);
}
int sodium_mlock(void *const addr, const size_t len)
{
return 0;
}
int sodium_munlock(void *const addr, const size_t len)
{
return 0;
}
int crypto_verify_32(const unsigned char *x, const unsigned char *y)
{
return memcmp(x, y, 32);
}
int crypto_verify_64(const unsigned char *x, const unsigned char *y)
{
return memcmp(x, y, 64);
}
int crypto_sign_detached(unsigned char *sig, unsigned long long *siglen_p,
const unsigned char *m, unsigned long long mlen,
const unsigned char *sk)
{
return 0;
}
int crypto_sign_verify_detached(const unsigned char *sig,
const unsigned char *m,
unsigned long long mlen,
const unsigned char *pk)
{
return 0;
}
int crypto_box_beforenm(unsigned char *k, const unsigned char *pk,
const unsigned char *sk)
{
memset(k, 0, 32);
return 0;
}
int crypto_box_afternm(unsigned char *c, const unsigned char *m,
unsigned long long mlen, const unsigned char *n,
const unsigned char *k)
{
memset(c, 0, 32);
return 0;
}
int crypto_box_open_afternm(unsigned char *m, const unsigned char *c,
unsigned long long clen, const unsigned char *n,
const unsigned char *k)
{
return 0;
}
int crypto_scalarmult_curve25519_base(unsigned char *q,
const unsigned char *n)
{
memset(q, 0, 32);
return 0;
}
int crypto_auth(unsigned char *out, const unsigned char *in,
unsigned long long inlen, const unsigned char *k)
{
return 0;
}
int crypto_auth_verify(const unsigned char *h, const unsigned char *in,
unsigned long long inlen, const unsigned char *k)
{
return 0;
}
int crypto_hash_sha256(unsigned char *out, const unsigned char *in,
unsigned long long inlen)
{
return 0;
}
int crypto_hash_sha512(unsigned char *out, const unsigned char *in,
unsigned long long inlen)
{
return 0;
}
int crypto_pwhash_scryptsalsa208sha256(unsigned char *const out,
unsigned long long outlen,
const char *const passwd,
unsigned long long passwdlen,
const unsigned char *const salt,
unsigned long long opslimit,
size_t memlimit)
{
memset(out, 0, outlen);
return 0;
}
void randombytes(unsigned char *const buf, const unsigned long long buf_len)
{
memset(buf, 0, buf_len);
}
uint32_t randombytes_uniform(const uint32_t upper_bound)
{
return upper_bound;
}
int sodium_init(void)
{
return 0;
}

View File

@@ -6,92 +6,79 @@ import sys
from dataclasses import dataclass from dataclasses import dataclass
from dataclasses import field from dataclasses import field
from typing import Any from typing import Any
from typing import Dict
from typing import List
STD_MODULE = """module std [system] { # We no longer define 'std' or 'musl' manually.
textual header "/usr/include/alloca.h" # We rely on -fimplicit-module-maps to find the system-provided ones.
textual header "/usr/include/assert.h" # We only define modules for our project and third-party libraries.
textual header "/usr/include/c++/14.2.0/algorithm" EXTRA_MODULES = """
textual header "/usr/include/c++/14.2.0/array" module "_system" [system] {
textual header "/usr/include/c++/14.2.0/atomic" header "/usr/include/stdint.h"
textual header "/usr/include/c++/14.2.0/cassert" header "/usr/include/stdbool.h"
textual header "/usr/include/c++/14.2.0/cerrno" header "/usr/include/stddef.h"
textual header "/usr/include/c++/14.2.0/chrono" header "/usr/include/stdio.h"
textual header "/usr/include/c++/14.2.0/climits" header "/usr/include/stdlib.h"
textual header "/usr/include/c++/14.2.0/compare" header "/usr/include/string.h"
textual header "/usr/include/c++/14.2.0/cstddef" header "/usr/include/inttypes.h"
textual header "/usr/include/c++/14.2.0/cstdint" header "/usr/include/limits.h"
textual header "/usr/include/c++/14.2.0/cstdio" header "/usr/include/errno.h"
textual header "/usr/include/c++/14.2.0/cstdlib" header "/usr/include/unistd.h"
textual header "/usr/include/c++/14.2.0/cstring" header "/usr/include/fcntl.h"
textual header "/usr/include/c++/14.2.0/deque" header "/usr/include/time.h"
textual header "/usr/include/c++/14.2.0/functional" header "/usr/include/assert.h"
textual header "/usr/include/c++/14.2.0/iomanip" header "/usr/include/pthread.h"
textual header "/usr/include/c++/14.2.0/iosfwd" header "/usr/include/arpa/inet.h"
textual header "/usr/include/c++/14.2.0/iostream" header "/usr/include/netdb.h"
textual header "/usr/include/c++/14.2.0/limits" header "/usr/include/netinet/in.h"
textual header "/usr/include/c++/14.2.0/map" header "/usr/include/sys/types.h"
textual header "/usr/include/c++/14.2.0/memory" header "/usr/include/sys/stat.h"
textual header "/usr/include/c++/14.2.0/mutex" header "/usr/include/sys/time.h"
textual header "/usr/include/c++/14.2.0/new" header "/usr/include/sys/socket.h"
textual header "/usr/include/c++/14.2.0/optional" header "/usr/include/sys/epoll.h"
textual header "/usr/include/c++/14.2.0/ostream" header "/usr/include/sys/ioctl.h"
textual header "/usr/include/c++/14.2.0/queue" header "/usr/include/sys/resource.h"
textual header "/usr/include/c++/14.2.0/random" header "/usr/include/sys/uio.h"
textual header "/usr/include/c++/14.2.0/stdlib.h" header "/usr/include/sys/mman.h"
textual header "/usr/include/c++/14.2.0/string" header "/usr/include/sys/wait.h"
textual header "/usr/include/c++/14.2.0/thread" header "/usr/include/sys/select.h"
textual header "/usr/include/c++/14.2.0/type_traits" header "/usr/include/poll.h"
textual header "/usr/include/c++/14.2.0/vector" header "/usr/include/sched.h"
textual header "/usr/include/errno.h" header "/usr/include/signal.h"
textual header "/usr/include/fortify/stdio.h" header "/usr/include/ctype.h"
textual header "/usr/include/fortify/string.h" header "/usr/include/alloca.h"
textual header "/usr/include/fortify/unistd.h" header "/usr/include/malloc.h"
textual header "/usr/include/limits.h" header "/usr/include/dirent.h"
textual header "/usr/include/stdarg.h" export *
textual header "/usr/include/stdbool.h"
textual header "/usr/include/stddef.h"
textual header "/usr/include/stdint.h"
textual header "/usr/include/sys/time.h"
textual header "/usr/include/sys/types.h"
textual header "/usr/include/time.h"
} }
module "c_toxcore_third_party_cmp" { module "_libsodium" [system] {
header "third_party/cmp/cmp.h" header "/usr/include/sodium.h"
use std use _system
export *
} }
module "c_toxcore_toxencryptsave_defines" { module "_benchmark" [system] {
header "toxencryptsave/defines.h" header "/usr/include/benchmark/benchmark.h"
use _system
export *
} }
module "_benchmark" { module "_com_google_googletest___gtest" [system] {
textual header "/usr/include/benchmark/benchmark.h" header "/usr/include/gtest/gtest.h"
use std header "/usr/include/gmock/gmock.h"
use _system
export *
} }
module "_com_google_googletest___gtest" { module "_com_google_googletest___gtest_main" [system] {
textual header "/usr/include/gmock/gmock.h" use _com_google_googletest___gtest
textual header "/usr/include/gtest/gtest.h" export *
use std
} }
module "_com_google_googletest___gtest_main" { module "_opus" [system] {
// Dummy module for gtest_main, assuming it links but headers are in gtest header "/usr/include/opus/opus.h"
use "_com_google_googletest___gtest" use _system
export *
} }
module "_libsodium" { module "_libvpx" [system] {
textual header "/usr/include/sodium.h" header "/usr/include/vpx/vpx_encoder.h"
} header "/usr/include/vpx/vpx_decoder.h"
module "_pthread" { use _system
textual header "/usr/include/pthread.h" export *
}
module "_psocket" {
textual header "/usr/include/arpa/inet.h"
textual header "/usr/include/fcntl.h"
textual header "/usr/include/fortify/sys/socket.h"
textual header "/usr/include/linux/if.h"
textual header "/usr/include/netdb.h"
textual header "/usr/include/netinet/in.h"
textual header "/usr/include/sys/epoll.h"
textual header "/usr/include/sys/ioctl.h"
} }
""" """
@@ -100,24 +87,16 @@ module "_psocket" {
class Target: class Target:
name: str name: str
package: str package: str
srcs: List[str] = field(default_factory=list) srcs: list[str] = field(default_factory=list)
hdrs: List[str] = field(default_factory=list) hdrs: list[str] = field(default_factory=list)
deps: List[str] = field(default_factory=list) deps: list[str] = field(default_factory=list)
@property @property
def label(self) -> str: def label(self) -> str:
# Sanitize label for module name return f"c-toxcore/{self.package}:{self.name}"
sanitized = (f"//c-toxcore/{self.package}:{self.name}".replace(
"/", "_").replace(":",
"_").replace(".",
"_").replace("-",
"_").replace("@", "_"))
if sanitized.startswith("__"):
sanitized = sanitized[2:]
return sanitized
TARGETS: List[Target] = [] TARGETS: list[Target] = []
class BuildContext: class BuildContext:
@@ -140,16 +119,29 @@ class BuildContext:
def bzl_cc_fuzz_test(self, *args: Any, **kwargs: Any) -> None: def bzl_cc_fuzz_test(self, *args: Any, **kwargs: Any) -> None:
pass pass
def bzl_select(self, selector: Dict[str, List[str]]) -> List[str]: def bzl_select(self, selector: dict[str, list[str]]) -> list[str]:
return selector.get("//tools/config:linux", return selector.get("//tools/config:linux",
selector.get("//conditions:default", [])) selector.get("//conditions:default", []))
def bzl_glob(self, include: List[str]) -> List[str]: def bzl_glob(self,
include: list[str],
exclude: list[str] | None = None,
**kwargs: Any) -> list[str]:
results = [] results = []
for pattern in include: for pattern in include:
full_pattern = os.path.join(self.package, pattern) full_pattern = os.path.join(self.package, pattern)
files = glob.glob(full_pattern) files = glob.glob(full_pattern, recursive=True)
results.extend([os.path.relpath(f, self.package) for f in files]) results.extend([os.path.relpath(f, self.package) for f in files])
if exclude:
excluded_files = set()
for pattern in exclude:
full_pattern = os.path.join(self.package, pattern)
files = glob.glob(full_pattern, recursive=True)
excluded_files.update(
[os.path.relpath(f, self.package) for f in files])
results = [f for f in results if f not in excluded_files]
return results return results
def _add_target(self, name: str, srcs: Any, hdrs: Any, deps: Any) -> None: def _add_target(self, name: str, srcs: Any, hdrs: Any, deps: Any) -> None:
@@ -174,6 +166,9 @@ class BuildContext:
**kwargs: Any) -> None: **kwargs: Any) -> None:
self._add_target(name, srcs, hdrs, deps) self._add_target(name, srcs, hdrs, deps)
def bzl_project(self, *args: Any, **kwargs: Any) -> None:
pass
def bzl_cc_test(self, def bzl_cc_test(self,
name: str, name: str,
srcs: Any = (), srcs: Any = (),
@@ -184,29 +179,44 @@ class BuildContext:
def resolve_module_name(dep: str, current_pkg: str) -> str: def resolve_module_name(dep: str, current_pkg: str) -> str:
# Resolve to canonical label first if dep in ["@psocket", "@pthread"]:
label = dep return "_system"
if dep.startswith("@"): if dep == "@libsodium":
label = dep return "_libsodium"
elif dep.startswith("//"): if dep == "@benchmark":
if ":" in dep: return "_benchmark"
label = dep if dep == "@com_google_googletest//:gtest":
else: return "_com_google_googletest___gtest"
pkg_name = os.path.basename(dep) if dep == "@com_google_googletest//:gtest_main":
label = f"{dep}:{pkg_name}" return "_com_google_googletest___gtest_main"
elif dep.startswith(":"): if dep == "@opus":
label = f"//c-toxcore/{current_pkg}{dep}" return "_opus"
if dep == "@libvpx":
return "_libvpx"
# Sanitize # Resolve to canonical label first
sanitized = (label.replace("/", "_").replace(":", "_").replace( if dep.startswith("@"):
".", "_").replace("-", "_").replace("@", "_")) return dep
if sanitized.startswith("__"): if dep.startswith("//"):
sanitized = sanitized[2:] label = dep[2:]
return sanitized if ":" in label:
return label
pkg_name = os.path.basename(label)
return f"{label}:{pkg_name}"
if dep.startswith(":"):
return f"c-toxcore/{current_pkg}{dep}"
return dep
def main() -> None: def main() -> None:
packages = ["toxcore", "testing/support"] packages = []
for root, dirs, files in os.walk("."):
if "BUILD.bazel" in files:
pkg = os.path.relpath(root, ".")
if pkg == ".":
pkg = ""
packages.append(pkg)
for pkg in packages: for pkg in packages:
ctx = BuildContext(pkg) ctx = BuildContext(pkg)
@@ -214,39 +224,53 @@ def main() -> None:
if not os.path.exists(build_file): if not os.path.exists(build_file):
continue continue
# Use a defaultdict to handle any unknown functions as no-ops
from collections import defaultdict
env: dict[str, Any] = defaultdict(lambda: lambda *args, **kwargs: None)
env.update({
"load": ctx.bzl_load,
"exports_files": ctx.bzl_exports_files,
"cc_library": ctx.bzl_cc_library,
"no_undefined_cc_library": ctx.bzl_cc_library,
"cc_binary": ctx.bzl_cc_binary,
"cc_test": ctx.bzl_cc_test,
"cc_fuzz_test": ctx.bzl_cc_fuzz_test,
"select": ctx.bzl_select,
"glob": ctx.bzl_glob,
"alias": ctx.bzl_alias,
"sh_library": ctx.bzl_sh_library,
"project": ctx.bzl_project,
})
with open(build_file, "r") as f: with open(build_file, "r") as f:
exec( exec(f.read(), env)
f.read(),
{
"load": ctx.bzl_load,
"exports_files": ctx.bzl_exports_files,
"cc_library": ctx.bzl_cc_library,
"cc_binary": ctx.bzl_cc_binary,
"cc_test": ctx.bzl_cc_test,
"cc_fuzz_test": ctx.bzl_cc_fuzz_test,
"select": ctx.bzl_select,
"glob": ctx.bzl_glob,
"alias": ctx.bzl_alias,
"sh_library": ctx.bzl_sh_library,
},
)
with open("module.modulemap", "w") as f: with open("module.modulemap", "w") as f:
f.write(STD_MODULE) f.write(EXTRA_MODULES)
for t in TARGETS: for t in TARGETS:
f.write(f'module "{t.label}" {{\n') f.write(f'module "{t.label}" {{\n')
for hdr in t.hdrs: for hdr in t.hdrs:
# Proper modular header
f.write(f' header "{os.path.join(t.package, hdr)}"\n') f.write(f' header "{os.path.join(t.package, hdr)}"\n')
f.write(" use std\n")
# Use all dependencies
for dep in t.deps: for dep in t.deps:
mod_name = resolve_module_name(dep, t.package) mod_name = resolve_module_name(dep, t.package)
# Export all dependencies to ensure visibility of types used in headers f.write(f' use "{mod_name}"\n')
f.write(f" use {mod_name}\n")
f.write(f" export {mod_name}\n") # Re-export everything we use to match standard C++ transitive include behavior.
f.write(" export *\n")
# Basic system modules used everywhere
f.write(' use "_system"\n')
f.write("}\n") f.write("}\n")
# with open("module.modulemap", "r") as f: if "--print-modulemap" in sys.argv:
# print(f.read(), file=sys.stderr) with open("module.modulemap", "r") as f:
print(f.read())
return
src_to_module = {} src_to_module = {}
for t in TARGETS: for t in TARGETS:
@@ -254,29 +278,44 @@ def main() -> None:
full_src = os.path.join(t.package, src) full_src = os.path.join(t.package, src)
src_to_module[full_src] = t.label src_to_module[full_src] = t.label
# Sort for deterministic output
all_srcs = sorted(src_to_module.keys()) all_srcs = sorted(src_to_module.keys())
all_srcs = [src for src in all_srcs if src.endswith(".c")]
os.makedirs("/tmp/clang-modules", exist_ok=True)
for src in all_srcs: for src in all_srcs:
print(f"Validating {src}", file=sys.stderr) print(f"Validating {src}", file=sys.stderr)
module_name = src_to_module[src] module_name = src_to_module[src]
lang = "-xc" if src.endswith(".c") else "-xc++"
std = "-std=c11" if src.endswith(".c") else "-std=c++23"
subprocess.run( subprocess.run(
[ [
"clang", "clang",
"-fsyntax-only", "-fsyntax-only",
"-xc++", lang,
"-stdlib=libc++" if lang == "-xc++" else "-nostdinc++",
"-Wall", "-Wall",
"-Werror", "-Werror",
"-Wno-missing-braces", "-Wno-missing-braces",
"-DTCP_SERVER_USE_EPOLL", "-DTCP_SERVER_USE_EPOLL",
"-std=c++23", "-D_XOPEN_SOURCE=600",
"-D_GNU_SOURCE",
std,
"-fdiagnostics-color=always", "-fdiagnostics-color=always",
"-fmodules", "-fmodules",
"-fmodules-strict-decluse", "-Xclang",
"-fmodules-local-submodule-visibility",
"-fmodules-decluse",
"-Xclang",
"-fno-modules-error-recovery",
"-fno-implicit-module-maps",
"-fno-builtin-module-map",
"-fmodules-cache-path=/tmp/clang-modules",
"-fmodule-map-file=module.modulemap", "-fmodule-map-file=module.modulemap",
f"-fmodule-name={module_name}", f"-fmodule-name={module_name}",
"-I.", "-I.",
"-I/usr/include/opus",
src, src,
], ],
check=True, check=True,

View File

@@ -1,10 +1,13 @@
FROM alpine:3.21.0 FROM alpine:3.23.0
RUN ["apk", "add", "--no-cache", \ RUN ["apk", "add", "--no-cache", \
"bash", \ "bash", \
"benchmark-dev", \ "benchmark-dev", \
"clang", \ "clang", \
"gmock", \
"gtest-dev", \ "gtest-dev", \
"libc++-dev", \
"libc++-static", \
"libconfig-dev", \ "libconfig-dev", \
"libsodium-dev", \ "libsodium-dev", \
"libvpx-dev", \ "libvpx-dev", \

View File

@@ -114,6 +114,21 @@ build() {
make install make install
cd .. cd ..
echo
echo "=== Building GTest $VERSION_GTEST $ARCH ==="
curl "${CURL_OPTIONS[@]}" "https://github.com/google/googletest/archive/refs/tags/v$VERSION_GTEST.tar.gz" -o "googletest-$VERSION_GTEST.tar.gz"
check_sha256 "65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c" "googletest-$VERSION_GTEST.tar.gz"
tar -xf "googletest-$VERSION_GTEST.tar.gz"
cd "googletest-$VERSION_GTEST"
cmake \
-DCMAKE_TOOLCHAIN_FILE=../windows_toolchain.cmake \
-DCMAKE_INSTALL_PREFIX="$PREFIX_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-S . -B build
cmake --build build --target install --parallel "$(nproc)"
cd ..
rm -rf /tmp/* rm -rf /tmp/*
} }

View File

@@ -0,0 +1 @@
!other/docker/windows/*.sh

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
DOCKERFLAGS=(--build-arg SUPPORT_TEST=true)
. "$(cd "$(dirname "${BASH_SOURCE[0]}")/../sources" && pwd)/run.sh"
# Create a temporary directory for the source to workaround potential bind mount issues
# (e.g. FUSE/SSHFS filesystems or Docker daemon visibility issues).
TEMP_SRC=$(mktemp -d -t toxcore-src-XXXXXX)
cleanup() {
rm -rf "$TEMP_SRC"
}
trap cleanup EXIT
echo "Copying source to temporary directory $TEMP_SRC..."
# Exclude .git and build artifacts to speed up copy
rsync -a --exclude .git --exclude _build . "$TEMP_SRC/"
docker run \
-e ENABLE_TEST=true \
-e EXTRA_CMAKE_FLAGS=-DUNITTEST=ON \
-v "$TEMP_SRC:/toxcore" \
-v /tmp/c-toxcore-build:/prefix \
-t \
--rm \
toxchat/c-toxcore:windows

View File

@@ -7,6 +7,7 @@ FROM debian:trixie-slim
ARG VERSION_OPUS=1.4 \ ARG VERSION_OPUS=1.4 \
VERSION_SODIUM=1.0.19 \ VERSION_SODIUM=1.0.19 \
VERSION_VPX=1.14.0 \ VERSION_VPX=1.14.0 \
VERSION_GTEST=1.17.0 \
ENABLE_HASH_VERIFICATION=true \ ENABLE_HASH_VERIFICATION=true \
\ \
SUPPORT_TEST=false \ SUPPORT_TEST=false \
@@ -21,14 +22,14 @@ ENV SUPPORT_TEST=${SUPPORT_TEST} \
CROSS_COMPILE=${CROSS_COMPILE} CROSS_COMPILE=${CROSS_COMPILE}
WORKDIR /work WORKDIR /work
COPY check_sha256.sh . COPY other/docker/windows/check_sha256.sh .
COPY get_packages.sh . COPY other/docker/windows/get_packages.sh .
RUN ./get_packages.sh RUN ./get_packages.sh
COPY build_dependencies.sh . COPY other/docker/windows/build_dependencies.sh .
RUN ./build_dependencies.sh RUN ./build_dependencies.sh
COPY build_toxcore.sh . COPY other/docker/windows/build_toxcore.sh .
ENV ENABLE_TEST=false \ ENV ENABLE_TEST=false \
ALLOW_TEST_FAILURE=false \ ALLOW_TEST_FAILURE=false \

View File

@@ -0,0 +1,24 @@
# ===== common =====
# Ignore everything ...
**/*
# ... except sources
!**/*.[ch]
!**/*.cc
!**/*.hh
!CHANGELOG.md
!LICENSE
!README.md
!auto_tests/data/*
!other/bootstrap_daemon/bash-completion/**
!other/bootstrap_daemon/tox-bootstrapd.*
!other/proxy/*.mod
!other/proxy/*.sum
!other/proxy/*.go
# ... and CMake build files (used by most builds).
!**/CMakeLists.txt
!.github/scripts/flags*.sh
!cmake/*.cmake
!other/pkgconfig/*
!other/rpm/*
!so.version
!other/docker/windows/*.sh

View File

@@ -4,13 +4,14 @@
// this file can be used to generate event.c files // this file can be used to generate event.c files
// requires c++17 // requires c++17
#include <algorithm>
#include <cstdint>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <variant>
#include <map> #include <map>
#include <string>
#include <variant>
#include <vector>
std::string str_tolower(std::string s) { std::string str_tolower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::tolower(c); }); std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::tolower(c); });
@@ -168,6 +169,7 @@ std::string zero_initializer_for_type(const std::string& type) {
void generate_event_impl(const std::string& event_name, const std::vector<EventType>& event_types) { void generate_event_impl(const std::string& event_name, const std::vector<EventType>& event_types) {
const std::string event_name_l = str_tolower(event_name); const std::string event_name_l = str_tolower(event_name);
const std::string event_name_u = str_toupper(event_name);
std::string file_name = output_folder + "/" + event_name_l + ".c"; std::string file_name = output_folder + "/" + event_name_l + ".c";
std::ofstream f(file_name); std::ofstream f(file_name);
@@ -218,11 +220,20 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
#include "../tox.h" #include "../tox.h"
#include "../tox_event.h" #include "../tox_event.h"
#include "../tox_events.h")"; #include "../tox_events.h")";
if (need_tox_unpack_h) {
f << R"(
#include "../tox_pack.h")";
}
f << R"(
#include "../tox_struct.h")";
if (need_tox_unpack_h) { if (need_tox_unpack_h) {
f << R"( f << R"(
#include "../tox_pack.h"
#include "../tox_unpack.h")"; #include "../tox_unpack.h")";
} }
f << R"( f << R"(
/***************************************************** /*****************************************************
@@ -242,7 +253,7 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
f << " " << t.type << " " << t.name << ";\n"; f << " " << t.type << " " << t.name << ";\n";
}, },
[&](const EventTypeByteRange& t) { [&](const EventTypeByteRange& t) {
f << " " << "uint8_t" << " *" << t.name_data << ";\n"; f << " " << t.type_c_arg << " *_Nullable " << t.name_data << ";\n";
f << " " << "uint32_t" << " " << t.name_length << ";\n"; f << " " << "uint32_t" << " " << t.name_length << ";\n";
}, },
[&](const EventTypeByteArray& t) { [&](const EventTypeByteArray& t) {
@@ -279,7 +290,7 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
f << " " << t.type << " " << t.name << ")\n"; f << " " << t.type << " " << t.name << ")\n";
}, },
[&](const EventTypeByteRange& t) { [&](const EventTypeByteRange& t) {
f << "\n const Memory *_Nonnull mem, const uint8_t *_Nullable " << t.name_data << ", uint32_t " << t.name_length << ")\n"; f << "\n const Memory *_Nonnull mem, const " << t.type_c_arg << " *_Nullable " << t.name_data << ", uint32_t " << t.name_length << ")\n";
}, },
[&](const EventTypeByteArray& t) { [&](const EventTypeByteArray& t) {
f << " const uint8_t " << t.name << "[" << t.length_constant << "])\n"; f << " const uint8_t " << t.name << "[" << t.length_constant << "])\n";
@@ -294,28 +305,41 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
f << " " << event_name_l << "->" << t.name << " = " << t.name << ";\n"; f << " " << event_name_l << "->" << t.name << " = " << t.name << ";\n";
}, },
[&](const EventTypeByteRange& t) { [&](const EventTypeByteRange& t) {
f << " if (" << event_name_l << "->" << t.name_data << " != nullptr) {\n"; f << " if (" << event_name_l << "->" << t.name_data << " != nullptr) {\n"
f << " mem_delete(mem, " << event_name_l << "->" << t.name_data << ");\n"; " mem_delete(mem, " << event_name_l << "->" << t.name_data << ");\n"
f << " " << event_name_l << "->" << t.name_data << " = nullptr;\n"; " " << event_name_l << "->" << t.name_data << " = nullptr;\n"
f << " " << event_name_l << "->" << t.name_length << " = 0;\n"; " " << event_name_l << "->" << t.name_length << " = 0;\n"
f << " }\n\n"; " }\n\n"
f << " if (" << t.name_data << " == nullptr) {\n"; " if (" << t.name_data << " == nullptr) {\n"
f << " assert(" << t.name_length << " == 0);\n"; " assert(" << t.name_length << " == 0);\n"
f << " return true;\n }\n\n"; " return true;\n"
" }\n\n";
if (t.null_terminated) { if (t.null_terminated) {
f << " uint8_t *" << t.name_data << "_copy = (uint8_t *)mem_balloc(mem, " << t.name_length << " + 1);\n\n"; f << " if (" << t.name_length << " == UINT32_MAX) {\n"
" return false;\n"
" }\n\n";
} else { } else {
f << " uint8_t *" << t.name_data << "_copy = (uint8_t *)mem_balloc(mem, " << t.name_length << ");\n\n"; f << " if (" << t.name_length << " == 0) {\n"
" " << event_name_l << "->" << t.name_data << " = nullptr;\n"
" " << event_name_l << "->" << t.name_length << " = 0;\n"
" return true;\n"
" }\n\n";
} }
f << " if (" << t.name_data << "_copy == nullptr) {\n";
f << " return false;\n }\n\n"; f << " " << t.type_c_arg << " *" << t.name_data << "_copy = (" << t.type_c_arg << " *)mem_balloc(mem, " << t.name_length << (t.null_terminated ? " + 1" : "") << ");\n\n"
f << " memcpy(" << t.name_data << "_copy, " << t.name_data << ", " << t.name_length << ");\n"; " if (" << t.name_data << "_copy == nullptr) {\n"
" return false;\n"
" }\n\n"
" memcpy(" << t.name_data << "_copy, " << t.name_data << ", " << t.name_length << ");\n";
if (t.null_terminated) { if (t.null_terminated) {
f << " " << t.name_data << "_copy[" << t.name_length << "] = 0;\n"; f << " " << t.name_data << "_copy[" << t.name_length << "] = 0;\n";
} }
f << " " << event_name_l << "->" << t.name_data << " = " << t.name_data << "_copy;\n";
f << " " << event_name_l << "->" << t.name_length << " = " << t.name_length << ";\n"; f << " " << event_name_l << "->" << t.name_data << " = " << t.name_data << "_copy;\n"
f << " return true;\n"; " " << event_name_l << "->" << t.name_length << " = " << t.name_length << ";\n"
" return true;\n";
}, },
[&](const EventTypeByteArray& t) { [&](const EventTypeByteArray& t) {
f << " memcpy(" << event_name_l << "->" << t.name << ", " << t.name << ", " << t.length_constant << ");\n"; f << " memcpy(" << event_name_l << "->" << t.name << ", " << t.name << ", " << t.length_constant << ");\n";
@@ -342,7 +366,7 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
f << "(const Tox_Event_" << event_name << " *" << event_name_l << ")\n"; f << "(const Tox_Event_" << event_name << " *" << event_name_l << ")\n";
f << "{\n assert(" << event_name_l << " != nullptr);\n"; f << "{\n assert(" << event_name_l << " != nullptr);\n";
f << " return " << event_name_l << "->" << t.name_length << ";\n}\n"; f << " return " << event_name_l << "->" << t.name_length << ";\n}\n";
f << "const uint8_t *tox_event_" << event_name_l << "_get_" << t.name_data; f << "const " << t.type_c_arg << " *tox_event_" << event_name_l << "_get_" << t.name_data;
f << "(const Tox_Event_" << event_name << " *" << event_name_l << ")\n"; f << "(const Tox_Event_" << event_name << " *" << event_name_l << ")\n";
f << "{\n assert(" << event_name_l << " != nullptr);\n"; f << "{\n assert(" << event_name_l << " != nullptr);\n";
f << " return " << event_name_l << "->" << t.name_data << ";\n}\n\n"; f << " return " << event_name_l << "->" << t.name_data << ";\n}\n\n";
@@ -435,7 +459,11 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
} }
}, },
[&](const EventTypeByteRange& t) { [&](const EventTypeByteRange& t) {
f << "bin_pack_bin(bp, event->" << t.name_data << ", event->" << t.name_length << ")"; if (t.type_c_arg == "char") {
f << "bin_pack_str(bp, event->" << t.name_data << ", event->" << t.name_length << ")";
} else {
f << "bin_pack_bin(bp, event->" << t.name_data << ", event->" << t.name_length << ")";
}
}, },
[&](const EventTypeByteArray& t) { [&](const EventTypeByteArray& t) {
f << "bin_pack_bin(bp, event->" << t.name << ", " << t.length_constant << ")"; f << "bin_pack_bin(bp, event->" << t.name << ", " << t.length_constant << ")";
@@ -471,7 +499,11 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
} }
}, },
[&](const EventTypeByteRange& t) { [&](const EventTypeByteRange& t) {
f << "bin_unpack_bin(bu, &event->" << t.name_data << ", &event->" << t.name_length << ")"; if (t.type_c_arg == "char") {
f << "bin_unpack_str(bu, &event->" << t.name_data << ", &event->" << t.name_length << ")";
} else {
f << "bin_unpack_bin(bu, &event->" << t.name_data << ", &event->" << t.name_length << ")";
}
}, },
[&](const EventTypeByteArray& t) { [&](const EventTypeByteArray& t) {
f << "bin_unpack_bin_fixed(bu, event->" << t.name << ", " << t.length_constant << ")"; f << "bin_unpack_bin_fixed(bu, event->" << t.name << ", " << t.length_constant << ")";
@@ -493,7 +525,7 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
)"; )";
f << "const Tox_Event_" << event_name << " *tox_event_get_" << event_name_l << "(const Tox_Event *event)\n{\n"; f << "const Tox_Event_" << event_name << " *tox_event_get_" << event_name_l << "(const Tox_Event *event)\n{\n";
f << " return event->type == TOX_EVENT_" << str_toupper(event_name) << " ? event->data." << event_name_l << " : nullptr;\n}\n\n"; f << " return event->type == TOX_EVENT_" << event_name_u << " ? event->data." << event_name_l << " : nullptr;\n}\n\n";
// new // new
f << "Tox_Event_" << event_name << " *tox_event_" << event_name_l << "_new(const Memory *mem)\n{\n"; f << "Tox_Event_" << event_name << " *tox_event_" << event_name_l << "_new(const Memory *mem)\n{\n";
@@ -506,7 +538,7 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
// free // free
f << "void tox_event_" << event_name_l << "_free(Tox_Event_" << event_name << " *" << event_name_l << ", const Memory *mem)\n{\n"; f << "void tox_event_" << event_name_l << "_free(Tox_Event_" << event_name << " *" << event_name_l << ", const Memory *mem)\n{\n";
f << " if (" << event_name_l << " != nullptr) {\n"; f << " if (" << event_name_l << " != nullptr) {\n";
f << " tox_event_" << event_name_l << "_destruct((Tox_Event_" << event_name << " * _Nonnull)" << event_name_l << ", mem);\n }\n"; f << " tox_event_" << event_name_l << "_destruct(" << event_name_l << ", mem);\n }\n";
f << " mem_delete(mem, " << event_name_l << ");\n}\n\n"; f << " mem_delete(mem, " << event_name_l << ");\n}\n\n";
// add // add
@@ -515,7 +547,7 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
f << " if (" << event_name_l << " == nullptr) {\n"; f << " if (" << event_name_l << " == nullptr) {\n";
f << " return nullptr;\n }\n\n"; f << " return nullptr;\n }\n\n";
f << " Tox_Event event;\n"; f << " Tox_Event event;\n";
f << " event.type = TOX_EVENT_" << str_toupper(event_name) << ";\n"; f << " event.type = TOX_EVENT_" << event_name_u << ";\n";
f << " event.data." << event_name_l << " = " << event_name_l << ";\n\n"; f << " event.data." << event_name_l << " = " << event_name_l << ";\n\n";
f << " if (!tox_events_add(events, &event)) {\n"; f << " if (!tox_events_add(events, &event)) {\n";
f << " tox_event_" << event_name_l << "_free(" << event_name_l << ", mem);\n"; f << " tox_event_" << event_name_l << "_free(" << event_name_l << ", mem);\n";
@@ -553,16 +585,17 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
f << " Tox *tox"; f << " Tox *tox";
for (const auto& t : event_types) { for (const auto& t : event_types) {
f << ",\n ";
std::visit( std::visit(
overloaded{ overloaded{
[&](const EventTypeTrivial& t) { [&](const EventTypeTrivial& t) {
f << ", " << (t.cb_type.empty() ? t.type : t.cb_type) << " " << t.name; f << (t.cb_type.empty() ? t.type : t.cb_type) << " " << t.name;
}, },
[&](const EventTypeByteRange& t) { [&](const EventTypeByteRange& t) {
f << ", const " << t.type_c_arg << " *" << t.name_data << ", " << t.type_length_cb << " " << t.name_length_cb; f << "const " << t.type_c_arg << " *" << t.name_data << ", " << t.type_length_cb << " " << t.name_length_cb;
}, },
[&](const EventTypeByteArray& t) { [&](const EventTypeByteArray& t) {
f << ", const uint8_t *" << t.name; f << "const uint8_t *" << t.name;
} }
}, },
t t
@@ -581,11 +614,10 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
f << " tox_event_" << event_name_l << "_set_" << t.name << "(" << event_name_l << ", " << t.name << ");\n"; f << " tox_event_" << event_name_l << "_set_" << t.name << "(" << event_name_l << ", " << t.name << ");\n";
}, },
[&](const EventTypeByteRange& t) { [&](const EventTypeByteRange& t) {
f << " tox_event_" << event_name_l << "_set_" << t.name_data << "(" << event_name_l << ", state->mem, "; f << " if (!tox_event_" << event_name_l << "_set_" << t.name_data << "(" << event_name_l << ", state->mem, ";
if (t.type_c_arg != "uint8_t") { f << t.name_data << ", " << t.name_length_cb << ")) {\n";
f << "(const uint8_t *)"; f << " state->error = TOX_ERR_EVENTS_ITERATE_MALLOC;\n";
} f << " }\n";
f << t.name_data << ", " << t.name_length_cb << ");\n";
}, },
[&](const EventTypeByteArray& t) { [&](const EventTypeByteArray& t) {
f << " tox_event_" << event_name_l << "_set_" << t.name << "(" << event_name_l << ", " << t.name << ");\n"; f << " tox_event_" << event_name_l << "_set_" << t.name << "(" << event_name_l << ", " << t.name << ");\n";
@@ -595,6 +627,45 @@ void generate_event_impl(const std::string& event_name, const std::vector<EventT
); );
} }
f << "}\n"; f << "}\n";
f << "\nvoid tox_events_handle_" << event_name_l << "_dispatch(Tox *tox, const Tox_Event_" << event_name << " *event, void *user_data)\n{\n";
if (event_name_l == "friend_lossy_packet" || event_name_l == "friend_lossless_packet") {
f << " if (event->data_length == 0 || tox->" << event_name_l << "_callback_per_pktid[event->data[0]] == nullptr) {\n";
f << " return;\n";
f << " }\n\n";
f << " tox_unlock(tox);\n";
f << " tox->" << event_name_l << "_callback_per_pktid[event->data[0]](tox, event->friend_number, event->data, event->data_length, user_data);\n";
f << " tox_lock(tox);\n";
} else {
f << " if (tox->" << event_name_l << "_callback == nullptr) {\n return;\n }\n\n";
f << " tox_unlock(tox);\n";
f << " tox->" << event_name_l << "_callback(tox, ";
bool first_arg = true;
for (const auto& t : event_types) {
if (!first_arg) f << ", ";
std::visit(
overloaded{
[&](const EventTypeTrivial& t) {
f << "event->" << t.name;
},
[&](const EventTypeByteRange& t) {
if (t.type_c_arg != "uint8_t") {
f << "(const " << t.type_c_arg << " *)";
}
f << "event->" << t.name_data << ", event->" << t.name_length;
},
[&](const EventTypeByteArray& t) {
f << "event->" << t.name;
}
},
t
);
first_arg = false;
}
f << ", user_data);\n";
f << " tox_lock(tox);\n";
}
f << "}\n";
} }
// c++ generate_event_c.cpp -std=c++17 && ./a.out Friend_Lossy_Packet && diff --color ../../toxcore/events/friend_lossy_packet.c out/friend_lossy_packet.c // c++ generate_event_c.cpp -std=c++17 && ./a.out Friend_Lossy_Packet && diff --color ../../toxcore/events/friend_lossy_packet.c out/friend_lossy_packet.c

View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# When editing, make sure to update /other/docker/windows/Dockerfile and # When editing, make sure to update /other/docker/windows/windows.Dockerfile and
# INSTALL.md to match. # INSTALL.md to match.
export VERSION_OPUS="1.4" export VERSION_OPUS="1.4"

View File

@@ -1,4 +1,5 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//hs-tokstyle/tools:tokstyle.bzl", "tokstyle_c_test")
CIMPLE_FILES = [ CIMPLE_FILES = [
"//c-toxcore/toxav:cimple_files", "//c-toxcore/toxav:cimple_files",
@@ -9,13 +10,16 @@ CIMPLE_FILES = [
sh_test( sh_test(
name = "cimple_test", name = "cimple_test",
size = "small", size = "medium",
srcs = ["//hs-tokstyle/tools:check-cimple"], srcs = ["//hs-tokstyle/tools:check-cimple"],
args = ["$(locations %s)" % f for f in CIMPLE_FILES] + [ args = ["$(locations %s)" % f for f in CIMPLE_FILES] + [
"-Wno-boolean-return", "-Wno-boolean-return",
"-Wno-callback-names", "-Wno-callback-names",
"-Wno-enum-from-int", "-Wno-enum-from-int",
"-Wno-nullability",
"-Wno-ownership-decls",
"-Wno-tagged-union", "-Wno-tagged-union",
"-Wno-type-check",
"+RTS", "+RTS",
"-N4", "-N4",
"-RTS", "-RTS",
@@ -27,37 +31,30 @@ sh_test(
], ],
) )
sh_test( tokstyle_c_test(
name = "c_test", name = "c_test",
size = "small", size = "medium",
srcs = ["//hs-tokstyle/tools:check-c"], srcs = CIMPLE_FILES,
args = [ args = [
"--cc=$(CC)", "-Wno-borrow-check",
"-Iexternal/libsodium/include", "-Wno-callback-discipline",
"-Iexternal/libvpx", "-Wno-strict-typedef",
"-Iexternal/opus/include",
"-Ihs-tokstyle/include",
] + ["$(locations %s)" % f for f in CIMPLE_FILES] + [
"+RTS",
"-N4",
"-RTS",
],
data = CIMPLE_FILES + [
"//hs-tokstyle:headers",
"@libsodium//:headers",
"@libvpx//:headers",
"@opus//:headers",
], ],
tags = [ tags = [
"haskell", "haskell",
"no-cross", "no-cross",
], ],
toolchains = ["@rules_cc//cc:current_cc_toolchain"], deps = [
"//hs-tokstyle:headers",
"@libsodium",
"@libvpx",
"@opus",
],
) )
sh_test( sh_test(
name = "cimplefmt_test", name = "cimplefmt_test",
size = "small", size = "medium",
srcs = ["//hs-cimple/tools:cimplefmt"], srcs = ["//hs-cimple/tools:cimplefmt"],
args = ["--reparse"] + ["$(locations %s)" % f for f in CIMPLE_FILES], args = ["--reparse"] + ["$(locations %s)" % f for f in CIMPLE_FILES],
data = CIMPLE_FILES, data = CIMPLE_FILES,

View File

@@ -9,11 +9,11 @@ else()
target_link_libraries(misc_tools PRIVATE toxcore_shared) target_link_libraries(misc_tools PRIVATE toxcore_shared)
endif() endif()
if(TARGET pthreads4w::pthreads4w) if(TARGET pthreads4w::pthreads4w)
target_link_libraries(misc_tools PRIVATE pthreads4w::pthreads4w) target_link_libraries(misc_tools PUBLIC pthreads4w::pthreads4w)
elseif(TARGET PThreads4W::PThreads4W) elseif(TARGET PThreads4W::PThreads4W)
target_link_libraries(misc_tools PRIVATE PThreads4W::PThreads4W) target_link_libraries(misc_tools PUBLIC PThreads4W::PThreads4W)
elseif(TARGET Threads::Threads) elseif(TARGET Threads::Threads)
target_link_libraries(misc_tools PRIVATE Threads::Threads) target_link_libraries(misc_tools PUBLIC Threads::Threads)
endif() endif()
################################################################################ ################################################################################
@@ -30,13 +30,7 @@ if(BUILD_MISC_TESTS)
else() else()
target_link_libraries(Messenger_test PRIVATE toxcore_shared) target_link_libraries(Messenger_test PRIVATE toxcore_shared)
endif() endif()
if(TARGET pthreads4w::pthreads4w)
target_link_libraries(Messenger_test PRIVATE pthreads4w::pthreads4w)
elseif(TARGET PThreads4W::PThreads4W)
target_link_libraries(Messenger_test PRIVATE PThreads4W::PThreads4W)
elseif(TARGET Threads::Threads)
target_link_libraries(Messenger_test PRIVATE Threads::Threads)
endif()
endif() endif()
add_subdirectory(bench)
add_subdirectory(support) add_subdirectory(support)

View File

@@ -102,14 +102,27 @@ int main(int argc, char *argv[])
exit(0); exit(0);
} }
Messenger_Options options = {0}; Messenger_Options options = {nullptr};
options.ipv6enabled = ipv6enabled; options.ipv6enabled = ipv6enabled;
Logger *logger = logger_new(mem);
if (logger == nullptr) {
fputs("Failed to allocate logger datastructure\n", stderr);
mono_time_free(mem, mono_time);
exit(1);
}
options.log = logger;
Messenger_Error err; Messenger_Error err;
m = new_messenger(mono_time, mem, os_random(), os_network(), &options, &err); m = new_messenger(mono_time, mem, os_random(), os_network(), &options, &err);
if (!m) { if (!m) {
fprintf(stderr, "Failed to allocate messenger datastructure: %u\n", err); fprintf(stderr, "Failed to allocate messenger datastructure: %u\n", err);
exit(0); logger_kill(logger);
mono_time_free(mem, mono_time);
exit(1);
} }
if (argc == argvoffset + 4) { if (argc == argvoffset + 4) {
@@ -117,6 +130,9 @@ int main(int argc, char *argv[])
if (port_conv <= 0 || port_conv > UINT16_MAX) { if (port_conv <= 0 || port_conv > UINT16_MAX) {
printf("Failed to convert \"%s\" into a valid port. Exiting...\n", argv[argvoffset + 2]); printf("Failed to convert \"%s\" into a valid port. Exiting...\n", argv[argvoffset + 2]);
kill_messenger(m);
logger_kill(logger);
mono_time_free(mem, mono_time);
exit(1); exit(1);
} }
@@ -131,6 +147,9 @@ int main(int argc, char *argv[])
if (!res) { if (!res) {
printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]); printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]);
kill_messenger(m);
logger_kill(logger);
mono_time_free(mem, mono_time);
exit(1); exit(1);
} }
} }
@@ -157,6 +176,9 @@ int main(int argc, char *argv[])
printf("\nEnter the address of the friend you wish to add (38 bytes HEX format):\n"); printf("\nEnter the address of the friend you wish to add (38 bytes HEX format):\n");
if (!fgets(temp_hex_id, sizeof(temp_hex_id), stdin)) { if (!fgets(temp_hex_id, sizeof(temp_hex_id), stdin)) {
kill_messenger(m);
logger_kill(logger);
mono_time_free(mem, mono_time);
exit(0); exit(0);
} }
@@ -186,6 +208,8 @@ int main(int argc, char *argv[])
if (file == nullptr) { if (file == nullptr) {
printf("Failed to open file %s\n", filename); printf("Failed to open file %s\n", filename);
kill_messenger(m); kill_messenger(m);
logger_kill(logger);
mono_time_free(mem, mono_time);
return 1; return 1;
} }
@@ -195,6 +219,8 @@ int main(int argc, char *argv[])
fputs("Failed to allocate memory\n", stderr); fputs("Failed to allocate memory\n", stderr);
fclose(file); fclose(file);
kill_messenger(m); kill_messenger(m);
logger_kill(logger);
mono_time_free(mem, mono_time);
return 1; return 1;
} }
@@ -205,6 +231,8 @@ int main(int argc, char *argv[])
free(buffer); free(buffer);
fclose(file); fclose(file);
kill_messenger(m); kill_messenger(m);
logger_kill(logger);
mono_time_free(mem, mono_time);
return 1; return 1;
} }

View File

@@ -0,0 +1,24 @@
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "tox_messenger_bench",
testonly = True,
srcs = ["tox_messenger_bench.cc"],
deps = [
"//c-toxcore/testing/support",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:tox",
"@benchmark",
],
)
cc_binary(
name = "tox_friends_scaling_bench",
testonly = True,
srcs = ["tox_friends_scaling_bench.cc"],
deps = [
"//c-toxcore/testing/support",
"//c-toxcore/toxcore:tox",
"@benchmark",
],
)

View File

@@ -0,0 +1,21 @@
if(NOT UNITTEST)
return()
endif()
find_package(benchmark QUIET)
if(benchmark_FOUND)
add_executable(tox_messenger_bench tox_messenger_bench.cc)
target_link_libraries(tox_messenger_bench PRIVATE
toxcore_static
support
benchmark::benchmark
)
add_executable(tox_friends_scaling_bench tox_friends_scaling_bench.cc)
target_link_libraries(tox_friends_scaling_bench PRIVATE
toxcore_static
support
benchmark::benchmark
)
endif()

View File

@@ -0,0 +1,479 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2026 The TokTok team.
*/
#include <benchmark/benchmark.h>
#include <iostream>
#include <memory>
#include <vector>
#include "../../testing/support/public/simulation.hh"
#include "../../testing/support/public/tox_network.hh"
#include "../../toxcore/tox.h"
namespace {
using tox::test::ConnectedFriend;
using tox::test::setup_connected_friends;
using tox::test::SimulatedNode;
using tox::test::Simulation;
// --- Helper Contexts ---
struct GroupContext {
uint32_t peer_count = 0;
uint32_t group_number = UINT32_MAX;
};
// --- Fixtures ---
class ToxIterateScalingFixture : public benchmark::Fixture {
public:
void SetUp(benchmark::State &state) override
{
// Explicitly clear members to handle fixture reuse or non-empty initial state.
// Order matters: dependent objects first.
friend_toxes.clear();
friend_nodes.clear();
main_tox.reset();
main_node.reset();
sim.reset();
int num_friends = state.range(0);
sim = std::make_unique<Simulation>();
main_node = sim->create_node();
main_tox = main_node->create_tox();
for (int i = 0; i < num_friends; ++i) {
auto node = sim->create_node();
auto tox = node->create_tox();
uint8_t friend_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox.get(), friend_pk);
Tox_Err_Friend_Add err;
tox_friend_add_norequest(main_tox.get(), friend_pk, &err);
friend_nodes.push_back(std::move(node));
friend_toxes.push_back(std::move(tox));
}
}
protected:
std::unique_ptr<Simulation> sim;
std::unique_ptr<SimulatedNode> main_node;
SimulatedNode::ToxPtr main_tox;
std::vector<std::unique_ptr<SimulatedNode>> friend_nodes;
std::vector<SimulatedNode::ToxPtr> friend_toxes;
};
// --- Contexts for Shared State Benchmarks ---
class ToxOnlineDisconnectedScalingFixture : public benchmark::Fixture {
static constexpr bool kVerbose = false;
public:
void SetUp(benchmark::State &state) override
{
// Explicitly clear members to handle fixture reuse or non-empty initial state.
// Order matters: dependent objects first (Tox depends on Node).
main_tox.reset();
bootstrap_tox.reset();
main_node.reset();
bootstrap_node.reset();
sim.reset();
int num_friends = state.range(0);
sim = std::make_unique<Simulation>();
sim->net().set_latency(1); // Low latency to encourage traffic
sim->net().set_verbose(kVerbose);
// Create a bootstrap node to ensure we are "online" on the DHT
bootstrap_node = sim->create_node();
auto log_cb = [](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line,
const char *func, const char *message, void *user_data) {
if (kVerbose) {
std::cerr << "Log: " << file << ":" << line << " (" << func << ") " << message
<< std::endl;
}
};
auto opts_bs = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
assert(opts_bs);
tox_options_set_local_discovery_enabled(opts_bs.get(), false);
tox_options_set_ipv6_enabled(opts_bs.get(), false);
tox_options_set_log_callback(opts_bs.get(), log_cb);
bootstrap_tox = bootstrap_node->create_tox(opts_bs.get());
uint8_t bootstrap_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(bootstrap_tox.get(), bootstrap_pk);
uint16_t bootstrap_port = bootstrap_node->get_primary_socket()->local_port();
main_node = sim->create_node();
auto opts = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
assert(opts);
// Disable local discovery to force DHT usage
tox_options_set_local_discovery_enabled(opts.get(), false);
tox_options_set_ipv6_enabled(opts.get(), false);
tox_options_set_log_callback(opts.get(), log_cb);
main_tox = main_node->create_tox(opts.get());
// Bootstrap to the network (Mutual bootstrap to ensure connectivity)
Ip_Ntoa bs_ip_str_buf;
const char *bs_ip_str = net_ip_ntoa(&bootstrap_node->ip, &bs_ip_str_buf);
Tox_Err_Bootstrap bs_err;
tox_bootstrap(main_tox.get(), bs_ip_str, bootstrap_port, bootstrap_pk, &bs_err);
if (bs_err != TOX_ERR_BOOTSTRAP_OK) {
std::cerr << "bootstrapping failed: " << bs_err << "\n";
std::abort();
}
// Run until we are connected to the DHT
sim->run_until(
[&]() {
tox_iterate(main_tox.get(), nullptr);
tox_iterate(bootstrap_tox.get(), nullptr);
return tox_self_get_connection_status(main_tox.get()) != TOX_CONNECTION_NONE;
},
15000);
if (tox_self_get_connection_status(main_tox.get()) == TOX_CONNECTION_NONE) {
std::cerr << "WARNING: Failed to connect to DHT in SetUp (timeout 30s)\n";
std::abort();
}
for (int i = 0; i < num_friends; ++i) {
// Add friend but don't create a node for them -> they are offline
uint8_t friend_pk[TOX_PUBLIC_KEY_SIZE];
// Just generate a random PK
main_node->fake_random().bytes(friend_pk, TOX_PUBLIC_KEY_SIZE);
Tox_Err_Friend_Add err;
tox_friend_add_norequest(main_tox.get(), friend_pk, &err);
}
}
protected:
std::unique_ptr<Simulation> sim;
std::unique_ptr<SimulatedNode> main_node;
SimulatedNode::ToxPtr main_tox;
std::unique_ptr<SimulatedNode> bootstrap_node;
SimulatedNode::ToxPtr bootstrap_tox;
};
BENCHMARK_DEFINE_F(ToxOnlineDisconnectedScalingFixture, Iterate)(benchmark::State &state)
{
if (tox_self_get_connection_status(main_tox.get()) == TOX_CONNECTION_NONE) {
state.SkipWithError("not connected to DHT");
}
for (auto _ : state) {
tox_iterate(main_tox.get(), nullptr);
tox_iterate(bootstrap_tox.get(), nullptr);
uint32_t interval = tox_iteration_interval(main_tox.get());
uint32_t interval_bs = tox_iteration_interval(bootstrap_tox.get());
sim->advance_time(std::min(interval, interval_bs));
}
state.counters["mem_current"]
= benchmark::Counter(static_cast<double>(main_node->fake_memory().current_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
}
BENCHMARK_REGISTER_F(ToxOnlineDisconnectedScalingFixture, Iterate)
->Arg(0)
->Arg(10)
->Arg(100)
->Arg(1000)
->Arg(2000);
struct ConnectedContext {
std::unique_ptr<Simulation> sim;
std::unique_ptr<SimulatedNode> main_node;
SimulatedNode::ToxPtr main_tox;
std::vector<ConnectedFriend> friends;
int num_friends = -1;
void Setup(int n)
{
if (num_friends == n)
return;
// Destruction order is critical
friends.clear();
main_tox.reset();
main_node.reset();
sim.reset();
sim = std::make_unique<Simulation>();
sim->net().set_latency(5);
main_node = sim->create_node();
main_tox = main_node->create_tox();
num_friends = n;
if (n > 0) {
friends = setup_connected_friends(*sim, main_tox.get(), *main_node, num_friends);
}
}
~ConnectedContext();
};
ConnectedContext::~ConnectedContext() = default;
struct GroupScalingContext {
static constexpr bool verbose = false;
std::unique_ptr<Simulation> sim;
std::unique_ptr<SimulatedNode> main_node;
SimulatedNode::ToxPtr main_tox;
GroupContext main_ctx;
std::vector<ConnectedFriend> friends;
int num_peers = -1;
void Setup(int peers)
{
if (num_peers == peers)
return;
// Destruction order is critical
friends.clear();
main_ctx = GroupContext();
main_tox.reset();
main_node.reset();
sim.reset();
sim = std::make_unique<Simulation>();
sim->net().set_latency(5);
main_node = sim->create_node();
auto opts = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
tox_options_set_ipv6_enabled(opts.get(), false);
tox_options_set_local_discovery_enabled(opts.get(), false);
main_tox = main_node->create_tox(opts.get());
num_peers = peers;
// Setup Group Callbacks
tox_callback_group_peer_join(
main_tox.get(), [](Tox *, uint32_t, uint32_t, void *user_data) {
static_cast<GroupContext *>(user_data)->peer_count++;
});
tox_callback_group_peer_exit(main_tox.get(),
[](Tox *, uint32_t, uint32_t, Tox_Group_Exit_Type, const uint8_t *, size_t,
const uint8_t *, size_t,
void *user_data) { static_cast<GroupContext *>(user_data)->peer_count--; });
Tox_Err_Group_New err_new;
main_ctx.group_number = tox_group_new(main_tox.get(), TOX_GROUP_PRIVACY_STATE_PUBLIC,
reinterpret_cast<const uint8_t *>("test"), 4, reinterpret_cast<const uint8_t *>("main"),
4, &err_new);
if (num_peers > 0) {
// Setup Friends
auto opts_friends = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
tox_options_set_ipv6_enabled(opts_friends.get(), false);
tox_options_set_local_discovery_enabled(opts_friends.get(), false);
friends = setup_connected_friends(
*sim, main_tox.get(), *main_node, num_peers, opts_friends.get());
// Invite Friends
for (const auto &f : friends) {
tox_group_invite_friend(
main_tox.get(), main_ctx.group_number, f.friend_number, nullptr);
}
// Wait for Joins
std::vector<uint32_t> peer_group_numbers(num_peers, UINT32_MAX);
sim->run_until(
[&]() {
tox_iterate(main_tox.get(), &main_ctx);
// Poll events
for (size_t i = 0; i < friends.size(); ++i) {
auto batches = friends[i].runner->poll_events();
for (const auto &batch : batches) {
size_t size = tox_events_get_size(batch.get());
for (size_t k = 0; k < size; ++k) {
const Tox_Event *e = tox_events_get(batch.get(), k);
if (tox_event_get_type(e) == TOX_EVENT_GROUP_INVITE) {
auto *ev = tox_event_get_group_invite(e);
uint32_t friend_number
= tox_event_group_invite_get_friend_number(ev);
const uint8_t *data
= tox_event_group_invite_get_invite_data(ev);
size_t len = tox_event_group_invite_get_invite_data_length(ev);
std::vector<uint8_t> invite_data(data, data + len);
friends[i].runner->execute([=](Tox *tox) {
tox_group_invite_accept(tox, friend_number,
invite_data.data(), invite_data.size(),
reinterpret_cast<const uint8_t *>("peer"), 4, nullptr,
0, nullptr);
});
} else if (tox_event_get_type(e) == TOX_EVENT_GROUP_SELF_JOIN) {
auto *ev = tox_event_get_group_self_join(e);
peer_group_numbers[i]
= tox_event_group_self_join_get_group_number(ev);
}
}
}
}
bool all_joined = true;
for (auto gn : peer_group_numbers)
if (gn == UINT32_MAX)
all_joined = false;
return all_joined;
},
60000);
// Wait for Convergence
sim->run_until(
[&]() {
tox_iterate(main_tox.get(), &main_ctx);
if (main_ctx.peer_count >= static_cast<uint32_t>(num_peers))
return true;
static uint64_t last_print = 0;
if (verbose && sim->clock().current_time_ms() - last_print > 1000) {
std::cerr << "Peers joined: " << main_ctx.peer_count << "/" << num_peers
<< std::endl;
last_print = sim->clock().current_time_ms();
}
return false;
},
120000);
}
}
~GroupScalingContext();
};
GroupScalingContext::~GroupScalingContext() = default;
// --- Benchmark Definitions ---
BENCHMARK_DEFINE_F(ToxIterateScalingFixture, Iterate)(benchmark::State &state)
{
for (auto _ : state) {
tox_iterate(main_tox.get(), nullptr);
}
state.counters["mem_current"]
= benchmark::Counter(static_cast<double>(main_node->fake_memory().current_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
state.counters["mem_max"]
= benchmark::Counter(static_cast<double>(main_node->fake_memory().max_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
}
BENCHMARK_REGISTER_F(ToxIterateScalingFixture, Iterate)
->Arg(0)
->Arg(10)
->Arg(100)
->Arg(200)
->Arg(300);
void RunConnectedScaling(benchmark::State &state, ConnectedContext &ctx)
{
ctx.Setup(state.range(0));
for (auto _ : state) {
tox_iterate(ctx.main_tox.get(), nullptr);
}
state.counters["mem_current"]
= benchmark::Counter(static_cast<double>(ctx.main_node->fake_memory().current_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
state.counters["mem_max"]
= benchmark::Counter(static_cast<double>(ctx.main_node->fake_memory().max_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
}
void RunGroupScaling(benchmark::State &state, GroupScalingContext &ctx)
{
ctx.Setup(state.range(0));
for (auto _ : state) {
tox_iterate(ctx.main_tox.get(), &ctx.main_ctx);
}
state.counters["mem_current"]
= benchmark::Counter(static_cast<double>(ctx.main_node->fake_memory().current_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
state.counters["mem_max"]
= benchmark::Counter(static_cast<double>(ctx.main_node->fake_memory().max_allocation()),
benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024);
state.counters["peers"] = benchmark::Counter(
static_cast<double>(ctx.main_ctx.peer_count + 1), benchmark::Counter::kDefaults);
}
/**
* @brief Benchmark the time and CPU required to discover and connect to many friends.
*
* This stresses the Onion Client's discovery mechanism (shared key caching)
* and the DHT's shared key cache efficiency.
*/
static void BM_MassDiscovery(benchmark::State &state)
{
const int num_friends = state.range(0);
for (auto _ : state) {
Simulation sim;
// Set a realistic latency to ensure packets are in flight and DHT/Onion logic
// has to run multiple iterations.
sim.net().set_latency(10);
auto alice_node = sim.create_node();
auto alice_tox = alice_node->create_tox();
// setup_connected_friends runs the simulation until all friends are connected.
auto friends = setup_connected_friends(sim, alice_tox.get(), *alice_node, num_friends);
benchmark::DoNotOptimize(friends);
}
}
BENCHMARK(BM_MassDiscovery)
->Arg(50)
->Arg(100)
->Arg(200)
->Unit(benchmark::kMillisecond)
->Iterations(5);
} // namespace
int main(int argc, char **argv)
{
::benchmark::Initialize(&argc, argv);
if (::benchmark::ReportUnrecognizedArguments(argc, argv)) {
return 1;
}
ConnectedContext connected_ctx;
benchmark::RegisterBenchmark("ToxConnectedScalingFixture/IterateConnected",
[&](benchmark::State &st) { RunConnectedScaling(st, connected_ctx); })
->Arg(0)
->Arg(10)
->Arg(20)
->Arg(50);
GroupScalingContext group_ctx;
benchmark::RegisterBenchmark("ToxGroupScalingFixture/IterateGroup",
[&](benchmark::State &st) { RunGroupScaling(st, group_ctx); })
->Arg(0)
->Arg(10)
->Arg(20)
->Arg(50);
::benchmark::RunSpecifiedBenchmarks();
::benchmark::Shutdown();
return 0;
}

View File

@@ -0,0 +1,221 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2026 The TokTok team.
*/
#include <benchmark/benchmark.h>
#include <iostream>
#include "../../testing/support/public/simulation.hh"
#include "../../toxcore/network.h"
#include "../../toxcore/tox.h"
namespace {
using tox::test::Simulation;
struct Context {
size_t count = 0;
};
void BM_ToxMessengerThroughput(benchmark::State &state)
{
Simulation sim;
sim.net().set_latency(5);
auto node1 = sim.create_node();
auto node2 = sim.create_node();
auto opts1 = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
tox_options_set_log_user_data(opts1.get(), const_cast<char *>("Tox1"));
tox_options_set_ipv6_enabled(opts1.get(), false);
tox_options_set_local_discovery_enabled(opts1.get(), false);
auto opts2 = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
tox_options_set_log_user_data(opts2.get(), const_cast<char *>("Tox2"));
tox_options_set_ipv6_enabled(opts2.get(), false);
tox_options_set_local_discovery_enabled(opts2.get(), false);
auto tox1 = node1->create_tox(opts1.get());
auto tox2 = node2->create_tox(opts2.get());
if (!tox1 || !tox2) {
state.SkipWithError("Failed to create Tox instances");
return;
}
uint8_t tox1_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox1.get(), tox1_pk);
uint8_t tox2_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox2.get(), tox2_pk);
uint8_t tox1_dht_id[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox1.get(), tox1_dht_id);
uint8_t tox2_dht_id[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox2.get(), tox2_dht_id);
Tox_Err_Friend_Add friend_add_err;
uint32_t f1 = tox_friend_add_norequest(tox1.get(), tox2_pk, &friend_add_err);
uint32_t f2 = tox_friend_add_norequest(tox2.get(), tox1_pk, &friend_add_err);
uint16_t port1 = node1->get_primary_socket()->local_port();
uint16_t port2 = node2->get_primary_socket()->local_port();
char ip1[TOX_INET6_ADDRSTRLEN];
ip_parse_addr(&node1->ip, ip1, sizeof(ip1));
char ip2[TOX_INET6_ADDRSTRLEN];
ip_parse_addr(&node2->ip, ip2, sizeof(ip2));
tox_bootstrap(tox2.get(), ip1, port1, tox1_dht_id, nullptr);
tox_bootstrap(tox1.get(), ip2, port2, tox2_dht_id, nullptr);
bool connected = false;
sim.run_until(
[&]() {
tox_iterate(tox1.get(), nullptr);
tox_iterate(tox2.get(), nullptr);
sim.advance_time(90); // +10ms from run_until = 100ms
connected
= (tox_friend_get_connection_status(tox1.get(), f1, nullptr) != TOX_CONNECTION_NONE
&& tox_friend_get_connection_status(tox2.get(), f2, nullptr)
!= TOX_CONNECTION_NONE);
return connected;
},
60000);
if (!connected) {
state.SkipWithError("Failed to connect toxes within 60s");
return;
}
const uint8_t msg[] = "benchmark message";
const size_t msg_len = sizeof(msg);
Context ctx;
tox_callback_friend_message(tox2.get(),
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) {
static_cast<Context *>(user_data)->count++;
});
for (auto _ : state) {
tox_friend_send_message(tox1.get(), f1, TOX_MESSAGE_TYPE_NORMAL, msg, msg_len, nullptr);
for (int i = 0; i < 5; ++i) {
sim.advance_time(1);
tox_iterate(tox1.get(), nullptr);
tox_iterate(tox2.get(), &ctx);
}
}
state.counters["messages_received"]
= benchmark::Counter(static_cast<double>(ctx.count), benchmark::Counter::kAvgThreads);
}
BENCHMARK(BM_ToxMessengerThroughput);
void BM_ToxMessengerBidirectional(benchmark::State &state)
{
Simulation sim;
sim.net().set_latency(5);
auto node1 = sim.create_node();
auto node2 = sim.create_node();
auto opts1 = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
tox_options_set_log_user_data(opts1.get(), const_cast<char *>("Tox1"));
tox_options_set_ipv6_enabled(opts1.get(), false);
tox_options_set_local_discovery_enabled(opts1.get(), false);
auto opts2 = std::unique_ptr<Tox_Options, decltype(&tox_options_free)>(
tox_options_new(nullptr), tox_options_free);
tox_options_set_log_user_data(opts2.get(), const_cast<char *>("Tox2"));
tox_options_set_ipv6_enabled(opts2.get(), false);
tox_options_set_local_discovery_enabled(opts2.get(), false);
auto tox1 = node1->create_tox(opts1.get());
auto tox2 = node2->create_tox(opts2.get());
if (!tox1 || !tox2) {
state.SkipWithError("Failed to create Tox instances");
return;
}
uint8_t tox1_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox1.get(), tox1_pk);
uint8_t tox2_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox2.get(), tox2_pk);
uint8_t tox1_dht_id[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox1.get(), tox1_dht_id);
uint8_t tox2_dht_id[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox2.get(), tox2_dht_id);
Tox_Err_Friend_Add friend_add_err;
uint32_t f1 = tox_friend_add_norequest(tox1.get(), tox2_pk, &friend_add_err);
uint32_t f2 = tox_friend_add_norequest(tox2.get(), tox1_pk, &friend_add_err);
uint16_t port1 = node1->get_primary_socket()->local_port();
uint16_t port2 = node2->get_primary_socket()->local_port();
char ip1[TOX_INET6_ADDRSTRLEN];
ip_parse_addr(&node1->ip, ip1, sizeof(ip1));
char ip2[TOX_INET6_ADDRSTRLEN];
ip_parse_addr(&node2->ip, ip2, sizeof(ip2));
tox_bootstrap(tox2.get(), ip1, port1, tox1_dht_id, nullptr);
tox_bootstrap(tox1.get(), ip2, port2, tox2_dht_id, nullptr);
bool connected = false;
sim.run_until(
[&]() {
tox_iterate(tox1.get(), nullptr);
tox_iterate(tox2.get(), nullptr);
sim.advance_time(90); // +10ms from run_until = 100ms
connected
= (tox_friend_get_connection_status(tox1.get(), f1, nullptr) != TOX_CONNECTION_NONE
&& tox_friend_get_connection_status(tox2.get(), f2, nullptr)
!= TOX_CONNECTION_NONE);
return connected;
},
60000);
if (!connected) {
state.SkipWithError("Failed to connect toxes within 60s");
return;
}
const uint8_t msg[] = "benchmark message";
const size_t msg_len = sizeof(msg);
Context ctx1, ctx2;
tox_callback_friend_message(tox1.get(),
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) {
static_cast<Context *>(user_data)->count++;
});
tox_callback_friend_message(tox2.get(),
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) {
static_cast<Context *>(user_data)->count++;
});
for (auto _ : state) {
tox_friend_send_message(tox1.get(), f1, TOX_MESSAGE_TYPE_NORMAL, msg, msg_len, nullptr);
tox_friend_send_message(tox2.get(), f2, TOX_MESSAGE_TYPE_NORMAL, msg, msg_len, nullptr);
for (int i = 0; i < 5; ++i) {
sim.advance_time(1);
tox_iterate(tox1.get(), &ctx1);
tox_iterate(tox2.get(), &ctx2);
}
}
state.counters["messages_received"] = benchmark::Counter(
static_cast<double>(ctx1.count + ctx2.count), benchmark::Counter::kAvgThreads);
}
BENCHMARK(BM_ToxMessengerBidirectional);
} // namespace
BENCHMARK_MAIN();

View File

@@ -18,6 +18,7 @@ cc_library(
"src/simulated_environment.cc", "src/simulated_environment.cc",
"src/simulation.cc", "src/simulation.cc",
"src/tox_network.cc", "src/tox_network.cc",
"src/tox_runner.cc",
], ],
hdrs = [ hdrs = [
"doubles/fake_clock.hh", "doubles/fake_clock.hh",
@@ -31,11 +32,13 @@ cc_library(
"public/fuzz_data.hh", "public/fuzz_data.hh",
"public/fuzz_helpers.hh", "public/fuzz_helpers.hh",
"public/memory.hh", "public/memory.hh",
"public/mpsc_queue.hh",
"public/network.hh", "public/network.hh",
"public/random.hh", "public/random.hh",
"public/simulated_environment.hh", "public/simulated_environment.hh",
"public/simulation.hh", "public/simulation.hh",
"public/tox_network.hh", "public/tox_network.hh",
"public/tox_runner.hh",
], ],
copts = select({ copts = select({
"//tools/config:windows": ["/wd4200"], # Zero-sized array in struct/union "//tools/config:windows": ["/wd4200"], # Zero-sized array in struct/union
@@ -43,12 +46,14 @@ cc_library(
}), }),
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:mem", "//c-toxcore/toxcore:mem",
"//c-toxcore/toxcore:net",
"//c-toxcore/toxcore:network", "//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:rng",
"//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_memory", "//c-toxcore/toxcore:tox_events",
"//c-toxcore/toxcore:tox_options", "//c-toxcore/toxcore:tox_options",
"//c-toxcore/toxcore:tox_random",
"@psocket", "@psocket",
], ],
) )
@@ -64,6 +69,28 @@ cc_test(
], ],
) )
cc_test(
name = "fake_network_udp_test",
srcs = ["doubles/fake_network_udp_test.cc"],
deps = [
":support",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
"@psocket",
],
)
cc_test(
name = "fake_network_tcp_test",
srcs = ["doubles/fake_network_tcp_test.cc"],
deps = [
":support",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
"@psocket",
],
)
cc_test( cc_test(
name = "fake_network_stack_test", name = "fake_network_stack_test",
srcs = ["doubles/fake_network_stack_test.cc"], srcs = ["doubles/fake_network_stack_test.cc"],
@@ -98,12 +125,24 @@ cc_test(
], ],
) )
cc_test(
name = "simulation_test",
srcs = ["simulation_test.cc"],
deps = [
":support",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_test( cc_test(
name = "tox_network_test", name = "tox_network_test",
timeout = "long", timeout = "long",
srcs = ["tox_network_test.cc"], srcs = ["tox_network_test.cc"],
deps = [ deps = [
":support", ":support",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",

View File

@@ -18,6 +18,7 @@ set(support_SOURCES
src/simulated_environment.cc src/simulated_environment.cc
src/simulation.cc src/simulation.cc
src/tox_network.cc src/tox_network.cc
src/tox_runner.cc
doubles/fake_clock.hh doubles/fake_clock.hh
doubles/fake_memory.hh doubles/fake_memory.hh
doubles/fake_network_stack.hh doubles/fake_network_stack.hh
@@ -29,11 +30,13 @@ set(support_SOURCES
public/fuzz_data.hh public/fuzz_data.hh
public/fuzz_helpers.hh public/fuzz_helpers.hh
public/memory.hh public/memory.hh
public/mpsc_queue.hh
public/network.hh public/network.hh
public/random.hh public/random.hh
public/simulated_environment.hh public/simulated_environment.hh
public/simulation.hh public/simulation.hh
public/tox_network.hh public/tox_network.hh
public/tox_runner.hh
) )
add_library(support STATIC ${support_SOURCES}) add_library(support STATIC ${support_SOURCES})
@@ -66,7 +69,10 @@ if(TARGET GTest::gtest_main)
support_test(fake_sockets_test doubles/fake_sockets_test.cc) support_test(fake_sockets_test doubles/fake_sockets_test.cc)
support_test(fake_network_stack_test doubles/fake_network_stack_test.cc) support_test(fake_network_stack_test doubles/fake_network_stack_test.cc)
support_test(fake_network_udp_test doubles/fake_network_udp_test.cc)
support_test(fake_network_tcp_test doubles/fake_network_tcp_test.cc)
support_test(network_universe_test doubles/network_universe_test.cc) support_test(network_universe_test doubles/network_universe_test.cc)
support_test(bootstrap_scaling_test bootstrap_scaling_test.cc) support_test(bootstrap_scaling_test bootstrap_scaling_test.cc)
support_test(tox_network_test tox_network_test.cc) # TODO(iphydf): Re-enable once we migrate TCP server to ev.
#support_test(tox_network_test tox_network_test.cc)
endif() endif()

View File

@@ -1,6 +1,9 @@
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H #ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H #define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
#include <atomic>
#include <cstdint>
#include "../public/clock.hh" #include "../public/clock.hh"
namespace tox::test { namespace tox::test {
@@ -15,7 +18,7 @@ public:
void advance(uint64_t ms); void advance(uint64_t ms);
private: private:
uint64_t now_ms_; std::atomic<uint64_t> now_ms_;
}; };
} // namespace tox::test } // namespace tox::test

View File

@@ -1,12 +1,13 @@
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H #ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H #define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
#include <atomic>
#include <functional> #include <functional>
#include "../public/memory.hh" #include "../public/memory.hh"
// Forward declaration // Forward declaration
struct Tox_Memory; struct Memory;
namespace tox::test { namespace tox::test {
@@ -18,9 +19,9 @@ public:
FakeMemory(); FakeMemory();
~FakeMemory() override; ~FakeMemory() override;
void *malloc(size_t size) override; void *_Nullable malloc(size_t size) override;
void *realloc(void *ptr, size_t size) override; void *_Nullable realloc(void *_Nullable ptr, size_t size) override;
void free(void *ptr) override; void free(void *_Nullable ptr) override;
// Configure failure injection // Configure failure injection
void set_failure_injector(FailureInjector injector); void set_failure_injector(FailureInjector injector);
@@ -28,10 +29,18 @@ public:
// Configure observer // Configure observer
void set_observer(Observer observer); void set_observer(Observer observer);
// Get the C-compatible struct /**
struct Tox_Memory get_c_memory(); * @brief Returns C-compatible Memory struct.
*/
struct Memory c_memory() override;
size_t current_allocation() const;
size_t max_allocation() const;
private: private:
void on_allocation(size_t size);
void on_deallocation(size_t size);
struct Header { struct Header {
size_t size; size_t size;
size_t magic; size_t magic;
@@ -39,8 +48,8 @@ private:
static constexpr size_t kMagic = 0xDEADC0DE; static constexpr size_t kMagic = 0xDEADC0DE;
static constexpr size_t kFreeMagic = 0xBAADF00D; static constexpr size_t kFreeMagic = 0xBAADF00D;
size_t current_allocation_ = 0; std::atomic<size_t> current_allocation_{0};
size_t max_allocation_ = 0; std::atomic<size_t> max_allocation_{0};
FailureInjector failure_injector_; FailureInjector failure_injector_;
Observer observer_; Observer observer_;

View File

@@ -8,55 +8,60 @@
namespace tox::test { namespace tox::test {
static const Network_Funcs kVtable = { static const Network_Funcs kNetworkVtable = {
.close .close = [](void *_Nonnull obj,
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->close(sock); }, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->close(sock); },
.accept .accept = [](void *_Nonnull obj,
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->accept(sock); }, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->accept(sock); },
.bind .bind =
= [](void *obj, Socket sock, [](void *_Nonnull obj, Socket sock, const IP_Port *_Nonnull addr) {
const IP_Port *addr) { return static_cast<FakeNetworkStack *>(obj)->bind(sock, addr); }, return static_cast<FakeNetworkStack *>(obj)->bind(sock, addr);
},
.listen .listen
= [](void *obj, Socket sock, = [](void *_Nonnull obj, Socket sock,
int backlog) { return static_cast<FakeNetworkStack *>(obj)->listen(sock, backlog); }, int backlog) { return static_cast<FakeNetworkStack *>(obj)->listen(sock, backlog); },
.connect = .connect =
[](void *obj, Socket sock, const IP_Port *addr) { [](void *_Nonnull obj, Socket sock, const IP_Port *_Nonnull addr) {
return static_cast<FakeNetworkStack *>(obj)->connect(sock, addr); return static_cast<FakeNetworkStack *>(obj)->connect(sock, addr);
}, },
.recvbuf .recvbuf = [](void *_Nonnull obj,
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->recvbuf(sock); }, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->recvbuf(sock); },
.recv = [](void *obj, Socket sock, uint8_t *buf, .recv = [](void *_Nonnull obj, Socket sock, uint8_t *_Nonnull buf,
size_t len) { return static_cast<FakeNetworkStack *>(obj)->recv(sock, buf, len); }, size_t len) { return static_cast<FakeNetworkStack *>(obj)->recv(sock, buf, len); },
.recvfrom = .recvfrom =
[](void *obj, Socket sock, uint8_t *buf, size_t len, IP_Port *addr) { [](void *_Nonnull obj, Socket sock, uint8_t *_Nonnull buf, size_t len,
IP_Port *_Nonnull addr) {
return static_cast<FakeNetworkStack *>(obj)->recvfrom(sock, buf, len, addr); return static_cast<FakeNetworkStack *>(obj)->recvfrom(sock, buf, len, addr);
}, },
.send = [](void *obj, Socket sock, const uint8_t *buf, .send = [](void *_Nonnull obj, Socket sock, const uint8_t *_Nonnull buf,
size_t len) { return static_cast<FakeNetworkStack *>(obj)->send(sock, buf, len); }, size_t len) { return static_cast<FakeNetworkStack *>(obj)->send(sock, buf, len); },
.sendto = .sendto =
[](void *obj, Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) { [](void *_Nonnull obj, Socket sock, const uint8_t *_Nonnull buf, size_t len,
const IP_Port *_Nonnull addr) {
return static_cast<FakeNetworkStack *>(obj)->sendto(sock, buf, len, addr); return static_cast<FakeNetworkStack *>(obj)->sendto(sock, buf, len, addr);
}, },
.socket .socket
= [](void *obj, int domain, int type, = [](void *_Nonnull obj, int domain, int type,
int proto) { return static_cast<FakeNetworkStack *>(obj)->socket(domain, type, proto); }, int proto) { return static_cast<FakeNetworkStack *>(obj)->socket(domain, type, proto); },
.socket_nonblock = .socket_nonblock =
[](void *obj, Socket sock, bool nonblock) { [](void *_Nonnull obj, Socket sock, bool nonblock) {
return static_cast<FakeNetworkStack *>(obj)->socket_nonblock(sock, nonblock); return static_cast<FakeNetworkStack *>(obj)->socket_nonblock(sock, nonblock);
}, },
.getsockopt = .getsockopt =
[](void *obj, Socket sock, int level, int optname, void *optval, size_t *optlen) { [](void *_Nonnull obj, Socket sock, int level, int optname, void *_Nonnull optval,
size_t *_Nonnull optlen) {
return static_cast<FakeNetworkStack *>(obj)->getsockopt( return static_cast<FakeNetworkStack *>(obj)->getsockopt(
sock, level, optname, optval, optlen); sock, level, optname, optval, optlen);
}, },
.setsockopt = .setsockopt =
[](void *obj, Socket sock, int level, int optname, const void *optval, size_t optlen) { [](void *_Nonnull obj, Socket sock, int level, int optname, const void *_Nonnull optval,
size_t optlen) {
return static_cast<FakeNetworkStack *>(obj)->setsockopt( return static_cast<FakeNetworkStack *>(obj)->setsockopt(
sock, level, optname, optval, optlen); sock, level, optname, optval, optlen);
}, },
.getaddrinfo = .getaddrinfo =
[](void *obj, const Memory *mem, const char *address, int family, int protocol, [](void *_Nonnull obj, const Memory *_Nonnull mem, const char *_Nonnull address, int family,
IP_Port **addrs) { int protocol, IP_Port *_Nullable *_Nonnull addrs) {
FakeNetworkStack *self = static_cast<FakeNetworkStack *>(obj); FakeNetworkStack *self = static_cast<FakeNetworkStack *>(obj);
if (self->universe().is_verbose()) { if (self->universe().is_verbose()) {
std::cerr << "[FakeNetworkStack] getaddrinfo for " << address << std::endl; std::cerr << "[FakeNetworkStack] getaddrinfo for " << address << std::endl;
@@ -83,7 +88,7 @@ static const Network_Funcs kVtable = {
return 0; return 0;
}, },
.freeaddrinfo = .freeaddrinfo =
[](void *obj, const Memory *mem, IP_Port *addrs) { [](void *_Nonnull obj, const Memory *_Nonnull mem, IP_Port *_Nullable addrs) {
mem_delete(mem, addrs); mem_delete(mem, addrs);
return 0; return 0;
}, },
@@ -97,7 +102,7 @@ FakeNetworkStack::FakeNetworkStack(NetworkUniverse &universe, const IP &node_ip)
FakeNetworkStack::~FakeNetworkStack() = default; FakeNetworkStack::~FakeNetworkStack() = default;
struct Network FakeNetworkStack::get_c_network() { return Network{&kVtable, this}; } struct Network FakeNetworkStack::c_network() { return Network{&kNetworkVtable, this}; }
Socket FakeNetworkStack::socket(int domain, int type, int protocol) Socket FakeNetworkStack::socket(int domain, int type, int protocol)
{ {

View File

@@ -2,8 +2,11 @@
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H #define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H
#include <map> #include <map>
#include <memory>
#include <mutex> #include <mutex>
#include <vector>
#include "../../../toxcore/net.h"
#include "../public/network.hh" #include "../public/network.hh"
#include "fake_sockets.hh" #include "fake_sockets.hh"
#include "network_universe.hh" #include "network_universe.hh"
@@ -17,33 +20,38 @@ public:
// NetworkSystem Implementation // NetworkSystem Implementation
Socket socket(int domain, int type, int protocol) override; Socket socket(int domain, int type, int protocol) override;
int bind(Socket sock, const IP_Port *addr) override; int bind(Socket sock, const IP_Port *_Nonnull addr) override;
int close(Socket sock) override; int close(Socket sock) override;
int sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) override; int sendto(Socket sock, const uint8_t *_Nonnull buf, size_t len,
int recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr) override; const IP_Port *_Nonnull addr) override;
int recvfrom(Socket sock, uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr) override;
int listen(Socket sock, int backlog) override; int listen(Socket sock, int backlog) override;
Socket accept(Socket sock) override; Socket accept(Socket sock) override;
int connect(Socket sock, const IP_Port *addr) override; int connect(Socket sock, const IP_Port *_Nonnull addr) override;
int send(Socket sock, const uint8_t *buf, size_t len) override; int send(Socket sock, const uint8_t *_Nonnull buf, size_t len) override;
int recv(Socket sock, uint8_t *buf, size_t len) override; int recv(Socket sock, uint8_t *_Nonnull buf, size_t len) override;
int recvbuf(Socket sock) override; int recvbuf(Socket sock) override;
int socket_nonblock(Socket sock, bool nonblock) override; int socket_nonblock(Socket sock, bool nonblock) override;
int getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen) override; int getsockopt(Socket sock, int level, int optname, void *_Nonnull optval,
int setsockopt(Socket sock, int level, int optname, const void *optval, size_t optlen) override; size_t *_Nonnull optlen) override;
int setsockopt(
Socket sock, int level, int optname, const void *_Nonnull optval, size_t optlen) override;
struct Network get_c_network(); /**
* @brief Returns C-compatible Network struct.
*/
struct Network c_network() override;
// For testing/fuzzing introspection // For testing/fuzzing introspection
FakeUdpSocket *get_udp_socket(Socket sock); FakeSocket *_Nullable get_sock(Socket sock);
FakeUdpSocket *_Nullable get_udp_socket(Socket sock);
std::vector<FakeUdpSocket *> get_bound_udp_sockets(); std::vector<FakeUdpSocket *> get_bound_udp_sockets();
NetworkUniverse &universe() { return universe_; } NetworkUniverse &universe() { return universe_; }
private: private:
FakeSocket *get_sock(Socket sock);
NetworkUniverse &universe_; NetworkUniverse &universe_;
std::map<int, std::unique_ptr<FakeSocket>> sockets_; std::map<int, std::unique_ptr<FakeSocket>> sockets_;
int next_fd_ = 100; int next_fd_ = 100;

View File

@@ -87,5 +87,62 @@ namespace {
ASSERT_NE(net_socket_to_native(accepted), -1); ASSERT_NE(net_socket_to_native(accepted), -1);
} }
TEST_F(FakeNetworkStackTest, LoopbackRedirection)
{
// 1. Create a stack with a specific IP (20.0.0.1)
FakeNetworkStack my_stack{universe, make_ip(0x14000001)};
Socket sock = my_stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
IP_Port bind_addr;
ip_init(&bind_addr.ip, false);
bind_addr.ip.ip.v4.uint32 = net_htonl(0x14000001);
bind_addr.port = net_htons(12345);
ASSERT_EQ(my_stack.bind(sock, &bind_addr), 0);
ASSERT_EQ(my_stack.listen(sock, 5), 0);
// 2. Connect to 127.0.0.1:12345 from the same stack
Socket client = my_stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
IP_Port connect_addr;
ip_init(&connect_addr.ip, false);
connect_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
connect_addr.port = net_htons(12345);
// Should redirect to 20.0.0.1:12345 because 127.0.0.1 is not bound
ASSERT_EQ(my_stack.connect(client, &connect_addr), -1);
ASSERT_EQ(errno, EINPROGRESS);
universe.process_events(0); // SYN
Socket accepted = my_stack.accept(sock);
ASSERT_NE(net_socket_to_native(accepted), -1);
}
TEST_F(FakeNetworkStackTest, ImplicitBindAvoidsCollision)
{
// Bind server to 33445 (default start of find_free_port)
Socket server = stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
IP_Port addr;
ip_init(&addr.ip, false);
addr.ip.ip.v4.uint32 = 0;
addr.port = net_htons(33445);
ASSERT_EQ(stack.bind(server, &addr), 0);
// Create client and connect (implicit bind)
Socket client = stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
IP_Port server_addr;
ip_init(&server_addr.ip, false);
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
server_addr.port = net_htons(33445);
// Should find a free port (not 33445)
ASSERT_EQ(stack.connect(client, &server_addr), -1);
ASSERT_EQ(errno, EINPROGRESS);
auto *client_obj = stack.get_sock(client);
ASSERT_NE(client_obj, nullptr);
ASSERT_NE(client_obj->local_port(), 33445);
}
} // namespace } // namespace
} // namespace tox::test } // namespace tox::test

View File

@@ -0,0 +1,459 @@
#include <gtest/gtest.h>
#include "fake_sockets.hh"
#include "network_universe.hh"
namespace tox::test {
namespace {
class FakeNetworkTcpTest : public ::testing::Test {
protected:
NetworkUniverse universe;
IP make_ip(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
IP ip;
ip_init(&ip, false);
ip.ip.v4.uint8[0] = a;
ip.ip.v4.uint8[1] = b;
ip.ip.v4.uint8[2] = c;
ip.ip.v4.uint8[3] = d;
return ip;
}
};
TEST_F(FakeNetworkTcpTest, MultipleConnectionsToSamePort)
{
universe.set_verbose(true);
IP server_ip = make_ip(10, 0, 0, 1);
uint16_t server_port = 12345;
FakeTcpSocket server(universe);
server.set_ip(server_ip);
IP_Port server_addr{server_ip, net_htons(server_port)};
ASSERT_EQ(server.bind(&server_addr), 0);
ASSERT_EQ(server.listen(5), 0);
// Client 1
IP client1_ip = make_ip(10, 0, 0, 2);
FakeTcpSocket client1(universe);
client1.set_ip(client1_ip);
client1.connect(&server_addr);
// Client 2 (same IP as client 1, different port)
FakeTcpSocket client2(universe);
client2.set_ip(client1_ip);
client2.connect(&server_addr);
// Handshake for both
// 1. SYNs
universe.process_events(0);
universe.process_events(0);
// 2. SYN-ACKs
universe.process_events(0);
universe.process_events(0);
// 3. ACKs
universe.process_events(0);
universe.process_events(0);
auto accepted1 = server.accept(nullptr);
auto accepted2 = server.accept(nullptr);
ASSERT_NE(accepted1, nullptr);
ASSERT_NE(accepted2, nullptr);
EXPECT_EQ(
static_cast<FakeTcpSocket *>(accepted1.get())->state(), FakeTcpSocket::ESTABLISHED);
EXPECT_EQ(
static_cast<FakeTcpSocket *>(accepted2.get())->state(), FakeTcpSocket::ESTABLISHED);
// Verify data isolation
const char *msg1 = "Message 1";
const char *msg2 = "Message 2";
client1.send(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1));
client2.send(reinterpret_cast<const uint8_t *>(msg2), strlen(msg2));
universe.process_events(0);
universe.process_events(0);
uint8_t buf[100];
int len1 = accepted1->recv(buf, sizeof(buf));
EXPECT_EQ(len1, strlen(msg1));
int len2 = accepted2->recv(buf, sizeof(buf));
EXPECT_EQ(len2, strlen(msg2));
}
TEST_F(FakeNetworkTcpTest, DuplicateSynCreatesDuplicateConnections)
{
universe.set_verbose(true);
IP server_ip = make_ip(10, 0, 0, 1);
uint16_t server_port = 12345;
FakeTcpSocket server(universe);
server.set_ip(server_ip);
IP_Port server_addr{server_ip, net_htons(server_port)};
server.bind(&server_addr);
server.listen(5);
IP client_ip = make_ip(10, 0, 0, 2);
IP_Port client_addr{client_ip, net_htons(33445)};
Packet p{};
p.from = client_addr;
p.to = server_addr;
p.is_tcp = true;
p.tcp_flags = 0x02; // SYN
p.seq = 100;
universe.send_packet(p);
universe.send_packet(p); // Duplicate SYN
universe.process_events(0);
universe.process_events(0);
// Now send ACK from client
Packet ack{};
ack.from = client_addr;
ack.to = server_addr;
ack.is_tcp = true;
ack.tcp_flags = 0x10; // ACK
ack.ack = 101;
universe.send_packet(ack);
universe.process_events(0);
auto accepted1 = server.accept(nullptr);
auto accepted2 = server.accept(nullptr);
ASSERT_NE(accepted1, nullptr);
EXPECT_EQ(accepted2, nullptr); // This should pass now
}
TEST_F(FakeNetworkTcpTest, PeerCloseClearsConnection)
{
universe.set_verbose(true);
IP server_ip = make_ip(10, 0, 0, 1);
uint16_t server_port = 12345;
FakeTcpSocket server(universe);
server.set_ip(server_ip);
IP_Port server_addr{server_ip, net_htons(server_port)};
server.bind(&server_addr);
server.listen(5);
IP client_ip = make_ip(10, 0, 0, 2);
FakeTcpSocket client(universe);
client.set_ip(client_ip);
client.connect(&server_addr);
// Handshake
universe.process_events(0); // SYN
universe.process_events(0); // SYN-ACK
universe.process_events(0); // ACK
auto accepted = server.accept(nullptr);
ASSERT_NE(accepted, nullptr);
EXPECT_EQ(
static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::ESTABLISHED);
// Client closes
client.close();
universe.process_events(0); // Deliver RST/FIN
// Server should no longer be ESTABLISHED
EXPECT_EQ(static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::CLOSED);
// Now if client reconnects with same port
FakeTcpSocket client2(universe);
client2.set_ip(client_ip);
client2.connect(&server_addr);
universe.process_events(0); // Deliver SYN
// Node 2 port 20002 should have: 1 LISTEN, 0 ESTABLISHED (old one gone), 1 SYN_RECEIVED
// (new one) Total targets should be 2.
}
TEST_F(FakeNetworkTcpTest, DataNotProcessedByMultipleSockets)
{
universe.set_verbose(true);
IP server_ip = make_ip(10, 0, 0, 1);
uint16_t server_port = 12345;
FakeTcpSocket server(universe);
server.set_ip(server_ip);
IP_Port server_addr{server_ip, net_htons(server_port)};
server.bind(&server_addr);
server.listen(5);
IP client_ip = make_ip(10, 0, 0, 2);
IP_Port client_addr{client_ip, net_htons(33445)};
// Manually create two "established" sockets on the same port for the same peer
// This simulates a bug where duplicate connections were allowed.
auto sock1 = FakeTcpSocket::create_connected(universe, client_addr, server_port);
sock1->set_ip(server_ip);
auto sock2 = FakeTcpSocket::create_connected(universe, client_addr, server_port);
sock2->set_ip(server_ip);
universe.bind_tcp(server_ip, server_port, sock1.get());
universe.bind_tcp(server_ip, server_port, sock2.get());
// Send data from client to server
Packet p{};
p.from = client_addr;
p.to = server_addr;
p.is_tcp = true;
p.tcp_flags = 0x10; // ACK (Data)
const char *data = "Unique";
p.data.assign(data, data + strlen(data));
universe.send_packet(p);
universe.process_events(0);
// Only ONE of them should have received it, or at least they shouldn't BOTH have it
// in a way that suggests duplicate delivery.
EXPECT_TRUE((sock1->recv_buffer_size() == strlen(data))
^ (sock2->recv_buffer_size() == strlen(data)));
}
TEST_F(FakeNetworkTcpTest, ConnectionCollision)
{
universe.set_verbose(true);
IP server_ip = make_ip(10, 0, 0, 1);
uint16_t server_port = 12345;
FakeTcpSocket server(universe);
server.set_ip(server_ip);
IP_Port server_addr{server_ip, net_htons(server_port)};
server.bind(&server_addr);
server.listen(5);
IP client_ip = make_ip(10, 0, 0, 2);
FakeTcpSocket client1(universe);
client1.set_ip(client_ip);
// Bind to specific port to force collision later
IP_Port client_bind_addr{client_ip, net_htons(33445)};
client1.bind(&client_bind_addr);
client1.connect(&server_addr);
// Handshake 1
universe.process_events(0); // SYN
universe.process_events(0); // SYN-ACK
universe.process_events(0); // ACK
auto accepted1 = server.accept(nullptr);
ASSERT_NE(accepted1, nullptr);
EXPECT_EQ(
static_cast<FakeTcpSocket *>(accepted1.get())->state(), FakeTcpSocket::ESTABLISHED);
// Now client 1 "reconnects" (e.g. after a crash or timeout, but using same port)
FakeTcpSocket client2(universe);
client2.set_ip(client_ip);
client2.bind(&client_bind_addr); // Forced collision
client2.connect(&server_addr);
// Deliver new SYN
universe.process_events(0);
// server_addr port 12345 now has:
// 1. LISTEN socket
// 2. accepted1 (ESTABLISHED with 10.0.0.2:33445)
// In our simplified simulation, the ESTABLISHED socket now handles the SYN by returning
// true (ignoring it). So no new connection is created.
auto accepted2 = server.accept(nullptr);
EXPECT_EQ(accepted2, nullptr);
const char *msg1 = "Data 1";
client1.send(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1));
universe.process_events(0);
// Data should still go to accepted1
EXPECT_EQ(accepted1->recv_buffer_size(), strlen(msg1));
}
TEST_F(FakeNetworkTcpTest, LoopbackConnection)
{
universe.set_verbose(true);
IP node_ip = make_ip(10, 0, 0, 1);
uint16_t port = 12345;
FakeTcpSocket server(universe);
server.set_ip(node_ip);
IP_Port listen_addr{node_ip, net_htons(port)};
server.bind(&listen_addr);
server.listen(5);
FakeTcpSocket client(universe);
client.set_ip(node_ip);
IP loopback_ip;
ip_init(&loopback_ip, false);
loopback_ip.ip.v4.uint32 = net_htonl(0x7F000001);
IP_Port server_loopback_addr{loopback_ip, net_htons(port)};
client.connect(&server_loopback_addr);
// SYN (Client -> 127.0.0.1:12345)
universe.process_events(0);
// SYN-ACK (Server -> Client)
universe.process_events(0);
// ACK (Client -> Server)
universe.process_events(0);
EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED);
auto accepted = server.accept(nullptr);
ASSERT_NE(accepted, nullptr);
EXPECT_EQ(
static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::ESTABLISHED);
// Data Transfer
const char *msg = "Loopback";
client.send(reinterpret_cast<const uint8_t *>(msg), strlen(msg));
universe.process_events(0);
uint8_t buf[100];
int len = accepted->recv(buf, sizeof(buf));
ASSERT_EQ(len, strlen(msg));
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), len), msg);
}
TEST_F(FakeNetworkTcpTest, SimultaneousConnect)
{
universe.set_verbose(true);
IP ipA = make_ip(10, 0, 0, 1);
IP ipB = make_ip(10, 0, 0, 2);
uint16_t portA = 10001;
uint16_t portB = 10002;
FakeTcpSocket sockA(universe);
sockA.set_ip(ipA);
IP_Port addrA{ipA, net_htons(portA)};
sockA.bind(&addrA);
sockA.listen(5);
FakeTcpSocket sockB(universe);
sockB.set_ip(ipB);
IP_Port addrB{ipB, net_htons(portB)};
sockB.bind(&addrB);
sockB.listen(5);
// A connects to B
sockA.connect(&addrB);
// B connects to A
sockB.connect(&addrA);
// This is "simultaneous open" in TCP but here they are also LISTENing.
// Toxcore uses this pattern sometimes.
universe.process_events(0); // SYN from A to B
universe.process_events(0); // SYN from B to A
universe.process_events(0); // SYN-ACK from B to A (for A's SYN)
universe.process_events(0); // SYN-ACK from A to B (for B's SYN)
universe.process_events(0); // ACK from A to B
universe.process_events(0); // ACK from B to A
EXPECT_EQ(sockA.state(), FakeTcpSocket::ESTABLISHED);
EXPECT_EQ(sockB.state(), FakeTcpSocket::ESTABLISHED);
}
TEST_F(FakeNetworkTcpTest, DataInHandshakeAck)
{
universe.set_verbose(true);
IP server_ip = make_ip(10, 0, 0, 1);
uint16_t server_port = 12345;
FakeTcpSocket server(universe);
server.set_ip(server_ip);
IP_Port server_addr{server_ip, net_htons(server_port)};
server.bind(&server_addr);
server.listen(5);
IP client_ip = make_ip(10, 0, 0, 2);
IP_Port client_addr{client_ip, net_htons(33445)};
// 1. SYN
Packet syn{};
syn.from = client_addr;
syn.to = server_addr;
syn.is_tcp = true;
syn.tcp_flags = 0x02;
universe.send_packet(syn);
universe.process_events(0);
// 2. SYN-ACK (Server -> Client)
universe.process_events(0);
// 3. ACK + Data (Client -> Server)
Packet ack{};
ack.from = client_addr;
ack.to = server_addr;
ack.is_tcp = true;
ack.tcp_flags = 0x10;
const char *data = "HandshakeData";
ack.data.assign(data, data + strlen(data));
universe.send_packet(ack);
universe.process_events(0);
auto accepted = server.accept(nullptr);
ASSERT_NE(accepted, nullptr);
EXPECT_EQ(accepted->recv_buffer_size(), strlen(data));
}
TEST_F(FakeNetworkTcpTest, LoopbackWithNodeIPMixed)
{
universe.set_verbose(true);
IP node_ip = make_ip(10, 0, 0, 1);
uint16_t port = 12345;
FakeTcpSocket server(universe);
server.set_ip(node_ip);
IP_Port listen_addr{node_ip, net_htons(port)};
server.bind(&listen_addr);
server.listen(5);
FakeTcpSocket client(universe);
client.set_ip(node_ip);
IP loopback_ip;
ip_init(&loopback_ip, false);
loopback_ip.ip.v4.uint32 = net_htonl(0x7F000001);
IP_Port server_loopback_addr{loopback_ip, net_htons(port)};
// Client connects to 127.0.0.1
client.connect(&server_loopback_addr);
universe.process_events(0); // SYN (Client -> 127.0.0.1)
universe.process_events(0); // SYN-ACK (Server -> Client)
universe.process_events(0); // ACK (Client -> Server)
EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED);
auto accepted = server.accept(nullptr);
ASSERT_NE(accepted, nullptr);
// Now manually simulate a packet coming from the server's EXTERNAL IP to the client.
// This happens because the server socket is bound to node_ip, so its packets might
// be delivered as coming from node_ip even if the client connected to 127.0.0.1.
Packet p{};
p.from = listen_addr; // node_ip:port
p.to.ip = node_ip;
p.to.port = net_htons(client.local_port());
p.is_tcp = true;
p.tcp_flags = 0x10; // ACK
const char *msg = "MixedIP";
p.data.assign(msg, msg + strlen(msg));
universe.send_packet(p);
universe.process_events(0);
EXPECT_EQ(client.recv_buffer_size(), strlen(msg));
}
}
}

View File

@@ -0,0 +1,75 @@
#include <gtest/gtest.h>
#include "fake_network_stack.hh"
#include "network_universe.hh"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <winsock2.h>
#else
#include <netinet/in.h>
#include <sys/socket.h>
#endif
namespace tox::test {
namespace {
class FakeNetworkUdpTest : public ::testing::Test {
public:
FakeNetworkUdpTest()
: ip1(make_ip(0x0A000001)) // 10.0.0.1
, ip2(make_ip(0x0A000002)) // 10.0.0.2
, stack1{universe, ip1}
, stack2{universe, ip2}
{
}
protected:
NetworkUniverse universe;
IP ip1, ip2;
FakeNetworkStack stack1;
FakeNetworkStack stack2;
};
TEST_F(FakeNetworkUdpTest, UdpExchange)
{
Socket sock1 = stack1.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Socket sock2 = stack2.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
IP_Port addr1;
addr1.ip = ip1;
addr1.port = net_htons(1234);
ASSERT_EQ(stack1.bind(sock1, &addr1), 0);
IP_Port addr2;
addr2.ip = ip2;
addr2.port = net_htons(5678);
ASSERT_EQ(stack2.bind(sock2, &addr2), 0);
const char *msg = "Hello UDP";
size_t msg_len = strlen(msg) + 1;
// Send from 1 to 2
ASSERT_EQ(stack1.sendto(sock1, reinterpret_cast<const uint8_t *>(msg), msg_len, &addr2),
static_cast<int>(msg_len));
// Delivery
universe.process_events(10); // With some time offset
// Receive at 2
uint8_t buffer[1024];
IP_Port from_addr;
int recv_len = stack2.recvfrom(sock2, buffer, sizeof(buffer), &from_addr);
ASSERT_EQ(recv_len, static_cast<int>(msg_len));
EXPECT_STREQ(reinterpret_cast<const char *>(buffer), msg);
EXPECT_EQ(net_ntohl(from_addr.ip.ip.v4.uint32), net_ntohl(ip1.ip.v4.uint32));
EXPECT_EQ(net_ntohs(from_addr.port), 1234);
stack1.close(sock1);
stack2.close(sock2);
}
} // namespace
} // namespace tox::test

View File

@@ -7,19 +7,19 @@
#include "../public/random.hh" #include "../public/random.hh"
// Forward declaration // Forward declaration
struct Tox_Random; struct Random;
namespace tox::test { namespace tox::test {
class FakeRandom : public RandomSystem { class FakeRandom : public RandomSystem {
public: public:
using EntropySource = std::function<void(uint8_t *out, size_t count)>; using EntropySource = std::function<void(uint8_t *_Nonnull out, size_t count)>;
using Observer = std::function<void(const uint8_t *data, size_t count)>; using Observer = std::function<void(const uint8_t *_Nonnull data, size_t count)>;
explicit FakeRandom(uint64_t seed); explicit FakeRandom(uint64_t seed);
uint32_t uniform(uint32_t upper_bound) override; uint32_t uniform(uint32_t upper_bound) override;
void bytes(uint8_t *out, size_t count) override; void bytes(uint8_t *_Nonnull out, size_t count) override;
/** /**
* @brief Set a custom entropy source. * @brief Set a custom entropy source.
@@ -32,7 +32,10 @@ public:
*/ */
void set_observer(Observer observer); void set_observer(Observer observer);
struct Tox_Random get_c_random(); /**
* @brief Returns C-compatible Random struct.
*/
struct Random c_random() override;
private: private:
std::minstd_rand rng_; std::minstd_rand rng_;

View File

@@ -3,7 +3,11 @@
#include <algorithm> #include <algorithm>
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
#include <deque>
#include <functional>
#include <iostream> #include <iostream>
#include <mutex>
#include <vector>
#include "network_universe.hh" #include "network_universe.hh"
@@ -27,8 +31,14 @@ int FakeSocket::close()
return 0; return 0;
} }
int FakeSocket::getsockopt(int level, int optname, void *optval, size_t *optlen) { return 0; } int FakeSocket::getsockopt(int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen)
int FakeSocket::setsockopt(int level, int optname, const void *optval, size_t optlen) { return 0; } {
return 0;
}
int FakeSocket::setsockopt(int level, int optname, const void *_Nonnull optval, size_t optlen)
{
return 0;
}
int FakeSocket::socket_nonblock(bool nonblock) int FakeSocket::socket_nonblock(bool nonblock)
{ {
nonblocking_ = nonblock; nonblocking_ = nonblock;
@@ -59,7 +69,7 @@ void FakeUdpSocket::close_impl()
} }
} }
int FakeUdpSocket::bind(const IP_Port *addr) int FakeUdpSocket::bind(const IP_Port *_Nonnull addr)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (local_port_ != 0) if (local_port_ != 0)
@@ -80,7 +90,7 @@ int FakeUdpSocket::bind(const IP_Port *addr)
return -1; return -1;
} }
int FakeUdpSocket::connect(const IP_Port *addr) int FakeUdpSocket::connect(const IP_Port *_Nonnull addr)
{ {
// UDP connect just sets default dest. // UDP connect just sets default dest.
// Not strictly needed for toxcore UDP but good for completeness. // Not strictly needed for toxcore UDP but good for completeness.
@@ -92,23 +102,29 @@ int FakeUdpSocket::listen(int backlog)
errno = EOPNOTSUPP; errno = EOPNOTSUPP;
return -1; return -1;
} }
std::unique_ptr<FakeSocket> FakeUdpSocket::accept(IP_Port *addr) std::unique_ptr<FakeSocket> FakeUdpSocket::accept(IP_Port *_Nullable addr)
{ {
errno = EOPNOTSUPP; errno = EOPNOTSUPP;
return nullptr; return nullptr;
} }
int FakeUdpSocket::send(const uint8_t *buf, size_t len) int FakeUdpSocket::send(const uint8_t *_Nonnull buf, size_t len)
{ {
errno = EDESTADDRREQ; errno = EDESTADDRREQ;
return -1; return -1;
} }
int FakeUdpSocket::recv(uint8_t *buf, size_t len) int FakeUdpSocket::recv(uint8_t *_Nonnull buf, size_t len)
{ {
errno = EOPNOTSUPP; errno = EOPNOTSUPP;
return -1; return -1;
} }
int FakeUdpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr) size_t FakeUdpSocket::recv_buffer_size()
{
std::lock_guard<std::mutex> lock(mutex_);
return recv_queue_.size();
}
int FakeUdpSocket::sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (local_port_ == 0) { if (local_port_ == 0) {
@@ -132,16 +148,15 @@ int FakeUdpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr)
universe_.send_packet(p); universe_.send_packet(p);
if (universe_.is_verbose()) { if (universe_.is_verbose()) {
uint32_t tip4 = net_ntohl(addr->ip.ip.v4.uint32); Ip_Ntoa ip_str;
net_ip_ntoa(&addr->ip, &ip_str);
std::cerr << "[FakeUdpSocket] sent " << len << " bytes from port " << local_port_ << " to " std::cerr << "[FakeUdpSocket] sent " << len << " bytes from port " << local_port_ << " to "
<< ((tip4 >> 24) & 0xFF) << "." << ((tip4 >> 16) & 0xFF) << "." << ip_str.buf << ":" << net_ntohs(addr->port) << std::endl;
<< ((tip4 >> 8) & 0xFF) << "." << (tip4 & 0xFF) << ":" << net_ntohs(addr->port)
<< std::endl;
} }
return len; return len;
} }
int FakeUdpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr) int FakeUdpSocket::recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr)
{ {
RecvObserver observer_copy; RecvObserver observer_copy;
std::vector<uint8_t> data_copy; std::vector<uint8_t> data_copy;
@@ -196,15 +211,13 @@ void FakeUdpSocket::push_packet(std::vector<uint8_t> data, IP_Port from)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (universe_.is_verbose()) { if (universe_.is_verbose()) {
uint32_t fip4 = net_ntohl(from.ip.ip.v4.uint32); Ip_Ntoa local_ip_str, from_ip_str;
net_ip_ntoa(&ip_, &local_ip_str);
net_ip_ntoa(&from.ip, &from_ip_str);
std::cerr << "[FakeUdpSocket] push " << data.size() << " bytes into queue for " std::cerr << "[FakeUdpSocket] push " << data.size() << " bytes into queue for "
<< ((ip_.ip.v4.uint32 >> 24) & 0xFF) << local_ip_str.buf << ":" << local_port_ << " from " << from_ip_str.buf << ":"
<< "." // ip_ is in network order from net_htonl << net_ntohs(from.port) << std::endl;
<< ((ip_.ip.v4.uint32 >> 16) & 0xFF) << "." << ((ip_.ip.v4.uint32 >> 8) & 0xFF)
<< "." << (ip_.ip.v4.uint32 & 0xFF) << ":" << local_port_ << " from "
<< ((fip4 >> 24) & 0xFF) << "." << ((fip4 >> 16) & 0xFF) << "."
<< ((fip4 >> 8) & 0xFF) << "." << (fip4 & 0xFF) << ":" << net_ntohs(from.port)
<< std::endl;
} }
recv_queue_.push_back({std::move(data), from}); recv_queue_.push_back({std::move(data), from});
} }
@@ -225,8 +238,8 @@ void FakeUdpSocket::set_recv_observer(RecvObserver observer)
FakeTcpSocket::FakeTcpSocket(NetworkUniverse &universe) FakeTcpSocket::FakeTcpSocket(NetworkUniverse &universe)
: FakeSocket(universe, SOCK_STREAM) : FakeSocket(universe, SOCK_STREAM)
, remote_addr_{}
{ {
ipport_reset(&remote_addr_);
} }
FakeTcpSocket::~FakeTcpSocket() { close_impl(); } FakeTcpSocket::~FakeTcpSocket() { close_impl(); }
@@ -234,6 +247,17 @@ FakeTcpSocket::~FakeTcpSocket() { close_impl(); }
int FakeTcpSocket::close() int FakeTcpSocket::close()
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (state_ == ESTABLISHED || state_ == SYN_SENT || state_ == SYN_RECEIVED
|| state_ == CLOSE_WAIT) {
// Send RST to peer
Packet p{};
p.from.ip = ip_;
p.from.port = net_htons(local_port_);
p.to = remote_addr_;
p.is_tcp = true;
p.tcp_flags = 0x04; // RST
universe_.send_packet(p);
}
close_impl(); close_impl();
return 0; return 0;
} }
@@ -247,7 +271,7 @@ void FakeTcpSocket::close_impl()
state_ = CLOSED; state_ = CLOSED;
} }
int FakeTcpSocket::bind(const IP_Port *addr) int FakeTcpSocket::bind(const IP_Port *_Nonnull addr)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (local_port_ != 0) if (local_port_ != 0)
@@ -276,14 +300,25 @@ int FakeTcpSocket::listen(int backlog)
return 0; return 0;
} }
int FakeTcpSocket::connect(const IP_Port *addr) int FakeTcpSocket::connect(const IP_Port *_Nonnull addr)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (universe_.is_verbose()) {
Ip_Ntoa ip_str, dest_str;
net_ip_ntoa(&ip_, &ip_str);
net_ip_ntoa(&addr->ip, &dest_str);
std::cerr << "[FakeTcpSocket] connect from " << ip_str.buf << " to " << dest_str.buf << ":"
<< net_ntohs(addr->port) << std::endl;
}
if (local_port_ == 0) { if (local_port_ == 0) {
// Implicit bind // Implicit bind
uint16_t p = universe_.find_free_port(ip_); uint16_t p = universe_.find_free_port(ip_);
if (universe_.bind_tcp(ip_, p, this)) { if (universe_.bind_tcp(ip_, p, this)) {
local_port_ = p; local_port_ = p;
if (universe_.is_verbose()) {
std::cerr << "[FakeTcpSocket] implicit bind to port " << local_port_ << std::endl;
}
} else { } else {
errno = EADDRINUSE; errno = EADDRINUSE;
return -1; return -1;
@@ -310,7 +345,7 @@ int FakeTcpSocket::connect(const IP_Port *addr)
return -1; return -1;
} }
std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *addr) std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *_Nullable addr)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (state_ != LISTEN) { if (state_ != LISTEN) {
@@ -318,13 +353,16 @@ std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *addr)
return nullptr; return nullptr;
} }
if (pending_connections_.empty()) { auto it = std::find_if(pending_connections_.begin(), pending_connections_.end(),
[](const std::unique_ptr<FakeTcpSocket> &s) { return s->state() == ESTABLISHED; });
if (it == pending_connections_.end()) {
errno = EWOULDBLOCK; errno = EWOULDBLOCK;
return nullptr; return nullptr;
} }
auto client = std::move(pending_connections_.front()); auto client = std::move(*it);
pending_connections_.pop_front(); pending_connections_.erase(it);
if (addr) { if (addr) {
*addr = client->remote_addr(); *addr = client->remote_addr();
@@ -332,11 +370,19 @@ std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *addr)
return client; return client;
} }
int FakeTcpSocket::send(const uint8_t *buf, size_t len) int FakeTcpSocket::send(const uint8_t *_Nonnull buf, size_t len)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (state_ != ESTABLISHED) { if (state_ != ESTABLISHED) {
errno = ENOTCONN; if (universe_.is_verbose()) {
std::cerr << "[FakeTcpSocket] send failed: state " << state_ << " port " << local_port_
<< std::endl;
}
if (state_ == SYN_SENT || state_ == SYN_RECEIVED) {
errno = EWOULDBLOCK;
} else {
errno = ENOTCONN;
}
return -1; return -1;
} }
@@ -357,7 +403,7 @@ int FakeTcpSocket::send(const uint8_t *buf, size_t len)
return len; return len;
} }
int FakeTcpSocket::recv(uint8_t *buf, size_t len) int FakeTcpSocket::recv(uint8_t *_Nonnull buf, size_t len)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (recv_buffer_.empty()) { if (recv_buffer_.empty()) {
@@ -368,6 +414,13 @@ int FakeTcpSocket::recv(uint8_t *buf, size_t len)
} }
size_t actual = std::min(len, recv_buffer_.size()); size_t actual = std::min(len, recv_buffer_.size());
if (universe_.is_verbose() && actual > 0) {
char remote_ip_str[TOX_INET_ADDRSTRLEN];
ip_parse_addr(&remote_addr_.ip, remote_ip_str, sizeof(remote_ip_str));
std::cerr << "[FakeTcpSocket] Port " << local_port_ << " (Peer: " << remote_ip_str << ":"
<< net_ntohs(remote_addr_.port) << ") recv requested " << len << " got " << actual
<< " (remaining " << recv_buffer_.size() - actual << ")" << std::endl;
}
for (size_t i = 0; i < actual; ++i) { for (size_t i = 0; i < actual; ++i) {
buf[i] = recv_buffer_.front(); buf[i] = recv_buffer_.front();
recv_buffer_.pop_front(); recv_buffer_.pop_front();
@@ -381,37 +434,109 @@ size_t FakeTcpSocket::recv_buffer_size()
return recv_buffer_.size(); return recv_buffer_.size();
} }
int FakeTcpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr) bool FakeTcpSocket::is_readable()
{
std::lock_guard<std::mutex> lock(mutex_);
if (state_ == LISTEN) {
return std::any_of(pending_connections_.begin(), pending_connections_.end(),
[](const std::unique_ptr<FakeTcpSocket> &s) { return s->state() == ESTABLISHED; });
}
return !recv_buffer_.empty() || state_ == CLOSED || state_ == CLOSE_WAIT;
}
bool FakeTcpSocket::is_writable()
{
std::lock_guard<std::mutex> lock(mutex_);
return state_ == ESTABLISHED;
}
int FakeTcpSocket::sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr)
{ {
errno = EOPNOTSUPP; errno = EOPNOTSUPP;
return -1; return -1;
} }
int FakeTcpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr) int FakeTcpSocket::recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr)
{ {
errno = EOPNOTSUPP; errno = EOPNOTSUPP;
return -1; return -1;
} }
void FakeTcpSocket::handle_packet(const Packet &p) int FakeTcpSocket::getsockopt(
int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen)
{
if (universe_.is_verbose()) {
std::cerr << "[FakeTcpSocket] getsockopt level=" << level << " optname=" << optname
<< " state=" << state_ << std::endl;
}
if (level == SOL_SOCKET && optname == SO_ERROR) {
int error = 0;
if (state_ == SYN_SENT || state_ == SYN_RECEIVED) {
error = EINPROGRESS;
} else if (state_ == CLOSED) {
error = ECONNREFUSED;
}
if (*optlen >= sizeof(int)) {
*static_cast<int *>(optval) = error;
*optlen = sizeof(int);
}
if (universe_.is_verbose()) {
std::cerr << "[FakeTcpSocket] getsockopt SO_ERROR returning error=" << error
<< std::endl;
}
return 0;
}
return 0;
}
bool FakeTcpSocket::handle_packet(const Packet &p)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (universe_.is_verbose()) { if (universe_.is_verbose()) {
std::cerr << "Handle Packet: Port " << local_port_ << " Flags " char remote_ip_str[TOX_INET_ADDRSTRLEN];
<< static_cast<int>(p.tcp_flags) << " State " << state_ << std::endl; ip_parse_addr(&remote_addr_.ip, remote_ip_str, sizeof(remote_ip_str));
std::cerr << "Handle Packet: Port " << local_port_ << " (Peer: " << remote_ip_str << ":"
<< net_ntohs(remote_addr_.port) << ") Flags " << TcpFlags{p.tcp_flags}
<< " State " << state_ << " From " << net_ntohs(p.from.port) << std::endl;
}
if (state_ != LISTEN) {
// Filter packets not from our peer
bool port_match = net_ntohs(p.from.port) == net_ntohs(remote_addr_.port);
bool ip_match = ip_equal(&p.from.ip, &remote_addr_.ip)
|| (is_loopback(p.from.ip) && ip_equal(&remote_addr_.ip, &ip_))
|| (is_loopback(remote_addr_.ip) && ip_equal(&p.from.ip, &ip_));
if (!port_match || !ip_match) {
return false;
}
if (p.tcp_flags & 0x04) { // RST
state_ = CLOSED;
if (local_port_ != 0) {
universe_.unbind_tcp(ip_, local_port_, this);
local_port_ = 0;
}
return true;
}
} }
if (state_ == LISTEN) { if (state_ == LISTEN) {
if (p.tcp_flags & 0x02) { // SYN if (p.tcp_flags & 0x02) { // SYN
// Check for duplicate SYN from same peer
for (const auto &pending : pending_connections_) {
if (ipport_equal(&p.from, &pending->remote_addr_)) {
return true;
}
}
// Create new socket for connection // Create new socket for connection
auto new_sock = std::make_unique<FakeTcpSocket>(universe_); auto new_sock = std::make_unique<FakeTcpSocket>(universe_);
// Bind to ephemeral? No, it's accepted on the same port but distinct 4-tuple.
// In our simplified model, the new socket is not bound to the global map
// until accepted? Or effectively bound to the 4-tuple.
// For now, let's just create it and queue it.
new_sock->state_ = SYN_RECEIVED; new_sock->state_ = SYN_RECEIVED;
new_sock->remote_addr_ = p.from; new_sock->remote_addr_ = p.from;
new_sock->local_port_ = local_port_; new_sock->local_port_ = local_port_;
new_sock->set_ip(ip_); // Inherit IP from listening socket
new_sock->last_ack_ = p.seq + 1; new_sock->last_ack_ = p.seq + 1;
new_sock->next_seq_ = 1000; // Random ISN new_sock->next_seq_ = 1000; // Random ISN
@@ -428,13 +553,9 @@ void FakeTcpSocket::handle_packet(const Packet &p)
universe_.send_packet(resp); universe_.send_packet(resp);
// In real TCP, we wait for ACK to move to ESTABLISHED and accept queue. // Add to pending, but it's still SYN_RECEIVED
// Here we cheat and move to ESTABLISHED immediately or wait for ACK?
// Let's wait for ACK.
// But where do we store this half-open socket?
// For simplicity: auto-transition to ESTABLISHED and queue it.
new_sock->state_ = ESTABLISHED;
pending_connections_.push_back(std::move(new_sock)); pending_connections_.push_back(std::move(new_sock));
return true;
} }
} else if (state_ == SYN_SENT) { } else if (state_ == SYN_SENT) {
if ((p.tcp_flags & 0x12) == 0x12) { // SYN | ACK if ((p.tcp_flags & 0x12) == 0x12) { // SYN | ACK
@@ -451,8 +572,31 @@ void FakeTcpSocket::handle_packet(const Packet &p)
ack.seq = next_seq_; ack.seq = next_seq_;
ack.ack = last_ack_; ack.ack = last_ack_;
universe_.send_packet(ack); universe_.send_packet(ack);
return true;
} else if (p.tcp_flags & 0x02) { // SYN (Simultaneous Open)
state_ = SYN_RECEIVED;
last_ack_ = p.seq + 1;
// Send SYN-ACK
Packet resp{};
resp.from = p.to;
resp.to = p.from;
resp.is_tcp = true;
resp.tcp_flags = 0x12; // SYN | ACK
resp.seq = next_seq_++;
resp.ack = last_ack_;
universe_.send_packet(resp);
return true;
} }
} else if (state_ == ESTABLISHED) { } else if (state_ == SYN_RECEIVED) {
if (p.tcp_flags & 0x10) { // ACK
state_ = ESTABLISHED;
} else {
return false;
}
}
if (state_ == ESTABLISHED) {
if (p.tcp_flags & 0x01) { // FIN if (p.tcp_flags & 0x01) { // FIN
state_ = CLOSE_WAIT; state_ = CLOSE_WAIT;
// Send ACK // Send ACK
@@ -464,12 +608,23 @@ void FakeTcpSocket::handle_packet(const Packet &p)
ack.seq = next_seq_; ack.seq = next_seq_;
ack.ack = p.seq + 1; // Consume FIN ack.ack = p.seq + 1; // Consume FIN
universe_.send_packet(ack); universe_.send_packet(ack);
} else if (!p.data.empty()) { return true;
recv_buffer_.insert(recv_buffer_.end(), p.data.begin(), p.data.end()); } else {
last_ack_ += p.data.size(); if (!p.data.empty()) {
// Should send ACK? if (universe_.is_verbose()) {
char remote_ip_str[TOX_INET_ADDRSTRLEN];
ip_parse_addr(&remote_addr_.ip, remote_ip_str, sizeof(remote_ip_str));
std::cerr << "[FakeTcpSocket] Port " << local_port_
<< " (Peer: " << remote_ip_str << ":" << net_ntohs(remote_addr_.port)
<< ") adding " << p.data.size() << " bytes to buffer (currently "
<< recv_buffer_.size() << ")" << std::endl;
}
recv_buffer_.insert(recv_buffer_.end(), p.data.begin(), p.data.end());
}
return true;
} }
} }
return false;
} }
std::unique_ptr<FakeTcpSocket> FakeTcpSocket::create_connected( std::unique_ptr<FakeTcpSocket> FakeTcpSocket::create_connected(
@@ -482,4 +637,23 @@ std::unique_ptr<FakeTcpSocket> FakeTcpSocket::create_connected(
return s; return s;
} }
std::ostream &operator<<(std::ostream &os, FakeTcpSocket::State state)
{
switch (state) {
case FakeTcpSocket::CLOSED:
return os << "CLOSED";
case FakeTcpSocket::LISTEN:
return os << "LISTEN";
case FakeTcpSocket::SYN_SENT:
return os << "SYN_SENT";
case FakeTcpSocket::SYN_RECEIVED:
return os << "SYN_RECEIVED";
case FakeTcpSocket::ESTABLISHED:
return os << "ESTABLISHED";
case FakeTcpSocket::CLOSE_WAIT:
return os << "CLOSE_WAIT";
}
return os << "UNKNOWN(" << static_cast<int>(state) << ")";
}
} // namespace tox::test } // namespace tox::test

View File

@@ -18,6 +18,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#endif #endif
#include "../../../toxcore/attributes.h"
#include "../../../toxcore/network.h" #include "../../../toxcore/network.h"
namespace tox::test { namespace tox::test {
@@ -33,21 +34,23 @@ public:
FakeSocket(NetworkUniverse &universe, int type); FakeSocket(NetworkUniverse &universe, int type);
virtual ~FakeSocket(); virtual ~FakeSocket();
virtual int bind(const IP_Port *addr) = 0; virtual int bind(const IP_Port *_Nonnull addr) = 0;
virtual int connect(const IP_Port *addr) = 0; virtual int connect(const IP_Port *_Nonnull addr) = 0;
virtual int listen(int backlog) = 0; virtual int listen(int backlog) = 0;
virtual std::unique_ptr<FakeSocket> accept(IP_Port *addr) = 0; virtual std::unique_ptr<FakeSocket> accept(IP_Port *_Nullable addr) = 0;
virtual int send(const uint8_t *buf, size_t len) = 0; virtual int send(const uint8_t *_Nonnull buf, size_t len) = 0;
virtual int recv(uint8_t *buf, size_t len) = 0; virtual int recv(uint8_t *_Nonnull buf, size_t len) = 0;
virtual size_t recv_buffer_size() { return 0; } virtual size_t recv_buffer_size() { return 0; }
virtual bool is_readable() { return recv_buffer_size() > 0; }
virtual bool is_writable() { return true; }
virtual int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) = 0; virtual int sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr) = 0;
virtual int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) = 0; virtual int recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr) = 0;
virtual int getsockopt(int level, int optname, void *optval, size_t *optlen); virtual int getsockopt(int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen);
virtual int setsockopt(int level, int optname, const void *optval, size_t optlen); virtual int setsockopt(int level, int optname, const void *_Nonnull optval, size_t optlen);
virtual int socket_nonblock(bool nonblock); virtual int socket_nonblock(bool nonblock);
virtual int close(); virtual int close();
@@ -76,17 +79,18 @@ public:
explicit FakeUdpSocket(NetworkUniverse &universe); explicit FakeUdpSocket(NetworkUniverse &universe);
~FakeUdpSocket() override; ~FakeUdpSocket() override;
int bind(const IP_Port *addr) override; int bind(const IP_Port *_Nonnull addr) override;
int connect(const IP_Port *addr) override; int connect(const IP_Port *_Nonnull addr) override;
int listen(int backlog) override; int listen(int backlog) override;
std::unique_ptr<FakeSocket> accept(IP_Port *addr) override; std::unique_ptr<FakeSocket> accept(IP_Port *_Nullable addr) override;
int close() override; int close() override;
int send(const uint8_t *buf, size_t len) override; int send(const uint8_t *_Nonnull buf, size_t len) override;
int recv(uint8_t *buf, size_t len) override; int recv(uint8_t *_Nonnull buf, size_t len) override;
size_t recv_buffer_size() override;
int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) override; int sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr) override;
int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) override; int recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr) override;
// Called by Universe to deliver a packet // Called by Universe to deliver a packet
void push_packet(std::vector<uint8_t> data, IP_Port from); void push_packet(std::vector<uint8_t> data, IP_Port from);
@@ -124,21 +128,25 @@ public:
explicit FakeTcpSocket(NetworkUniverse &universe); explicit FakeTcpSocket(NetworkUniverse &universe);
~FakeTcpSocket() override; ~FakeTcpSocket() override;
int bind(const IP_Port *addr) override; int bind(const IP_Port *_Nonnull addr) override;
int connect(const IP_Port *addr) override; int connect(const IP_Port *_Nonnull addr) override;
int listen(int backlog) override; int listen(int backlog) override;
std::unique_ptr<FakeSocket> accept(IP_Port *addr) override; std::unique_ptr<FakeSocket> accept(IP_Port *_Nullable addr) override;
int close() override; int close() override;
int send(const uint8_t *buf, size_t len) override; int send(const uint8_t *_Nonnull buf, size_t len) override;
int recv(uint8_t *buf, size_t len) override; int recv(uint8_t *_Nonnull buf, size_t len) override;
size_t recv_buffer_size() override; size_t recv_buffer_size() override;
bool is_readable() override;
bool is_writable() override;
int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) override; int sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr) override;
int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) override; int recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr) override;
int getsockopt(int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen) override;
// Internal events // Internal events
void handle_packet(const Packet &p); bool handle_packet(const Packet &p);
State state() const { return state_; } State state() const { return state_; }
const IP_Port &remote_addr() const { return remote_addr_; } const IP_Port &remote_addr() const { return remote_addr_; }
@@ -160,6 +168,8 @@ private:
uint32_t last_ack_ = 0; uint32_t last_ack_ = 0;
}; };
std::ostream &operator<<(std::ostream &os, FakeTcpSocket::State state);
} // namespace tox::test } // namespace tox::test
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H #endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H

View File

@@ -82,6 +82,46 @@ namespace {
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 5), "Hello"); EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 5), "Hello");
} }
TEST_F(FakeTcpSocketTest, RecvBuffering)
{
IP_Port server_addr;
ip_init(&server_addr.ip, false);
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
server_addr.port = net_htons(8082);
server.bind(&server_addr);
server.listen(5);
client.connect(&server_addr);
universe.process_events(0); // SYN
universe.process_events(0); // SYN-ACK
universe.process_events(0); // ACK
auto accepted = server.accept(nullptr);
ASSERT_NE(accepted, nullptr);
uint8_t msg1[] = "Part1";
uint8_t msg2[] = "Part2";
client.send(msg1, 5);
client.send(msg2, 5);
universe.process_events(0); // Deliver Part1
universe.process_events(0); // Deliver Part2
EXPECT_EQ(accepted->recv_buffer_size(), 10);
uint8_t recv_buf[20];
// Read partial
ASSERT_EQ(accepted->recv(recv_buf, 3), 3);
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 3), "Par");
EXPECT_EQ(accepted->recv_buffer_size(), 7);
// Read rest
ASSERT_EQ(accepted->recv(recv_buf, 7), 7);
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 7), "t1Part2");
EXPECT_EQ(accepted->recv_buffer_size(), 0);
}
class FakeUdpSocketTest : public ::testing::Test { class FakeUdpSocketTest : public ::testing::Test {
public: public:
~FakeUdpSocketTest() override; ~FakeUdpSocketTest() override;
@@ -119,6 +159,40 @@ namespace {
EXPECT_EQ(sender_addr.port, net_htons(client.local_port())); EXPECT_EQ(sender_addr.port, net_htons(client.local_port()));
} }
TEST_F(FakeUdpSocketTest, RecvBuffering)
{
IP_Port server_addr;
ip_init(&server_addr.ip, false);
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
server_addr.port = net_htons(9001);
server.bind(&server_addr);
const char *msg1 = "Msg1";
const char *msg2 = "Msg2";
client.sendto(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1), &server_addr);
client.sendto(reinterpret_cast<const uint8_t *>(msg2), strlen(msg2), &server_addr);
universe.process_events(0); // Deliver msg1
universe.process_events(0); // Deliver msg2
EXPECT_EQ(server.recv_buffer_size(), 2);
IP_Port sender;
uint8_t buf[10];
int len = server.recvfrom(buf, sizeof(buf), &sender);
ASSERT_EQ(len, 4);
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), len), "Msg1");
EXPECT_EQ(server.recv_buffer_size(), 1);
len = server.recvfrom(buf, sizeof(buf), &sender);
ASSERT_EQ(len, 4);
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), len), "Msg2");
EXPECT_EQ(server.recv_buffer_size(), 0);
}
} // namespace } // namespace
} // namespace tox::test } // namespace tox::test

View File

@@ -7,6 +7,30 @@
namespace tox::test { namespace tox::test {
std::ostream &operator<<(std::ostream &os, TcpFlags flags)
{
bool first = true;
if (flags.value & 0x02) {
os << (first ? "" : "|") << "SYN";
first = false;
}
if (flags.value & 0x10) {
os << (first ? "" : "|") << "ACK";
first = false;
}
if (flags.value & 0x01) {
os << (first ? "" : "|") << "FIN";
first = false;
}
if (flags.value & 0x04) {
os << (first ? "" : "|") << "RST";
first = false;
}
if (first)
os << "NONE";
return os << "(" << static_cast<int>(flags.value) << ")";
}
bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const
{ {
if (port != other.port) if (port != other.port)
@@ -24,7 +48,7 @@ bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const
NetworkUniverse::NetworkUniverse() { } NetworkUniverse::NetworkUniverse() { }
NetworkUniverse::~NetworkUniverse() { } NetworkUniverse::~NetworkUniverse() { }
bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket) bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *_Nonnull socket)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
IP_Port_Key key{ip, port}; IP_Port_Key key{ip, port};
@@ -40,14 +64,14 @@ void NetworkUniverse::unbind_udp(IP ip, uint16_t port)
udp_bindings_.erase({ip, port}); udp_bindings_.erase({ip, port});
} }
bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket) bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
tcp_bindings_.insert({{ip, port}, socket}); tcp_bindings_.insert({{ip, port}, socket});
return true; return true;
} }
void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket) void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
auto range = tcp_bindings_.equal_range({ip, port}); auto range = tcp_bindings_.equal_range({ip, port});
@@ -75,9 +99,64 @@ void NetworkUniverse::send_packet(Packet p)
p.delivery_time += global_latency_ms_; p.delivery_time += global_latency_ms_;
std::lock_guard<std::recursive_mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
p.sequence_number = next_packet_id_++;
if (verbose_) {
Ip_Ntoa from_str, to_str;
net_ip_ntoa(&p.from.ip, &from_str);
net_ip_ntoa(&p.to.ip, &to_str);
std::cerr << "[NetworkUniverse] Enqueued packet #" << p.sequence_number << " from "
<< from_str.buf << ":" << net_ntohs(p.from.port) << " to " << to_str.buf << ":"
<< net_ntohs(p.to.port);
if (p.is_tcp) {
std::cerr << " (TCP Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq
<< " Ack=" << p.ack << ")";
}
std::cerr << " with size " << p.data.size() << std::endl;
}
event_queue_.push(std::move(p)); event_queue_.push(std::move(p));
} }
static bool is_ipv4_mapped(const IP &ip)
{
if (!net_family_is_ipv6(ip.family))
return false;
const uint8_t *b = ip.ip.v6.uint8;
for (int i = 0; i < 10; ++i)
if (b[i] != 0)
return false;
if (b[10] != 0xFF || b[11] != 0xFF)
return false;
return true;
}
static IP extract_ipv4(const IP &ip)
{
IP ip4;
ip_init(&ip4, false);
const uint8_t *b = ip.ip.v6.uint8;
std::memcpy(ip4.ip.v4.uint8, b + 12, 4);
return ip4;
}
bool is_loopback(const IP &ip)
{
if (net_family_is_ipv4(ip.family)) {
return ip.ip.v4.uint32 == net_htonl(0x7F000001);
}
if (net_family_is_ipv6(ip.family)) {
const uint8_t *b = ip.ip.v6.uint8;
for (int i = 0; i < 15; ++i) {
if (b[i] != 0) {
return false;
}
}
return b[15] == 1;
}
return false;
}
void NetworkUniverse::process_events(uint64_t current_time_ms) void NetworkUniverse::process_events(uint64_t current_time_ms)
{ {
while (true) { while (true) {
@@ -88,19 +167,101 @@ void NetworkUniverse::process_events(uint64_t current_time_ms)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
if (!event_queue_.empty()) {
const Packet &top = event_queue_.top();
if (verbose_) {
std::cerr << "[NetworkUniverse] Peek packet: time=" << top.delivery_time
<< " current=" << current_time_ms << " tcp=" << top.is_tcp
<< std::endl;
}
}
if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) { if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) {
p = event_queue_.top(); p = event_queue_.top();
event_queue_.pop(); event_queue_.pop();
has_packet = true; has_packet = true;
if (verbose_) {
Ip_Ntoa from_str, to_str;
net_ip_ntoa(&p.from.ip, &from_str);
net_ip_ntoa(&p.to.ip, &to_str);
std::cerr << "[NetworkUniverse] Processing packet #" << p.sequence_number
<< " from " << from_str.buf << ":" << net_ntohs(p.from.port) << " to "
<< to_str.buf << ":" << net_ntohs(p.to.port)
<< " (TCP=" << (p.is_tcp ? "true" : "false");
if (p.is_tcp) {
std::cerr << " Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq
<< " Ack=" << p.ack;
}
std::cerr << " Size=" << p.data.size() << ")" << std::endl;
}
IP target_ip = p.to.ip;
if (p.is_tcp) { if (p.is_tcp) {
auto range = tcp_bindings_.equal_range({p.to.ip, net_ntohs(p.to.port)}); if (is_loopback(target_ip)
&& tcp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) {
if (verbose_) {
std::cerr << "[NetworkUniverse] Loopback packet to "
<< static_cast<int>(target_ip.ip.v4.uint8[3])
<< " redirected to "
<< static_cast<int>(p.from.ip.ip.v4.uint8[3]) << std::endl;
}
target_ip = p.from.ip;
}
auto range = tcp_bindings_.equal_range({target_ip, net_ntohs(p.to.port)});
FakeTcpSocket *listen_match = nullptr;
for (auto it = range.first; it != range.second; ++it) { for (auto it = range.first; it != range.second; ++it) {
tcp_targets.push_back(it->second); FakeTcpSocket *s = it->second;
if (s->state() == FakeTcpSocket::LISTEN) {
listen_match = s;
} else {
const IP_Port &remote = s->remote_addr();
if (net_ntohs(p.from.port) == net_ntohs(remote.port)) {
if (ip_equal(&p.from.ip, &remote.ip)
|| (is_loopback(p.from.ip) && ip_equal(&remote.ip, &target_ip))
|| (is_loopback(remote.ip)
&& ip_equal(&p.from.ip, &target_ip))) {
tcp_targets.push_back(s);
}
}
}
}
if (listen_match && (p.tcp_flags & 0x02)) {
tcp_targets.push_back(listen_match);
}
if (verbose_) {
std::cerr << "[NetworkUniverse] Routing TCP to "
<< static_cast<int>(target_ip.ip.v4.uint8[0]) << "."
<< static_cast<int>(target_ip.ip.v4.uint8[3]) << ":"
<< net_ntohs(p.to.port)
<< ". Targets found: " << tcp_targets.size() << std::endl;
}
if (tcp_targets.empty()) {
if (verbose_) {
std::cerr << "[NetworkUniverse] WARNING: No TCP targets for "
<< static_cast<int>(target_ip.ip.v4.uint8[0]) << "."
<< static_cast<int>(target_ip.ip.v4.uint8[3]) << ":"
<< net_ntohs(p.to.port) << std::endl;
}
} }
} else { } else {
if (udp_bindings_.count({p.to.ip, net_ntohs(p.to.port)})) { if (is_loopback(target_ip)
udp_target = udp_bindings_[{p.to.ip, net_ntohs(p.to.port)}]; && udp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) {
target_ip = p.from.ip;
}
if (udp_bindings_.count({target_ip, net_ntohs(p.to.port)})) {
udp_target = udp_bindings_[{target_ip, net_ntohs(p.to.port)}];
} else if (is_ipv4_mapped(target_ip)) {
IP ip4 = extract_ipv4(target_ip);
if (udp_bindings_.count({ip4, net_ntohs(p.to.port)})) {
udp_target = udp_bindings_[{ip4, net_ntohs(p.to.port)}];
}
} }
} }
} }
@@ -112,7 +273,9 @@ void NetworkUniverse::process_events(uint64_t current_time_ms)
if (p.is_tcp) { if (p.is_tcp) {
for (auto *it : tcp_targets) { for (auto *it : tcp_targets) {
it->handle_packet(p); if (it->handle_packet(p)) {
break;
}
} }
} else { } else {
if (udp_target) { if (udp_target) {
@@ -136,7 +299,7 @@ uint16_t NetworkUniverse::find_free_port(IP ip, uint16_t start)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
for (uint16_t port = start; port < 65535; ++port) { for (uint16_t port = start; port < 65535; ++port) {
if (!udp_bindings_.count({ip, port})) if (!udp_bindings_.count({ip, port}) && !tcp_bindings_.count({ip, port}))
return port; return port;
} }
return 0; return 0;

View File

@@ -9,6 +9,7 @@
#include <queue> #include <queue>
#include <vector> #include <vector>
#include "../../../toxcore/attributes.h"
#include "../../../toxcore/network.h" #include "../../../toxcore/network.h"
namespace tox::test { namespace tox::test {
@@ -16,11 +17,17 @@ namespace tox::test {
class FakeUdpSocket; class FakeUdpSocket;
class FakeTcpSocket; class FakeTcpSocket;
struct TcpFlags {
uint8_t value;
};
std::ostream &operator<<(std::ostream &os, TcpFlags flags);
struct Packet { struct Packet {
IP_Port from; IP_Port from;
IP_Port to; IP_Port to;
std::vector<uint8_t> data; std::vector<uint8_t> data;
uint64_t delivery_time; uint64_t delivery_time;
uint64_t sequence_number = 0;
bool is_tcp = false; bool is_tcp = false;
// TCP Simulation Fields // TCP Simulation Fields
@@ -28,9 +35,17 @@ struct Packet {
uint32_t seq = 0; uint32_t seq = 0;
uint32_t ack = 0; uint32_t ack = 0;
bool operator>(const Packet &other) const { return delivery_time > other.delivery_time; } bool operator>(const Packet &other) const
{
if (delivery_time != other.delivery_time) {
return delivery_time > other.delivery_time;
}
return sequence_number > other.sequence_number;
}
}; };
bool is_loopback(const IP &ip);
/** /**
* @brief The God Object for the network simulation. * @brief The God Object for the network simulation.
* Manages routing, latency, and connectivity. * Manages routing, latency, and connectivity.
@@ -45,11 +60,11 @@ public:
// Registration // Registration
// Returns true if binding succeeded // Returns true if binding succeeded
bool bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket); bool bind_udp(IP ip, uint16_t port, FakeUdpSocket *_Nonnull socket);
void unbind_udp(IP ip, uint16_t port); void unbind_udp(IP ip, uint16_t port);
bool bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket); bool bind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket);
void unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket); void unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket);
// Routing // Routing
void send_packet(Packet p); void send_packet(Packet p);
@@ -81,6 +96,7 @@ private:
std::vector<PacketSink> observers_; std::vector<PacketSink> observers_;
uint64_t global_latency_ms_ = 0; uint64_t global_latency_ms_ = 0;
uint64_t next_packet_id_ = 0;
bool verbose_ = false; bool verbose_ = false;
std::recursive_mutex mutex_; std::recursive_mutex mutex_;
}; };

View File

@@ -236,5 +236,86 @@ namespace {
std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len)), "Padding test"); std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len)), "Padding test");
} }
TEST_F(NetworkUniverseTest, TcpRoutingSpecificity)
{
IP ip1{};
ip_init(&ip1, false);
ip1.ip.v4.uint32 = net_htonl(0x0A000001); // 10.0.0.1
uint16_t port = 12345;
IP_Port local_addr{ip1, net_htons(port)};
FakeTcpSocket listen_sock(universe);
listen_sock.set_ip(ip1);
listen_sock.bind(&local_addr);
listen_sock.listen(5);
IP remote_ip{};
ip_init(&remote_ip, false);
remote_ip.ip.v4.uint32 = net_htonl(0x0A000002); // 10.0.0.2
IP_Port remote_addr{remote_ip, net_htons(33445)};
auto established_sock = FakeTcpSocket::create_connected(universe, remote_addr, port);
established_sock->set_ip(ip1);
universe.bind_tcp(ip1, port, established_sock.get());
// Send a data packet from remote to local
Packet p{};
p.from = remote_addr;
p.to = local_addr;
p.is_tcp = true;
p.tcp_flags = 0x10; // ACK (Data)
const char *data = "Specific";
p.data.assign(data, data + strlen(data));
universe.send_packet(p);
universe.process_events(0);
// established_sock should have received it
EXPECT_EQ(established_sock->recv_buffer_size(), strlen(data));
// listen_sock should NOT have received it (it doesn't have a buffer, but it shouldn't have
// been called) We can't easily check listen_sock wasn't called without mocks or checking
// logs, but we can check that it didn't create a new pending connection.
EXPECT_FALSE(listen_sock.is_readable());
}
TEST_F(NetworkUniverseTest, PacketOrdering)
{
IP ip1{}, ip2{};
ip_init(&ip1, false);
ip1.ip.v4.uint32 = net_htonl(0x0A000001);
ip_init(&ip2, false);
ip2.ip.v4.uint32 = net_htonl(0x0A000002);
uint16_t port = 33445;
IP_Port addr1{ip1, net_htons(port)};
IP_Port addr2{ip2, net_htons(port)};
FakeUdpSocket sock1{universe};
sock1.set_ip(ip1);
sock1.bind(&addr1);
FakeUdpSocket sock2{universe};
sock2.set_ip(ip2);
sock2.bind(&addr2);
// Send 10 packets with the same delivery time (global latency = 0)
for (int i = 0; i < 10; ++i) {
uint8_t data = static_cast<uint8_t>(i);
sock1.sendto(&data, 1, &addr2);
}
universe.process_events(0);
// They should be received in the exact order they were sent
for (int i = 0; i < 10; ++i) {
uint8_t buf[1];
IP_Port from;
ASSERT_EQ(sock2.recvfrom(buf, 1, &from), 1);
EXPECT_EQ(buf[0], i) << "Packet " << i << " was delivered out of order";
}
}
} // namespace } // namespace
} // namespace tox::test } // namespace tox::test

View File

@@ -7,6 +7,8 @@
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include "../../../toxcore/attributes.h"
namespace tox::test { namespace tox::test {
struct Fuzz_Data { struct Fuzz_Data {
@@ -14,12 +16,12 @@ struct Fuzz_Data {
static constexpr std::size_t TRACE_TRAP = -1; static constexpr std::size_t TRACE_TRAP = -1;
private: private:
const uint8_t *data_; const uint8_t *_Nonnull data_;
const uint8_t *base_; const uint8_t *_Nonnull base_;
std::size_t size_; std::size_t size_;
public: public:
Fuzz_Data(const uint8_t *input_data, std::size_t input_size) Fuzz_Data(const uint8_t *_Nonnull input_data, std::size_t input_size)
: data_(input_data) : data_(input_data)
, base_(input_data) , base_(input_data)
, size_(input_size) , size_(input_size)
@@ -30,7 +32,7 @@ public:
Fuzz_Data(const Fuzz_Data &rhs) = delete; Fuzz_Data(const Fuzz_Data &rhs) = delete;
struct Consumer { struct Consumer {
const char *func; const char *_Nonnull func;
Fuzz_Data &fd; Fuzz_Data &fd;
operator bool() operator bool()
@@ -51,14 +53,14 @@ public:
{ {
if (sizeof(T) > fd.size()) if (sizeof(T) > fd.size())
return T{}; return T{};
const uint8_t *bytes = fd.consume(func, sizeof(T)); const uint8_t *_Nonnull bytes = fd.consume(func, sizeof(T));
T val; T val;
std::memcpy(&val, bytes, sizeof(T)); std::memcpy(&val, bytes, sizeof(T));
return val; return val;
} }
}; };
Consumer consume1(const char *func) { return Consumer{func, *this}; } Consumer consume1(const char *_Nonnull func) { return Consumer{func, *this}; }
template <typename T> template <typename T>
T consume_integral() T consume_integral()
@@ -81,7 +83,7 @@ public:
{ {
if (count == 0 || count > size_) if (count == 0 || count > size_)
return {}; return {};
const uint8_t *start = consume("consume_bytes", count); const uint8_t *_Nullable start = consume("consume_bytes", count);
if (!start) if (!start)
return {}; return {};
return std::vector<uint8_t>(start, start + count); return std::vector<uint8_t>(start, start + count);
@@ -92,20 +94,20 @@ public:
if (empty()) if (empty())
return {}; return {};
std::size_t count = size(); std::size_t count = size();
const uint8_t *start = consume("consume_remaining_bytes", count); const uint8_t *_Nonnull start = consume("consume_remaining_bytes", count);
return std::vector<uint8_t>(start, start + count); return std::vector<uint8_t>(start, start + count);
} }
std::size_t size() const { return size_; } std::size_t size() const { return size_; }
std::size_t pos() const { return data_ - base_; } std::size_t pos() const { return data_ - base_; }
const uint8_t *data() const { return data_; } const uint8_t *_Nonnull data() const { return data_; }
bool empty() const { return size_ == 0; } bool empty() const { return size_ == 0; }
const uint8_t *consume(const char *func, std::size_t count) const uint8_t *_Nullable consume(const char *_Nonnull func, std::size_t count)
{ {
if (count > size_) if (count > size_)
return nullptr; return nullptr;
const uint8_t *val = data_; const uint8_t *_Nonnull val = data_;
if (FUZZ_DEBUG) { if (FUZZ_DEBUG) {
if (count == 1) { if (count == 1) {
std::printf("consume@%zu(%s): %d (0x%02x)\n", pos(), func, val[0], val[0]); std::printf("consume@%zu(%s): %d (0x%02x)\n", pos(), func, val[0], val[0]);
@@ -170,7 +172,7 @@ struct Fuzz_Target_Selector<> {
}; };
template <Fuzz_Target... Args> template <Fuzz_Target... Args>
void fuzz_select_target(const uint8_t *data, std::size_t size) void fuzz_select_target(const uint8_t *_Nonnull data, std::size_t size)
{ {
Fuzz_Data input{data, size}; Fuzz_Data input{data, size};

View File

@@ -4,6 +4,11 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include "../../../toxcore/attributes.h"
// Forward declaration
struct Memory;
namespace tox::test { namespace tox::test {
/** /**
@@ -13,9 +18,14 @@ class MemorySystem {
public: public:
virtual ~MemorySystem(); virtual ~MemorySystem();
virtual void *malloc(size_t size) = 0; virtual void *_Nullable malloc(size_t size) = 0;
virtual void *realloc(void *ptr, size_t size) = 0; virtual void *_Nullable realloc(void *_Nullable ptr, size_t size) = 0;
virtual void free(void *ptr) = 0; virtual void free(void *_Nullable ptr) = 0;
/**
* @brief Returns C-compatible Memory struct.
*/
virtual struct Memory c_memory() = 0;
}; };
} // namespace tox::test } // namespace tox::test

View File

@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2026 The TokTok team.
*/
#ifndef C_TOXCORE_TESTING_SUPPORT_MPSC_QUEUE_H
#define C_TOXCORE_TESTING_SUPPORT_MPSC_QUEUE_H
#include <condition_variable>
#include <deque>
#include <mutex>
namespace tox::test {
/**
* @brief Multiple Producer, Single Consumer Queue.
*
* This queue implementation provides thread-safe access for multiple producers
* pushing items and a single consumer popping items. It uses a `std::mutex`
* and `std::condition_variable` for synchronization.
*
* @tparam T The type of elements stored in the queue.
*/
template <typename T>
class MpscQueue {
public:
MpscQueue() = default;
~MpscQueue() = default;
// Disable copy/move to prevent accidental sharing/slicing issues
MpscQueue(const MpscQueue &) = delete;
MpscQueue &operator=(const MpscQueue &) = delete;
/**
* @brief Pushes a value onto the queue.
* Thread-safe (Multiple Producers).
*/
void push(T value)
{
{
std::lock_guard<std::mutex> lock(mutex_);
queue_.push_back(std::move(value));
}
cv_.notify_one();
}
/**
* @brief Pops a value from the queue, blocking if empty.
* Thread-safe (Single Consumer).
*/
T pop()
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return !queue_.empty(); });
T value = std::move(queue_.front());
queue_.pop_front();
return value;
}
/**
* @brief Tries to pop a value from the queue without blocking.
* Thread-safe (Single Consumer).
*
* @param out Reference to store the popped value.
* @return true if a value was popped, false if the queue was empty.
*/
bool try_pop(T &out)
{
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.empty())
return false;
out = std::move(queue_.front());
queue_.pop_front();
return true;
}
private:
std::deque<T> queue_;
std::mutex mutex_;
std::condition_variable cv_;
};
} // namespace tox::test
#endif // C_TOXCORE_TESTING_SUPPORT_MPSC_QUEUE_H

View File

@@ -4,6 +4,8 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "../../../toxcore/attributes.h"
#include "../../../toxcore/net.h"
#include "../../../toxcore/network.h" #include "../../../toxcore/network.h"
namespace tox::test { namespace tox::test {
@@ -16,24 +18,35 @@ public:
virtual ~NetworkSystem(); virtual ~NetworkSystem();
virtual Socket socket(int domain, int type, int protocol) = 0; virtual Socket socket(int domain, int type, int protocol) = 0;
virtual int bind(Socket sock, const IP_Port *addr) = 0; virtual int bind(Socket sock, const IP_Port *_Nonnull addr) = 0;
virtual int close(Socket sock) = 0; virtual int close(Socket sock) = 0;
virtual int sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) = 0; virtual int sendto(
virtual int recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr) = 0; Socket sock, const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr)
= 0;
virtual int recvfrom(Socket sock, uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr)
= 0;
// TCP Support // TCP Support
virtual int listen(Socket sock, int backlog) = 0; virtual int listen(Socket sock, int backlog) = 0;
virtual Socket accept(Socket sock) = 0; virtual Socket accept(Socket sock) = 0;
virtual int connect(Socket sock, const IP_Port *addr) = 0; virtual int connect(Socket sock, const IP_Port *_Nonnull addr) = 0;
virtual int send(Socket sock, const uint8_t *buf, size_t len) = 0; virtual int send(Socket sock, const uint8_t *_Nonnull buf, size_t len) = 0;
virtual int recv(Socket sock, uint8_t *buf, size_t len) = 0; virtual int recv(Socket sock, uint8_t *_Nonnull buf, size_t len) = 0;
virtual int recvbuf(Socket sock) = 0; virtual int recvbuf(Socket sock) = 0;
// Auxiliary // Auxiliary
virtual int socket_nonblock(Socket sock, bool nonblock) = 0; virtual int socket_nonblock(Socket sock, bool nonblock) = 0;
virtual int getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen) = 0; virtual int getsockopt(
virtual int setsockopt(Socket sock, int level, int optname, const void *optval, size_t optlen) Socket sock, int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen)
= 0; = 0;
virtual int setsockopt(
Socket sock, int level, int optname, const void *_Nonnull optval, size_t optlen)
= 0;
/**
* @brief Returns C-compatible Network struct.
*/
virtual struct Network c_network() = 0;
}; };
/** /**

View File

@@ -4,6 +4,11 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "../../../toxcore/attributes.h"
// Forward declaration
struct Random;
namespace tox::test { namespace tox::test {
/** /**
@@ -14,7 +19,12 @@ public:
virtual ~RandomSystem(); virtual ~RandomSystem();
virtual uint32_t uniform(uint32_t upper_bound) = 0; virtual uint32_t uniform(uint32_t upper_bound) = 0;
virtual void bytes(uint8_t *out, size_t count) = 0; virtual void bytes(uint8_t *_Nonnull out, size_t count) = 0;
/**
* @brief Returns C-compatible Random struct.
*/
virtual struct Random c_random() = 0;
}; };
} // namespace tox::test } // namespace tox::test

View File

@@ -13,9 +13,10 @@
#include <sys/socket.h> #include <sys/socket.h>
#endif #endif
#include "../../../toxcore/tox_memory_impl.h" #include "../../../toxcore/attributes.h"
#include "../../../toxcore/mem.h"
#include "../../../toxcore/rng.h"
#include "../../../toxcore/tox_private.h" #include "../../../toxcore/tox_private.h"
#include "../../../toxcore/tox_random_impl.h"
#include "../doubles/fake_clock.hh" #include "../doubles/fake_clock.hh"
#include "../doubles/fake_memory.hh" #include "../doubles/fake_memory.hh"
#include "../doubles/fake_random.hh" #include "../doubles/fake_random.hh"
@@ -29,12 +30,12 @@ struct ScopedToxSystem {
std::unique_ptr<SimulatedNode> node; std::unique_ptr<SimulatedNode> node;
// Direct access to primary socket (for fuzzer injection) // Direct access to primary socket (for fuzzer injection)
FakeUdpSocket *endpoint; FakeUdpSocket *_Nullable endpoint;
// C structs // C structs
struct Network c_network; struct Network c_network;
struct Tox_Random c_random; struct Random c_random;
struct Tox_Memory c_memory; struct Memory c_memory;
// The main struct passed to tox_new // The main struct passed to tox_new
Tox_System system; Tox_System system;

View File

@@ -1,6 +1,8 @@
#ifndef C_TOXCORE_TESTING_SUPPORT_SIMULATION_H #ifndef C_TOXCORE_TESTING_SUPPORT_SIMULATION_H
#define C_TOXCORE_TESTING_SUPPORT_SIMULATION_H #define C_TOXCORE_TESTING_SUPPORT_SIMULATION_H
#include <atomic>
#include <condition_variable>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <vector> #include <vector>
@@ -14,10 +16,13 @@
#include <sys/socket.h> #include <sys/socket.h>
#endif #endif
#include <string>
#include "../../../toxcore/attributes.h"
#include "../../../toxcore/mem.h"
#include "../../../toxcore/rng.h"
#include "../../../toxcore/tox.h" #include "../../../toxcore/tox.h"
#include "../../../toxcore/tox_memory_impl.h"
#include "../../../toxcore/tox_private.h" #include "../../../toxcore/tox_private.h"
#include "../../../toxcore/tox_random_impl.h"
#include "../doubles/fake_clock.hh" #include "../doubles/fake_clock.hh"
#include "../doubles/fake_memory.hh" #include "../doubles/fake_memory.hh"
#include "../doubles/fake_network_stack.hh" #include "../doubles/fake_network_stack.hh"
@@ -29,12 +34,61 @@ namespace tox::test {
class SimulatedNode; class SimulatedNode;
struct LogMetadata {
Tox_Log_Level level;
const char *_Nonnull file;
uint32_t line;
const char *_Nonnull func;
const char *_Nonnull message;
uint32_t node_id;
};
using LogPredicate = std::function<bool(const LogMetadata &)>;
struct LogFilter {
LogPredicate pred;
LogFilter() = default;
explicit LogFilter(LogPredicate p)
: pred(std::move(p))
{
}
bool operator()(const LogMetadata &md) const { return !pred || pred(md); }
};
LogFilter operator&&(const LogFilter &lhs, const LogFilter &rhs);
LogFilter operator||(const LogFilter &lhs, const LogFilter &rhs);
LogFilter operator!(const LogFilter &target);
namespace log_filter {
LogFilter level(Tox_Log_Level min_level);
struct LevelPlaceholder {
LogFilter operator>(Tox_Log_Level rhs) const;
LogFilter operator>=(Tox_Log_Level rhs) const;
LogFilter operator<(Tox_Log_Level rhs) const;
LogFilter operator<=(Tox_Log_Level rhs) const;
LogFilter operator==(Tox_Log_Level rhs) const;
LogFilter operator!=(Tox_Log_Level rhs) const;
};
LevelPlaceholder level();
LogFilter file(std::string pattern);
LogFilter func(std::string pattern);
LogFilter message(std::string pattern);
LogFilter node(uint32_t id);
} // namespace log_filter
/** /**
* @brief The Simulation World. * @brief The Simulation World.
* Holds the Clock and the Universe. * Holds the Clock and the Universe.
*/ */
class Simulation { class Simulation {
public: public:
static constexpr uint32_t kDefaultTickIntervalMs = 50;
Simulation(); Simulation();
~Simulation(); ~Simulation();
@@ -42,9 +96,66 @@ public:
void advance_time(uint64_t ms); void advance_time(uint64_t ms);
void run_until(std::function<bool()> condition, uint64_t timeout_ms = 5000); void run_until(std::function<bool()> condition, uint64_t timeout_ms = 5000);
// Logging
void set_log_filter(LogFilter filter);
const LogFilter &log_filter() const { return log_filter_; }
// Synchronization Barrier
// These methods coordinate the lock-step execution of multiple Tox runners.
/**
* @brief Registers a new runner with the simulation barrier.
* @return The current generation ID of the simulation.
*/
uint64_t register_runner();
/**
* @brief Unregisters a runner from the simulation barrier.
*
* This ensures the simulation does not block waiting for a terminated runner.
*/
void unregister_runner();
using TickListenerId = int;
/**
* @brief Registers a callback to be invoked when a new simulation tick starts.
*
* @param listener The function to call with the new generation ID.
* @return An ID handle for unregistering the listener.
*/
TickListenerId register_tick_listener(std::function<void(uint64_t)> listener);
/**
* @brief Unregisters a tick listener.
*/
void unregister_tick_listener(TickListenerId id);
/**
* @brief Blocks until the simulation advances to the next tick.
*
* Called by runner threads to wait for the global clock to advance.
*
* @param last_gen The generation ID of the last processed tick.
* @param stop_token Atomic flag to signal termination while waiting.
* @param timeout_ms Maximum time to wait for the tick.
* @return The new generation ID, or `last_gen` on timeout/stop.
*/
uint64_t wait_for_tick(
uint64_t last_gen, const std::atomic<bool> &stop_token, uint64_t timeout_ms = 10);
/**
* @brief Signals that a runner has completed its work for the current tick.
*
* @param next_delay_ms The requested delay until the next tick (from `tox_iteration_interval`).
*/
void tick_complete(uint32_t next_delay_ms = kDefaultTickIntervalMs);
// Global Access // Global Access
FakeClock &clock() { return *clock_; } FakeClock &clock() { return *clock_; }
const FakeClock &clock() const { return *clock_; }
NetworkUniverse &net() { return *net_; } NetworkUniverse &net() { return *net_; }
const NetworkUniverse &net() const { return *net_; }
// Node Factory // Node Factory
std::unique_ptr<SimulatedNode> create_node(); std::unique_ptr<SimulatedNode> create_node();
@@ -52,7 +163,23 @@ public:
private: private:
std::unique_ptr<FakeClock> clock_; std::unique_ptr<FakeClock> clock_;
std::unique_ptr<NetworkUniverse> net_; std::unique_ptr<NetworkUniverse> net_;
LogFilter log_filter_;
uint32_t node_count_ = 0; uint32_t node_count_ = 0;
// Barrier State
std::mutex barrier_mutex_;
std::condition_variable barrier_cv_;
uint64_t current_generation_ = 0;
int registered_runners_ = 0;
std::atomic<int> active_runners_{0};
std::atomic<uint32_t> next_step_min_{kDefaultTickIntervalMs};
struct TickListener {
TickListenerId id;
std::function<void(uint64_t)> callback;
};
std::vector<TickListener> tick_listeners_;
TickListenerId next_listener_id_ = 0;
}; };
/** /**
@@ -79,19 +206,16 @@ public:
// Returns a configured Tox instance bound to this node's environment. // Returns a configured Tox instance bound to this node's environment.
// The user owns the Tox instance. // The user owns the Tox instance.
struct ToxDeleter { struct ToxDeleter {
void operator()(Tox *t) const { tox_kill(t); } void operator()(Tox *_Nonnull t) const { tox_kill(t); }
}; };
using ToxPtr = std::unique_ptr<Tox, ToxDeleter>; using ToxPtr = std::unique_ptr<Tox, ToxDeleter>;
ToxPtr create_tox(const Tox_Options *options = nullptr); ToxPtr create_tox(const Tox_Options *_Nullable options = nullptr);
// Helper to get C structs for manual injection Simulation &simulation() { return sim_; }
struct Network get_c_network() { return network_->get_c_network(); }
struct Tox_Random get_c_random() { return random_->get_c_random(); }
struct Tox_Memory get_c_memory() { return memory_->get_c_memory(); }
// For fuzzing compatibility (exposes first bound UDP socket as "endpoint") // For fuzzing compatibility (exposes first bound UDP socket as "endpoint")
FakeUdpSocket *get_primary_socket(); FakeUdpSocket *_Nullable get_primary_socket();
private: private:
Simulation &sim_; Simulation &sim_;
@@ -102,8 +226,8 @@ private:
// C-compatible views (must stay valid for the lifetime of Tox) // C-compatible views (must stay valid for the lifetime of Tox)
public: public:
struct Network c_network; struct Network c_network;
struct Tox_Random c_random; struct Random c_random;
struct Tox_Memory c_memory; struct Memory c_memory;
struct IP ip; struct IP ip;
}; };

View File

@@ -5,21 +5,25 @@
#ifndef C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H #ifndef C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H
#define C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H #define C_TOXCORE_TESTING_SUPPORT_TOX_NETWORK_H
#include <memory>
#include <utility>
#include <vector> #include <vector>
#include "../../../toxcore/attributes.h"
#include "simulation.hh" #include "simulation.hh"
#include "tox_runner.hh"
namespace tox::test { namespace tox::test {
struct ConnectedFriend { struct ConnectedFriend {
std::unique_ptr<SimulatedNode> node; std::unique_ptr<SimulatedNode> node;
SimulatedNode::ToxPtr tox; std::unique_ptr<ToxRunner> runner;
uint32_t friend_number; uint32_t friend_number;
ConnectedFriend(std::unique_ptr<SimulatedNode> node_in, SimulatedNode::ToxPtr tox_in, ConnectedFriend(std::unique_ptr<SimulatedNode> node_in, std::unique_ptr<ToxRunner> runner_in,
uint32_t friend_number_in) uint32_t friend_number_in)
: node(std::move(node_in)) : node(std::move(node_in))
, tox(std::move(tox_in)) , runner(std::move(runner_in))
, friend_number(friend_number_in) , friend_number(friend_number_in)
{ {
} }
@@ -43,8 +47,9 @@ struct ConnectedFriend {
* @param options Optional Tox_Options to use for the friend Tox instances. * @param options Optional Tox_Options to use for the friend Tox instances.
* @return A vector of ConnectedFriend structures, each representing a friend. * @return A vector of ConnectedFriend structures, each representing a friend.
*/ */
std::vector<ConnectedFriend> setup_connected_friends(Simulation &sim, Tox *main_tox, std::vector<ConnectedFriend> setup_connected_friends(Simulation &sim, Tox *_Nonnull main_tox,
SimulatedNode &main_node, int num_friends, const Tox_Options *options = nullptr); SimulatedNode &main_node, int num_friends, const Tox_Options *_Nullable options = nullptr,
bool verbose = false);
/** /**
* @brief Connects two existing Tox instances as friends. * @brief Connects two existing Tox instances as friends.
@@ -59,8 +64,8 @@ std::vector<ConnectedFriend> setup_connected_friends(Simulation &sim, Tox *main_
* @param tox2 The second Tox instance. * @param tox2 The second Tox instance.
* @return True if connected successfully, false otherwise. * @return True if connected successfully, false otherwise.
*/ */
bool connect_friends( bool connect_friends(Simulation &sim, SimulatedNode &node1, Tox *_Nonnull tox1,
Simulation &sim, SimulatedNode &node1, Tox *tox1, SimulatedNode &node2, Tox *tox2); SimulatedNode &node2, Tox *_Nonnull tox2);
/** /**
* @brief Sets up a group and has all friends join it. * @brief Sets up a group and has all friends join it.
@@ -75,7 +80,7 @@ bool connect_friends(
* @return The group number on the main Tox instance, or UINT32_MAX on failure. * @return The group number on the main Tox instance, or UINT32_MAX on failure.
*/ */
uint32_t setup_connected_group( uint32_t setup_connected_group(
Simulation &sim, Tox *main_tox, const std::vector<ConnectedFriend> &friends); Simulation &sim, Tox *_Nonnull main_tox, const std::vector<ConnectedFriend> &friends);
} // namespace tox::test } // namespace tox::test

View File

@@ -0,0 +1,137 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2026 The TokTok team.
*/
#ifndef C_TOXCORE_TESTING_SUPPORT_TOX_RUNNER_H
#define C_TOXCORE_TESTING_SUPPORT_TOX_RUNNER_H
#include <atomic>
#include <functional>
#include <future>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
#include "../../../toxcore/attributes.h"
#include "../../../toxcore/tox_events.h"
#include "mpsc_queue.hh"
#include "simulation.hh"
namespace tox::test {
class ToxRunner {
public:
explicit ToxRunner(SimulatedNode &node, const Tox_Options *_Nullable options = nullptr);
~ToxRunner();
ToxRunner(const ToxRunner &) = delete;
ToxRunner &operator=(const ToxRunner &) = delete;
struct ToxEventsDeleter {
void operator()(Tox_Events *_Nonnull e) const { tox_events_free(e); }
};
using ToxEventsPtr = std::unique_ptr<Tox_Events, ToxEventsDeleter>;
/**
* @brief Schedules a task for execution on the runner's thread.
*
* This method is thread-safe and non-blocking. The task is queued and will
* be executed during the runner's event loop cycle.
*
* @param task The function to execute, taking a raw Tox pointer.
*/
void execute(std::function<void(Tox *_Nonnull)> task);
/**
* @brief Executes a task on the runner's thread and waits for the result.
*
* This method blocks the calling thread until the task has been executed
* by the runner. It automatically handles return value propagation and
* exception safety (though exceptions are not currently propagated).
*
* @tparam Func The type of the callable object.
* @param func The callable to execute, taking a raw Tox pointer.
* @return The result of the callable execution.
*/
template <typename Func>
auto invoke(Func &&func) -> std::invoke_result_t<Func, Tox *_Nonnull>
{
using R = std::invoke_result_t<Func, Tox *_Nonnull>;
auto promise = std::make_shared<std::promise<R>>();
auto future = promise->get_future();
execute([p = promise, f = std::forward<Func>(func)](Tox *_Nonnull tox) {
if constexpr (std::is_void_v<R>) {
f(tox);
p->set_value();
} else {
p->set_value(f(tox));
}
});
return future.get();
}
/**
* @brief Retrieves all accumulated Tox event batches.
*
* Returns a vector of unique pointers to Tox_Events structures that have
* been collected by the runner since the last call. Ownership is transferred
* to the caller. This method is thread-safe.
*
* @return A vector of Tox_Events pointers.
*/
std::vector<ToxEventsPtr> poll_events();
/**
* @brief Accesses the underlying Tox instance directly.
*
* @warning Thread-Safety Violation: This method provides unsafe access to the
* Tox instance. It should ONLY be used when the runner thread is known to be
* idle (e.g., before the loop starts) or for accessing constant/read-only properties.
* For all other operations, use `execute` or `invoke`.
*/
Tox *_Nullable unsafe_tox() { return tox_.get(); }
/**
* @brief Temporarily stops the runner from participating in the simulation.
*
* Unregisters the runner and its tick listener from the simulation.
* While paused, the runner will not call tox_iterate.
*/
void pause();
/**
* @brief Resumes the runner's participation in the simulation.
*/
void resume();
/**
* @brief Returns true if the runner is currently active.
*/
bool is_active() const { return active_; }
private:
void loop();
SimulatedNode::ToxPtr tox_;
std::thread thread_;
std::atomic<bool> active_{true};
struct Message {
enum Type { Task, Tick, Stop } type;
std::function<void(Tox *_Nonnull)> task;
uint64_t generation = 0;
};
MpscQueue<Message> queue_;
MpscQueue<ToxEventsPtr> events_queue_;
Simulation::TickListenerId tick_listener_id_ = -1;
SimulatedNode &node_;
};
} // namespace tox::test
#endif // C_TOXCORE_TESTING_SUPPORT_TOX_RUNNER_H

View File

@@ -0,0 +1,76 @@
#include "public/simulation.hh"
#include <gtest/gtest.h>
namespace tox::test {
TEST(LogFilterTest, Operators)
{
LogMetadata md1{TOX_LOG_LEVEL_INFO, "file1.c", 10, "func1", "message1", 1};
LogMetadata md2{TOX_LOG_LEVEL_DEBUG, "file2.c", 20, "func2", "message2", 2};
auto f1 = log_filter::file("file1");
auto f2 = log_filter::level(TOX_LOG_LEVEL_INFO);
EXPECT_TRUE(f1(md1));
EXPECT_FALSE(f1(md2));
EXPECT_TRUE(f2(md1));
EXPECT_FALSE(f2(md2));
auto f_and = f1 && f2;
EXPECT_TRUE(f_and(md1));
EXPECT_FALSE(f_and(md2));
auto f_or = f1 || log_filter::file("file2");
EXPECT_TRUE(f_or(md1));
EXPECT_TRUE(f_or(md2));
auto f_not = !f1;
EXPECT_FALSE(f_not(md1));
EXPECT_TRUE(f_not(md2));
}
TEST(LogFilterTest, LevelComparison)
{
LogMetadata md_trace{TOX_LOG_LEVEL_TRACE, "file.c", 1, "func", "msg", 1};
LogMetadata md_debug{TOX_LOG_LEVEL_DEBUG, "file.c", 1, "func", "msg", 1};
LogMetadata md_info{TOX_LOG_LEVEL_INFO, "file.c", 1, "func", "msg", 1};
LogMetadata md_warn{TOX_LOG_LEVEL_WARNING, "file.c", 1, "func", "msg", 1};
LogMetadata md_error{TOX_LOG_LEVEL_ERROR, "file.c", 1, "func", "msg", 1};
// level() > DEBUG
auto f_gt = log_filter::level() > TOX_LOG_LEVEL_DEBUG;
EXPECT_FALSE(f_gt(md_trace));
EXPECT_FALSE(f_gt(md_debug));
EXPECT_TRUE(f_gt(md_info));
EXPECT_TRUE(f_gt(md_error));
// level() < INFO
auto f_lt = log_filter::level() < TOX_LOG_LEVEL_INFO;
EXPECT_TRUE(f_lt(md_trace));
EXPECT_TRUE(f_lt(md_debug));
EXPECT_FALSE(f_lt(md_info));
EXPECT_FALSE(f_lt(md_error));
// level() == WARNING
auto f_eq = log_filter::level() == TOX_LOG_LEVEL_WARNING;
EXPECT_FALSE(f_eq(md_info));
EXPECT_TRUE(f_eq(md_warn));
EXPECT_FALSE(f_eq(md_error));
}
TEST(LogFilterTest, SimulationIntegration)
{
Simulation sim;
sim.net().set_verbose(true);
sim.set_log_filter(log_filter::level(TOX_LOG_LEVEL_ERROR) && log_filter::node(1));
auto node = sim.create_node();
auto tox = node->create_tox();
SUCCEED();
}
} // namespace tox::test

View File

@@ -4,19 +4,20 @@
#include <iostream> #include <iostream>
#include <new> #include <new>
#include "../../../toxcore/tox_memory_impl.h" #include "../../../toxcore/mem.h"
namespace tox::test { namespace tox::test {
// --- Trampolines --- // --- Trampolines ---
static const Tox_Memory_Funcs kFakeMemoryVtable = { static const Memory_Funcs kFakeMemoryVtable = {
.malloc_callback .malloc_callback = [](void *_Nonnull obj,
= [](void *obj, uint32_t size) { return static_cast<FakeMemory *>(obj)->malloc(size); }, uint32_t size) { return static_cast<FakeMemory *>(obj)->malloc(size); },
.realloc_callback .realloc_callback
= [](void *obj, void *ptr, = [](void *_Nonnull obj, void *_Nullable ptr,
uint32_t size) { return static_cast<FakeMemory *>(obj)->realloc(ptr, size); }, uint32_t size) { return static_cast<FakeMemory *>(obj)->realloc(ptr, size); },
.dealloc_callback = [](void *obj, void *ptr) { static_cast<FakeMemory *>(obj)->free(ptr); }, .dealloc_callback
= [](void *_Nonnull obj, void *_Nullable ptr) { static_cast<FakeMemory *>(obj)->free(ptr); },
}; };
// --- Implementation --- // --- Implementation ---
@@ -24,7 +25,7 @@ static const Tox_Memory_Funcs kFakeMemoryVtable = {
FakeMemory::FakeMemory() = default; FakeMemory::FakeMemory() = default;
FakeMemory::~FakeMemory() = default; FakeMemory::~FakeMemory() = default;
void *FakeMemory::malloc(size_t size) void *_Nullable FakeMemory::malloc(size_t size)
{ {
bool fail = failure_injector_ && failure_injector_(size); bool fail = failure_injector_ && failure_injector_(size);
@@ -45,18 +46,12 @@ void *FakeMemory::malloc(size_t size)
header->size = size; header->size = size;
header->magic = kMagic; header->magic = kMagic;
current_allocation_ += size; on_allocation(size);
if (current_allocation_ > max_allocation_) {
max_allocation_ = current_allocation_;
}
void *res = header + 1; return header + 1;
// std::cerr << "[FakeMemory] malloc(" << size << ") -> " << res << " (header=" << header << ")"
// << std::endl;
return res;
} }
void *FakeMemory::realloc(void *ptr, size_t size) void *_Nullable FakeMemory::realloc(void *_Nullable ptr, size_t size)
{ {
if (!ptr) { if (!ptr) {
return malloc(size); return malloc(size);
@@ -82,7 +77,6 @@ void *FakeMemory::realloc(void *ptr, size_t size)
} }
if (fail) { if (fail) {
// If realloc fails, original block is left untouched.
return nullptr; return nullptr;
} }
@@ -92,22 +86,17 @@ void *FakeMemory::realloc(void *ptr, size_t size)
return nullptr; return nullptr;
} }
Header *header = static_cast<Header *>(new_ptr); on_deallocation(old_size);
current_allocation_ -= old_size; on_allocation(size);
current_allocation_ += size;
if (current_allocation_ > max_allocation_) {
max_allocation_ = current_allocation_;
}
Header *header = static_cast<Header *>(new_ptr);
header->size = size; header->size = size;
header->magic = kMagic; header->magic = kMagic;
void *res = header + 1;
// std::cerr << "[FakeMemory] realloc(" << ptr << ", " << size << ") -> " << res << " (header=" return header + 1;
// << header << ")" << std::endl;
return res;
} }
void FakeMemory::free(void *ptr) void FakeMemory::free(void *_Nullable ptr)
{ {
if (!ptr) { if (!ptr) {
return; return;
@@ -127,7 +116,7 @@ void FakeMemory::free(void *ptr)
} }
size_t size = header->size; size_t size = header->size;
current_allocation_ -= size; on_deallocation(size);
header->magic = kFreeMagic; // Mark as free header->magic = kFreeMagic; // Mark as free
std::free(header); std::free(header);
} }
@@ -139,6 +128,19 @@ void FakeMemory::set_failure_injector(FailureInjector injector)
void FakeMemory::set_observer(Observer observer) { observer_ = std::move(observer); } void FakeMemory::set_observer(Observer observer) { observer_ = std::move(observer); }
struct Tox_Memory FakeMemory::get_c_memory() { return Tox_Memory{&kFakeMemoryVtable, this}; } struct Memory FakeMemory::c_memory() { return Memory{&kFakeMemoryVtable, this}; }
size_t FakeMemory::current_allocation() const { return current_allocation_.load(); }
size_t FakeMemory::max_allocation() const { return max_allocation_.load(); }
void FakeMemory::on_allocation(size_t size)
{
size_t current = current_allocation_.fetch_add(size) + size;
size_t max = max_allocation_.load(std::memory_order_relaxed);
while (current > max && !max_allocation_.compare_exchange_weak(max, current)) { }
}
void FakeMemory::on_deallocation(size_t size) { current_allocation_.fetch_sub(size); }
} // namespace tox::test } // namespace tox::test

View File

@@ -2,18 +2,18 @@
#include <algorithm> #include <algorithm>
#include "../../../toxcore/tox_random_impl.h" #include "../../../toxcore/rng.h"
namespace tox::test { namespace tox::test {
// --- Trampolines for Tox_Random_Funcs --- // --- Trampolines for Random_Funcs ---
static const Tox_Random_Funcs kFakeRandomVtable = { static const Random_Funcs kFakeRandomVtable = {
.bytes_callback .bytes_callback
= [](void *obj, uint8_t *bytes, = [](void *_Nonnull obj, uint8_t *_Nonnull bytes,
uint32_t length) { static_cast<FakeRandom *>(obj)->bytes(bytes, length); }, uint32_t length) { static_cast<FakeRandom *>(obj)->bytes(bytes, length); },
.uniform_callback .uniform_callback
= [](void *obj, = [](void *_Nonnull obj,
uint32_t upper_bound) { return static_cast<FakeRandom *>(obj)->uniform(upper_bound); }, uint32_t upper_bound) { return static_cast<FakeRandom *>(obj)->uniform(upper_bound); },
}; };
@@ -44,7 +44,7 @@ uint32_t FakeRandom::uniform(uint32_t upper_bound)
return dist(rng_); return dist(rng_);
} }
void FakeRandom::bytes(uint8_t *out, size_t count) void FakeRandom::bytes(uint8_t *_Nonnull out, size_t count)
{ {
if (entropy_source_) { if (entropy_source_) {
entropy_source_(out, count); entropy_source_(out, count);
@@ -58,6 +58,6 @@ void FakeRandom::bytes(uint8_t *out, size_t count)
} }
} }
struct Tox_Random FakeRandom::get_c_random() { return Tox_Random{&kFakeRandomVtable, this}; } struct Random FakeRandom::c_random() { return Random{&kFakeRandomVtable, this}; }
} // namespace tox::test } // namespace tox::test

View File

@@ -56,7 +56,7 @@ void configure_fuzz_memory_source(FakeMemory &memory, Fuzz_Data &input)
void configure_fuzz_random_source(FakeRandom &random, Fuzz_Data &input) void configure_fuzz_random_source(FakeRandom &random, Fuzz_Data &input)
{ {
random.set_entropy_source([&input](uint8_t *out, size_t count) { random.set_entropy_source([&input](uint8_t *_Nonnull out, size_t count) {
// Initialize with zeros in case of underflow // Initialize with zeros in case of underflow
std::memset(out, 0, count); std::memset(out, 0, count);

View File

@@ -14,8 +14,8 @@ IP make_ip(uint32_t ipv4)
IP make_node_ip(uint32_t node_id) IP make_node_ip(uint32_t node_id)
{ {
// Use 10.x.y.z range: 10. (id >> 16) . (id >> 8) . (id & 0xFF) // Use 20.x.y.z range: 20. (id >> 16) . (id >> 8) . (id & 0xFF)
return make_ip(0x0A000000 | (node_id & 0x00FFFFFF)); return make_ip(0x14000000 | (node_id & 0x00FFFFFF));
} }
} // namespace tox::test } // namespace tox::test

View File

@@ -52,11 +52,11 @@ std::unique_ptr<ScopedToxSystem> SimulatedEnvironment::create_node(uint16_t port
scoped->endpoint = scoped->node->get_primary_socket(); scoped->endpoint = scoped->node->get_primary_socket();
// Use global Random and Memory for legacy compatibility. // Use global Random and Memory for legacy compatibility.
scoped->c_random = global_random_->get_c_random(); scoped->c_random = global_random_->c_random();
scoped->c_memory = global_memory_->get_c_memory(); scoped->c_memory = global_memory_->c_memory();
// Use Node's Network // Use Node's Network
scoped->c_network = scoped->node->get_c_network(); scoped->c_network = scoped->node->c_network;
// Setup System // Setup System
scoped->system.mem = &scoped->c_memory; scoped->system.mem = &scoped->c_memory;
@@ -64,7 +64,7 @@ std::unique_ptr<ScopedToxSystem> SimulatedEnvironment::create_node(uint16_t port
scoped->system.rng = &scoped->c_random; scoped->system.rng = &scoped->c_random;
scoped->system.mono_time_user_data = &sim_->clock(); scoped->system.mono_time_user_data = &sim_->clock();
scoped->system.mono_time_callback = [](void *user_data) -> uint64_t { scoped->system.mono_time_callback = [](void *_Nullable user_data) -> uint64_t {
return static_cast<FakeClock *>(user_data)->current_time_ms(); return static_cast<FakeClock *>(user_data)->current_time_ms();
}; };

View File

@@ -1,10 +1,96 @@
#include "../public/simulation.hh" #include "../public/simulation.hh"
#include <cassert> #include <cassert>
#include <chrono>
#include <iostream> #include <iostream>
#include <thread>
namespace tox::test { namespace tox::test {
// --- LogFilter ---
LogFilter operator&&(const LogFilter &lhs, const LogFilter &rhs)
{
return LogFilter([=](const LogMetadata &md) { return lhs(md) && rhs(md); });
}
LogFilter operator||(const LogFilter &lhs, const LogFilter &rhs)
{
return LogFilter([=](const LogMetadata &md) { return lhs(md) || rhs(md); });
}
LogFilter operator!(const LogFilter &target)
{
return LogFilter([=](const LogMetadata &md) { return !target(md); });
}
namespace log_filter {
LogFilter level(Tox_Log_Level min_level)
{
return LogFilter([=](const LogMetadata &md) { return md.level >= min_level; });
}
LevelPlaceholder level() { return {}; }
LogFilter LevelPlaceholder::operator>(Tox_Log_Level rhs) const
{
return LogFilter([=](const LogMetadata &md) { return md.level > rhs; });
}
LogFilter LevelPlaceholder::operator>=(Tox_Log_Level rhs) const
{
return LogFilter([=](const LogMetadata &md) { return md.level >= rhs; });
}
LogFilter LevelPlaceholder::operator<(Tox_Log_Level rhs) const
{
return LogFilter([=](const LogMetadata &md) { return md.level < rhs; });
}
LogFilter LevelPlaceholder::operator<=(Tox_Log_Level rhs) const
{
return LogFilter([=](const LogMetadata &md) { return md.level <= rhs; });
}
LogFilter LevelPlaceholder::operator==(Tox_Log_Level rhs) const
{
return LogFilter([=](const LogMetadata &md) { return md.level == rhs; });
}
LogFilter LevelPlaceholder::operator!=(Tox_Log_Level rhs) const
{
return LogFilter([=](const LogMetadata &md) { return md.level != rhs; });
}
LogFilter file(std::string pattern)
{
return LogFilter([p = std::move(pattern)](const LogMetadata &md) {
return std::string(md.file).find(p) != std::string::npos;
});
}
LogFilter func(std::string pattern)
{
return LogFilter([p = std::move(pattern)](const LogMetadata &md) {
return std::string(md.func).find(p) != std::string::npos;
});
}
LogFilter message(std::string pattern)
{
return LogFilter([p = std::move(pattern)](const LogMetadata &md) {
return std::string(md.message).find(p) != std::string::npos;
});
}
LogFilter node(uint32_t id)
{
return LogFilter([=](const LogMetadata &md) { return md.node_id == id; });
}
} // namespace log_filter
// --- Simulation --- // --- Simulation ---
Simulation::Simulation() Simulation::Simulation()
@@ -24,11 +110,125 @@ void Simulation::advance_time(uint64_t ms)
void Simulation::run_until(std::function<bool()> condition, uint64_t timeout_ms) void Simulation::run_until(std::function<bool()> condition, uint64_t timeout_ms)
{ {
uint64_t start_time = clock_->current_time_ms(); uint64_t start_time = clock_->current_time_ms();
while (!condition()) {
if (clock_->current_time_ms() - start_time > timeout_ms) { // Initial check
if (condition())
return;
while (true) {
if (clock_->current_time_ms() - start_time >= timeout_ms) {
break; break;
} }
advance_time(10); // 10ms ticks
// 1. Advance Global Time
// Determine the time step based on the minimum requested delay from all runners
// during the previous tick. We default to kDefaultTickIntervalMs if no specific request was
// made. The `exchange` operation resets the minimum accumulator for the current tick.
uint32_t step = next_step_min_.exchange(kDefaultTickIntervalMs);
advance_time(step);
// 2. Start Barrier (Signal Runners)
// Notify all registered runners that time has advanced and they should proceed
// with their next iteration.
{
std::lock_guard<std::mutex> lock(barrier_mutex_);
current_generation_++;
// Initialize the countdown of active runners for this tick.
active_runners_.store(registered_runners_);
for (const auto &l : tick_listeners_) {
l.callback(current_generation_);
}
}
barrier_cv_.notify_all();
// 3. End Barrier (Wait for Completion)
// Block until all active runners have reported completion via `tick_complete()`.
{
std::unique_lock<std::mutex> lock(barrier_mutex_);
// We use a lambda predicate to handle spurious wakeups.
// The wait finishes when `active_runners_` reaches zero.
barrier_cv_.wait(lock, [this] { return active_runners_.load() == 0; });
}
// 4. Check condition
if (condition())
return;
}
}
void Simulation::set_log_filter(LogFilter filter) { log_filter_ = std::move(filter); }
Simulation::TickListenerId Simulation::register_tick_listener(
std::function<void(uint64_t)> listener)
{
std::lock_guard<std::mutex> lock(barrier_mutex_);
TickListenerId id = next_listener_id_++;
tick_listeners_.push_back({id, std::move(listener)});
return id;
}
void Simulation::unregister_tick_listener(TickListenerId id)
{
std::lock_guard<std::mutex> lock(barrier_mutex_);
for (auto it = tick_listeners_.begin(); it != tick_listeners_.end(); ++it) {
if (it->id == id) {
tick_listeners_.erase(it);
break;
}
}
}
uint64_t Simulation::register_runner()
{
std::lock_guard<std::mutex> lock(barrier_mutex_);
registered_runners_++;
return current_generation_;
}
void Simulation::unregister_runner()
{
std::lock_guard<std::mutex> lock(barrier_mutex_);
registered_runners_--;
// If we are currently running a tick (active_runners > 0), we need to decrement it
// because this runner will not be calling tick_complete()
if (active_runners_.load() > 0) {
if (active_runners_.fetch_sub(1) == 1) {
barrier_cv_.notify_all();
}
}
}
uint64_t Simulation::wait_for_tick(
uint64_t last_gen, const std::atomic<bool> &stop_token, uint64_t timeout_ms)
{
std::unique_lock<std::mutex> lock(barrier_mutex_);
// Wait until generation increases (new tick started) OR we are stopped OR timeout
bool result = barrier_cv_.wait_for(lock, std::chrono::milliseconds(timeout_ms),
[&] { return current_generation_ > last_gen || stop_token; });
if (stop_token)
return last_gen;
if (!result)
return last_gen; // Timeout
return current_generation_;
}
void Simulation::tick_complete(uint32_t next_delay_ms)
{
// Atomic min reduction
uint32_t current = next_step_min_.load(std::memory_order_relaxed);
while (
next_delay_ms < current && !next_step_min_.compare_exchange_weak(current, next_delay_ms)) {
// If exchange failed, current was updated to actual value, so loop checks again
}
// We don't need the mutex to decrement the atomic
if (active_runners_.fetch_sub(1) == 1) {
// Last runner to finish: notify main thread
std::lock_guard<std::mutex> lock(barrier_mutex_);
barrier_cv_.notify_all();
} }
} }
@@ -51,9 +251,9 @@ SimulatedNode::SimulatedNode(Simulation &sim, uint32_t node_id)
, network_(std::make_unique<FakeNetworkStack>(sim.net(), make_node_ip(node_id))) , network_(std::make_unique<FakeNetworkStack>(sim.net(), make_node_ip(node_id)))
, random_(std::make_unique<FakeRandom>(12345 + node_id)) // Unique seed , random_(std::make_unique<FakeRandom>(12345 + node_id)) // Unique seed
, memory_(std::make_unique<FakeMemory>()) , memory_(std::make_unique<FakeMemory>())
, c_network(network_->get_c_network()) , c_network(network_->c_network())
, c_random(random_->get_c_random()) , c_random(random_->c_random())
, c_memory(memory_->get_c_memory()) , c_memory(memory_->c_memory())
, ip(make_node_ip(node_id)) , ip(make_node_ip(node_id))
{ {
} }
@@ -65,26 +265,49 @@ ClockSystem &SimulatedNode::clock() { return sim_.clock(); }
RandomSystem &SimulatedNode::random() { return *random_; } RandomSystem &SimulatedNode::random() { return *random_; }
MemorySystem &SimulatedNode::memory() { return *memory_; } MemorySystem &SimulatedNode::memory() { return *memory_; }
SimulatedNode::ToxPtr SimulatedNode::create_tox(const Tox_Options *options) SimulatedNode::ToxPtr SimulatedNode::create_tox(const Tox_Options *_Nullable options)
{ {
std::unique_ptr<Tox_Options, decltype(&tox_options_free)> default_options( std::unique_ptr<Tox_Options, decltype(&tox_options_free)> local_opts(
nullptr, tox_options_free); tox_options_new(nullptr), tox_options_free);
assert(local_opts != nullptr);
if (options == nullptr) { if (options != nullptr) {
default_options.reset(tox_options_new(nullptr)); tox_options_copy(local_opts.get(), options);
assert(default_options != nullptr); } else {
tox_options_set_ipv6_enabled(default_options.get(), false); tox_options_set_ipv6_enabled(local_opts.get(), false);
tox_options_set_start_port(default_options.get(), 33445); tox_options_set_start_port(local_opts.get(), 33445);
tox_options_set_end_port(default_options.get(), 55555); tox_options_set_end_port(local_opts.get(), 55555);
options = default_options.get();
} }
tox_options_set_log_callback(local_opts.get(),
[](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func,
const char *message, void *user_data) {
SimulatedNode *node = static_cast<SimulatedNode *>(user_data);
uint32_t ip4 = net_ntohl(node->ip.ip.v4.uint32);
LogMetadata md{level, file, line, func, message, ip4 & 0xFF};
const auto &filter = node->simulation().log_filter();
bool allow = false;
if (filter.pred) {
allow = filter(md);
} else {
allow = node->simulation().net().is_verbose() && level >= TOX_LOG_LEVEL_TRACE;
}
if (allow) {
std::cerr << "[Tox Log] [Node " << (ip4 & 0xFF) << "] " << file << ":" << line
<< " (" << func << "): " << message << std::endl;
}
});
tox_options_set_log_user_data(local_opts.get(), this);
Tox_Options_Testing opts_testing; Tox_Options_Testing opts_testing;
Tox_System system; Tox_System system;
system.ns = &c_network; system.ns = &c_network;
system.rng = &c_random; system.rng = &c_random;
system.mem = &c_memory; system.mem = &c_memory;
system.mono_time_callback = [](void *user_data) -> uint64_t { system.mono_time_callback = [](void *_Nullable user_data) -> uint64_t {
return static_cast<FakeClock *>(user_data)->current_time_ms(); return static_cast<FakeClock *>(user_data)->current_time_ms();
}; };
system.mono_time_user_data = &sim_.clock(); system.mono_time_user_data = &sim_.clock();
@@ -94,15 +317,17 @@ SimulatedNode::ToxPtr SimulatedNode::create_tox(const Tox_Options *options)
Tox_Err_New err; Tox_Err_New err;
Tox_Err_New_Testing err_testing; Tox_Err_New_Testing err_testing;
Tox *t = tox_new_testing(options, &err, &opts_testing, &err_testing); Tox *t = tox_new_testing(local_opts.get(), &err, &opts_testing, &err_testing);
if (!t) { if (!t) {
std::cerr << "tox_new_testing failed: " << err << " (testing err: " << err_testing << ")"
<< std::endl;
return nullptr; return nullptr;
} }
return ToxPtr(t); return ToxPtr(t);
} }
FakeUdpSocket *SimulatedNode::get_primary_socket() FakeUdpSocket *_Nullable SimulatedNode::get_primary_socket()
{ {
auto sockets = network_->get_bound_udp_sockets(); auto sockets = network_->get_bound_udp_sockets();
if (sockets.empty()) if (sockets.empty())

View File

@@ -5,17 +5,21 @@
#include "../public/tox_network.hh" #include "../public/tox_network.hh"
#include <cstring> #include <cstring>
#include <future>
#include <iostream> #include <iostream>
#include <vector>
#include "../../../toxcore/network.h" #include "../../../toxcore/network.h"
#include "../../../toxcore/tox.h" #include "../../../toxcore/tox.h"
#include "../../../toxcore/tox_events.h"
#include "../public/tox_runner.hh"
namespace tox::test { namespace tox::test {
ConnectedFriend::~ConnectedFriend() = default; ConnectedFriend::~ConnectedFriend() = default;
std::vector<ConnectedFriend> setup_connected_friends(Simulation &sim, Tox *main_tox, std::vector<ConnectedFriend> setup_connected_friends(Simulation &sim, Tox *_Nonnull main_tox,
SimulatedNode &main_node, int num_friends, const Tox_Options *options) SimulatedNode &main_node, int num_friends, const Tox_Options *_Nullable options, bool verbose)
{ {
std::vector<ConnectedFriend> friends; std::vector<ConnectedFriend> friends;
friends.reserve(num_friends); friends.reserve(num_friends);
@@ -42,108 +46,115 @@ std::vector<ConnectedFriend> setup_connected_friends(Simulation &sim, Tox *main_
for (int i = 0; i < num_friends; ++i) { for (int i = 0; i < num_friends; ++i) {
auto node = sim.create_node(); auto node = sim.create_node();
auto tox = node->create_tox(options); auto runner = std::make_unique<ToxRunner>(*node, options);
if (!tox) {
return {};
}
uint8_t friend_pk[TOX_PUBLIC_KEY_SIZE]; uint8_t friend_pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox.get(), friend_pk); runner->invoke([&](Tox *_Nonnull tox) { tox_self_get_public_key(tox, friend_pk); });
Tox_Err_Friend_Add err; Tox_Err_Friend_Add err;
uint32_t fn = tox_friend_add_norequest(main_tox, friend_pk, &err); uint32_t fn = tox_friend_add_norequest(main_tox, friend_pk, &err);
if (fn == UINT32_MAX || err != TOX_ERR_FRIEND_ADD_OK) { if (fn == UINT32_MAX || err != TOX_ERR_FRIEND_ADD_OK) {
return {}; return {};
} }
if (tox_friend_add_norequest(tox.get(), main_pk, &err) == UINT32_MAX
|| err != TOX_ERR_FRIEND_ADD_OK) {
return {};
}
// Bootstrap to the main node AND the PREVIOUS node in the chain // Execute add friend and bootstrap on runner
tox_bootstrap(tox.get(), main_ip_str, main_port, main_dht_id, nullptr); runner->execute([=](Tox *_Nonnull tox) {
if (i > 0) { tox_friend_add_norequest(tox, main_pk, nullptr);
tox_bootstrap(tox.get(), prev_ip_str, prev_port, prev_dht_id, nullptr); tox_bootstrap(tox, main_ip_str, main_port, main_dht_id, nullptr);
} if (i > 0) {
tox_bootstrap(tox, prev_ip_str, prev_port, prev_dht_id, nullptr);
}
});
// Retrieve previous node's DHT ID and update IP for the next iteration.
// We use invoke to safely fetch data from the runner thread.
runner->invoke([&](Tox *_Nonnull tox) { tox_self_get_dht_id(tox, prev_dht_id); });
// Update prev for next node
tox_self_get_dht_id(tox.get(), prev_dht_id);
ip_parse_addr(&node->ip, prev_ip_str, sizeof(prev_ip_str)); ip_parse_addr(&node->ip, prev_ip_str, sizeof(prev_ip_str));
FakeUdpSocket *node_socket = node->get_primary_socket(); FakeUdpSocket *_Nullable node_socket = node->get_primary_socket();
if (!node_socket) { if (!node_socket) {
return {}; return {};
} }
prev_port = node_socket->local_port(); prev_port = node_socket->local_port();
friends.push_back({std::move(node), std::move(tox), fn}); friends.push_back({std::move(node), std::move(runner), fn});
// Run simulation to let DHT stabilize // Run the simulation periodically to allow the DHT to stabilize incrementally
sim.run_until( // as we add nodes, rather than waiting until the end.
[&]() { if (friends.size() % 10 == 0) {
tox_iterate(main_tox, nullptr); sim.run_until([&]() { return false; }, 20);
for (auto &f : friends) { }
tox_iterate(f.tox.get(), nullptr);
}
return false;
},
200);
} }
// Optional: Bootstrap main_tox to the last node to complete the circle
if (!friends.empty()) { if (!friends.empty()) {
tox_bootstrap(main_tox, prev_ip_str, prev_port, prev_dht_id, nullptr); tox_bootstrap(main_tox, prev_ip_str, prev_port, prev_dht_id, nullptr);
} }
// Run simulation until all are connected std::vector<bool> friends_connected(friends.size(), false);
sim.run_until( sim.run_until(
[&]() { [&]() {
bool all_connected = true;
int connected_count = 0;
tox_iterate(main_tox, nullptr); tox_iterate(main_tox, nullptr);
for (auto &f : friends) {
tox_iterate(f.tox.get(), nullptr);
if (tox_friend_get_connection_status(main_tox, f.friend_number, nullptr)
!= TOX_CONNECTION_NONE
&& tox_friend_get_connection_status(f.tox.get(), 0, nullptr)
!= TOX_CONNECTION_NONE) {
connected_count++;
} else {
all_connected = false;
}
}
static uint64_t last_print = 0;
if (sim.clock().current_time_ms() - last_print > 1000) {
std::cerr << "[setup_connected_friends] Friends connected: " << connected_count
<< "/" << friends.size() << " (time: " << sim.clock().current_time_ms()
<< "ms)" << std::endl;
if (connected_count < static_cast<int>(friends.size()) // Check connection status
&& sim.clock().current_time_ms() > 10000) { int connected_count = 0;
for (size_t i = 0; i < friends.size(); ++i) {
auto s1 = tox_friend_get_connection_status( for (size_t i = 0; i < friends.size(); ++i) {
main_tox, friends[i].friend_number, nullptr); // Check if main sees friend
auto s2 bool main_sees_friend
= tox_friend_get_connection_status(friends[i].tox.get(), 0, nullptr); = tox_friend_get_connection_status(main_tox, friends[i].friend_number, nullptr)
if (s1 == TOX_CONNECTION_NONE || s2 == TOX_CONNECTION_NONE) { != TOX_CONNECTION_NONE;
std::cerr << " Friend " << i << " not connected (Main->F: " << s1
<< ", F->Main: " << s2 << ")" << std::endl; // Check if friend sees main by polling events from the runner
auto batches = friends[i].runner->poll_events();
for (const auto &batch : batches) {
size_t size = tox_events_get_size(batch.get());
for (size_t k = 0; k < size; ++k) {
const Tox_Event *e = tox_events_get(batch.get(), k);
if (tox_event_get_type(e) == TOX_EVENT_FRIEND_CONNECTION_STATUS) {
auto *ev = tox_event_get_friend_connection_status(e);
if (tox_event_friend_connection_status_get_connection_status(ev)
!= TOX_CONNECTION_NONE) {
friends_connected[i] = true;
} else {
friends_connected[i] = false;
}
} }
} }
} }
if (main_sees_friend && friends_connected[i]) {
connected_count++;
}
}
if (connected_count == static_cast<int>(friends.size())) {
return true;
}
static uint64_t last_print = 0;
if (verbose && sim.clock().current_time_ms() - last_print > 1000) {
std::cerr << "[setup_connected_friends] Friends connected: " << connected_count
<< "/" << friends.size() << " (time: " << sim.clock().current_time_ms()
<< "ms)" << std::endl;
last_print = sim.clock().current_time_ms(); last_print = sim.clock().current_time_ms();
} }
return all_connected;
return false;
}, },
300000); // 5 minutes simulation time for 100 nodes to converge 300000);
return friends; return friends;
} }
bool connect_friends( bool connect_friends(Simulation &sim, SimulatedNode &node1, Tox *_Nonnull tox1,
Simulation &sim, SimulatedNode &node1, Tox *tox1, SimulatedNode &node2, Tox *tox2) SimulatedNode &node2, Tox *_Nonnull tox2)
{ {
// This helper function assumes the Tox instances are running in the current thread
// (e.g., standard unit test) or that the caller is handling thread safety if they
// are part of a runner. It uses direct tox_iterate calls.
uint8_t pk1[TOX_PUBLIC_KEY_SIZE]; uint8_t pk1[TOX_PUBLIC_KEY_SIZE];
uint8_t pk2[TOX_PUBLIC_KEY_SIZE]; uint8_t pk2[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox1, pk1); tox_self_get_public_key(tox1, pk1);
@@ -199,7 +210,7 @@ bool connect_friends(
} }
uint32_t setup_connected_group( uint32_t setup_connected_group(
Simulation &sim, Tox *main_tox, const std::vector<ConnectedFriend> &friends) Simulation &sim, Tox *_Nonnull main_tox, const std::vector<ConnectedFriend> &friends)
{ {
struct NodeGroupState { struct NodeGroupState {
uint32_t peer_count = 0; uint32_t peer_count = 0;
@@ -207,9 +218,10 @@ uint32_t setup_connected_group(
}; };
NodeGroupState main_state; NodeGroupState main_state;
tox_callback_group_peer_join(main_tox, [](Tox *, uint32_t, uint32_t, void *user_data) { tox_callback_group_peer_join(
static_cast<NodeGroupState *>(user_data)->peer_count++; main_tox, [](Tox *_Nonnull, uint32_t, uint32_t, void *_Nullable user_data) {
}); static_cast<NodeGroupState *>(user_data)->peer_count++;
});
Tox_Err_Group_New err_new; Tox_Err_Group_New err_new;
main_state.group_number = tox_group_new(main_tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, main_state.group_number = tox_group_new(main_tox, TOX_GROUP_PRIVACY_STATE_PUBLIC,
@@ -217,52 +229,26 @@ uint32_t setup_connected_group(
&err_new); &err_new);
if (main_state.group_number == UINT32_MAX || err_new != TOX_ERR_GROUP_NEW_OK) { if (main_state.group_number == UINT32_MAX || err_new != TOX_ERR_GROUP_NEW_OK) {
std::cerr << "tox_group_new failed with error: " << err_new << std::endl;
return UINT32_MAX; return UINT32_MAX;
} }
std::vector<std::unique_ptr<NodeGroupState>> friend_states; // Friend states tracked via events
friend_states.reserve(friends.size()); std::vector<NodeGroupState> friend_states(friends.size());
for (size_t i = 0; i < friends.size(); ++i) { // Main tox sends invites; friends accept via events polled from their runners.
auto state = std::make_unique<NodeGroupState>();
tox_callback_group_peer_join(
friends[i].tox.get(), [](Tox *, uint32_t, uint32_t, void *user_data) {
static_cast<NodeGroupState *>(user_data)->peer_count++;
});
tox_callback_group_invite(friends[i].tox.get(),
[](Tox *tox, uint32_t friend_number, const uint8_t *invite_data,
size_t invite_data_length, const uint8_t *, size_t, void *user_data) {
NodeGroupState *ng_state = static_cast<NodeGroupState *>(user_data);
Tox_Err_Group_Invite_Accept err_accept;
ng_state->group_number
= tox_group_invite_accept(tox, friend_number, invite_data, invite_data_length,
reinterpret_cast<const uint8_t *>("peer"), 4, nullptr, 0, &err_accept);
if (ng_state->group_number == UINT32_MAX
|| err_accept != TOX_ERR_GROUP_INVITE_ACCEPT_OK) {
ng_state->group_number = UINT32_MAX;
}
});
friend_states.push_back(std::move(state));
}
// Run until all have joined and see everyone
bool success = false; bool success = false;
uint64_t last_print = 0;
size_t invites_sent = 0; size_t invites_sent = 0;
sim.run_until( sim.run_until(
[&]() { [&]() {
tox_iterate(main_tox, &main_state); tox_iterate(main_tox, &main_state);
// Throttle invites: keep max 5 pending // Throttle invites
size_t accepted_count = 0; size_t accepted_count = 0;
for (size_t k = 0; k < invites_sent; ++k) { for (const auto &fs : friend_states) {
if (friend_states[k]->group_number != UINT32_MAX) { if (fs.group_number != UINT32_MAX)
accepted_count++; accepted_count++;
}
} }
while (invites_sent < friends.size() && (invites_sent - accepted_count) < 5) { while (invites_sent < friends.size() && (invites_sent - accepted_count) < 5) {
@@ -271,58 +257,58 @@ uint32_t setup_connected_group(
friends[invites_sent].friend_number, &err_invite)) { friends[invites_sent].friend_number, &err_invite)) {
invites_sent++; invites_sent++;
} else { } else {
if (err_invite != TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND) { break;
std::cerr << "Invite failed for friend " << invites_sent << ": "
<< err_invite << std::endl;
}
break; // Stop trying to send for this tick if we failed
} }
} }
bool all_see_all = true; // Process friend events
if (main_state.peer_count < friends.size()) {
all_see_all = false;
}
for (size_t i = 0; i < friends.size(); ++i) { for (size_t i = 0; i < friends.size(); ++i) {
tox_iterate(friends[i].tox.get(), friend_states[i].get()); auto batches = friends[i].runner->poll_events();
if (friend_states[i]->group_number == UINT32_MAX for (const auto &batch : batches) {
|| friend_states[i]->peer_count < friends.size()) { size_t size = tox_events_get_size(batch.get());
all_see_all = false; for (size_t k = 0; k < size; ++k) {
} const Tox_Event *e = tox_events_get(batch.get(), k);
} Tox_Event_Type type = tox_event_get_type(e);
if ((sim.clock().current_time_ms() - last_print) % 5000 == 0) { if (type == TOX_EVENT_GROUP_INVITE) {
int joined = 0; auto *ev = tox_event_get_group_invite(e);
int fully_connected = 0; uint32_t friend_number = tox_event_group_invite_get_friend_number(ev);
if (main_state.group_number != UINT32_MAX) const uint8_t *data = tox_event_group_invite_get_invite_data(ev);
joined++; size_t len = tox_event_group_invite_get_invite_data_length(ev);
if (main_state.peer_count >= friends.size())
fully_connected++;
for (const auto &fs : friend_states) { // Accept invite on runner thread.
if (fs->group_number != UINT32_MAX) { // We must copy data because the event structure will be freed.
joined++; std::vector<uint8_t> invite_data(data, data + len);
if (fs->peer_count >= friends.size())
fully_connected++; friends[i].runner->execute([=](Tox *_Nonnull tox) {
Tox_Err_Group_Invite_Accept err;
tox_group_invite_accept(tox, friend_number, invite_data.data(),
invite_data.size(), reinterpret_cast<const uint8_t *>("peer"),
4, nullptr, 0, &err);
});
} else if (type == TOX_EVENT_GROUP_PEER_JOIN) {
friend_states[i].peer_count++;
} else if (type == TOX_EVENT_GROUP_SELF_JOIN) {
auto *ev = tox_event_get_group_self_join(e);
friend_states[i].group_number
= tox_event_group_self_join_get_group_number(ev);
}
} }
} }
std::cerr << "[setup_connected_group] Main peer count: " << main_state.peer_count
<< "/" << friends.size() << ", Nodes joined: " << joined << "/"
<< (friends.size() + 1) << ", fully connected: " << fully_connected << "/"
<< (friends.size() + 1) << " (time: " << sim.clock().current_time_ms()
<< "ms)" << std::endl;
last_print = sim.clock().current_time_ms();
} }
if (all_see_all) { if (main_state.peer_count < friends.size())
success = true; return false;
return true;
for (const auto &fs : friend_states) {
if (fs.group_number == UINT32_MAX || fs.peer_count < friends.size())
return false;
} }
return false;
success = true;
return true;
}, },
300000); // 5 minutes 300000);
return success ? main_state.group_number : UINT32_MAX; return success ? main_state.group_number : UINT32_MAX;
} }

View File

@@ -0,0 +1,125 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2026 The TokTok team.
*/
#include "../public/tox_runner.hh"
namespace tox::test {
ToxRunner::ToxRunner(SimulatedNode &node, const Tox_Options *_Nullable options)
: tox_(node.create_tox(options))
, node_(node)
{
if (tox_) {
tox_events_init(tox_.get());
}
node_.simulation().register_runner();
tick_listener_id_ = node_.simulation().register_tick_listener([this](uint64_t gen) {
Message msg;
msg.type = Message::Tick;
msg.generation = gen;
queue_.push(std::move(msg));
});
thread_ = std::thread([this] { loop(); });
}
ToxRunner::~ToxRunner()
{
// Unregister first to prevent new ticks and update simulation counters
node_.simulation().unregister_tick_listener(tick_listener_id_);
node_.simulation().unregister_runner();
Message msg;
msg.type = Message::Stop;
queue_.push(std::move(msg));
if (thread_.joinable()) {
thread_.join();
}
}
void ToxRunner::execute(std::function<void(Tox *_Nonnull)> task)
{
Message msg;
msg.type = Message::Task;
msg.task = std::move(task);
queue_.push(std::move(msg));
}
std::vector<ToxRunner::ToxEventsPtr> ToxRunner::poll_events()
{
std::vector<ToxEventsPtr> ret;
ToxEventsPtr ptr;
while (events_queue_.try_pop(ptr)) {
ret.push_back(std::move(ptr));
ptr = nullptr; // Reset ptr to avoid use-after-move warning, although try_pop overwrites
// it.
}
return ret;
}
void ToxRunner::pause()
{
if (!active_.exchange(false)) {
return;
}
node_.simulation().unregister_tick_listener(tick_listener_id_);
node_.simulation().unregister_runner();
tick_listener_id_ = -1;
}
void ToxRunner::resume()
{
if (active_.exchange(true)) {
return;
}
node_.simulation().register_runner();
tick_listener_id_ = node_.simulation().register_tick_listener([this](uint64_t gen) {
Message msg;
msg.type = Message::Tick;
msg.generation = gen;
queue_.push(std::move(msg));
});
}
void ToxRunner::loop()
{
while (true) {
Message msg = queue_.pop(); // Blocking wait
switch (msg.type) {
case Message::Stop:
return;
case Message::Task:
if (msg.task && tox_) {
msg.task(tox_.get());
}
break;
case Message::Tick: {
if (!tox_) {
node_.simulation().tick_complete();
break;
}
// Run Tox Events
Tox_Err_Events_Iterate err;
Tox_Events *events = tox_events_iterate(tox_.get(), false, &err);
if (events) {
events_queue_.push(ToxEventsPtr(events));
}
uint32_t interval = tox_iteration_interval(tox_.get());
node_.simulation().tick_complete(interval);
break;
}
}
}
}
} // namespace tox::test

View File

@@ -6,6 +6,13 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <atomic>
#include <iomanip>
#include <sstream>
#include "../../toxcore/attributes.h"
#include "../../toxcore/network.h"
namespace tox::test { namespace tox::test {
namespace { namespace {
@@ -22,6 +29,8 @@ namespace {
ASSERT_EQ(friends.size(), num_friends); ASSERT_EQ(friends.size(), num_friends);
// Verification of connection status is done inside setup_connected_friends now,
// but we can double check main_tox's view.
for (const auto &f : friends) { for (const auto &f : friends) {
EXPECT_NE(tox_friend_get_connection_status(main_tox.get(), f.friend_number, nullptr), EXPECT_NE(tox_friend_get_connection_status(main_tox.get(), f.friend_number, nullptr),
TOX_CONNECTION_NONE); TOX_CONNECTION_NONE);
@@ -29,25 +38,22 @@ namespace {
// Verify they can actually communicate // Verify they can actually communicate
struct Context { struct Context {
int count = 0; std::atomic<int> count{0};
} ctx; } ctx;
tox_callback_friend_message(main_tox.get(), tox_callback_friend_message(main_tox.get(),
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) { [](Tox *_Nonnull, uint32_t, Tox_Message_Type, const uint8_t *_Nonnull, size_t,
static_cast<Context *>(user_data)->count++; void *_Nullable user_data) { static_cast<Context *>(user_data)->count++; });
});
for (const auto &f : friends) { for (const auto &f : friends) {
const uint8_t msg[] = "hello"; f.runner->execute([](Tox *tox) {
tox_friend_send_message( const uint8_t msg[] = "hello";
f.tox.get(), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr); tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr);
});
} }
sim.run_until([&]() { sim.run_until([&]() {
tox_iterate(main_tox.get(), &ctx); tox_iterate(main_tox.get(), &ctx);
for (auto &f : friends) {
tox_iterate(f.tox.get(), nullptr);
}
return ctx.count == num_friends; return ctx.count == num_friends;
}); });
@@ -68,26 +74,23 @@ namespace {
ASSERT_EQ(friends.size(), num_friends); ASSERT_EQ(friends.size(), num_friends);
struct Context { struct Context {
int count = 0; std::atomic<int> count{0};
} ctx; } ctx;
tox_callback_friend_message(main_tox.get(), tox_callback_friend_message(main_tox.get(),
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) { [](Tox *_Nonnull, uint32_t, Tox_Message_Type, const uint8_t *_Nonnull, size_t,
static_cast<Context *>(user_data)->count++; void *_Nullable user_data) { static_cast<Context *>(user_data)->count++; });
});
for (const auto &f : friends) { for (const auto &f : friends) {
const uint8_t msg[] = "hello"; f.runner->execute([](Tox *tox) {
tox_friend_send_message( const uint8_t msg[] = "hello";
f.tox.get(), 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr); tox_friend_send_message(tox, 0, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), nullptr);
});
} }
sim.run_until( sim.run_until(
[&]() { [&]() {
tox_iterate(main_tox.get(), &ctx); tox_iterate(main_tox.get(), &ctx);
for (auto &f : friends) {
tox_iterate(f.tox.get(), nullptr);
}
return ctx.count == num_friends; return ctx.count == num_friends;
}, },
60000); 60000);
@@ -113,10 +116,11 @@ namespace {
EXPECT_NE(tox_friend_get_connection_status(tox2.get(), 0, nullptr), TOX_CONNECTION_NONE); EXPECT_NE(tox_friend_get_connection_status(tox2.get(), 0, nullptr), TOX_CONNECTION_NONE);
// Verify communication // Verify communication
bool received = false; std::atomic<bool> received{false};
tox_callback_friend_message(tox2.get(), tox_callback_friend_message(tox2.get(),
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) { [](Tox *_Nonnull, uint32_t, Tox_Message_Type, const uint8_t *_Nonnull, size_t,
*static_cast<bool *>(user_data) = true; void *_Nullable user_data) {
*static_cast<std::atomic<bool> *>(user_data) = true;
}); });
const uint8_t msg[] = "hello"; const uint8_t msg[] = "hello";
@@ -125,7 +129,7 @@ namespace {
sim.run_until([&]() { sim.run_until([&]() {
tox_iterate(tox1.get(), nullptr); tox_iterate(tox1.get(), nullptr);
tox_iterate(tox2.get(), &received); tox_iterate(tox2.get(), &received);
return received; return received.load();
}); });
EXPECT_TRUE(received); EXPECT_TRUE(received);
@@ -148,28 +152,27 @@ namespace {
// Verify we can send a group message // Verify we can send a group message
struct Context { struct Context {
int count = 0; std::atomic<int> count{0};
} ctx; } ctx;
tox_callback_group_message(main_tox.get(), tox_callback_group_message(main_tox.get(),
[](Tox *, uint32_t, uint32_t, Tox_Message_Type, const uint8_t *, size_t, uint32_t, [](Tox *_Nonnull, uint32_t, uint32_t, Tox_Message_Type, const uint8_t *_Nonnull, size_t,
void *user_data) { static_cast<Context *>(user_data)->count++; }); uint32_t,
void *_Nullable user_data) { static_cast<Context *>(user_data)->count++; });
for (const auto &f : friends) { for (const auto &f : friends) {
const uint8_t msg[] = "hello"; f.runner->execute([](Tox *tox) {
uint32_t f_gn = 0; // It should be 0 since it's the first group. const uint8_t msg[] = "hello";
Tox_Err_Group_Send_Message err_send; uint32_t f_gn = 0; // First group
tox_group_send_message( Tox_Err_Group_Send_Message err_send;
f.tox.get(), f_gn, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err_send); tox_group_send_message(
EXPECT_EQ(err_send, TOX_ERR_GROUP_SEND_MESSAGE_OK); tox, f_gn, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err_send);
});
} }
sim.run_until( sim.run_until(
[&]() { [&]() {
tox_iterate(main_tox.get(), &ctx); tox_iterate(main_tox.get(), &ctx);
for (auto &f : friends) {
tox_iterate(f.tox.get(), nullptr);
}
return ctx.count == num_friends; return ctx.count == num_friends;
}, },
10000); 10000);
@@ -193,28 +196,27 @@ namespace {
EXPECT_NE(group_number, UINT32_MAX); EXPECT_NE(group_number, UINT32_MAX);
struct Context { struct Context {
int count = 0; std::atomic<int> count{0};
} ctx; } ctx;
tox_callback_group_message(main_tox.get(), tox_callback_group_message(main_tox.get(),
[](Tox *, uint32_t, uint32_t, Tox_Message_Type, const uint8_t *, size_t, uint32_t, [](Tox *_Nonnull, uint32_t, uint32_t, Tox_Message_Type, const uint8_t *_Nonnull, size_t,
void *user_data) { static_cast<Context *>(user_data)->count++; }); uint32_t,
void *_Nullable user_data) { static_cast<Context *>(user_data)->count++; });
for (const auto &f : friends) { for (const auto &f : friends) {
const uint8_t msg[] = "hello"; f.runner->execute([](Tox *tox) {
uint32_t f_gn = 0; const uint8_t msg[] = "hello";
Tox_Err_Group_Send_Message err_send; uint32_t f_gn = 0;
tox_group_send_message( Tox_Err_Group_Send_Message err_send;
f.tox.get(), f_gn, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err_send); tox_group_send_message(
EXPECT_EQ(err_send, TOX_ERR_GROUP_SEND_MESSAGE_OK); tox, f_gn, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &err_send);
});
} }
sim.run_until( sim.run_until(
[&]() { [&]() {
tox_iterate(main_tox.get(), &ctx); tox_iterate(main_tox.get(), &ctx);
for (auto &f : friends) {
tox_iterate(f.tox.get(), nullptr);
}
return ctx.count == num_friends; return ctx.count == num_friends;
}, },
120000); 120000);
@@ -222,5 +224,180 @@ namespace {
EXPECT_EQ(ctx.count, num_friends); EXPECT_EQ(ctx.count, num_friends);
} }
TEST(ToxNetworkTest, TcpRelayChaining)
{
constexpr bool kDebug = false;
Simulation sim;
sim.net().set_verbose(false);
if (kDebug) {
using namespace log_filter;
sim.set_log_filter(level(TOX_LOG_LEVEL_DEBUG)
|| (level(TOX_LOG_LEVEL_TRACE)
&& (file("TCP") || file("onion.c") || func("dht_isconnected"))
&& !message("not sending repeated announce request")));
}
struct ToxOptionsDeleter {
void operator()(Tox_Options *opts) { tox_options_free(opts); }
};
std::unique_ptr<Tox_Options, ToxOptionsDeleter> opts(tox_options_new(nullptr));
tox_options_set_udp_enabled(opts.get(), false);
tox_options_set_ipv6_enabled(opts.get(), false);
tox_options_set_local_discovery_enabled(opts.get(), false);
auto create = [&](const char *name, uint16_t port, bool udp_enabled = false) {
tox_options_set_tcp_port(opts.get(), port);
if (udp_enabled) {
tox_options_set_start_port(opts.get(), port);
tox_options_set_end_port(opts.get(), port);
}
tox_options_set_udp_enabled(opts.get(), udp_enabled);
auto node = sim.create_node();
auto tox = node->create_tox(opts.get());
if (!tox) {
std::cerr << "Failed to create node " << name << " on port " << port << std::endl;
std::abort();
}
return std::make_pair(std::move(node), std::move(tox));
};
// Servers (Enable UDP for relays so they can talk to each other)
auto [nodeA, toxA] = create("A", 20001, true);
auto [nodeB, toxB] = create("B", 20002, true);
// Clients
auto [nodeC, toxC] = create("C", 0);
auto [nodeD, toxD] = create("D", 0);
auto [nodeE, toxE] = create("E", 0);
auto [nodeF, toxF] = create("F", 0);
auto get_info = [](SimulatedNode &node, Tox *tox) {
uint8_t pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox, pk);
uint8_t dht_id[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(tox, dht_id);
char ip[TOX_INET_ADDRSTRLEN];
ip_parse_addr(&node.ip, ip, sizeof(ip));
uint16_t port = tox_self_get_tcp_port(tox, nullptr);
if (kDebug) {
std::cout << "Node Info: IP=" << ip << " Port=" << port << std::endl;
auto to_hex = [](const uint8_t *data) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (int i = 0; i < TOX_PUBLIC_KEY_SIZE; ++i)
ss << std::setw(2) << static_cast<int>(data[i]);
return ss.str();
};
std::cout << "PK: " << to_hex(pk) << std::endl;
std::cout << "DHT ID: " << to_hex(dht_id) << std::endl;
}
return std::make_tuple(std::vector<uint8_t>(pk, pk + TOX_PUBLIC_KEY_SIZE),
std::vector<uint8_t>(dht_id, dht_id + TOX_PUBLIC_KEY_SIZE), std::string(ip), port);
};
auto [pkA, dhtIdA, ipA, portA] = get_info(*nodeA, toxA.get());
auto [pkB, dhtIdB, ipB, portB] = get_info(*nodeB, toxB.get());
// Helper to connect to a relay (bootstrap + add_tcp_relay)
auto connect_to_relay
= [](Tox *tox, const std::string &ip, uint16_t port, const std::vector<uint8_t> &pk,
const std::vector<uint8_t> &dht_id) {
Tox_Err_Bootstrap err_bs;
tox_bootstrap(tox, ip.c_str(), port, dht_id.data(), &err_bs);
if (err_bs != TOX_ERR_BOOTSTRAP_OK) {
std::cout << "tox_bootstrap failed with " << err_bs << " for " << ip << ":"
<< port << std::endl;
}
Tox_Err_Bootstrap err_relay;
// Use dht_id for TCP relay as well, as server uses DHT key
tox_add_tcp_relay(tox, ip.c_str(), port, dht_id.data(), &err_relay);
if (err_relay != TOX_ERR_BOOTSTRAP_OK) {
std::cout << "tox_add_tcp_relay failed with " << err_relay << " for " << ip
<< ":" << port << std::endl;
}
};
// Connect {C,D} -> A, {E,F} -> B
connect_to_relay(toxC.get(), ipA, portA, pkA, dhtIdA);
connect_to_relay(toxD.get(), ipA, portA, pkA, dhtIdA);
connect_to_relay(toxE.get(), ipB, portB, pkB, dhtIdB);
connect_to_relay(toxF.get(), ipB, portB, pkB, dhtIdB);
// B -> A (Connect the two TCP relays, but only one initial link)
connect_to_relay(toxB.get(), ipA, portA, pkA, dhtIdA);
// Connect C and F
uint8_t pkF[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(toxF.get(), pkF);
uint8_t pkC[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(toxC.get(), pkC);
Tox_Err_Friend_Add err;
const uint32_t fC = tox_friend_add_norequest(toxC.get(), pkF, &err);
ASSERT_EQ(err, TOX_ERR_FRIEND_ADD_OK);
const uint32_t fF = tox_friend_add_norequest(toxF.get(), pkC, &err);
ASSERT_EQ(err, TOX_ERR_FRIEND_ADD_OK);
struct Context {
bool received = false;
} ctx;
tox_callback_friend_message(toxF.get(),
[](Tox *, uint32_t, Tox_Message_Type, const uint8_t *, size_t, void *user_data) {
static_cast<Context *>(user_data)->received = true;
});
bool sent = false;
sim.run_until(
[&]() {
tox_iterate(toxA.get(), nullptr);
tox_iterate(toxB.get(), nullptr);
tox_iterate(toxC.get(), nullptr);
tox_iterate(toxD.get(), nullptr);
tox_iterate(toxE.get(), nullptr);
tox_iterate(toxF.get(), &ctx);
Tox_Connection statusC = tox_friend_get_connection_status(toxC.get(), fC, nullptr);
Tox_Connection statusF = tox_friend_get_connection_status(toxF.get(), fF, nullptr);
if (kDebug) {
static int loop_counter = 0;
if (loop_counter++ % 100 == 0) {
std::cout << "Conn Status: "
<< "A=" << tox_self_get_connection_status(toxA.get()) << " "
<< "B=" << tox_self_get_connection_status(toxB.get()) << " "
<< "C=" << tox_self_get_connection_status(toxC.get()) << " "
<< "D=" << tox_self_get_connection_status(toxC.get()) << " "
<< "E=" << tox_self_get_connection_status(toxC.get()) << " "
<< "F=" << tox_self_get_connection_status(toxF.get()) << " "
<< " Friend Status C->F: " << statusC << ", F->C: " << statusF
<< std::endl;
}
}
if (!sent && statusC != TOX_CONNECTION_NONE) {
const uint8_t msg[] = "hello";
Tox_Err_Friend_Send_Message send_err;
tox_friend_send_message(
toxC.get(), fC, TOX_MESSAGE_TYPE_NORMAL, msg, sizeof(msg), &send_err);
if (kDebug) {
std::cout << "Message sent from C to F, err=" << send_err << std::endl;
}
sent = true;
}
return ctx.received;
},
120000);
EXPECT_TRUE(ctx.received);
}
} // namespace } // namespace
} // namespace tox::test } // namespace tox::test

View File

@@ -17,7 +17,10 @@ cc_library(
name = "ring_buffer", name = "ring_buffer",
srcs = ["ring_buffer.c"], srcs = ["ring_buffer.c"],
hdrs = ["ring_buffer.h"], hdrs = ["ring_buffer.h"],
deps = ["//c-toxcore/toxcore:ccompat"], deps = [
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:ccompat",
],
) )
cc_test( cc_test(
@@ -26,6 +29,7 @@ cc_test(
srcs = ["ring_buffer_test.cc"], srcs = ["ring_buffer_test.cc"],
deps = [ deps = [
":ring_buffer", ":ring_buffer",
"//c-toxcore/toxcore:attributes",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
], ],
@@ -63,6 +67,7 @@ cc_test(
srcs = ["rtp_test.cc"], srcs = ["rtp_test.cc"],
deps = [ deps = [
":rtp", ":rtp",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:net_crypto", "//c-toxcore/toxcore:net_crypto",
@@ -80,6 +85,7 @@ cc_fuzz_test(
deps = [ deps = [
":rtp", ":rtp",
"//c-toxcore/testing/support", "//c-toxcore/testing/support",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
@@ -106,8 +112,10 @@ cc_test(
srcs = ["bwcontroller_test.cc"], srcs = ["bwcontroller_test.cc"],
deps = [ deps = [
":bwcontroller", ":bwcontroller",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
@@ -139,6 +147,7 @@ cc_library(
":audio", ":audio",
":rtp", ":rtp",
":video", ":video",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
@@ -156,6 +165,7 @@ cc_test(
":rtp", ":rtp",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
@@ -187,6 +197,7 @@ cc_test(
":video", ":video",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",
@@ -201,6 +212,7 @@ cc_binary(
":av_test_support", ":av_test_support",
":rtp", ":rtp",
":video", ":video",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
@@ -216,8 +228,10 @@ cc_binary(
":audio", ":audio",
":av_test_support", ":av_test_support",
":rtp", ":rtp",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
"@benchmark", "@benchmark",
], ],
@@ -230,6 +244,7 @@ cc_binary(
deps = [ deps = [
":av_test_support", ":av_test_support",
":rtp", ":rtp",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger", "//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time", "//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
@@ -255,6 +270,8 @@ cc_test(
srcs = ["msi_test.cc"], srcs = ["msi_test.cc"],
deps = [ deps = [
":msi", ":msi",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:os_memory", "//c-toxcore/toxcore:os_memory",
"@com_google_googletest//:gtest", "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest_main",

View File

@@ -41,6 +41,8 @@ struct ACSession {
pthread_mutex_t queue_mutex[1]; pthread_mutex_t queue_mutex[1];
int16_t *decode_buffer;
uint32_t friend_number; uint32_t friend_number;
/* Audio frame receive callback */ /* Audio frame receive callback */
ac_audio_receive_frame_cb *acb; ac_audio_receive_frame_cb *acb;
@@ -96,6 +98,13 @@ ACSession *ac_new(Mono_Time *mono_time, const Logger *log, uint32_t friend_numbe
ac->mono_time = mono_time; ac->mono_time = mono_time;
ac->log = log; ac->log = log;
ac->decode_buffer = (int16_t *)malloc(AUDIO_MAX_BUFFER_SIZE_PCM16 * AUDIO_MAX_CHANNEL_COUNT * sizeof(int16_t));
if (ac->decode_buffer == nullptr) {
LOGGER_ERROR(log, "Failed to allocate memory for audio buffer");
goto DECODER_CLEANUP;
}
/* Initialize encoders with default values */ /* Initialize encoders with default values */
ac->encoder = create_audio_encoder(log, AUDIO_START_BITRATE, AUDIO_START_SAMPLE_RATE, AUDIO_START_CHANNEL_COUNT); ac->encoder = create_audio_encoder(log, AUDIO_START_BITRATE, AUDIO_START_SAMPLE_RATE, AUDIO_START_CHANNEL_COUNT);
@@ -124,6 +133,7 @@ ACSession *ac_new(Mono_Time *mono_time, const Logger *log, uint32_t friend_numbe
return ac; return ac;
DECODER_CLEANUP: DECODER_CLEANUP:
free(ac->decode_buffer);
opus_decoder_destroy(ac->decoder); opus_decoder_destroy(ac->decoder);
jbuf_free((struct JitterBuffer *)ac->j_buf); jbuf_free((struct JitterBuffer *)ac->j_buf);
BASE_CLEANUP: BASE_CLEANUP:
@@ -141,6 +151,7 @@ void ac_kill(ACSession *ac)
opus_encoder_destroy(ac->encoder); opus_encoder_destroy(ac->encoder);
opus_decoder_destroy(ac->decoder); opus_decoder_destroy(ac->decoder);
jbuf_free((struct JitterBuffer *)ac->j_buf); jbuf_free((struct JitterBuffer *)ac->j_buf);
free(ac->decode_buffer);
pthread_mutex_destroy(ac->queue_mutex); pthread_mutex_destroy(ac->queue_mutex);
@@ -156,20 +167,12 @@ void ac_iterate(ACSession *ac)
/* TODO: fix this and jitter buffering */ /* TODO: fix this and jitter buffering */
/* Enough space for the maximum frame size (120 ms 48 KHz stereo audio) */
int16_t *temp_audio_buffer = (int16_t *)malloc(AUDIO_MAX_BUFFER_SIZE_PCM16 * AUDIO_MAX_CHANNEL_COUNT * sizeof(int16_t));
if (temp_audio_buffer == nullptr) {
LOGGER_ERROR(ac->log, "Failed to allocate memory for audio buffer");
return;
}
int rc = 0; int rc = 0;
pthread_mutex_lock(ac->queue_mutex); pthread_mutex_lock(ac->queue_mutex);
struct JitterBuffer *const j_buf = (struct JitterBuffer *)ac->j_buf;
while (true) { while (true) {
struct JitterBuffer *const j_buf = (struct JitterBuffer *)ac->j_buf;
struct RTPMessage *msg = jbuf_read(j_buf, &rc); struct RTPMessage *msg = jbuf_read(j_buf, &rc);
if (msg == nullptr && rc != 2) { if (msg == nullptr && rc != 2) {
@@ -190,7 +193,7 @@ void ac_iterate(ACSession *ac)
LOGGER_WARNING(ac->log, "Invalid PLC parameters: sr %u, dur %u", sampling_rate, frame_duration); LOGGER_WARNING(ac->log, "Invalid PLC parameters: sr %u, dur %u", sampling_rate, frame_duration);
} else { } else {
const int fs = (sampling_rate * frame_duration) / 1000; const int fs = (sampling_rate * frame_duration) / 1000;
rc = opus_decode(ac->decoder, nullptr, 0, temp_audio_buffer, fs, 1); rc = opus_decode(ac->decoder, nullptr, 0, ac->decode_buffer, fs, 1);
} }
} else { } else {
const uint8_t *msg_data = rtp_message_data(msg); const uint8_t *msg_data = rtp_message_data(msg);
@@ -240,7 +243,7 @@ void ac_iterate(ACSession *ac)
* max_size is the max duration of the frame in samples (per channel) that can fit * max_size is the max duration of the frame in samples (per channel) that can fit
* into the decoded_frame array * into the decoded_frame array
*/ */
rc = opus_decode(ac->decoder, msg_data + 4, msg_length - 4, temp_audio_buffer, AUDIO_MAX_BUFFER_SIZE_PCM16, 0); rc = opus_decode(ac->decoder, msg_data + 4, msg_length - 4, ac->decode_buffer, AUDIO_MAX_BUFFER_SIZE_PCM16, 0);
free(msg); free(msg);
} }
@@ -249,7 +252,7 @@ void ac_iterate(ACSession *ac)
} else if (ac->acb != nullptr && ac->lp_sampling_rate != 0) { } else if (ac->acb != nullptr && ac->lp_sampling_rate != 0) {
ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate; ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate;
ac->acb(ac->friend_number, temp_audio_buffer, (size_t)rc, ac->lp_channel_count, ac->acb(ac->friend_number, ac->decode_buffer, (size_t)rc, ac->lp_channel_count,
ac->lp_sampling_rate, ac->user_data); ac->lp_sampling_rate, ac->user_data);
} }
@@ -257,8 +260,6 @@ void ac_iterate(ACSession *ac)
} }
pthread_mutex_unlock(ac->queue_mutex); pthread_mutex_unlock(ac->queue_mutex);
free(temp_audio_buffer);
} }
int ac_queue_message(const Mono_Time *mono_time, void *cs, struct RTPMessage *msg) int ac_queue_message(const Mono_Time *mono_time, void *cs, struct RTPMessage *msg)

View File

@@ -5,9 +5,12 @@
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <cmath> #include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h" #include "../toxcore/logger.h"
#include "../toxcore/mono_time.h" #include "../toxcore/mono_time.h"
#include "../toxcore/network.h" #include "../toxcore/network.h"
@@ -22,15 +25,15 @@ class AudioBench : public benchmark::Fixture {
public: public:
void SetUp(const ::benchmark::State &state) override void SetUp(const ::benchmark::State &state) override
{ {
const Memory *mem = os_memory(); const Memory *_Nonnull mem = os_memory();
log = logger_new(mem); log = logger_new(mem);
tm.t = 1000; tm.t = 1000;
mono_time = mono_time_new(mem, mock_time_cb, &tm); mono_time = mono_time_new(mem, mock_time_cb, &tm);
ac = ac_new(mono_time, log, 123, nullptr, nullptr); ac = ac_new(mono_time, log, 123, nullptr, nullptr);
sampling_rate = static_cast<uint32_t>(state.range(0)); sampling_rate = static_cast<std::uint32_t>(state.range(0));
channels = static_cast<uint8_t>(state.range(1)); channels = static_cast<std::uint8_t>(state.range(1));
uint32_t bitrate = (channels == 1) ? 32000 : 64000; std::uint32_t bitrate = (channels == 1) ? 32000 : 64000;
ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels); ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels);
@@ -61,24 +64,24 @@ public:
} }
} }
Logger *log = nullptr; Logger *_Nullable log = nullptr;
Mono_Time *mono_time = nullptr; Mono_Time *_Nullable mono_time = nullptr;
MockTime tm; MockTime tm;
ACSession *ac = nullptr; ACSession *_Nullable ac = nullptr;
RtpMock rtp_mock; RtpMock rtp_mock;
uint32_t sampling_rate = 0; std::uint32_t sampling_rate = 0;
uint8_t channels = 0; std::uint8_t channels = 0;
size_t sample_count = 0; std::size_t sample_count = 0;
std::vector<int16_t> pcm; std::vector<std::int16_t> pcm;
}; };
// Benchmark encoding a sequence of silent audio frames. // Benchmark encoding a sequence of silent audio frames.
BENCHMARK_DEFINE_F(AudioBench, EncodeSilentSequence)(benchmark::State &state) BENCHMARK_DEFINE_F(AudioBench, EncodeSilentSequence)(benchmark::State &state)
{ {
std::vector<int16_t> silent_pcm(sample_count * channels); std::vector<std::int16_t> silent_pcm(sample_count * channels);
fill_silent_frame(channels, sample_count, silent_pcm); fill_silent_frame(channels, sample_count, silent_pcm);
std::vector<uint8_t> encoded(2000); std::vector<std::uint8_t> encoded(2000);
for (auto _ : state) { for (auto _ : state) {
int encoded_size int encoded_size
@@ -97,13 +100,13 @@ BENCHMARK_DEFINE_F(AudioBench, EncodeSequence)(benchmark::State &state)
{ {
int frame_index = 0; int frame_index = 0;
const int num_prefilled = 50; const int num_prefilled = 50;
std::vector<std::vector<int16_t>> pcms( std::vector<std::vector<std::int16_t>> pcms(
num_prefilled, std::vector<int16_t>(sample_count * channels)); num_prefilled, std::vector<std::int16_t>(sample_count * channels));
for (int i = 0; i < num_prefilled; ++i) { for (int i = 0; i < num_prefilled; ++i) {
fill_audio_frame(sampling_rate, channels, i, sample_count, pcms[i]); fill_audio_frame(sampling_rate, channels, i, sample_count, pcms[i]);
} }
std::vector<uint8_t> encoded(2000); std::vector<std::uint8_t> encoded(2000);
for (auto _ : state) { for (auto _ : state) {
int idx = frame_index % num_prefilled; int idx = frame_index % num_prefilled;
@@ -125,16 +128,16 @@ BENCHMARK_REGISTER_F(AudioBench, EncodeSequence)
BENCHMARK_DEFINE_F(AudioBench, DecodeSequence)(benchmark::State &state) BENCHMARK_DEFINE_F(AudioBench, DecodeSequence)(benchmark::State &state)
{ {
const int num_frames = 50; const int num_frames = 50;
std::vector<std::vector<uint8_t>> encoded_frames(num_frames); std::vector<std::vector<std::uint8_t>> encoded_frames(num_frames);
// Pre-encode // Pre-encode
std::vector<uint8_t> encoded_tmp(2000); std::vector<std::uint8_t> encoded_tmp(2000);
for (int i = 0; i < num_frames; ++i) { for (int i = 0; i < num_frames; ++i) {
fill_audio_frame(sampling_rate, channels, i, sample_count, pcm); fill_audio_frame(sampling_rate, channels, i, sample_count, pcm);
int size = ac_encode(ac, pcm.data(), sample_count, encoded_tmp.data(), encoded_tmp.size()); int size = ac_encode(ac, pcm.data(), sample_count, encoded_tmp.data(), encoded_tmp.size());
encoded_frames[i].resize(4 + size); encoded_frames[i].resize(4 + size);
uint32_t net_sr = net_htonl(sampling_rate); std::uint32_t net_sr = net_htonl(sampling_rate);
std::memcpy(encoded_frames[i].data(), &net_sr, 4); std::memcpy(encoded_frames[i].data(), &net_sr, 4);
std::memcpy(encoded_frames[i].data() + 4, encoded_tmp.data(), size); std::memcpy(encoded_frames[i].data() + 4, encoded_tmp.data(), size);
} }
@@ -143,7 +146,7 @@ BENCHMARK_DEFINE_F(AudioBench, DecodeSequence)(benchmark::State &state)
for (auto _ : state) { for (auto _ : state) {
int idx = frame_index % num_frames; int idx = frame_index % num_frames;
rtp_send_data(log, rtp_mock.recv_session, encoded_frames[idx].data(), rtp_send_data(log, rtp_mock.recv_session, encoded_frames[idx].data(),
static_cast<uint32_t>(encoded_frames[idx].size()), false); static_cast<std::uint32_t>(encoded_frames[idx].size()), false);
ac_iterate(ac); ac_iterate(ac);
frame_index++; frame_index++;
} }
@@ -161,26 +164,26 @@ BENCHMARK_DEFINE_F(AudioBench, FullSequence)(benchmark::State &state)
{ {
int frame_index = 0; int frame_index = 0;
const int num_prefilled = 50; const int num_prefilled = 50;
std::vector<std::vector<int16_t>> pcms( std::vector<std::vector<std::int16_t>> pcms(
num_prefilled, std::vector<int16_t>(sample_count * channels)); num_prefilled, std::vector<std::int16_t>(sample_count * channels));
for (int i = 0; i < num_prefilled; ++i) { for (int i = 0; i < num_prefilled; ++i) {
fill_audio_frame(sampling_rate, channels, i, sample_count, pcms[i]); fill_audio_frame(sampling_rate, channels, i, sample_count, pcms[i]);
} }
std::vector<uint8_t> encoded_tmp(2000); std::vector<std::uint8_t> encoded_tmp(2000);
for (auto _ : state) { for (auto _ : state) {
int idx = frame_index % num_prefilled; int idx = frame_index % num_prefilled;
int size int size
= ac_encode(ac, pcms[idx].data(), sample_count, encoded_tmp.data(), encoded_tmp.size()); = ac_encode(ac, pcms[idx].data(), sample_count, encoded_tmp.data(), encoded_tmp.size());
std::vector<uint8_t> payload(4 + size); std::vector<std::uint8_t> payload(4 + size);
uint32_t net_sr = net_htonl(sampling_rate); std::uint32_t net_sr = net_htonl(sampling_rate);
std::memcpy(payload.data(), &net_sr, 4); std::memcpy(payload.data(), &net_sr, 4);
std::memcpy(payload.data() + 4, encoded_tmp.data(), size); std::memcpy(payload.data() + 4, encoded_tmp.data(), size);
rtp_send_data(log, rtp_mock.recv_session, payload.data(), rtp_send_data(log, rtp_mock.recv_session, payload.data(),
static_cast<uint32_t>(payload.size()), false); static_cast<std::uint32_t>(payload.size()), false);
ac_iterate(ac); ac_iterate(ac);
frame_index++; frame_index++;

View File

@@ -4,6 +4,11 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector> #include <vector>
#include "../toxcore/logger.h" #include "../toxcore/logger.h"
@@ -38,31 +43,31 @@ TEST_F(AudioTest, EncodeDecodeLoop)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint32_t sampling_rate = 48000; std::uint32_t sampling_rate = 48000;
uint8_t channels = 1; std::uint8_t channels = 1;
size_t sample_count = 960; // 20ms at 48kHz std::size_t sample_count = 960; // 20ms at 48kHz
// Reconfigure to mono // Reconfigure to mono
ASSERT_EQ(ac_reconfigure_encoder(ac, 48000, sampling_rate, channels), 0); ASSERT_EQ(ac_reconfigure_encoder(ac, 48000, sampling_rate, channels), 0);
std::vector<int16_t> pcm(sample_count * channels); std::vector<std::int16_t> pcm(sample_count * channels);
for (size_t i = 0; i < pcm.size(); ++i) { for (std::size_t i = 0; i < pcm.size(); ++i) {
pcm[i] = static_cast<int16_t>(i * 10); pcm[i] = static_cast<std::int16_t>(i * 10);
} }
std::vector<uint8_t> encoded(2000); std::vector<std::uint8_t> encoded(2000);
int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size()); int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size());
ASSERT_GT(encoded_size, 0); ASSERT_GT(encoded_size, 0);
// Prepare payload: 4 bytes sampling rate + Opus data // Prepare payload: 4 bytes sampling rate + Opus data
std::vector<uint8_t> payload(4 + static_cast<size_t>(encoded_size)); std::vector<std::uint8_t> payload(4 + static_cast<std::size_t>(encoded_size));
uint32_t net_sr = net_htonl(sampling_rate); std::uint32_t net_sr = net_htonl(sampling_rate);
memcpy(payload.data(), &net_sr, 4); std::memcpy(payload.data(), &net_sr, 4);
memcpy(payload.data() + 4, encoded.data(), static_cast<size_t>(encoded_size)); std::memcpy(payload.data() + 4, encoded.data(), static_cast<std::size_t>(encoded_size));
// Send via RTP // Send via RTP
int rc = rtp_send_data( int rc = rtp_send_data(
log, send_rtp, payload.data(), static_cast<uint32_t>(payload.size()), false); log, send_rtp, payload.data(), static_cast<std::uint32_t>(payload.size()), false);
ASSERT_EQ(rc, 0); ASSERT_EQ(rc, 0);
// Decode // Decode
@@ -92,10 +97,10 @@ TEST_F(AudioTest, EncodeDecodeRealistic)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint32_t sampling_rate = 48000; std::uint32_t sampling_rate = 48000;
uint8_t channels = 1; std::uint8_t channels = 1;
size_t sample_count = 960; // 20ms at 48kHz std::size_t sample_count = 960; // 20ms at 48kHz
uint32_t bitrate = 48000; std::uint32_t bitrate = 48000;
ASSERT_EQ(ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels), 0); ASSERT_EQ(ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels), 0);
@@ -103,27 +108,28 @@ TEST_F(AudioTest, EncodeDecodeRealistic)
double amplitude = 10000.0; double amplitude = 10000.0;
const double pi = std::acos(-1.0); const double pi = std::acos(-1.0);
std::vector<int16_t> all_sent; std::vector<std::int16_t> all_sent;
std::vector<int16_t> all_recv; std::vector<std::int16_t> all_recv;
for (int frame = 0; frame < 50; ++frame) { for (int frame = 0; frame < 50; ++frame) {
std::vector<int16_t> pcm(sample_count * channels); std::vector<std::int16_t> pcm(sample_count * channels);
for (size_t i = 0; i < sample_count; ++i) { for (std::size_t i = 0; i < sample_count; ++i) {
double t = static_cast<double>(frame * sample_count + i) / sampling_rate; double t = static_cast<double>(frame * sample_count + i) / sampling_rate;
pcm[i] = static_cast<int16_t>(std::sin(2.0 * pi * frequency * t) * amplitude); pcm[i] = static_cast<std::int16_t>(std::sin(2.0 * pi * frequency * t) * amplitude);
} }
all_sent.insert(all_sent.end(), pcm.begin(), pcm.end()); all_sent.insert(all_sent.end(), pcm.begin(), pcm.end());
std::vector<uint8_t> encoded(2000); std::vector<std::uint8_t> encoded(2000);
int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size()); int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size());
ASSERT_GT(encoded_size, 0); ASSERT_GT(encoded_size, 0);
std::vector<uint8_t> payload(4 + static_cast<size_t>(encoded_size)); std::vector<std::uint8_t> payload(4 + static_cast<std::size_t>(encoded_size));
uint32_t net_sr = net_htonl(sampling_rate); std::uint32_t net_sr = net_htonl(sampling_rate);
memcpy(payload.data(), &net_sr, 4); std::memcpy(payload.data(), &net_sr, 4);
memcpy(payload.data() + 4, encoded.data(), static_cast<size_t>(encoded_size)); std::memcpy(payload.data() + 4, encoded.data(), static_cast<std::size_t>(encoded_size));
rtp_send_data(log, send_rtp, payload.data(), static_cast<uint32_t>(payload.size()), false); rtp_send_data(
log, send_rtp, payload.data(), static_cast<std::uint32_t>(payload.size()), false);
ac_iterate(ac); ac_iterate(ac);
@@ -143,7 +149,7 @@ TEST_F(AudioTest, EncodeDecodeRealistic)
for (int delay = 3000; delay < 3500; ++delay) { for (int delay = 3000; delay < 3500; ++delay) {
double mse = 0; double mse = 0;
int count = 0; int count = 0;
for (size_t i = 0; i < 2000; ++i) { // Compare a decent chunk for (std::size_t i = 0; i < 2000; ++i) { // Compare a decent chunk
if (i + delay < all_sent.size() && i < all_recv.size()) { if (i + delay < all_sent.size() && i < all_recv.size()) {
int diff = all_sent[i + delay] - all_recv[i]; int diff = all_sent[i + delay] - all_recv[i];
mse += static_cast<double>(diff) * diff; mse += static_cast<double>(diff) * diff;
@@ -159,7 +165,7 @@ TEST_F(AudioTest, EncodeDecodeRealistic)
} }
} }
printf("Best audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse); std::printf("Best audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse);
// For 48kbps Opus, the MSE for a sine wave should be quite low once aligned. // For 48kbps Opus, the MSE for a sine wave should be quite low once aligned.
// 10M is about 20% of the signal power (50M), which is a safe threshold for verification. // 10M is about 20% of the signal power (50M), which is a safe threshold for verification.
@@ -183,42 +189,43 @@ TEST_F(AudioTest, EncodeDecodeSiren)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint32_t sampling_rate = 48000; std::uint32_t sampling_rate = 48000;
uint8_t channels = 1; std::uint8_t channels = 1;
size_t sample_count = 960; // 20ms at 48kHz std::size_t sample_count = 960; // 20ms at 48kHz
uint32_t bitrate = 64000; std::uint32_t bitrate = 64000;
ASSERT_EQ(ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels), 0); ASSERT_EQ(ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels), 0);
double amplitude = 10000.0; double amplitude = 10000.0;
const double pi = std::acos(-1.0); const double pi = std::acos(-1.0);
std::vector<int16_t> all_sent; std::vector<std::int16_t> all_sent;
std::vector<int16_t> all_recv; std::vector<std::int16_t> all_recv;
// 1 second of audio (50 frames) is enough for a siren test // 1 second of audio (50 frames) is enough for a siren test
for (int frame = 0; frame < 50; ++frame) { for (int frame = 0; frame < 50; ++frame) {
std::vector<int16_t> pcm(sample_count * channels); std::vector<std::int16_t> pcm(sample_count * channels);
for (size_t i = 0; i < sample_count; ++i) { for (std::size_t i = 0; i < sample_count; ++i) {
double t = static_cast<double>(frame * sample_count + i) / sampling_rate; double t = static_cast<double>(frame * sample_count + i) / sampling_rate;
// Linear frequency sweep from 50Hz to 440Hz over 1 second // Linear frequency sweep from 50Hz to 440Hz over 1 second
// f(t) = 50 + (440-50)/1 * t = 50 + 390t // f(t) = 50 + (440-50)/1 * t = 50 + 390t
// phi(t) = 2*pi * integral(f(t)) = 2*pi * (50t + 195t^2) // phi(t) = 2*pi * integral(f(t)) = 2*pi * (50t + 195t^2)
double phi = 2.0 * pi * (50.0 * t + 195.0 * t * t); double phi = 2.0 * pi * (50.0 * t + 195.0 * t * t);
pcm[i] = static_cast<int16_t>(std::sin(phi) * amplitude); pcm[i] = static_cast<std::int16_t>(std::sin(phi) * amplitude);
} }
all_sent.insert(all_sent.end(), pcm.begin(), pcm.end()); all_sent.insert(all_sent.end(), pcm.begin(), pcm.end());
std::vector<uint8_t> encoded(2000); std::vector<std::uint8_t> encoded(2000);
int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size()); int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size());
ASSERT_GT(encoded_size, 0); ASSERT_GT(encoded_size, 0);
std::vector<uint8_t> payload(4 + static_cast<size_t>(encoded_size)); std::vector<std::uint8_t> payload(4 + static_cast<std::size_t>(encoded_size));
uint32_t net_sr = net_htonl(sampling_rate); std::uint32_t net_sr = net_htonl(sampling_rate);
memcpy(payload.data(), &net_sr, 4); std::memcpy(payload.data(), &net_sr, 4);
memcpy(payload.data() + 4, encoded.data(), static_cast<size_t>(encoded_size)); std::memcpy(payload.data() + 4, encoded.data(), static_cast<std::size_t>(encoded_size));
rtp_send_data(log, send_rtp, payload.data(), static_cast<uint32_t>(payload.size()), false); rtp_send_data(
log, send_rtp, payload.data(), static_cast<std::uint32_t>(payload.size()), false);
ac_iterate(ac); ac_iterate(ac);
@@ -229,14 +236,14 @@ TEST_F(AudioTest, EncodeDecodeSiren)
ASSERT_FALSE(all_recv.empty()); ASSERT_FALSE(all_recv.empty());
auto calculate_mse_at = [&](int delay, size_t window) { auto calculate_mse_at = [&](int delay, std::size_t window) {
double mse = 0; double mse = 0;
int count = 0; int count = 0;
for (size_t i = 0; i < window; ++i) { for (std::size_t i = 0; i < window; ++i) {
int sent_idx = static_cast<int>(i) + delay; int sent_idx = static_cast<int>(i) + delay;
if (sent_idx >= 0 && static_cast<size_t>(sent_idx) < all_sent.size() if (sent_idx >= 0 && static_cast<std::size_t>(sent_idx) < all_sent.size()
&& i < all_recv.size()) { && i < all_recv.size()) {
int diff = all_sent[static_cast<size_t>(sent_idx)] - all_recv[i]; int diff = all_sent[static_cast<std::size_t>(sent_idx)] - all_recv[i];
mse += static_cast<double>(diff) * diff; mse += static_cast<double>(diff) * diff;
count++; count++;
} }
@@ -267,7 +274,7 @@ TEST_F(AudioTest, EncodeDecodeSiren)
} }
} }
printf("Best siren audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse); std::printf("Best siren audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse);
// For 64kbps Opus, the MSE for a siren wave should be reasonably low once aligned. // For 64kbps Opus, the MSE for a siren wave should be reasonably low once aligned.
EXPECT_LT(min_mse, 20000000.0); EXPECT_LT(min_mse, 20000000.0);
@@ -287,11 +294,11 @@ TEST_F(AudioTest, ReconfigureEncoder)
int rc = ac_reconfigure_encoder(ac, 32000, 24000, 2); int rc = ac_reconfigure_encoder(ac, 32000, 24000, 2);
ASSERT_EQ(rc, 0); ASSERT_EQ(rc, 0);
size_t sample_count = 480; // 20ms at 24kHz std::size_t sample_count = 480; // 20ms at 24kHz
uint8_t channels = 2; std::uint8_t channels = 2;
std::vector<int16_t> pcm(sample_count * channels, 0); std::vector<std::int16_t> pcm(sample_count * channels, 0);
std::vector<uint8_t> encoded(1000); std::vector<std::uint8_t> encoded(1000);
int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size()); int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size());
ASSERT_GT(encoded_size, 0); ASSERT_GT(encoded_size, 0);
@@ -324,9 +331,9 @@ TEST_F(AudioTest, QueueInvalidMessage)
&rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); &rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = audio_recv_rtp; rtp_mock.recv_session = audio_recv_rtp;
std::vector<uint8_t> dummy_video(100, 0); std::vector<std::uint8_t> dummy_video(100, 0);
int rc = rtp_send_data( int rc = rtp_send_data(
log, video_rtp, dummy_video.data(), static_cast<uint32_t>(dummy_video.size()), true); log, video_rtp, dummy_video.data(), static_cast<std::uint32_t>(dummy_video.size()), true);
ASSERT_EQ(rc, 0); ASSERT_EQ(rc, 0);
// Iterate should NOT trigger callback because payload type was wrong // Iterate should NOT trigger callback because payload type was wrong
@@ -352,9 +359,9 @@ TEST_F(AudioTest, JitterBufferDuplicate)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0}; std::uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000); std::uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4); std::memcpy(dummy_data, &net_sr, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
ASSERT_EQ(rtp_mock.captured_packets.size(), 1u); ASSERT_EQ(rtp_mock.captured_packets.size(), 1u);
@@ -393,9 +400,9 @@ TEST_F(AudioTest, JitterBufferOutOfOrder)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0}; std::uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000); std::uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4); std::memcpy(dummy_data, &net_sr, 4);
// Capture 3 packets // Capture 3 packets
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
@@ -440,9 +447,9 @@ TEST_F(AudioTest, PacketLossConcealment)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0}; std::uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000); std::uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4); std::memcpy(dummy_data, &net_sr, 4);
// Send packet 0 and deliver it immediately. // Send packet 0 and deliver it immediately.
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
@@ -486,9 +493,9 @@ TEST_F(AudioTest, JitterBufferReset)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0}; std::uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000); std::uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4); std::memcpy(dummy_data, &net_sr, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
rtp_receive_packet( rtp_receive_packet(
@@ -531,12 +538,12 @@ TEST_F(AudioTest, DecoderReconfigureCooldown)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0}; std::uint8_t dummy_data[100] = {0};
uint32_t net_sr_48 = net_htonl(48000); std::uint32_t net_sr_48 = net_htonl(48000);
uint32_t net_sr_24 = net_htonl(24000); std::uint32_t net_sr_24 = net_htonl(24000);
// 1. Reconfigure to 24kHz. The initial sampling rate is 48kHz. // 1. Reconfigure to 24kHz. The initial sampling rate is 48kHz.
memcpy(dummy_data, &net_sr_24, 4); std::memcpy(dummy_data, &net_sr_24, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
rtp_receive_packet( rtp_receive_packet(
recv_rtp, rtp_mock.captured_packets.back().data(), rtp_mock.captured_packets.back().size()); recv_rtp, rtp_mock.captured_packets.back().data(), rtp_mock.captured_packets.back().size());
@@ -550,7 +557,7 @@ TEST_F(AudioTest, DecoderReconfigureCooldown)
mono_time_update(mono_time); mono_time_update(mono_time);
// 3. Attempt to reconfigure back to 48kHz. // 3. Attempt to reconfigure back to 48kHz.
memcpy(dummy_data, &net_sr_48, 4); std::memcpy(dummy_data, &net_sr_48, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
rtp_receive_packet( rtp_receive_packet(
recv_rtp, rtp_mock.captured_packets.back().data(), rtp_mock.captured_packets.back().size()); recv_rtp, rtp_mock.captured_packets.back().data(), rtp_mock.captured_packets.back().size());
@@ -592,9 +599,9 @@ TEST_F(AudioTest, QueueDummyMessage)
&rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); &rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = audio_recv_rtp; rtp_mock.recv_session = audio_recv_rtp;
std::vector<uint8_t> dummy_payload(100, 0); std::vector<std::uint8_t> dummy_payload(100, 0);
int rc = rtp_send_data( int rc = rtp_send_data(log, dummy_rtp, dummy_payload.data(),
log, dummy_rtp, dummy_payload.data(), static_cast<uint32_t>(dummy_payload.size()), false); static_cast<std::uint32_t>(dummy_payload.size()), false);
ASSERT_EQ(rc, 0); ASSERT_EQ(rc, 0);
// Iterate should NOT trigger callback because it was a dummy packet // Iterate should NOT trigger callback because it was a dummy packet
@@ -620,9 +627,9 @@ TEST_F(AudioTest, LatePacketReset)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0}; std::uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000); std::uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4); std::memcpy(dummy_data, &net_sr, 4);
// 1. Send and process the first packet. // 1. Send and process the first packet.
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); // seq 0 rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); // seq 0
@@ -633,14 +640,14 @@ TEST_F(AudioTest, LatePacketReset)
data.sample_count = 0; data.sample_count = 0;
// 2. Buffer another packet with a different sampling rate (24kHz) but don't process it yet. // 2. Buffer another packet with a different sampling rate (24kHz) but don't process it yet.
uint32_t net_sr_24 = net_htonl(24000); std::uint32_t net_sr_24 = net_htonl(24000);
memcpy(dummy_data, &net_sr_24, 4); std::memcpy(dummy_data, &net_sr_24, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); // seq 1 rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); // seq 1
rtp_receive_packet( rtp_receive_packet(
recv_rtp, rtp_mock.captured_packets[1].data(), rtp_mock.captured_packets[1].size()); recv_rtp, rtp_mock.captured_packets[1].data(), rtp_mock.captured_packets[1].size());
// 3. Receive the late packet (seq 0) again. // 3. Receive the late packet (seq 0) again.
// This triggers the bug: (uint32_t)(0 - 1) > 16, causing a full jitter buffer reset. // This triggers the bug: (std::uint32_t)(0 - 1) > 16, causing a full jitter buffer reset.
rtp_receive_packet( rtp_receive_packet(
recv_rtp, rtp_mock.captured_packets[0].data(), rtp_mock.captured_packets[0].size()); recv_rtp, rtp_mock.captured_packets[0].data(), rtp_mock.captured_packets[0].size());
@@ -672,9 +679,9 @@ TEST_F(AudioTest, InvalidSamplingRate)
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
// 1. Send a packet with an absurdly large sampling rate. // 1. Send a packet with an absurdly large sampling rate.
uint8_t malicious_data[100] = {0}; std::uint8_t malicious_data[100] = {0};
uint32_t net_sr = net_htonl(1000000000); // 1 GHz std::uint32_t net_sr = net_htonl(1000000000); // 1 GHz
memcpy(malicious_data, &net_sr, 4); std::memcpy(malicious_data, &net_sr, 4);
// Add some dummy Opus data so it's not too short // Add some dummy Opus data so it's not too short
malicious_data[4] = 0x08; malicious_data[4] = 0x08;
@@ -720,7 +727,7 @@ TEST_F(AudioTest, ShortPacket)
// 1. Send a packet that is too short (only sampling rate, no Opus data). // 1. Send a packet that is too short (only sampling rate, no Opus data).
// The protocol requires 4 bytes SR + at least 1 byte Opus data. // The protocol requires 4 bytes SR + at least 1 byte Opus data.
uint8_t short_data[4] = {0, 0, 0xBB, 0x80}; // 48000 std::uint8_t short_data[4] = {0, 0, 0xBB, 0x80}; // 48000
// rtp_send_data might not like 4 bytes if it expects more, but let's see. // rtp_send_data might not like 4 bytes if it expects more, but let's see.
rtp_send_data(log, send_rtp, short_data, sizeof(short_data), false); rtp_send_data(log, send_rtp, short_data, sizeof(short_data), false);
@@ -750,16 +757,16 @@ TEST_F(AudioTest, JitterBufferWrapAround)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb); nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp; rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0}; std::uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000); std::uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4); std::memcpy(dummy_data, &net_sr, 4);
// Send enough packets to reach the sequence number wrap-around point (0xFFFF -> 0x0000). // Send enough packets to reach the sequence number wrap-around point (0xFFFF -> 0x0000).
// We detect the current sequence number to minimize the number of iterations. // We detect the current sequence number to minimize the number of iterations.
uint16_t seq = 0; std::uint16_t seq = 0;
{ {
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
const uint8_t *pkt = rtp_mock.captured_packets.back().data(); const std::uint8_t *pkt = rtp_mock.captured_packets.back().data();
seq = (pkt[3] << 8) | pkt[4]; seq = (pkt[3] << 8) | pkt[4];
rtp_receive_packet(recv_rtp, pkt, rtp_mock.captured_packets.back().size()); rtp_receive_packet(recv_rtp, pkt, rtp_mock.captured_packets.back().size());
rtp_mock.captured_packets.clear(); rtp_mock.captured_packets.clear();
@@ -770,7 +777,7 @@ TEST_F(AudioTest, JitterBufferWrapAround)
int to_send = (65532 - seq + 65536) % 65536; int to_send = (65532 - seq + 65536) % 65536;
for (int i = 0; i < to_send; ++i) { for (int i = 0; i < to_send; ++i) {
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
const uint8_t *pkt = rtp_mock.captured_packets.back().data(); const std::uint8_t *pkt = rtp_mock.captured_packets.back().data();
rtp_receive_packet(recv_rtp, pkt, rtp_mock.captured_packets.back().size()); rtp_receive_packet(recv_rtp, pkt, rtp_mock.captured_packets.back().size());
rtp_mock.captured_packets.clear(); rtp_mock.captured_packets.clear();
ac_iterate(ac); ac_iterate(ac);

View File

@@ -2,15 +2,18 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring> #include <cstring>
#include "../toxcore/os_memory.h" #include "../toxcore/os_memory.h"
// Mock Time // Mock Time
uint64_t mock_time_cb(void *ud) { return static_cast<MockTime *>(ud)->t; } std::uint64_t mock_time_cb(void *_Nullable ud) { return static_cast<MockTime *>(ud)->t; }
// RTP Mock // RTP Mock
int RtpMock::send_packet(void *user_data, const uint8_t *data, uint16_t length) int RtpMock::send_packet(
void *_Nullable user_data, const std::uint8_t *_Nonnull data, std::uint16_t length)
{ {
auto *self = static_cast<RtpMock *>(user_data); auto *self = static_cast<RtpMock *>(user_data);
if (self->capture_packets) { if (self->capture_packets) {
@@ -21,7 +24,7 @@ int RtpMock::send_packet(void *user_data, const uint8_t *data, uint16_t length)
self->captured_packets[0].assign(data, data + length); self->captured_packets[0].assign(data, data + length);
} }
} else { } else {
self->captured_packets.push_back(std::vector<uint8_t>(data, data + length)); self->captured_packets.push_back(std::vector<std::uint8_t>(data, data + length));
} }
} }
if (self->auto_forward && self->recv_session) { if (self->auto_forward && self->recv_session) {
@@ -30,45 +33,49 @@ int RtpMock::send_packet(void *user_data, const uint8_t *data, uint16_t length)
return 0; return 0;
} }
int RtpMock::audio_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg) int RtpMock::audio_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg)
{ {
return ac_queue_message(mono_time, cs, msg); return ac_queue_message(mono_time, cs, msg);
} }
int RtpMock::video_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg) int RtpMock::video_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg)
{ {
return vc_queue_message(mono_time, cs, msg); return vc_queue_message(mono_time, cs, msg);
} }
int RtpMock::noop_cb(const Mono_Time * /*mono_time*/, void * /*cs*/, RTPMessage *msg) int RtpMock::noop_cb(
const Mono_Time *_Nonnull /*mono_time*/, void *_Nullable /*cs*/, RTPMessage *_Nonnull msg)
{ {
std::free(msg); std::free(msg);
return 0; return 0;
} }
// Audio Helpers // Audio Helpers
void fill_audio_frame(uint32_t sampling_rate, uint8_t channels, int frame_index, void fill_audio_frame(std::uint32_t sampling_rate, std::uint8_t channels, int frame_index,
size_t sample_count, std::vector<int16_t> &pcm) std::size_t sample_count, std::vector<std::int16_t> &pcm)
{ {
const double pi = std::acos(-1.0); const double pi = std::acos(-1.0);
double amplitude = 10000.0; double amplitude = 10000.0;
for (size_t i = 0; i < sample_count; ++i) { for (std::size_t i = 0; i < sample_count; ++i) {
double t = static_cast<double>(frame_index * sample_count + i) / sampling_rate; double t = static_cast<double>(frame_index * sample_count + i) / sampling_rate;
// Linear frequency sweep from 50Hz to 440Hz over 1 second (50 frames) // Linear frequency sweep from 50Hz to 440Hz over 1 second (50 frames)
// f(t) = 50 + 390t // f(t) = 50 + 390t
// phi(t) = 2*pi * (50t + 195t^2) // phi(t) = 2*pi * (50t + 195t^2)
double phi = 2.0 * pi * (50.0 * t + 195.0 * t * t); double phi = 2.0 * pi * (50.0 * t + 195.0 * t * t);
int16_t val = static_cast<int16_t>(std::sin(phi) * amplitude); std::int16_t val = static_cast<std::int16_t>(std::sin(phi) * amplitude);
for (uint8_t c = 0; c < channels; ++c) { for (std::uint8_t c = 0; c < channels; ++c) {
pcm[i * channels + c] = val; pcm[i * channels + c] = val;
} }
} }
} }
void fill_silent_frame(uint8_t channels, size_t sample_count, std::vector<int16_t> &pcm) void fill_silent_frame(
std::uint8_t channels, std::size_t sample_count, std::vector<std::int16_t> &pcm)
{ {
for (size_t i = 0; i < sample_count * channels; ++i) { for (std::size_t i = 0; i < sample_count * channels; ++i) {
// Very low amplitude white noise (simulating silence with background hiss) // Very low amplitude white noise (simulating silence with background hiss)
pcm[i] = (std::rand() % 21) - 10; pcm[i] = (std::rand() % 21) - 10;
} }
@@ -77,8 +84,9 @@ void fill_silent_frame(uint8_t channels, size_t sample_count, std::vector<int16_
AudioTestData::AudioTestData() = default; AudioTestData::AudioTestData() = default;
AudioTestData::~AudioTestData() = default; AudioTestData::~AudioTestData() = default;
void AudioTestData::receive_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, void AudioTestData::receive_frame(std::uint32_t friend_number, const std::int16_t *_Nonnull pcm,
uint8_t channels, uint32_t sampling_rate, void *user_data) std::size_t sample_count, std::uint8_t channels, std::uint32_t sampling_rate,
void *_Nullable user_data)
{ {
auto *self = static_cast<AudioTestData *>(user_data); auto *self = static_cast<AudioTestData *>(user_data);
self->friend_number = friend_number; self->friend_number = friend_number;
@@ -89,8 +97,8 @@ void AudioTestData::receive_frame(uint32_t friend_number, const int16_t *pcm, si
} }
// Video Helpers // Video Helpers
void fill_video_frame(uint16_t width, uint16_t height, int frame_index, std::vector<uint8_t> &y, void fill_video_frame(std::uint16_t width, std::uint16_t height, int frame_index,
std::vector<uint8_t> &u, std::vector<uint8_t> &v) std::vector<std::uint8_t> &y, std::vector<std::uint8_t> &u, std::vector<std::uint8_t> &v)
{ {
// Background (dark gray) // Background (dark gray)
std::fill(y.begin(), y.end(), 32); std::fill(y.begin(), y.end(), 32);
@@ -110,10 +118,10 @@ void fill_video_frame(uint16_t width, uint16_t height, int frame_index, std::vec
} }
} }
double calculate_video_mse(uint16_t width, uint16_t height, int32_t ystride, double calculate_video_mse(std::uint16_t width, std::uint16_t height, std::int32_t ystride,
const std::vector<uint8_t> &y_recv, const std::vector<uint8_t> &y_orig) const std::vector<std::uint8_t> &y_recv, const std::vector<std::uint8_t> &y_orig)
{ {
if (y_recv.empty() || y_orig.size() != static_cast<size_t>(width) * height) { if (y_recv.empty() || y_orig.size() != static_cast<std::size_t>(width) * height) {
return 1e10; return 1e10;
} }
@@ -124,16 +132,17 @@ double calculate_video_mse(uint16_t width, uint16_t height, int32_t ystride,
mse += diff * diff; mse += diff * diff;
} }
} }
return mse / (static_cast<size_t>(width) * height); return mse / (static_cast<std::size_t>(width) * height);
} }
// Video Test Data Helper // Video Test Data Helper
VideoTestData::VideoTestData() = default; VideoTestData::VideoTestData() = default;
VideoTestData::~VideoTestData() = default; VideoTestData::~VideoTestData() = default;
void VideoTestData::receive_frame(uint32_t friend_number, uint16_t width, uint16_t height, void VideoTestData::receive_frame(std::uint32_t friend_number, std::uint16_t width,
const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, std::uint16_t height, const std::uint8_t *_Nonnull y, const std::uint8_t *_Nonnull u,
int32_t vstride, void *user_data) const std::uint8_t *_Nonnull v, std::int32_t ystride, std::int32_t ustride,
std::int32_t vstride, void *_Nullable user_data)
{ {
auto *self = static_cast<VideoTestData *>(user_data); auto *self = static_cast<VideoTestData *>(user_data);
self->friend_number = friend_number; self->friend_number = friend_number;
@@ -143,12 +152,12 @@ void VideoTestData::receive_frame(uint32_t friend_number, uint16_t width, uint16
self->ustride = ustride; self->ustride = ustride;
self->vstride = vstride; self->vstride = vstride;
self->y.assign(y, y + static_cast<size_t>(std::abs(ystride)) * height); self->y.assign(y, y + static_cast<std::size_t>(std::abs(ystride)) * height);
self->u.assign(u, u + static_cast<size_t>(std::abs(ustride)) * (height / 2)); self->u.assign(u, u + static_cast<std::size_t>(std::abs(ustride)) * (height / 2));
self->v.assign(v, v + static_cast<size_t>(std::abs(vstride)) * (height / 2)); self->v.assign(v, v + static_cast<std::size_t>(std::abs(vstride)) * (height / 2));
} }
double VideoTestData::calculate_mse(const std::vector<uint8_t> &y_orig) const double VideoTestData::calculate_mse(const std::vector<std::uint8_t> &y_orig) const
{ {
return calculate_video_mse(width, height, ystride, y, y_orig); return calculate_video_mse(width, height, ystride, y, y_orig);
} }
@@ -156,7 +165,7 @@ double VideoTestData::calculate_mse(const std::vector<uint8_t> &y_orig) const
// Common Test Fixture // Common Test Fixture
void AvTest::SetUp() void AvTest::SetUp()
{ {
const Memory *mem = os_memory(); mem = os_memory();
log = logger_new(mem); log = logger_new(mem);
tm.t = 1000; tm.t = 1000;
mono_time = mono_time_new(mem, mock_time_cb, &tm); mono_time = mono_time_new(mem, mock_time_cb, &tm);
@@ -165,7 +174,6 @@ void AvTest::SetUp()
void AvTest::TearDown() void AvTest::TearDown()
{ {
const Memory *mem = os_memory();
mono_time_free(mem, mono_time); mono_time_free(mem, mono_time);
logger_kill(log); logger_kill(log);
} }

View File

@@ -3,9 +3,11 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h" #include "../toxcore/logger.h"
#include "../toxcore/mono_time.h" #include "../toxcore/mono_time.h"
#include "audio.h" #include "audio.h"
@@ -14,65 +16,72 @@
// Mock Time // Mock Time
struct MockTime { struct MockTime {
uint64_t t = 1000; std::uint64_t t = 1000;
}; };
uint64_t mock_time_cb(void *ud); std::uint64_t mock_time_cb(void *_Nullable ud);
// RTP Mock // RTP Mock
struct RtpMock { struct RtpMock {
RTPSession *recv_session = nullptr; RTPSession *_Nullable recv_session = nullptr;
std::vector<std::vector<uint8_t>> captured_packets; std::vector<std::vector<std::uint8_t>> captured_packets;
bool auto_forward = true; bool auto_forward = true;
bool capture_packets = true; bool capture_packets = true;
bool store_last_packet_only = false; bool store_last_packet_only = false;
static int send_packet(void *user_data, const uint8_t *data, uint16_t length); static int send_packet(
static int audio_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg); void *_Nullable user_data, const std::uint8_t *_Nonnull data, std::uint16_t length);
static int video_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg); static int audio_cb(
static int noop_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg); const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg);
static int video_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg);
static int noop_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg);
}; };
// Audio Helpers // Audio Helpers
void fill_audio_frame(uint32_t sampling_rate, uint8_t channels, int frame_index, void fill_audio_frame(std::uint32_t sampling_rate, std::uint8_t channels, int frame_index,
size_t sample_count, std::vector<int16_t> &pcm); std::size_t sample_count, std::vector<std::int16_t> &pcm);
void fill_silent_frame(uint8_t channels, size_t sample_count, std::vector<int16_t> &pcm); void fill_silent_frame(
std::uint8_t channels, std::size_t sample_count, std::vector<std::int16_t> &pcm);
struct AudioTestData { struct AudioTestData {
uint32_t friend_number = 0; std::uint32_t friend_number = 0;
std::vector<int16_t> last_pcm; std::vector<std::int16_t> last_pcm;
size_t sample_count = 0; std::size_t sample_count = 0;
uint8_t channels = 0; std::uint8_t channels = 0;
uint32_t sampling_rate = 0; std::uint32_t sampling_rate = 0;
AudioTestData(); AudioTestData();
~AudioTestData(); ~AudioTestData();
static void receive_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, static void receive_frame(std::uint32_t friend_number, const std::int16_t *_Nonnull pcm,
uint8_t channels, uint32_t sampling_rate, void *user_data); std::size_t sample_count, std::uint8_t channels, std::uint32_t sampling_rate,
void *_Nullable user_data);
}; };
// Video Helpers // Video Helpers
void fill_video_frame(uint16_t width, uint16_t height, int frame_index, std::vector<uint8_t> &y, void fill_video_frame(std::uint16_t width, std::uint16_t height, int frame_index,
std::vector<uint8_t> &u, std::vector<uint8_t> &v); std::vector<std::uint8_t> &y, std::vector<std::uint8_t> &u, std::vector<std::uint8_t> &v);
double calculate_video_mse(uint16_t width, uint16_t height, int32_t ystride, double calculate_video_mse(std::uint16_t width, std::uint16_t height, std::int32_t ystride,
const std::vector<uint8_t> &y_recv, const std::vector<uint8_t> &y_orig); const std::vector<std::uint8_t> &y_recv, const std::vector<std::uint8_t> &y_orig);
// Video Test Data Helper // Video Test Data Helper
struct VideoTestData { struct VideoTestData {
uint32_t friend_number = 0; std::uint32_t friend_number = 0;
uint16_t width = 0; std::uint16_t width = 0;
uint16_t height = 0; std::uint16_t height = 0;
std::vector<uint8_t> y, u, v; std::vector<std::uint8_t> y, u, v;
int32_t ystride = 0, ustride = 0, vstride = 0; std::int32_t ystride = 0, ustride = 0, vstride = 0;
VideoTestData(); VideoTestData();
~VideoTestData(); ~VideoTestData();
static void receive_frame(uint32_t friend_number, uint16_t width, uint16_t height, static void receive_frame(std::uint32_t friend_number, std::uint16_t width,
const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, std::uint16_t height, const std::uint8_t *_Nonnull y, const std::uint8_t *_Nonnull u,
int32_t vstride, void *user_data); const std::uint8_t *_Nonnull v, std::int32_t ystride, std::int32_t ustride,
std::int32_t vstride, void *_Nullable user_data);
double calculate_mse(const std::vector<uint8_t> &y_orig) const; double calculate_mse(const std::vector<std::uint8_t> &y_orig) const;
}; };
// Common Test Fixture // Common Test Fixture
@@ -81,8 +90,9 @@ protected:
void SetUp() override; void SetUp() override;
void TearDown() override; void TearDown() override;
Logger *log = nullptr; Logger *_Nullable log = nullptr;
Mono_Time *mono_time = nullptr; Mono_Time *_Nullable mono_time = nullptr;
const Memory *_Nullable mem = nullptr;
MockTime tm; MockTime tm;
}; };

View File

@@ -3,8 +3,11 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cmath> #include <cmath>
#include <cstddef>
#include <cstdint>
#include <vector> #include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h" #include "../toxcore/logger.h"
#include "../toxcore/mono_time.h" #include "../toxcore/mono_time.h"
#include "../toxcore/network.h" #include "../toxcore/network.h"
@@ -13,17 +16,18 @@
namespace { namespace {
struct BwcTimeMock { struct BwcTimeMock {
uint64_t t; std::uint64_t t;
}; };
uint64_t bwc_mock_time_cb(void *ud) { return static_cast<BwcTimeMock *>(ud)->t; } std::uint64_t bwc_mock_time_cb(void *ud) { return static_cast<BwcTimeMock *>(ud)->t; }
struct MockBwcData { struct MockBwcData {
std::vector<std::vector<uint8_t>> sent_packets; std::vector<std::vector<std::uint8_t>> sent_packets;
std::vector<float> reported_losses; std::vector<float> reported_losses;
uint32_t friend_number = 0; std::uint32_t friend_number = 0;
static int send_packet(void *user_data, const uint8_t *data, uint16_t length) static int send_packet(
void *_Nullable user_data, const std::uint8_t *_Nonnull data, std::uint16_t length)
{ {
auto *sd = static_cast<MockBwcData *>(user_data); auto *sd = static_cast<MockBwcData *>(user_data);
if (sd->fail_send) { if (sd->fail_send) {
@@ -33,8 +37,8 @@ struct MockBwcData {
return 0; return 0;
} }
static void loss_report( static void loss_report(BWController *_Nonnull /*bwc*/, std::uint32_t friend_number, float loss,
BWController * /*bwc*/, uint32_t friend_number, float loss, void *user_data) void *_Nullable user_data)
{ {
auto *sd = static_cast<MockBwcData *>(user_data); auto *sd = static_cast<MockBwcData *>(user_data);
sd->friend_number = friend_number; sd->friend_number = friend_number;
@@ -57,13 +61,13 @@ protected:
void TearDown() override void TearDown() override
{ {
const Memory *mem = os_memory(); const Memory *_Nonnull mem = os_memory();
mono_time_free(mem, mono_time); mono_time_free(mem, mono_time);
logger_kill(log); logger_kill(log);
} }
Logger *log; Logger *_Nullable log;
Mono_Time *mono_time; Mono_Time *_Nullable mono_time;
BwcTimeMock tm; BwcTimeMock tm;
}; };
@@ -79,7 +83,7 @@ TEST_F(BwcTest, BasicNewKill)
TEST_F(BwcTest, SendUpdate) TEST_F(BwcTest, SendUpdate)
{ {
MockBwcData sd; MockBwcData sd;
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
BWController *bwc = bwc_new(log, friend_number, MockBwcData::loss_report, &sd, BWController *bwc = bwc_new(log, friend_number, MockBwcData::loss_report, &sd,
MockBwcData::send_packet, &sd, mono_time); MockBwcData::send_packet, &sd, mono_time);
ASSERT_NE(bwc, nullptr); ASSERT_NE(bwc, nullptr);
@@ -107,7 +111,7 @@ TEST_F(BwcTest, SendUpdate)
EXPECT_EQ(sd.sent_packets[0][0], BWC_PACKET_ID); EXPECT_EQ(sd.sent_packets[0][0], BWC_PACKET_ID);
// Packet contains lost (4 bytes) and recv (4 bytes) // Packet contains lost (4 bytes) and recv (4 bytes)
uint32_t lost, recv; std::uint32_t lost, recv;
net_unpack_u32(sd.sent_packets[0].data() + 1, &lost); net_unpack_u32(sd.sent_packets[0].data() + 1, &lost);
net_unpack_u32(sd.sent_packets[0].data() + 5, &recv); net_unpack_u32(sd.sent_packets[0].data() + 5, &recv);
@@ -120,12 +124,12 @@ TEST_F(BwcTest, SendUpdate)
TEST_F(BwcTest, HandlePacket) TEST_F(BwcTest, HandlePacket)
{ {
MockBwcData sd; MockBwcData sd;
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
BWController *bwc = bwc_new(log, friend_number, MockBwcData::loss_report, &sd, BWController *bwc = bwc_new(log, friend_number, MockBwcData::loss_report, &sd,
MockBwcData::send_packet, &sd, mono_time); MockBwcData::send_packet, &sd, mono_time);
ASSERT_NE(bwc, nullptr); ASSERT_NE(bwc, nullptr);
uint8_t packet[9]; std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID; packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 100); // lost net_pack_u32(packet + 1, 100); // lost
net_pack_u32(packet + 5, 900); // recv net_pack_u32(packet + 5, 900); // recv
@@ -155,7 +159,7 @@ TEST_F(BwcTest, InvalidPacketSize)
MockBwcData sd; MockBwcData sd;
BWController *bwc = bwc_new( BWController *bwc = bwc_new(
log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time); log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time);
uint8_t packet[10] = {0}; std::uint8_t packet[10] = {0};
// Correct size is 9 // Correct size is 9
bwc_handle_packet(bwc, packet, 8); bwc_handle_packet(bwc, packet, 8);
@@ -193,7 +197,7 @@ TEST_F(BwcTest, NullCallback)
BWController *bwc BWController *bwc
= bwc_new(log, 123, nullptr, nullptr, MockBwcData::send_packet, &sd, mono_time); = bwc_new(log, 123, nullptr, nullptr, MockBwcData::send_packet, &sd, mono_time);
uint8_t packet[9]; std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID; packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 100); // lost net_pack_u32(packet + 1, 100); // lost
net_pack_u32(packet + 5, 900); // recv net_pack_u32(packet + 5, 900); // recv
@@ -213,7 +217,7 @@ TEST_F(BwcTest, ZeroLoss)
log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time); log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time);
// 1. Peer sends update with zero loss // 1. Peer sends update with zero loss
uint8_t packet[9]; std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID; packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 0); // lost net_pack_u32(packet + 1, 0); // lost
net_pack_u32(packet + 5, 1000); // recv net_pack_u32(packet + 5, 1000); // recv
@@ -255,7 +259,7 @@ TEST_F(BwcTest, Overflow)
bwc_add_recv(bwc, 1); bwc_add_recv(bwc, 1);
ASSERT_EQ(sd.sent_packets.size(), 1); ASSERT_EQ(sd.sent_packets.size(), 1);
uint32_t lost, recv; std::uint32_t lost, recv;
net_unpack_u32(sd.sent_packets[0].data() + 1, &lost); net_unpack_u32(sd.sent_packets[0].data() + 1, &lost);
net_unpack_u32(sd.sent_packets[0].data() + 5, &recv); net_unpack_u32(sd.sent_packets[0].data() + 5, &recv);
@@ -324,7 +328,7 @@ TEST_F(BwcTest, RecvPlusLostOverflowBug)
MockBwcData sd; MockBwcData sd;
BWController *bwc = bwc_new( BWController *bwc = bwc_new(
log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time); log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time);
uint8_t packet[9]; std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID; packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 1); net_pack_u32(packet + 1, 1);
net_pack_u32(packet + 5, 0xFFFFFFFF); net_pack_u32(packet + 5, 0xFFFFFFFF);
@@ -342,7 +346,7 @@ TEST_F(BwcTest, RateLimitBypassBug)
log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time); log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time);
tm.t = 0xFFFFFFF0; tm.t = 0xFFFFFFF0;
mono_time_update(mono_time); mono_time_update(mono_time);
uint8_t packet[9]; std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID; packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 1); net_pack_u32(packet + 1, 1);
net_pack_u32(packet + 5, 100); net_pack_u32(packet + 5, 100);

View File

@@ -6,16 +6,19 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cstddef>
#include <cstdint>
#include <vector> #include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h" #include "../toxcore/logger.h"
#include "../toxcore/os_memory.h" #include "../toxcore/os_memory.h"
namespace { namespace {
struct MockMsi { struct MockMsi {
std::vector<std::vector<uint8_t>> sent_packets; std::vector<std::vector<std::uint8_t>> sent_packets;
std::vector<uint32_t> sent_to_friends; std::vector<std::uint32_t> sent_to_friends;
struct CallbackStats { struct CallbackStats {
int invite = 0; int invite = 0;
@@ -26,11 +29,11 @@ struct MockMsi {
int capabilities = 0; int capabilities = 0;
} stats; } stats;
MSICall *last_call = nullptr; MSICall *_Nullable last_call = nullptr;
MSIError last_error = MSI_E_NONE; MSIError last_error = MSI_E_NONE;
static int send_packet( static int send_packet(void *_Nullable user_data, std::uint32_t friend_number,
void *user_data, uint32_t friend_number, const uint8_t *data, size_t length) const std::uint8_t *_Nonnull data, std::size_t length)
{ {
auto *self = static_cast<MockMsi *>(user_data); auto *self = static_cast<MockMsi *>(user_data);
self->sent_packets.emplace_back(data, data + length); self->sent_packets.emplace_back(data, data + length);
@@ -38,7 +41,7 @@ struct MockMsi {
return 0; return 0;
} }
static int on_invite(void *object, MSICall *call) static int on_invite(void *_Nullable object, MSICall *_Nonnull call)
{ {
auto *self = static_cast<MockMsi *>(object); auto *self = static_cast<MockMsi *>(object);
self->stats.invite++; self->stats.invite++;
@@ -46,7 +49,7 @@ struct MockMsi {
return 0; return 0;
} }
static int on_start(void *object, MSICall *call) static int on_start(void *_Nullable object, MSICall *_Nonnull call)
{ {
auto *self = static_cast<MockMsi *>(object); auto *self = static_cast<MockMsi *>(object);
self->stats.start++; self->stats.start++;
@@ -54,7 +57,7 @@ struct MockMsi {
return 0; return 0;
} }
static int on_end(void *object, MSICall *call) static int on_end(void *_Nullable object, MSICall *_Nonnull call)
{ {
auto *self = static_cast<MockMsi *>(object); auto *self = static_cast<MockMsi *>(object);
self->stats.end++; self->stats.end++;
@@ -62,7 +65,7 @@ struct MockMsi {
return 0; return 0;
} }
static int on_error(void *object, MSICall *call) static int on_error(void *_Nullable object, MSICall *_Nonnull call)
{ {
auto *self = static_cast<MockMsi *>(object); auto *self = static_cast<MockMsi *>(object);
self->stats.error++; self->stats.error++;
@@ -71,7 +74,7 @@ struct MockMsi {
return 0; return 0;
} }
static int on_peertimeout(void *object, MSICall *call) static int on_peertimeout(void *_Nullable object, MSICall *_Nonnull call)
{ {
auto *self = static_cast<MockMsi *>(object); auto *self = static_cast<MockMsi *>(object);
self->stats.peertimeout++; self->stats.peertimeout++;
@@ -79,7 +82,7 @@ struct MockMsi {
return 0; return 0;
} }
static int on_capabilities(void *object, MSICall *call) static int on_capabilities(void *_Nullable object, MSICall *_Nonnull call)
{ {
auto *self = static_cast<MockMsi *>(object); auto *self = static_cast<MockMsi *>(object);
self->stats.capabilities++; self->stats.capabilities++;
@@ -92,7 +95,7 @@ class MsiTest : public ::testing::Test {
protected: protected:
void SetUp() override void SetUp() override
{ {
const Memory *mem = os_memory(); const Memory *_Nonnull mem = os_memory();
log = logger_new(mem); log = logger_new(mem);
MSICallbacks callbacks = {MockMsi::on_invite, MockMsi::on_start, MockMsi::on_end, MSICallbacks callbacks = {MockMsi::on_invite, MockMsi::on_start, MockMsi::on_end,
@@ -109,8 +112,8 @@ protected:
logger_kill(log); logger_kill(log);
} }
Logger *log; Logger *_Nullable log;
MSISession *session = nullptr; MSISession *_Nullable session = nullptr;
MockMsi mock; MockMsi mock;
}; };
@@ -122,8 +125,8 @@ TEST_F(MsiTest, BasicNewKill)
TEST_F(MsiTest, Invite) TEST_F(MsiTest, Invite)
{ {
MSICall *call = nullptr; MSICall *call = nullptr;
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
uint8_t capabilities = MSI_CAP_S_AUDIO | MSI_CAP_R_AUDIO; std::uint8_t capabilities = MSI_CAP_S_AUDIO | MSI_CAP_R_AUDIO;
int rc = msi_invite(log, session, &call, friend_number, capabilities); int rc = msi_invite(log, session, &call, friend_number, capabilities);
ASSERT_EQ(rc, 0); ASSERT_EQ(rc, 0);
@@ -146,11 +149,11 @@ TEST_F(MsiTest, Invite)
TEST_F(MsiTest, HandleIncomingInvite) TEST_F(MsiTest, HandleIncomingInvite)
{ {
uint32_t friend_number = 456; std::uint32_t friend_number = 456;
uint8_t peer_caps = MSI_CAP_S_VIDEO | MSI_CAP_R_VIDEO; std::uint8_t peer_caps = MSI_CAP_S_VIDEO | MSI_CAP_R_VIDEO;
// Craft invite packet // Craft invite packet
uint8_t invite_pkt[] = { std::uint8_t invite_pkt[] = {
1, 1, 0, // ID_REQUEST, len 1, REQU_INIT 1, 1, 0, // ID_REQUEST, len 1, REQU_INIT
3, 1, peer_caps, // ID_CAPABILITIES, len 1, caps 3, 1, peer_caps, // ID_CAPABILITIES, len 1, caps
0 // end 0 // end
@@ -168,14 +171,14 @@ TEST_F(MsiTest, HandleIncomingInvite)
TEST_F(MsiTest, Answer) TEST_F(MsiTest, Answer)
{ {
// 1. Receive invite first // 1. Receive invite first
uint32_t friend_number = 456; std::uint32_t friend_number = 456;
uint8_t peer_caps = MSI_CAP_S_VIDEO | MSI_CAP_R_VIDEO; std::uint8_t peer_caps = MSI_CAP_S_VIDEO | MSI_CAP_R_VIDEO;
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, peer_caps, 0}; std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, peer_caps, 0};
msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt)); msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt));
MSICall *call = mock.last_call; MSICall *call = mock.last_call;
// 2. Answer it // 2. Answer it
uint8_t my_caps = MSI_CAP_S_AUDIO | MSI_CAP_R_AUDIO; std::uint8_t my_caps = MSI_CAP_S_AUDIO | MSI_CAP_R_AUDIO;
int rc = msi_answer(log, call, my_caps); int rc = msi_answer(log, call, my_caps);
ASSERT_EQ(rc, 0); ASSERT_EQ(rc, 0);
EXPECT_EQ(call->state, MSI_CALL_ACTIVE); EXPECT_EQ(call->state, MSI_CALL_ACTIVE);
@@ -206,14 +209,14 @@ TEST_F(MsiTest, Hangup)
TEST_F(MsiTest, ChangeCapabilities) TEST_F(MsiTest, ChangeCapabilities)
{ {
// Setup active call // Setup active call
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0}; std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt)); msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt));
MSICall *call = mock.last_call; MSICall *call = mock.last_call;
msi_answer(log, call, 0); msi_answer(log, call, 0);
mock.sent_packets.clear(); mock.sent_packets.clear();
uint8_t new_caps = MSI_CAP_S_VIDEO; std::uint8_t new_caps = MSI_CAP_S_VIDEO;
int rc = msi_change_capabilities(log, call, new_caps); int rc = msi_change_capabilities(log, call, new_caps);
ASSERT_EQ(rc, 0); ASSERT_EQ(rc, 0);
EXPECT_EQ(call->self_capabilities, new_caps); EXPECT_EQ(call->self_capabilities, new_caps);
@@ -226,7 +229,7 @@ TEST_F(MsiTest, ChangeCapabilities)
TEST_F(MsiTest, PeerTimeout) TEST_F(MsiTest, PeerTimeout)
{ {
MSICall *call = nullptr; MSICall *call = nullptr;
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
msi_invite(log, session, &call, friend_number, 0); msi_invite(log, session, &call, friend_number, 0);
msi_call_timeout(session, log, friend_number); msi_call_timeout(session, log, friend_number);
@@ -236,12 +239,12 @@ TEST_F(MsiTest, PeerTimeout)
TEST_F(MsiTest, RemoteHangup) TEST_F(MsiTest, RemoteHangup)
{ {
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
MSICall *call = nullptr; MSICall *call = nullptr;
msi_invite(log, session, &call, friend_number, 0); msi_invite(log, session, &call, friend_number, 0);
// Craft pop packet // Craft pop packet
uint8_t pop_pkt[] = {1, 1, 2, 0}; // REQU_POP std::uint8_t pop_pkt[] = {1, 1, 2, 0}; // REQU_POP
msi_handle_packet(session, log, friend_number, pop_pkt, sizeof(pop_pkt)); msi_handle_packet(session, log, friend_number, pop_pkt, sizeof(pop_pkt));
EXPECT_EQ(mock.stats.end, 1); EXPECT_EQ(mock.stats.end, 1);
@@ -249,12 +252,12 @@ TEST_F(MsiTest, RemoteHangup)
TEST_F(MsiTest, RemoteError) TEST_F(MsiTest, RemoteError)
{ {
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
MSICall *call = nullptr; MSICall *call = nullptr;
msi_invite(log, session, &call, friend_number, 0); msi_invite(log, session, &call, friend_number, 0);
// Craft error packet (ID_ERROR = 2) // Craft error packet (ID_ERROR = 2)
uint8_t error_pkt[] = {1, 1, 2, 2, 1, 1, 0}; // REQU_POP + MSI_E_INVALID_MESSAGE std::uint8_t error_pkt[] = {1, 1, 2, 2, 1, 1, 0}; // REQU_POP + MSI_E_INVALID_MESSAGE
msi_handle_packet(session, log, friend_number, error_pkt, sizeof(error_pkt)); msi_handle_packet(session, log, friend_number, error_pkt, sizeof(error_pkt));
EXPECT_EQ(mock.stats.error, 1); EXPECT_EQ(mock.stats.error, 1);
@@ -278,7 +281,7 @@ TEST_F(MsiTest, MultipleConcurrentCalls)
msi_hangup(log, call1); msi_hangup(log, call1);
// Call 2 should still be there // Call 2 should still be there
uint8_t pop_pkt[] = {1, 1, 2, 0}; std::uint8_t pop_pkt[] = {1, 1, 2, 0};
msi_handle_packet(session, log, 2, pop_pkt, sizeof(pop_pkt)); msi_handle_packet(session, log, 2, pop_pkt, sizeof(pop_pkt));
EXPECT_EQ(mock.stats.end, 1); EXPECT_EQ(mock.stats.end, 1);
} }
@@ -288,8 +291,8 @@ TEST_F(MsiTest, RemoteAnswer)
MSICall *call = nullptr; MSICall *call = nullptr;
msi_invite(log, session, &call, 123, 0); msi_invite(log, session, &call, 123, 0);
uint8_t peer_caps = MSI_CAP_S_AUDIO; std::uint8_t peer_caps = MSI_CAP_S_AUDIO;
uint8_t push_pkt[] = {1, 1, 1, 3, 1, peer_caps, 0}; // REQU_PUSH + capabilities std::uint8_t push_pkt[] = {1, 1, 1, 3, 1, peer_caps, 0}; // REQU_PUSH + capabilities
msi_handle_packet(session, log, 123, push_pkt, sizeof(push_pkt)); msi_handle_packet(session, log, 123, push_pkt, sizeof(push_pkt));
EXPECT_EQ(mock.stats.start, 1); EXPECT_EQ(mock.stats.start, 1);
@@ -299,14 +302,14 @@ TEST_F(MsiTest, RemoteAnswer)
TEST_F(MsiTest, RemoteCapabilitiesChange) TEST_F(MsiTest, RemoteCapabilitiesChange)
{ {
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0}; std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt)); msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt));
MSICall *call = mock.last_call; MSICall *call = mock.last_call;
msi_answer(log, call, 0); msi_answer(log, call, 0);
uint8_t new_caps = MSI_CAP_S_VIDEO; std::uint8_t new_caps = MSI_CAP_S_VIDEO;
uint8_t push_pkt[] = {1, 1, 1, 3, 1, new_caps, 0}; // REQU_PUSH + new capabilities std::uint8_t push_pkt[] = {1, 1, 1, 3, 1, new_caps, 0}; // REQU_PUSH + new capabilities
msi_handle_packet(session, log, friend_number, push_pkt, sizeof(push_pkt)); msi_handle_packet(session, log, friend_number, push_pkt, sizeof(push_pkt));
EXPECT_EQ(mock.stats.capabilities, 1); EXPECT_EQ(mock.stats.capabilities, 1);
@@ -315,8 +318,8 @@ TEST_F(MsiTest, RemoteCapabilitiesChange)
TEST_F(MsiTest, FriendRecall) TEST_F(MsiTest, FriendRecall)
{ {
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0}; std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt)); msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt));
MSICall *call = mock.last_call; MSICall *call = mock.last_call;
msi_answer(log, call, 0); msi_answer(log, call, 0);
@@ -348,30 +351,30 @@ TEST_F(MsiTest, GapInFriendNumbers)
TEST_F(MsiTest, InvalidPackets) TEST_F(MsiTest, InvalidPackets)
{ {
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
// Empty packet // Empty packet
uint8_t empty = 0; std::uint8_t empty = 0;
msi_handle_packet(session, log, friend_number, &empty, 0); msi_handle_packet(session, log, friend_number, &empty, 0);
// Missing end byte // Missing end byte
uint8_t no_end[] = {1, 1, 0}; std::uint8_t no_end[] = {1, 1, 0};
msi_handle_packet(session, log, friend_number, no_end, sizeof(no_end)); msi_handle_packet(session, log, friend_number, no_end, sizeof(no_end));
// Invalid ID // Invalid ID
uint8_t invalid_id[] = {99, 1, 0, 0}; std::uint8_t invalid_id[] = {99, 1, 0, 0};
msi_handle_packet(session, log, friend_number, invalid_id, sizeof(invalid_id)); msi_handle_packet(session, log, friend_number, invalid_id, sizeof(invalid_id));
// Invalid size (too large) // Invalid size (too large)
uint8_t invalid_size[] = {1, 10, 0, 0}; std::uint8_t invalid_size[] = {1, 10, 0, 0};
msi_handle_packet(session, log, friend_number, invalid_size, sizeof(invalid_size)); msi_handle_packet(session, log, friend_number, invalid_size, sizeof(invalid_size));
// Invalid size (mismatch) // Invalid size (mismatch)
uint8_t size_mismatch[] = {1, 2, 0, 0}; std::uint8_t size_mismatch[] = {1, 2, 0, 0};
msi_handle_packet(session, log, friend_number, size_mismatch, sizeof(size_mismatch)); msi_handle_packet(session, log, friend_number, size_mismatch, sizeof(size_mismatch));
// Missing request field // Missing request field
uint8_t no_request[] = {3, 1, 0, 0}; std::uint8_t no_request[] = {3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, no_request, sizeof(no_request)); msi_handle_packet(session, log, friend_number, no_request, sizeof(no_request));
} }
@@ -387,7 +390,7 @@ TEST_F(MsiTest, CallbackFailure)
MSISession *fail_session = msi_new(log, MockMsi::send_packet, &mock, &callbacks, &mock); MSISession *fail_session = msi_new(log, MockMsi::send_packet, &mock, &callbacks, &mock);
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0}; std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
msi_handle_packet(fail_session, log, 123, invite_pkt, sizeof(invite_pkt)); msi_handle_packet(fail_session, log, 123, invite_pkt, sizeof(invite_pkt));
// Should have sent an error back // Should have sent an error back
@@ -417,14 +420,14 @@ TEST_F(MsiTest, InvalidStates)
TEST_F(MsiTest, StrayPackets) TEST_F(MsiTest, StrayPackets)
{ {
uint32_t friend_number = 123; std::uint32_t friend_number = 123;
// PUSH for non-existent call // PUSH for non-existent call
uint8_t push_pkt[] = {1, 1, 1, 3, 1, 0, 0}; std::uint8_t push_pkt[] = {1, 1, 1, 3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, push_pkt, sizeof(push_pkt)); msi_handle_packet(session, log, friend_number, push_pkt, sizeof(push_pkt));
// POP for non-existent call // POP for non-existent call
uint8_t pop_pkt[] = {1, 1, 2, 0}; std::uint8_t pop_pkt[] = {1, 1, 2, 0};
msi_handle_packet(session, log, friend_number, pop_pkt, sizeof(pop_pkt)); msi_handle_packet(session, log, friend_number, pop_pkt, sizeof(pop_pkt));
// Error sent back for stray PUSH // Error sent back for stray PUSH

View File

@@ -66,6 +66,10 @@ bool rb_read(RingBuffer *b, void **p)
RingBuffer *rb_new(int size) RingBuffer *rb_new(int size)
{ {
if (size < 0 || size >= 65535) {
return nullptr;
}
RingBuffer *buf = (RingBuffer *)calloc(1, sizeof(RingBuffer)); RingBuffer *buf = (RingBuffer *)calloc(1, sizeof(RingBuffer));
if (buf == nullptr) { if (buf == nullptr) {
@@ -103,10 +107,14 @@ uint16_t rb_size(const RingBuffer *b)
(b->size - b->start) + b->end; (b->size - b->start) + b->end;
} }
uint16_t rb_data(const RingBuffer *b, void **dest) uint16_t rb_data(const RingBuffer *b, void **dest, uint16_t dest_size)
{ {
uint16_t i; uint16_t i;
const uint16_t size = rb_size(b); uint16_t size = rb_size(b);
if (size > dest_size) {
size = dest_size;
}
for (i = 0; i < size; ++i) { for (i = 0; i < size; ++i) {
dest[i] = b->data[(b->start + i) % b->size]; dest[i] = b->data[(b->start + i) % b->size];

View File

@@ -24,7 +24,7 @@ bool rb_read(RingBuffer *_Nonnull b, void *_Nonnull *_Nullable p);
RingBuffer *_Nullable rb_new(int size); RingBuffer *_Nullable rb_new(int size);
void rb_kill(RingBuffer *_Nullable b); void rb_kill(RingBuffer *_Nullable b);
uint16_t rb_size(const RingBuffer *_Nonnull b); uint16_t rb_size(const RingBuffer *_Nonnull b);
uint16_t rb_data(const RingBuffer *_Nonnull b, void *_Nonnull *_Nonnull dest); uint16_t rb_data(const RingBuffer *_Nonnull b, void *_Nonnull *_Nonnull dest, uint16_t dest_size);
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@@ -4,8 +4,11 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cstdint>
#include <vector> #include <vector>
#include "../toxcore/attributes.h"
namespace { namespace {
template <typename T> template <typename T>
@@ -23,37 +26,39 @@ public:
bool full() const { return rb_full(rb_); } bool full() const { return rb_full(rb_); }
bool empty() const { return rb_empty(rb_); } bool empty() const { return rb_empty(rb_); }
T *write(T *p) { return static_cast<T *>(rb_write(rb_, p)); } T *_Nullable write(T *_Nullable p) { return static_cast<T *>(rb_write(rb_, p)); }
bool read(T **p) bool read(T *_Nullable *_Nonnull p)
{ {
void *vp; void *_Nullable vp;
bool res = rb_read(rb_, &vp); bool res = rb_read(rb_, &vp);
*p = static_cast<T *>(vp); *p = static_cast<T *>(vp);
return res; return res;
} }
uint16_t size() const { return rb_size(rb_); } std::uint16_t size() const { return rb_size(rb_); }
uint16_t data(T **dest) const std::uint16_t data(T *_Nullable *_Nonnull dest, std::uint16_t dest_size) const
{ {
std::vector<void *> vdest(size()); const std::uint16_t current_size = std::min(size(), dest_size);
uint16_t res = rb_data(rb_, vdest.data()); std::vector<void *_Nullable> vdest(current_size);
for (uint16_t i = 0; i < size(); i++) { const std::uint16_t res = rb_data(rb_, vdest.data(), current_size);
for (std::uint16_t i = 0; i < res; i++) {
dest[i] = static_cast<T *>(vdest.at(i)); dest[i] = static_cast<T *>(vdest.at(i));
} }
return res; return res;
} }
bool contains(T *p) const bool contains(T *_Nullable p) const
{ {
std::vector<T *> elts(size()); const std::uint16_t current_size = size();
data(elts.data()); std::vector<T *_Nullable> elts(current_size);
data(elts.data(), current_size);
return std::find(elts.begin(), elts.end(), p) != elts.end(); return std::find(elts.begin(), elts.end(), p) != elts.end();
} }
bool ok() const { return rb_ != nullptr; } bool ok() const { return rb_ != nullptr; }
private: private:
RingBuffer *rb_; RingBuffer *_Nullable rb_;
}; };
TEST(RingBuffer, EmptyBufferReportsEmpty) TEST(RingBuffer, EmptyBufferReportsEmpty)
@@ -211,4 +216,27 @@ TEST(RingBuffer, SizeIsLimitedByMaxSize)
EXPECT_EQ(rb.size(), 4); EXPECT_EQ(rb.size(), 4);
} }
TEST(RingBuffer, NewWithInvalidSizeReturnsNull)
{
EXPECT_EQ(nullptr, rb_new(-1));
EXPECT_EQ(nullptr, rb_new(65535));
}
TEST(RingBuffer, DataWithSmallerDestSizeIsTruncated)
{
TypedRingBuffer<int *> rb(4);
ASSERT_TRUE(rb.ok());
int values[] = {1, 2, 3, 4};
rb.write(&values[0]);
rb.write(&values[1]);
rb.write(&values[2]);
rb.write(&values[3]);
int *dest[2];
std::uint16_t res = rb.data(dest, 2);
EXPECT_EQ(res, 2);
EXPECT_EQ(dest[0], &values[0]);
EXPECT_EQ(dest[1], &values[1]);
}
} // namespace } // namespace

View File

@@ -4,9 +4,12 @@
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <cstddef>
#include <cstdint>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h" #include "../toxcore/logger.h"
#include "../toxcore/mono_time.h" #include "../toxcore/mono_time.h"
#include "../toxcore/os_memory.h" #include "../toxcore/os_memory.h"
@@ -19,7 +22,7 @@ class RtpBench : public benchmark::Fixture {
public: public:
void SetUp(const ::benchmark::State &) override void SetUp(const ::benchmark::State &) override
{ {
const Memory *mem = os_memory(); const Memory *_Nonnull mem = os_memory();
log = logger_new(mem); log = logger_new(mem);
mono_time = mono_time_new(mem, nullptr, nullptr); mono_time = mono_time_new(mem, nullptr, nullptr);
@@ -37,19 +40,19 @@ public:
logger_kill(log); logger_kill(log);
} }
Logger *log = nullptr; Logger *_Nullable log = nullptr;
Mono_Time *mono_time = nullptr; Mono_Time *_Nullable mono_time = nullptr;
RTPSession *session = nullptr; RTPSession *_Nullable session = nullptr;
RtpMock mock; RtpMock mock;
}; };
BENCHMARK_DEFINE_F(RtpBench, SendData)(benchmark::State &state) BENCHMARK_DEFINE_F(RtpBench, SendData)(benchmark::State &state)
{ {
size_t data_size = static_cast<size_t>(state.range(0)); std::size_t data_size = static_cast<std::size_t>(state.range(0));
std::vector<uint8_t> data(data_size, 0xAA); std::vector<std::uint8_t> data(data_size, 0xAA);
for (auto _ : state) { for (auto _ : state) {
rtp_send_data(log, session, data.data(), static_cast<uint32_t>(data.size()), false); rtp_send_data(log, session, data.data(), static_cast<std::uint32_t>(data.size()), false);
benchmark::DoNotOptimize(mock.captured_packets.back()); benchmark::DoNotOptimize(mock.captured_packets.back());
} }
} }
@@ -57,10 +60,10 @@ BENCHMARK_REGISTER_F(RtpBench, SendData)->Arg(100)->Arg(1000)->Arg(5000);
BENCHMARK_DEFINE_F(RtpBench, ReceivePacket)(benchmark::State &state) BENCHMARK_DEFINE_F(RtpBench, ReceivePacket)(benchmark::State &state)
{ {
size_t data_size = static_cast<size_t>(state.range(0)); std::size_t data_size = static_cast<std::size_t>(state.range(0));
std::vector<uint8_t> data(data_size, 0xAA); std::vector<std::uint8_t> data(data_size, 0xAA);
rtp_send_data(log, session, data.data(), static_cast<uint32_t>(data.size()), false); rtp_send_data(log, session, data.data(), static_cast<std::uint32_t>(data.size()), false);
std::vector<uint8_t> packet = mock.captured_packets.back(); std::vector<std::uint8_t> packet = mock.captured_packets.back();
for (auto _ : state) { for (auto _ : state) {
rtp_receive_packet(session, packet.data(), packet.size()); rtp_receive_packet(session, packet.data(), packet.size());

View File

@@ -1,10 +1,13 @@
#include "rtp.h" #include "rtp.h"
#include <cstddef>
#include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "../testing/support/public/fuzz_data.hh" #include "../testing/support/public/fuzz_data.hh"
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h" #include "../toxcore/logger.h"
#include "../toxcore/mono_time.h" #include "../toxcore/mono_time.h"
#include "../toxcore/os_memory.h" #include "../toxcore/os_memory.h"
@@ -15,12 +18,14 @@ using tox::test::Fuzz_Data;
struct MockSessionData { }; struct MockSessionData { };
static int mock_send_packet(void * /*user_data*/, const uint8_t * /*data*/, uint16_t /*length*/) static int mock_send_packet(
void *_Nullable /*user_data*/, const std::uint8_t *_Nonnull /*data*/, std::uint16_t /*length*/)
{ {
return 0; return 0;
} }
static int mock_m_cb(const Mono_Time * /*mono_time*/, void * /*cs*/, RTPMessage *msg) static int mock_m_cb(
const Mono_Time *_Nonnull /*mono_time*/, void *_Nullable /*cs*/, RTPMessage *_Nonnull msg)
{ {
std::free(msg); std::free(msg);
return 0; return 0;
@@ -28,28 +33,28 @@ static int mock_m_cb(const Mono_Time * /*mono_time*/, void * /*cs*/, RTPMessage
void fuzz_rtp_receive(Fuzz_Data &input) void fuzz_rtp_receive(Fuzz_Data &input)
{ {
const Memory *mem = os_memory(); const Memory *_Nonnull mem = os_memory();
struct LoggerDeleter { struct LoggerDeleter {
void operator()(Logger *l) { logger_kill(l); } void operator()(Logger *_Nullable l) { logger_kill(l); }
}; };
std::unique_ptr<Logger, LoggerDeleter> log(logger_new(mem)); std::unique_ptr<Logger, LoggerDeleter> log(logger_new(mem));
auto time_cb = [](void *) -> uint64_t { return 0; }; auto time_cb = [](void *_Nullable) -> std::uint64_t { return 0; };
struct MonoTimeDeleter { struct MonoTimeDeleter {
const Memory *m; const Memory *_Nonnull m;
void operator()(Mono_Time *t) { mono_time_free(m, t); } void operator()(Mono_Time *_Nullable t) { mono_time_free(m, t); }
}; };
std::unique_ptr<Mono_Time, MonoTimeDeleter> mono_time( std::unique_ptr<Mono_Time, MonoTimeDeleter> mono_time(
mono_time_new(mem, time_cb, nullptr), MonoTimeDeleter{mem}); mono_time_new(mem, time_cb, nullptr), MonoTimeDeleter{mem});
MockSessionData sd; MockSessionData sd;
CONSUME1_OR_RETURN(uint8_t, payload_type_byte, input); CONSUME1_OR_RETURN(std::uint8_t, payload_type_byte, input);
int payload_type = (payload_type_byte % 2 == 0) ? RTP_TYPE_AUDIO : RTP_TYPE_VIDEO; int payload_type = (payload_type_byte % 2 == 0) ? RTP_TYPE_AUDIO : RTP_TYPE_VIDEO;
struct RtpSessionDeleter { struct RtpSessionDeleter {
Logger *l; Logger *_Nonnull l;
void operator()(RTPSession *s) { rtp_kill(l, s); } void operator()(RTPSession *_Nullable s) { rtp_kill(l, s); }
}; };
std::unique_ptr<RTPSession, RtpSessionDeleter> session( std::unique_ptr<RTPSession, RtpSessionDeleter> session(
rtp_new(log.get(), payload_type, mono_time.get(), mock_send_packet, &sd, nullptr, nullptr, rtp_new(log.get(), payload_type, mono_time.get(), mock_send_packet, &sd, nullptr, nullptr,
@@ -57,7 +62,7 @@ void fuzz_rtp_receive(Fuzz_Data &input)
RtpSessionDeleter{log.get()}); RtpSessionDeleter{log.get()});
while (!input.empty()) { while (!input.empty()) {
CONSUME1_OR_RETURN(uint16_t, len, input); CONSUME1_OR_RETURN(std::uint16_t, len, input);
if (input.size() < len) { if (input.size() < len) {
len = input.size(); len = input.size();
@@ -67,16 +72,16 @@ void fuzz_rtp_receive(Fuzz_Data &input)
break; break;
} }
const uint8_t *pkt_data = input.consume(__func__, len); const std::uint8_t *pkt_data = input.consume(__func__, len);
rtp_receive_packet(session.get(), pkt_data, len); rtp_receive_packet(session.get(), pkt_data, len);
} }
} }
} // namespace } // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size);
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size)
{ {
Fuzz_Data input(data, size); Fuzz_Data input(data, size);
fuzz_rtp_receive(input); fuzz_rtp_receive(input);

View File

@@ -3,9 +3,14 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <algorithm> #include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h" #include "../toxcore/logger.h"
#include "../toxcore/mono_time.h" #include "../toxcore/mono_time.h"
#include "../toxcore/net_crypto.h" #include "../toxcore/net_crypto.h"
@@ -17,45 +22,47 @@ struct MockSessionData {
MockSessionData(); MockSessionData();
~MockSessionData(); ~MockSessionData();
std::vector<std::vector<uint8_t>> sent_packets; std::vector<std::vector<std::uint8_t>> sent_packets;
std::vector<std::vector<uint8_t>> received_frames; std::vector<std::vector<std::uint8_t>> received_frames;
std::vector<uint16_t> received_frame_lengths; std::vector<std::uint16_t> received_frame_lengths;
std::vector<uint32_t> received_32bit_lengths; std::vector<std::uint32_t> received_32bit_lengths;
std::vector<uint32_t> received_full_lengths; std::vector<std::uint32_t> received_full_lengths;
std::vector<uint16_t> received_sequnums; std::vector<std::uint16_t> received_sequnums;
std::vector<uint8_t> received_pts; std::vector<std::uint8_t> received_pts;
std::vector<uint64_t> received_flags; std::vector<std::uint64_t> received_flags;
uint32_t total_bytes_received = 0; std::uint32_t total_bytes_received = 0;
uint32_t total_bytes_lost = 0; std::uint32_t total_bytes_lost = 0;
}; };
MockSessionData::MockSessionData() = default; MockSessionData::MockSessionData() = default;
MockSessionData::~MockSessionData() = default; MockSessionData::~MockSessionData() = default;
static int mock_send_packet(void *user_data, const uint8_t *data, uint16_t length) static int mock_send_packet(
void *_Nullable user_data, const std::uint8_t *_Nonnull data, std::uint16_t length)
{ {
auto *sd = static_cast<MockSessionData *>(user_data); auto *sd = static_cast<MockSessionData *>(user_data);
sd->sent_packets.emplace_back(data, data + length); sd->sent_packets.emplace_back(data, data + length);
return 0; return 0;
} }
static int mock_m_cb(const Mono_Time * /*mono_time*/, void *cs, RTPMessage *msg) static int mock_m_cb(
const Mono_Time *_Nonnull /*mono_time*/, void *_Nullable cs, RTPMessage *_Nonnull msg)
{ {
auto *sd = static_cast<MockSessionData *>(cs); auto *sd = static_cast<MockSessionData *>(cs);
sd->received_pts.push_back(rtp_message_pt(msg)); sd->received_pts.push_back(rtp_message_pt(msg));
sd->received_flags.push_back(rtp_message_flags(msg)); sd->received_flags.push_back(rtp_message_flags(msg));
const uint8_t *data = rtp_message_data(msg); const std::uint8_t *_Nonnull data = rtp_message_data(msg);
uint32_t len = rtp_message_len(msg); std::uint32_t len = rtp_message_len(msg);
uint32_t full_len = rtp_message_data_length_full(msg); std::uint32_t full_len = rtp_message_data_length_full(msg);
// If full_len is not set (old protocol), use len // If full_len is not set (old protocol), use len
uint32_t actual_len = (full_len > 0) ? full_len : len; std::uint32_t actual_len = (full_len > 0) ? full_len : len;
sd->received_frames.emplace_back(data, data + actual_len); sd->received_frames.emplace_back(data, data + actual_len);
sd->received_frame_lengths.push_back(static_cast<uint16_t>(len)); sd->received_frame_lengths.push_back(static_cast<std::uint16_t>(len));
sd->received_32bit_lengths.push_back(len); sd->received_32bit_lengths.push_back(len);
sd->received_full_lengths.push_back(full_len); sd->received_full_lengths.push_back(full_len);
sd->received_sequnums.push_back(rtp_message_sequnum(msg)); sd->received_sequnums.push_back(rtp_message_sequnum(msg));
@@ -64,13 +71,13 @@ static int mock_m_cb(const Mono_Time * /*mono_time*/, void *cs, RTPMessage *msg)
return 0; return 0;
} }
static void mock_add_recv(void *user_data, uint32_t bytes) static void mock_add_recv(void *_Nullable user_data, std::uint32_t bytes)
{ {
auto *sd = static_cast<MockSessionData *>(user_data); auto *sd = static_cast<MockSessionData *>(user_data);
sd->total_bytes_received += bytes; sd->total_bytes_received += bytes;
} }
static void mock_add_lost(void *user_data, uint32_t bytes) static void mock_add_lost(void *_Nullable user_data, std::uint32_t bytes)
{ {
auto *sd = static_cast<MockSessionData *>(user_data); auto *sd = static_cast<MockSessionData *>(user_data);
sd->total_bytes_lost += bytes; sd->total_bytes_lost += bytes;
@@ -88,13 +95,13 @@ protected:
void TearDown() override void TearDown() override
{ {
const Memory *mem = os_memory(); const Memory *_Nonnull mem = os_memory();
mono_time_free(mem, mono_time); mono_time_free(mem, mono_time);
logger_kill(log); logger_kill(log);
} }
Logger *log; Logger *_Nullable log;
Mono_Time *mono_time; Mono_Time *_Nullable mono_time;
}; };
TEST_F(RtpPublicTest, BasicAudioSendReceive) TEST_F(RtpPublicTest, BasicAudioSendReceive)
@@ -104,7 +111,7 @@ TEST_F(RtpPublicTest, BasicAudioSendReceive)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb); mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
ASSERT_NE(session, nullptr); ASSERT_NE(session, nullptr);
uint8_t data[] = "Hello RTP"; std::uint8_t data[] = "Hello RTP";
rtp_send_data(log, session, data, sizeof(data), false); rtp_send_data(log, session, data, sizeof(data), false);
ASSERT_EQ(sd.sent_packets.size(), 1); ASSERT_EQ(sd.sent_packets.size(), 1);
@@ -128,9 +135,9 @@ TEST_F(RtpPublicTest, LargeVideoFrameFragmentation)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb); mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
// Frame larger than MAX_CRYPTO_DATA_SIZE // Frame larger than MAX_CRYPTO_DATA_SIZE
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 500; const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 500;
std::vector<uint8_t> data(frame_size); std::vector<std::uint8_t> data(frame_size);
for (uint32_t i = 0; i < frame_size; ++i) for (std::uint32_t i = 0; i < frame_size; ++i)
data[i] = i & 0xFF; data[i] = i & 0xFF;
rtp_send_data(log, session, data.data(), frame_size, true); rtp_send_data(log, session, data.data(), frame_size, true);
@@ -158,8 +165,8 @@ TEST_F(RtpPublicTest, OutOfOrderVideoPackets)
RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd, RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd,
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb); mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100; const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<uint8_t> data(frame_size, 0x55); std::vector<std::uint8_t> data(frame_size, 0x55);
rtp_send_data(log, session, data.data(), frame_size, false); rtp_send_data(log, session, data.data(), frame_size, false);
ASSERT_EQ(sd.sent_packets.size(), 2); ASSERT_EQ(sd.sent_packets.size(), 2);
@@ -183,15 +190,15 @@ TEST_F(RtpPublicTest, HandlingInvalidPackets)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb); mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
// Packet too short to even contain the Tox packet ID // Packet too short to even contain the Tox packet ID
uint8_t empty[1]; std::uint8_t empty[1];
rtp_receive_packet(session, empty, 0); rtp_receive_packet(session, empty, 0);
// Packet too short (less than RTP_HEADER_SIZE + 1) // Packet too short (less than RTP_HEADER_SIZE + 1)
uint8_t short_pkt[10] = {RTP_TYPE_AUDIO}; std::uint8_t short_pkt[10] = {RTP_TYPE_AUDIO};
rtp_receive_packet(session, short_pkt, sizeof(short_pkt)); rtp_receive_packet(session, short_pkt, sizeof(short_pkt));
// Wrong packet ID (Tox level) // Wrong packet ID (Tox level)
uint8_t wrong_id[RTP_HEADER_SIZE + 10]; std::uint8_t wrong_id[RTP_HEADER_SIZE + 10];
std::memset(wrong_id, 0, sizeof(wrong_id)); std::memset(wrong_id, 0, sizeof(wrong_id));
wrong_id[0] = RTP_TYPE_VIDEO; // Session expects AUDIO wrong_id[0] = RTP_TYPE_VIDEO; // Session expects AUDIO
rtp_receive_packet(session, wrong_id, sizeof(wrong_id)); rtp_receive_packet(session, wrong_id, sizeof(wrong_id));
@@ -239,8 +246,8 @@ TEST_F(RtpPublicTest, LargeAudioFragmentationOldProtocol)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb); mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
// Audio doesn't use RTP_LARGE_FRAME, so it uses the old 16-bit offset/length fields // Audio doesn't use RTP_LARGE_FRAME, so it uses the old 16-bit offset/length fields
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 500; const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 500;
std::vector<uint8_t> data(frame_size, 0x44); std::vector<std::uint8_t> data(frame_size, 0x44);
rtp_send_data(log, session, data.data(), frame_size, false); rtp_send_data(log, session, data.data(), frame_size, false);
@@ -263,17 +270,17 @@ TEST_F(RtpPublicTest, WorkBufferEvictionAndKeyframePreservation)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb); mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
struct TimeMock { struct TimeMock {
uint64_t t; std::uint64_t t;
} tm = {1000}; } tm = {1000};
auto time_cb = [](void *ud) -> uint64_t { return static_cast<TimeMock *>(ud)->t; }; auto time_cb = [](void *ud) -> std::uint64_t { return static_cast<TimeMock *>(ud)->t; };
mono_time_set_current_time_callback(mono_time, time_cb, &tm); mono_time_set_current_time_callback(mono_time, time_cb, &tm);
mono_time_update(mono_time); mono_time_update(mono_time);
// USED_RTP_WORKBUFFER_COUNT is 3. // USED_RTP_WORKBUFFER_COUNT is 3.
// 1. Start a keyframe (frame 0) but don't finish it. // 1. Start a keyframe (frame 0) but don't finish it.
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100; const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<uint8_t> kf_data(frame_size, 0x11); std::vector<std::uint8_t> kf_data(frame_size, 0x11);
rtp_send_data(log, session, kf_data.data(), frame_size, true); rtp_send_data(log, session, kf_data.data(), frame_size, true);
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size()); rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
sd.sent_packets.clear(); sd.sent_packets.clear();
@@ -282,7 +289,7 @@ TEST_F(RtpPublicTest, WorkBufferEvictionAndKeyframePreservation)
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
tm.t += 1; tm.t += 1;
mono_time_update(mono_time); mono_time_update(mono_time);
std::vector<uint8_t> if_data(frame_size, 0x20 + i); std::vector<std::uint8_t> if_data(frame_size, 0x20 + i);
rtp_send_data(log, session, if_data.data(), frame_size, false); rtp_send_data(log, session, if_data.data(), frame_size, false);
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size()); rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
sd.sent_packets.clear(); sd.sent_packets.clear();
@@ -297,7 +304,7 @@ TEST_F(RtpPublicTest, WorkBufferEvictionAndKeyframePreservation)
// The new IF should be DROPPED because there's no space and slot 0 is a protected KF. // The new IF should be DROPPED because there's no space and slot 0 is a protected KF.
tm.t += 1; tm.t += 1;
mono_time_update(mono_time); mono_time_update(mono_time);
std::vector<uint8_t> if3_data(frame_size, 0x33); std::vector<std::uint8_t> if3_data(frame_size, 0x33);
rtp_send_data(log, session, if3_data.data(), frame_size, false); rtp_send_data(log, session, if3_data.data(), frame_size, false);
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size()); rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
sd.sent_packets.clear(); sd.sent_packets.clear();
@@ -311,7 +318,7 @@ TEST_F(RtpPublicTest, WorkBufferEvictionAndKeyframePreservation)
// 5. Start another frame (frame 4). // 5. Start another frame (frame 4).
// Now the old KF should be evicted and processed (sent to callback), making room. // Now the old KF should be evicted and processed (sent to callback), making room.
std::vector<uint8_t> if4_data(frame_size, 0x44); std::vector<std::uint8_t> if4_data(frame_size, 0x44);
rtp_send_data(log, session, if4_data.data(), frame_size, false); rtp_send_data(log, session, if4_data.data(), frame_size, false);
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size()); rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
@@ -328,7 +335,7 @@ TEST_F(RtpPublicTest, BwcReporting)
RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd, RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd,
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb); mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
uint8_t data[] = "test"; std::uint8_t data[] = "test";
// DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT is 10. // DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT is 10.
// Packets 1-9 are dismissed. Packet 10 is reported. // Packets 1-9 are dismissed. Packet 10 is reported.
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
@@ -351,8 +358,8 @@ TEST_F(RtpPublicTest, OldProtocolEdgeCases)
nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, &sd, mock_m_cb);
// 1. Multipart message interrupted by a newer message. // 1. Multipart message interrupted by a newer message.
const uint32_t large_size = 5000; const std::uint32_t large_size = 5000;
std::vector<uint8_t> data(large_size, 0xAA); std::vector<std::uint8_t> data(large_size, 0xAA);
rtp_send_data(log, session, data.data(), large_size, false); rtp_send_data(log, session, data.data(), large_size, false);
ASSERT_GE(sd.sent_packets.size(), 2); ASSERT_GE(sd.sent_packets.size(), 2);
@@ -361,7 +368,7 @@ TEST_F(RtpPublicTest, OldProtocolEdgeCases)
EXPECT_EQ(sd.received_frames.size(), 0); EXPECT_EQ(sd.received_frames.size(), 0);
// Send a second message (newer) // Send a second message (newer)
std::vector<uint8_t> data2 = {0x1, 0x2, 0x3}; std::vector<std::uint8_t> data2 = {0x1, 0x2, 0x3};
rtp_send_data(log, session, data2.data(), data2.size(), false); rtp_send_data(log, session, data2.data(), data2.size(), false);
// The second message is the last one in sent_packets. // The second message is the last one in sent_packets.
rtp_receive_packet(session, sd.sent_packets.back().data(), sd.sent_packets.back().size()); rtp_receive_packet(session, sd.sent_packets.back().data(), sd.sent_packets.back().size());
@@ -370,7 +377,7 @@ TEST_F(RtpPublicTest, OldProtocolEdgeCases)
ASSERT_EQ(sd.received_frames.size(), 2); ASSERT_EQ(sd.received_frames.size(), 2);
EXPECT_LT(sd.received_frame_lengths[0], large_size); EXPECT_LT(sd.received_frame_lengths[0], large_size);
EXPECT_EQ(sd.received_pts[0], RTP_TYPE_AUDIO % 128); EXPECT_EQ(sd.received_pts[0], RTP_TYPE_AUDIO % 128);
EXPECT_EQ(sd.received_frame_lengths[1], static_cast<uint16_t>(data2.size())); EXPECT_EQ(sd.received_frame_lengths[1], static_cast<std::uint16_t>(data2.size()));
// 2. Discarding old message part // 2. Discarding old message part
sd.received_frames.clear(); sd.received_frames.clear();
@@ -379,7 +386,7 @@ TEST_F(RtpPublicTest, OldProtocolEdgeCases)
sd.received_pts.clear(); sd.received_pts.clear();
// Send a very new message. // Send a very new message.
std::vector<uint8_t> data3 = {0xDE, 0xAD}; std::vector<std::uint8_t> data3 = {0xDE, 0xAD};
rtp_send_data(log, session, data3.data(), data3.size(), false); rtp_send_data(log, session, data3.data(), data3.size(), false);
rtp_receive_packet(session, sd.sent_packets.back().data(), sd.sent_packets.back().size()); rtp_receive_packet(session, sd.sent_packets.back().data(), sd.sent_packets.back().size());
EXPECT_EQ(sd.received_frames.size(), 1); EXPECT_EQ(sd.received_frames.size(), 1);
@@ -399,13 +406,19 @@ TEST_F(RtpPublicTest, MoreInvalidPackets)
nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, &sd, mock_m_cb);
// Get a valid packet to start with // Get a valid packet to start with
uint8_t data[] = "test"; std::uint8_t data[] = "test";
rtp_send_data(log, session, data, sizeof(data), false); rtp_send_data(log, session, data, sizeof(data), false);
std::vector<uint8_t> valid_pkt = sd.sent_packets[0]; ASSERT_FALSE(sd.sent_packets.empty());
if (sd.sent_packets.empty())
return;
const std::vector<std::uint8_t> &src_pkt = sd.sent_packets[0];
if (src_pkt.size() > 65536)
return;
std::vector<std::uint8_t> valid_pkt(src_pkt.begin(), src_pkt.end());
sd.sent_packets.clear(); sd.sent_packets.clear();
// 1. RTPHeader packet type and Tox protocol packet type do not agree // 1. RTPHeader packet type and Tox protocol packet type do not agree
std::vector<uint8_t> bad_pkt_1 = valid_pkt; std::vector<std::uint8_t> bad_pkt_1 = valid_pkt;
bad_pkt_1[0] = RTP_TYPE_AUDIO; // Tox ID says AUDIO, but header (byte 2) still says VIDEO bad_pkt_1[0] = RTP_TYPE_AUDIO; // Tox ID says AUDIO, but header (byte 2) still says VIDEO
rtp_receive_packet(session, bad_pkt_1.data(), bad_pkt_1.size()); rtp_receive_packet(session, bad_pkt_1.data(), bad_pkt_1.size());
EXPECT_EQ(sd.received_frames.size(), 0); EXPECT_EQ(sd.received_frames.size(), 0);
@@ -421,7 +434,7 @@ TEST_F(RtpPublicTest, MoreInvalidPackets)
// 3. Invalid video packet: offset >= length // 3. Invalid video packet: offset >= length
// From rtp.c, offset_full is at byte 20 and data_length_full at byte 24 of the RTP header. // From rtp.c, offset_full is at byte 20 and data_length_full at byte 24 of the RTP header.
// The RTP header starts at index 1 of the packet. // The RTP header starts at index 1 of the packet.
std::vector<uint8_t> bad_pkt_3 = valid_pkt; std::vector<std::uint8_t> bad_pkt_3 = valid_pkt;
// Set offset (bytes 21-24) to be equal to length (bytes 25-28) // Set offset (bytes 21-24) to be equal to length (bytes 25-28)
// For a small packet, both are usually 0 and sizeof(data) respectively. // For a small packet, both are usually 0 and sizeof(data) respectively.
// Let's just make offset very large. // Let's just make offset very large.
@@ -437,10 +450,16 @@ TEST_F(RtpPublicTest, MoreInvalidPackets)
nullptr, nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, nullptr, &sd, mock_m_cb);
rtp_send_data(log, session_audio2, data, sizeof(data), false); rtp_send_data(log, session_audio2, data, sizeof(data), false);
std::vector<uint8_t> audio_pkt = sd.sent_packets[0]; ASSERT_FALSE(sd.sent_packets.empty());
if (sd.sent_packets.empty())
return;
const std::vector<std::uint8_t> &src_audio = sd.sent_packets[0];
if (src_audio.size() > 65536)
return;
std::vector<std::uint8_t> audio_pkt(src_audio.begin(), src_audio.end());
sd.sent_packets.clear(); sd.sent_packets.clear();
std::vector<uint8_t> bad_pkt_4 = audio_pkt; std::vector<std::uint8_t> bad_pkt_4 = audio_pkt;
// Set offset_lower (byte 1 + 76) > data_length_lower (byte 1 + 78) // Set offset_lower (byte 1 + 76) > data_length_lower (byte 1 + 78)
bad_pkt_4[1 + 76] = 0x01; // offset = 256 bad_pkt_4[1 + 76] = 0x01; // offset = 256
bad_pkt_4[1 + 77] = 0x00; bad_pkt_4[1 + 77] = 0x00;
@@ -461,20 +480,20 @@ TEST_F(RtpPublicTest, VideoJitterBufferEdgeCases)
nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, &sd, mock_m_cb);
// Use a large frame size to force fragmentation and keep slots occupied // Use a large frame size to force fragmentation and keep slots occupied
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100; const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<uint8_t> data(frame_size, 0); std::vector<std::uint8_t> data(frame_size, 0);
// Advancing time for subsequent frames // Advancing time for subsequent frames
struct TimeMock { struct TimeMock {
uint64_t t; std::uint64_t t;
} tm = {1000}; } tm = {1000};
auto time_cb = [](void *ud) -> uint64_t { return static_cast<TimeMock *>(ud)->t; }; auto time_cb = [](void *ud) -> std::uint64_t { return static_cast<TimeMock *>(ud)->t; };
mono_time_set_current_time_callback(mono_time, time_cb, &tm); mono_time_set_current_time_callback(mono_time, time_cb, &tm);
mono_time_update(mono_time); mono_time_update(mono_time);
// 1. Packet too old for work buffer // 1. Packet too old for work buffer
rtp_send_data(log, session, data.data(), frame_size, false); // Time 1000ms rtp_send_data(log, session, data.data(), frame_size, false); // Time 1000ms
std::vector<uint8_t> old_pkt = sd.sent_packets[0]; std::vector<std::uint8_t> old_pkt = sd.sent_packets[0];
// Receive only first part to keep slot occupied // Receive only first part to keep slot occupied
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size()); rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
EXPECT_EQ(sd.received_frames.size(), 0); EXPECT_EQ(sd.received_frames.size(), 0);
@@ -501,7 +520,7 @@ TEST_F(RtpPublicTest, VideoJitterBufferEdgeCases)
nullptr, &sd, mock_m_cb); nullptr, &sd, mock_m_cb);
// Fill slot 0 with an incomplete Keyframe // Fill slot 0 with an incomplete Keyframe
std::vector<uint8_t> kf_data(frame_size, 0x11); std::vector<std::uint8_t> kf_data(frame_size, 0x11);
tm.t = 3000; tm.t = 3000;
mono_time_update(mono_time); mono_time_update(mono_time);
rtp_send_data(log, session, kf_data.data(), frame_size, true); rtp_send_data(log, session, kf_data.data(), frame_size, true);
@@ -510,7 +529,7 @@ TEST_F(RtpPublicTest, VideoJitterBufferEdgeCases)
sd.sent_packets.clear(); sd.sent_packets.clear();
// Now send a complete Interframe // Now send a complete Interframe
std::vector<uint8_t> if_data(10, 0x22); std::vector<std::uint8_t> if_data(10, 0x22);
tm.t += 1; tm.t += 1;
mono_time_update(mono_time); mono_time_update(mono_time);
rtp_send_data(log, session, if_data.data(), if_data.size(), false); rtp_send_data(log, session, if_data.data(), if_data.size(), false);
@@ -531,9 +550,9 @@ TEST_F(RtpPublicTest, OldProtocolCorruption)
// 1. Packet claiming a smaller length than its payload. // 1. Packet claiming a smaller length than its payload.
// This triggers the condition that previously caused a DoS crash via // This triggers the condition that previously caused a DoS crash via
// an assertion failure in new_message(). // an assertion failure in new_message().
uint8_t data[10] = {0}; std::uint8_t data[10] = {0};
rtp_send_data(log, session, data, sizeof(data), false); rtp_send_data(log, session, data, sizeof(data), false);
std::vector<uint8_t> pkt = sd.sent_packets[0]; std::vector<std::uint8_t> pkt = sd.sent_packets[0];
sd.sent_packets.clear(); sd.sent_packets.clear();
// Modify data_length_lower (byte 1 + 78) to be 2, while payload is 10. // Modify data_length_lower (byte 1 + 78) to be 2, while payload is 10.
@@ -545,8 +564,8 @@ TEST_F(RtpPublicTest, OldProtocolCorruption)
EXPECT_EQ(sd.received_frames.size(), 0); EXPECT_EQ(sd.received_frames.size(), 0);
// 2. Corruption check for an EXISTING multipart message. // 2. Corruption check for an EXISTING multipart message.
const uint32_t multipart_size = 5000; const std::uint32_t multipart_size = 5000;
std::vector<uint8_t> multipart_data(multipart_size, 0xBB); std::vector<std::uint8_t> multipart_data(multipart_size, 0xBB);
rtp_send_data(log, session, multipart_data.data(), multipart_size, false); rtp_send_data(log, session, multipart_data.data(), multipart_size, false);
// Receive the first part // Receive the first part
@@ -554,7 +573,7 @@ TEST_F(RtpPublicTest, OldProtocolCorruption)
EXPECT_EQ(sd.received_frames.size(), 0); EXPECT_EQ(sd.received_frames.size(), 0);
// Now receive a corrupted second part that claims a weird offset // Now receive a corrupted second part that claims a weird offset
std::vector<uint8_t> corrupted_part = sd.sent_packets[1]; std::vector<std::uint8_t> corrupted_part = sd.sent_packets[1];
// offset_lower is at byte 76. Set it beyond data_length_lower. // offset_lower is at byte 76. Set it beyond data_length_lower.
corrupted_part[1 + 76] = 0xFF; corrupted_part[1 + 76] = 0xFF;
corrupted_part[1 + 77] = 0xFF; corrupted_part[1 + 77] = 0xFF;
@@ -572,11 +591,11 @@ TEST_F(RtpPublicTest, HugeVideoFrameInternalLength)
RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd, nullptr, RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd, nullptr,
nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, &sd, mock_m_cb);
// Frame larger than 64KB (uint16_t max) // Frame larger than 64KB (std::uint16_t max)
const uint32_t huge_frame_size = 65540; const std::uint32_t huge_frame_size = 65540;
std::vector<uint8_t> data(huge_frame_size); std::vector<std::uint8_t> data(huge_frame_size);
for (uint32_t i = 0; i < huge_frame_size; ++i) { for (std::uint32_t i = 0; i < huge_frame_size; ++i) {
data[i] = static_cast<uint8_t>(i & 0xFF); data[i] = static_cast<std::uint8_t>(i & 0xFF);
} }
rtp_send_data(log, session, data.data(), huge_frame_size, false); rtp_send_data(log, session, data.data(), huge_frame_size, false);
@@ -592,7 +611,7 @@ TEST_F(RtpPublicTest, HugeVideoFrameInternalLength)
ASSERT_EQ(sd.received_frames.size(), 1); ASSERT_EQ(sd.received_frames.size(), 1);
// This verifies that the internal 32-bit length is working correctly. // This verifies that the internal 32-bit length is working correctly.
// We cast huge_frame_size to 16-bit to show what it would have been if it truncated. // We cast huge_frame_size to 16-bit to show what it would have been if it truncated.
EXPECT_NE(static_cast<uint16_t>(sd.received_32bit_lengths[0]), huge_frame_size); EXPECT_NE(static_cast<std::uint16_t>(sd.received_32bit_lengths[0]), huge_frame_size);
EXPECT_EQ(sd.received_32bit_lengths[0], huge_frame_size); EXPECT_EQ(sd.received_32bit_lengths[0], huge_frame_size);
EXPECT_EQ(sd.received_full_lengths[0], huge_frame_size); EXPECT_EQ(sd.received_full_lengths[0], huge_frame_size);
EXPECT_EQ(sd.received_frames[0].size(), huge_frame_size); EXPECT_EQ(sd.received_frames[0].size(), huge_frame_size);
@@ -609,10 +628,10 @@ TEST_F(RtpPublicTest, HeapBufferOverflowRaw)
// Manually construct a malicious packet. // Manually construct a malicious packet.
// 1 byte ID + 80 bytes Header + 200 bytes Payload // 1 byte ID + 80 bytes Header + 200 bytes Payload
const size_t header_size = 80; const std::size_t header_size = 80;
const size_t payload_size = 200; const std::size_t payload_size = 200;
const size_t total_size = 1 + header_size + payload_size; const std::size_t total_size = 1 + header_size + payload_size;
std::vector<uint8_t> pkt(total_size, 0); std::vector<std::uint8_t> pkt(total_size, 0);
// 0: Packet ID // 0: Packet ID
pkt[0] = RTP_TYPE_VIDEO; pkt[0] = RTP_TYPE_VIDEO;
@@ -650,21 +669,21 @@ TEST_F(RtpPublicTest, HeapBufferOverflow)
nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, &sd, mock_m_cb);
// Common parameters // Common parameters
uint16_t sequnum = 100; std::uint16_t sequnum = 100;
uint32_t timestamp = 12345; std::uint32_t timestamp = 12345;
uint32_t ssrc = 0x11223344; std::uint32_t ssrc = 0x11223344;
// --- Packet 1: Small allocation --- // --- Packet 1: Small allocation ---
// data_length_full = 10 // data_length_full = 10
// offset_full = 0 // offset_full = 0
// payload_len = 5 // payload_len = 5
{ {
uint8_t packet[100]; std::uint8_t packet[100];
std::memset(packet, 0, sizeof(packet)); std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_VIDEO; // Tox Packet ID packet[0] = RTP_TYPE_VIDEO; // Tox Packet ID
// RTP Header // RTP Header
uint8_t *h = &packet[1]; std::uint8_t *h = &packet[1];
// Byte 0: VE=2 (0x80) // Byte 0: VE=2 (0x80)
h[0] = 0x80; h[0] = 0x80;
// Byte 1: PT=0x41 // Byte 1: PT=0x41
@@ -720,11 +739,11 @@ TEST_F(RtpPublicTest, HeapBufferOverflow)
// Memcpy to buf->data + 10. Buf was allocated with size 10. Writing 100 // Memcpy to buf->data + 10. Buf was allocated with size 10. Writing 100
// bytes to offset 10 -> Overflow. // bytes to offset 10 -> Overflow.
{ {
uint8_t packet[200]; std::uint8_t packet[200];
std::memset(packet, 0, sizeof(packet)); std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_VIDEO; packet[0] = RTP_TYPE_VIDEO;
uint8_t *h = &packet[1]; std::uint8_t *h = &packet[1];
h[0] = 0x80; h[0] = 0x80;
h[1] = 0x41; h[1] = 0x41;
h[2] = (sequnum >> 8) & 0xFF; h[2] = (sequnum >> 8) & 0xFF;
@@ -769,15 +788,15 @@ TEST_F(RtpPublicTest, AudioHeapBufferOverflow)
RTPSession *session = rtp_new(log, RTP_TYPE_AUDIO, mono_time, mock_send_packet, &sd, nullptr, RTPSession *session = rtp_new(log, RTP_TYPE_AUDIO, mono_time, mock_send_packet, &sd, nullptr,
nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, &sd, mock_m_cb);
uint16_t sequnum = 100; std::uint16_t sequnum = 100;
uint32_t timestamp = 12345; std::uint32_t timestamp = 12345;
uint32_t ssrc = 0x11223344; std::uint32_t ssrc = 0x11223344;
uint8_t packet[200]; std::uint8_t packet[200];
std::memset(packet, 0, sizeof(packet)); std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_AUDIO; packet[0] = RTP_TYPE_AUDIO;
uint8_t *h = &packet[1]; std::uint8_t *h = &packet[1];
h[0] = 0x80; h[0] = 0x80;
h[1] = 0x40; // 64 (Audio) h[1] = 0x40; // 64 (Audio)
h[2] = (sequnum >> 8) & 0xFF; h[2] = (sequnum >> 8) & 0xFF;
@@ -816,21 +835,21 @@ TEST_F(RtpPublicTest, HeapBufferOverflowMultipartAudio)
RTPSession *session = rtp_new(log, RTP_TYPE_AUDIO, mono_time, mock_send_packet, &sd, nullptr, RTPSession *session = rtp_new(log, RTP_TYPE_AUDIO, mono_time, mock_send_packet, &sd, nullptr,
nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, &sd, mock_m_cb);
uint16_t sequnum = 200; std::uint16_t sequnum = 200;
uint32_t timestamp = 67890; std::uint32_t timestamp = 67890;
uint32_t ssrc = 0x55667788; std::uint32_t ssrc = 0x55667788;
uint16_t total_len = 100; std::uint16_t total_len = 100;
// --- Packet 1: Allocate buffer --- // --- Packet 1: Allocate buffer ---
// data_length_lower = 100 // data_length_lower = 100
// offset_lower = 0 // offset_lower = 0
// payload_len = 10 // payload_len = 10
{ {
uint8_t packet[200]; std::uint8_t packet[200];
std::memset(packet, 0, sizeof(packet)); std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_AUDIO; packet[0] = RTP_TYPE_AUDIO;
uint8_t *h = &packet[1]; std::uint8_t *h = &packet[1];
h[0] = 0x80; h[0] = 0x80;
h[1] = 0x40; // Audio h[1] = 0x40; // Audio
h[2] = (sequnum >> 8) & 0xFF; h[2] = (sequnum >> 8) & 0xFF;
@@ -865,11 +884,11 @@ TEST_F(RtpPublicTest, HeapBufferOverflowMultipartAudio)
// Check 2: total (100) > offset (95). Safe. // Check 2: total (100) > offset (95). Safe.
// Write: 95 + 10 = 105. Overflow. // Write: 95 + 10 = 105. Overflow.
{ {
uint8_t packet[200]; std::uint8_t packet[200];
std::memset(packet, 0, sizeof(packet)); std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_AUDIO; packet[0] = RTP_TYPE_AUDIO;
uint8_t *h = &packet[1]; std::uint8_t *h = &packet[1];
h[0] = 0x80; h[0] = 0x80;
h[1] = 0x40; h[1] = 0x40;
h[2] = (sequnum >> 8) & 0xFF; h[2] = (sequnum >> 8) & 0xFF;
@@ -905,18 +924,18 @@ TEST_F(RtpPublicTest, HeapBufferOverflowLogRead)
RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd, nullptr, RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd, nullptr,
nullptr, nullptr, &sd, mock_m_cb); nullptr, nullptr, &sd, mock_m_cb);
uint16_t sequnum = 123; std::uint16_t sequnum = 123;
uint32_t timestamp = 99999; std::uint32_t timestamp = 99999;
uint32_t ssrc = 0x88776655; std::uint32_t ssrc = 0x88776655;
// Packet with data_length_full = 1. // Packet with data_length_full = 1.
// The logger tries to read data[0] and data[1]. // The logger tries to read data[0] and data[1].
// data[1] will be out of bounds if only 1 byte is allocated. // data[1] will be out of bounds if only 1 byte is allocated.
uint8_t packet[100]; std::uint8_t packet[100];
std::memset(packet, 0, sizeof(packet)); std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_VIDEO; packet[0] = RTP_TYPE_VIDEO;
uint8_t *h = &packet[1]; std::uint8_t *h = &packet[1];
h[0] = 0x80; h[0] = 0x80;
h[1] = 0x41; // Video h[1] = 0x41; // Video
h[2] = (sequnum >> 8) & 0xFF; h[2] = (sequnum >> 8) & 0xFF;

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