Squashed 'external/toxcore/c-toxcore/' changes from c9cdae001..9ed2fa80d
9ed2fa80d fix(toxav): remove extra copy of video frame on encode de30cf3ad docs: Add new file kinds, that should be useful to all clients. d5b5e879d fix(DHT): Correct node skipping logic timed out nodes. 30e71fe97 refactor: Generate event dispatch functions and add tox_events_dispatch. 8fdbb0b50 style: Format parameter lists in event handlers. d00dee12b refactor: Add warning logs when losing chat invites. b144e8db1 feat: Add a way to look up a file number by ID. 849281ea0 feat: Add a way to fetch groups by chat ID. a2c177396 refactor: Harden event system and improve type safety. 8f5caa656 refactor: Add MessagePack string support to bin_pack. 34e8d5ad5 chore: Add GitHub CodeQL workflow and local Docker runner. f7b068010 refactor: Add nullability annotations to event headers. 788abe651 refactor(toxav): Use system allocator for mutexes. 2e4b423eb refactor: Use specific typedefs for public API arrays. 2baf34775 docs(toxav): update idle iteration interval see 679444751876fa3882a717772918ebdc8f083354 2f87ac67b feat: Add Event Loop abstraction (Ev). f8dfc38d8 test: Fix data race in ToxScenario virtual_clock. 38313921e test(TCP): Add regression test for TCP priority queue integrity. f94a50d9a refactor(toxav): Replace mutable_mutex with dynamically allocated mutex. ad054511e refactor: Internalize DHT structs and add debug helpers. 8b467cc96 fix: Prevent potential integer overflow in group chat handshake. 4962bdbb8 test: Improve TCP simulation and add tests 5f0227093 refactor: Allow nullable data in group chat handlers. e97b18ea9 chore: Improve Windows Docker support. b14943bbd refactor: Move Logger out of Messenger into Tox. dd3136250 cleanup: Apply nullability qualifiers to C++ codebase. 1849f70fc refactor: Extract low-level networking code to net and os_network. 8fec75421 refactor: Delete tox_random, align on rng and os_random. a03ae8051 refactor: Delete tox_memory, align on mem and os_memory. 4c88fed2c refactor: Use `std::` prefixes more consistently in C++ code. 72452f2ae test: Add some more tests for onion and shared key cache. d5a51b09a cleanup: Use tox_attributes.h in tox_private.h and install it. b6f5b9fc5 test: Add some benchmarks for various high level things. 8a8d02785 test(support): Introduce threaded Tox runner and simulation barrier d68d1d095 perf(toxav): optimize audio and video intermediate buffers by keeping them around REVERT: c9cdae001 fix(toxav): remove extra copy of video frame on encode git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: 9ed2fa80d582c714d6bdde6a7648220a92cddff8
This commit is contained in:
@@ -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
|
||||||
|
|||||||
3
.github/scripts/flags-gcc.sh
vendored
3
.github/scripts/flags-gcc.sh
vendored
@@ -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.
|
||||||
|
|||||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -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
|
||||||
|
|||||||
57
.github/workflows/codeql.yml
vendored
Normal file
57
.github/workflows/codeql.yml
vendored
Normal 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 }}"
|
||||||
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -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: |
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -98,5 +98,10 @@ tox.spec
|
|||||||
.cache/
|
.cache/
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
|
||||||
|
# gtags
|
||||||
|
/GPATH
|
||||||
|
/GRTAGS
|
||||||
|
/GTAGS
|
||||||
|
|
||||||
/infer
|
/infer
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
125
CMakeLists.txt
125
CMakeLists.txt
@@ -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()
|
||||||
|
|||||||
@@ -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 \
|
||||||
.
|
.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
96
auto_tests/scenarios/scenario_group_by_id_test.c
Normal file
96
auto_tests/scenarios/scenario_group_by_id_test.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ run() {
|
|||||||
"${CPPFLAGS[@]}" \
|
"${CPPFLAGS[@]}" \
|
||||||
"${LDFLAGS[@]}" \
|
"${LDFLAGS[@]}" \
|
||||||
"$@" \
|
"$@" \
|
||||||
-std=c++17 \
|
-std=c++20 \
|
||||||
-Werror \
|
-Werror \
|
||||||
-Weverything \
|
-Weverything \
|
||||||
-Wno-alloca \
|
-Wno-alloca \
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
4
other/docker/codeql/build.sh
Normal file
4
other/docker/codeql/build.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
cmake -GNinja -B build -S .
|
||||||
|
cmake --build build --parallel "$(nproc)"
|
||||||
53
other/docker/codeql/codeql.Dockerfile
Normal file
53
other/docker/codeql/codeql.Dockerfile
Normal 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"]
|
||||||
15
other/docker/codeql/run
Executable file
15
other/docker/codeql/run
Executable 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"
|
||||||
8
other/docker/codeql/run-analysis.sh
Normal file
8
other/docker/codeql/run-analysis.sh
Normal 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
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
|
||||||
|
|
||||||
cc_library(
|
|
||||||
name = "sodium",
|
|
||||||
testonly = True,
|
|
||||||
srcs = ["sodium.c"],
|
|
||||||
deps = ["@libsodium"],
|
|
||||||
)
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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" .
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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", \
|
||||||
|
|||||||
@@ -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/*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
other/docker/windows/dockerignore
Normal file
1
other/docker/windows/dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!other/docker/windows/*.sh
|
||||||
26
other/docker/windows/run
Executable file
26
other/docker/windows/run
Executable 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
|
||||||
@@ -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 \
|
||||||
24
other/docker/windows/windows.Dockerfile.dockerignore
Normal file
24
other/docker/windows/windows.Dockerfile.dockerignore
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
24
testing/bench/BUILD.bazel
Normal file
24
testing/bench/BUILD.bazel
Normal 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",
|
||||||
|
],
|
||||||
|
)
|
||||||
21
testing/bench/CMakeLists.txt
Normal file
21
testing/bench/CMakeLists.txt
Normal 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()
|
||||||
479
testing/bench/tox_friends_scaling_bench.cc
Normal file
479
testing/bench/tox_friends_scaling_bench.cc
Normal 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;
|
||||||
|
}
|
||||||
221
testing/bench/tox_messenger_bench.cc
Normal file
221
testing/bench/tox_messenger_bench.cc
Normal 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();
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
459
testing/support/doubles/fake_network_tcp_test.cc
Normal file
459
testing/support/doubles/fake_network_tcp_test.cc
Normal 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
75
testing/support/doubles/fake_network_udp_test.cc
Normal file
75
testing/support/doubles/fake_network_udp_test.cc
Normal 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
|
||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
83
testing/support/public/mpsc_queue.hh
Normal file
83
testing/support/public/mpsc_queue.hh
Normal 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
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
137
testing/support/public/tox_runner.hh
Normal file
137
testing/support/public/tox_runner.hh
Normal 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
|
||||||
76
testing/support/simulation_test.cc
Normal file
76
testing/support/simulation_test.cc
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
125
testing/support/src/tox_runner.cc
Normal file
125
testing/support/src/tox_runner.cc
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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++;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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" */
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user