commit 227425b90e9a671118026689dd30967e127a1090 Author: Green Sky Date: Tue Jul 25 11:53:09 2023 +0200 Squashed 'external/toxcore/c-toxcore/' content from commit 67badf69 git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: 67badf69416a74e74f6d7eb51dd96f37282b8455 diff --git a/.circleci/cmake-asan b/.circleci/cmake-asan new file mode 100755 index 0000000..724f33e --- /dev/null +++ b/.circleci/cmake-asan @@ -0,0 +1,39 @@ +#!/bin/bash + +set -eu + +CACHEDIR="$HOME/cache" + +. ".github/scripts/flags-$CC.sh" +add_flag -Werror +add_flag -fdiagnostics-color=always +add_flag -fno-omit-frame-pointer +add_flag -fsanitize=address +cmake -B_build -H. -GNinja \ + -DCMAKE_C_FLAGS="$C_FLAGS" \ + -DCMAKE_CXX_FLAGS="$CXX_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_INSTALL_PREFIX:PATH="$PWD/_install" \ + -DCMAKE_UNITY_BUILD=ON \ + -DMIN_LOGGER_LEVEL=TRACE \ + -DMUST_BUILD_TOXAV=ON \ + -DNON_HERMETIC_TESTS=ON \ + -DSTRICT_ABI=ON \ + -DTEST_TIMEOUT_SECONDS=120 \ + -DUSE_IPV6=OFF \ + -DAUTOTEST=ON \ + -DBUILD_MISC_TESTS=ON \ + -DBUILD_FUN_UTILS=ON + +cd _build + +ninja install -j"$(nproc)" + +export ASAN_OPTIONS="color=always" +export ASAN_OPTIONS="$ASAN_OPTIONS,detect_invalid_pointer_pairs=1" +export ASAN_OPTIONS="$ASAN_OPTIONS,detect_stack_use_after_return=1" +export ASAN_OPTIONS="$ASAN_OPTIONS,strict_init_order=1" +export ASAN_OPTIONS="$ASAN_OPTIONS,strict_string_checks=1" +export ASAN_OPTIONS="$ASAN_OPTIONS,symbolize=1" +ctest -j50 --output-on-failure --rerun-failed --repeat until-pass:6 diff --git a/.circleci/cmake-tsan b/.circleci/cmake-tsan new file mode 100755 index 0000000..ab98e0d --- /dev/null +++ b/.circleci/cmake-tsan @@ -0,0 +1,35 @@ +#!/bin/bash + +set -eu + +CACHEDIR="$HOME/cache" + +. ".github/scripts/flags-$CC.sh" +add_flag -Werror +add_flag -fdiagnostics-color=always +add_flag -fno-omit-frame-pointer +add_flag -fsanitize=thread +cmake -B_build -H. -GNinja \ + -DCMAKE_C_FLAGS="$C_FLAGS" \ + -DCMAKE_CXX_FLAGS="$CXX_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_INSTALL_PREFIX:PATH="$PWD/_install" \ + -DCMAKE_UNITY_BUILD=ON \ + -DMIN_LOGGER_LEVEL=TRACE \ + -DMUST_BUILD_TOXAV=ON \ + -DNON_HERMETIC_TESTS=ON \ + -DSTRICT_ABI=ON \ + -DTEST_TIMEOUT_SECONDS=120 \ + -DUSE_IPV6=OFF \ + -DAUTOTEST=ON + +cd _build + +ninja install -j"$(nproc)" + +export TSAN_OPTIONS="color=always" +export TSAN_OPTIONS="$TSAN_OPTIONS,halt_on_error=1" +export TSAN_OPTIONS="$TSAN_OPTIONS,second_deadlock_stack=1" +export TSAN_OPTIONS="$TSAN_OPTIONS,symbolize=1" +ctest -j50 --output-on-failure --rerun-failed --repeat until-pass:6 diff --git a/.circleci/cmake-ubsan b/.circleci/cmake-ubsan new file mode 100755 index 0000000..45e64d8 --- /dev/null +++ b/.circleci/cmake-ubsan @@ -0,0 +1,40 @@ +#!/bin/bash + +set -eu + +CACHEDIR="$HOME/cache" + +. ".github/scripts/flags-$CC.sh" +add_flag -Werror +add_flag -fdiagnostics-color=always +add_flag -fno-omit-frame-pointer +add_flag -fno-sanitize-recover=all +add_flag -fsanitize=undefined,nullability,local-bounds,float-divide-by-zero,integer +add_flag -fno-sanitize=implicit-conversion,unsigned-integer-overflow +# Enable extra checks. We only do this on ubsan because it shows useful error +# messages for the kind of bugs this catches (mostly incorrect nullability +# annotations). Other builds will segfault, ubsan will show a stack trace. +add_flag -D_DEBUG +cmake -B_build -H. -GNinja \ + -DCMAKE_C_FLAGS="$C_FLAGS" \ + -DCMAKE_CXX_FLAGS="$CXX_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_INSTALL_PREFIX:PATH="$PWD/_install" \ + -DCMAKE_UNITY_BUILD=ON \ + -DMIN_LOGGER_LEVEL=TRACE \ + -DMUST_BUILD_TOXAV=ON \ + -DNON_HERMETIC_TESTS=ON \ + -DSTRICT_ABI=ON \ + -DTEST_TIMEOUT_SECONDS=120 \ + -DUSE_IPV6=OFF \ + -DAUTOTEST=ON + +cd _build + +ninja install -j"$(nproc)" + +export UBSAN_OPTIONS="color=always" +export UBSAN_OPTIONS="$UBSAN_OPTIONS,print_stacktrace=1" +export UBSAN_OPTIONS="$UBSAN_OPTIONS,symbolize=1" +ctest -j50 --output-on-failure --rerun-failed --repeat until-pass:6 diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..27d396f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,170 @@ +--- +version: 2 + +workflows: + version: 2 + program-analysis: + jobs: + # Dynamic analysis + - asan + - tsan + - msan + - ubsan + # Static analysis + - clang-analyze + - clang-tidy + - cpplint + - infer + - static-analysis + +jobs: + asan: + working_directory: ~/work + docker: + - image: ubuntu + + steps: + - run: &apt_install + apt-get update && + DEBIAN_FRONTEND=noninteractive + apt-get install -y --no-install-recommends + ca-certificates + clang + cmake + git + libconfig-dev + libgtest-dev + libopus-dev + libsodium-dev + libvpx-dev + llvm-dev + ninja-build + pkg-config + - checkout + - run: git submodule update --init --recursive + - run: CC=clang .circleci/cmake-asan + + tsan: + working_directory: ~/work + docker: + - image: ubuntu + + steps: + - run: *apt_install + - checkout + - run: git submodule update --init --recursive + - run: CC=clang .circleci/cmake-tsan + + ubsan: + working_directory: ~/work + docker: + - image: ubuntu + + steps: + - run: *apt_install + - checkout + - run: git submodule update --init --recursive + - run: CC=clang .circleci/cmake-ubsan + + msan: + working_directory: ~/work + docker: + - image: toxchat/toktok-stack:latest-msan + + steps: + - checkout + - run: git submodule update --init --recursive + - run: rm -rf /src/workspace/c-toxcore/* && mv * /src/workspace/c-toxcore/ + - run: + cd /src/workspace && bazel test + //c-toxcore/auto_tests:lossless_packet_test + //c-toxcore/toxav/... + //c-toxcore/toxcore/... + + infer: + working_directory: ~/work + docker: + - image: toxchat/infer + + steps: + - run: *apt_install + - checkout + - run: git submodule update --init --recursive + - run: infer --no-progress-bar -- cc + auto_tests/auto_test_support.c + auto_tests/lossless_packet_test.c + testing/misc_tools.c + toxav/*.c + toxcore/*.c + toxcore/*/*.c + toxencryptsave/*.c + third_party/cmp/*.c + -lpthread + $(pkg-config --cflags --libs libsodium opus vpx) + + static-analysis: + working_directory: ~/work + docker: + - image: ubuntu + + steps: + - run: *apt_install + - run: + apt-get install -y --no-install-recommends + ca-certificates + cppcheck + g++ + llvm-dev + - checkout + - run: git submodule update --init --recursive + - run: other/analysis/check_includes + - run: other/analysis/check_logger_levels + - run: other/analysis/run-clang + - run: other/analysis/run-cppcheck + - run: other/analysis/run-gcc + + clang-analyze: + working_directory: ~/work + docker: + - image: ubuntu + + steps: + - run: *apt_install + - checkout + - run: git submodule update --init --recursive + - run: other/analysis/run-clang-analyze + + clang-tidy: + working_directory: ~/work + docker: + - image: ubuntu + + steps: + - run: *apt_install + - run: + apt-get install -y --no-install-recommends + ca-certificates + clang-tidy-12 + - checkout + - run: git submodule update --init --recursive + - run: cmake . -B_build -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + - run: + other/analysis/run-clang-tidy || + other/analysis/run-clang-tidy || + other/analysis/run-clang-tidy + + cpplint: + working_directory: ~/work + docker: + - image: ubuntu + + steps: + - run: *apt_install + - run: + apt-get install -y --no-install-recommends + ca-certificates + python3-pip + - checkout + - run: git submodule update --init --recursive + - run: pip install cpplint + - run: other/analysis/run-cpplint diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 0000000..9a3d27f --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,114 @@ +--- +bazel-opt_task: + container: + image: toxchat/toktok-stack:latest-release + cpu: 2 + memory: 2G + configure_script: + - git submodule update --init --recursive + - /src/workspace/tools/inject-repo c-toxcore + test_all_script: + - cd /src/workspace && bazel test -k + --remote_http_cache=http://$CIRRUS_HTTP_CACHE_HOST + --build_tag_filters=-haskell + --test_tag_filters=-haskell + --remote_download_minimal + -- + //c-toxcore/... + -//c-toxcore/auto_tests:tcp_relay_test # TODO(robinlinden): Why does this pass locally but not in Cirrus? + +bazel-dbg_task: + container: + image: toxchat/toktok-stack:latest-debug + cpu: 2 + memory: 2G + configure_script: + - git submodule update --init --recursive + - /src/workspace/tools/inject-repo c-toxcore + test_all_script: + - cd /src/workspace && bazel test -k + --remote_http_cache=http://$CIRRUS_HTTP_CACHE_HOST + --build_tag_filters=-haskell + --test_tag_filters=-haskell + --remote_download_minimal + -- + //c-toxcore/... + -//c-toxcore/auto_tests:tcp_relay_test # TODO(robinlinden): Why does this pass locally but not in Cirrus? + +bazel-asan_task: + container: + image: toxchat/toktok-stack:latest-asan + cpu: 2 + memory: 4G + configure_script: + - git submodule update --init --recursive + - /src/workspace/tools/inject-repo c-toxcore + test_all_script: + - cd /src/workspace && bazel test -k + --remote_http_cache=http://$CIRRUS_HTTP_CACHE_HOST + --build_tag_filters=-haskell + --test_tag_filters=-haskell + --remote_download_minimal + -- + //c-toxcore/... + -//c-toxcore/auto_tests:tcp_relay_test # TODO(robinlinden): Why does this pass locally but not in Cirrus? + +# TODO(iphydf): Enable once this works properly. +#bazel-msan_task: +# container: +# image: toxchat/toktok-stack:latest-msan +# cpu: 2 +# memory: 4G +# configure_script: +# - git submodule update --init --recursive +# - /src/workspace/tools/inject-repo c-toxcore +# test_all_script: +# - cd /src/workspace && bazel test -k +# --remote_http_cache=http://$CIRRUS_HTTP_CACHE_HOST +# --build_tag_filters=-haskell +# --test_tag_filters=-haskell +# --remote_download_minimal +# -- +# //c-toxcore/... +# -//c-toxcore/auto_tests:tcp_relay_test # TODO(robinlinden): Why does this pass locally but not in Cirrus? + +# TODO(iphydf): Fix test timeouts. +bazel-tsan_task: + container: + image: toxchat/toktok-stack:latest-tsan + cpu: 2 + memory: 4G + configure_script: + - git submodule update --init --recursive + - /src/workspace/tools/inject-repo c-toxcore + test_all_script: + - cd /src/workspace && bazel test -k + --remote_http_cache=http://$CIRRUS_HTTP_CACHE_HOST + --build_tag_filters=-haskell + --test_tag_filters=-haskell + --remote_download_minimal + -- + //c-toxcore/... + -//c-toxcore/auto_tests:conference_av_test + -//c-toxcore/auto_tests:conference_test + -//c-toxcore/auto_tests:file_transfer_test + -//c-toxcore/auto_tests:group_tcp_test + -//c-toxcore/auto_tests:onion_test + -//c-toxcore/auto_tests:tcp_relay_test + -//c-toxcore/auto_tests:tox_many_test + +cimple_task: + container: + image: toxchat/toktok-stack:latest-release + cpu: 2 + memory: 4G + configure_script: + - git submodule update --init --recursive + - /src/workspace/tools/inject-repo c-toxcore + test_all_script: + - cd /src/workspace && bazel test -k + --remote_http_cache=http://$CIRRUS_HTTP_CACHE_HOST + --build_tag_filters=haskell + --test_tag_filters=haskell + -- + //c-toxcore/... diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d472818 --- /dev/null +++ b/.clang-format @@ -0,0 +1,22 @@ +BasedOnStyle: WebKit +ColumnLimit: 100 +PointerAlignment: Right +SpacesBeforeTrailingComments: 2 +AlignConsecutiveMacros: true +AlignEscapedNewlines: Left +AlwaysBreakTemplateDeclarations: Yes +SpaceBeforeCpp11BracedList: false +Cpp11BracedListStyle: true + +IncludeIsMainRegex: '([-_](test|fuzz_test))?$' +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000..e563011 --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,26 @@ +# c-toxcore Clusterfuzzlite build environment + +# We want to use the latest tools always +FROM gcr.io/oss-fuzz-base/base-builder:latest + +RUN apt-get update && \ + apt-get -y install --no-install-suggests --no-install-recommends \ + cmake libtool autoconf automake pkg-config \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Static builds of dependencies + +# libsodium +RUN git clone --depth 1 --branch 1.0.18 https://github.com/jedisct1/libsodium libsodium +WORKDIR $SRC/libsodium +RUN ./autogen.sh && ./configure --enable-shared=no && make install +WORKDIR $SRC + +# Copy your project's source code. +COPY . $SRC/c-toxcore +# Working directory for build.sh. +WORKDIR $SRC/c-toxcore +RUN git submodule update --init --recursive +# Copy build.sh into $SRC dir. +COPY ./.clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100644 index 0000000..57985e7 --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,25 @@ +#!/bin/bash -eu + +FUZZ_TARGETS="bootstrap_fuzzer toxsave_fuzzer" + +# out of tree build +cd "$WORK" + +ls /usr/local/lib/ + +# Debug build for asserts +cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER="$CC" \ + -DCMAKE_CXX_COMPILER="$CXX" \ + -DCMAKE_C_FLAGS="$CFLAGS" \ + -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LIB_FUZZING_ENGINE" \ + -DBUILD_TOXAV=OFF -DENABLE_SHARED=NO -DBUILD_FUZZ_TESTS=ON \ + -DDHT_BOOTSTRAP=OFF -DBOOTSTRAP_DAEMON=OFF "$SRC"/c-toxcore + +for TARGET in $FUZZ_TARGETS; do + # build fuzzer target + cmake --build ./ --target "$TARGET" + + # copy to output files + cp "$WORK/testing/fuzzing/$TARGET" "$OUT"/ +done diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0a93060 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{c,h}] +indent_style = space +indent_size = 4 + +[*.{yml,sh,cmake}] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab +indent_size = 4 + +[CMakeLists.txt] +indent_style = space +indent_size = 2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..5912298 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +/.github/ @TokTok/admins diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fd314b9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +--- +version: 2 +updates: +- package-ecosystem: gitsubmodule + directory: "/" + schedule: + interval: daily + time: "11:00" + open-pull-requests-limit: 2 diff --git a/.github/scripts/autotools-linux b/.github/scripts/autotools-linux new file mode 100755 index 0000000..a6e36f5 --- /dev/null +++ b/.github/scripts/autotools-linux @@ -0,0 +1,27 @@ +#!/bin/bash + +set -eu + +NPROC=$(nproc) + +. ".github/scripts/flags-$CC.sh" + +add_ld_flag -Wl,-z,defs + +# Make compilation error on a warning +add_flag -Werror + +add_config_flag --with-nacl-libs="$CACHEDIR/lib/amd64" +add_config_flag --with-nacl-headers="$CACHEDIR/include/amd64" +add_config_flag --disable-ipv6 +add_config_flag --enable-nacl +add_config_flag --enable-daemon +add_config_flag --with-log-level=TRACE + +autoreconf -fi +mkdir -p _build +cd _build # pushd +../configure "${CONFIG_FLAGS[@]}" || (cat config.log && false) +make "-j$NPROC" -k CFLAGS="$C_FLAGS" LDFLAGS="$LD_FLAGS" +make -j50 -k distcheck DISTCHECK_CONFIGURE_FLAGS="${CONFIG_FLAGS[*]}" || (cat tox-*/_build/build/test-suite.log && false) +cd - # popd diff --git a/.github/scripts/cmake-android b/.github/scripts/cmake-android new file mode 100755 index 0000000..0c4e4cc --- /dev/null +++ b/.github/scripts/cmake-android @@ -0,0 +1,65 @@ +#!/bin/bash + +set -eu + +# Set up environment +NDK=$ANDROID_NDK_HOME + +ABI=${1:-"armeabi-v7a"} + +case $ABI in + armeabi-v7a) + TARGET=armv7a-linux-androideabi + NDK_API=16 + ;; + arm64-v8a) + TARGET=aarch64-linux-android + NDK_API=21 + ;; + x86) + TARGET=i686-linux-android + NDK_API=16 + ;; + x86_64) + TARGET=x86_64-linux-android + NDK_API=21 + ;; + *) + exit 1 + ;; +esac + +rm -rf _android_prefix +mkdir -p _android_prefix +PREFIX=$PWD/_android_prefix + +TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64 +SYSROOT=$TOOLCHAIN/sysroot + +export CC="$TOOLCHAIN/bin/$TARGET$NDK_API"-clang +export LDFLAGS=-static-libstdc++ +export PKG_CONFIG_PATH="$PREFIX"/lib/pkgconfig + +# Build libsodium +if [ ! -d libsodium ]; then + git clone --branch=1.0.18 https://github.com/jedisct1/libsodium.git +fi +cd libsodium +git clean -ffdx +autoreconf -fi +./configure --prefix="$PREFIX" --host="$TARGET" --with-sysroot="$SYSROOT" --disable-shared +make -j"$(nproc)" install +cd .. + +# Build c-toxcore +rm -rf _build +mkdir -p _build +cd _build +cmake .. \ + -DBUILD_TOXAV=OFF \ + -DBOOTSTRAP_DAEMON=OFF \ + -DCMAKE_TOOLCHAIN_FILE="$NDK/build/cmake/android.toolchain.cmake" \ + -DANDROID_ABI="$ABI" \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" \ + -DCMAKE_PREFIX_PATH="$PREFIX" +cmake --build . diff --git a/.github/scripts/cmake-freebsd-stage2 b/.github/scripts/cmake-freebsd-stage2 new file mode 100755 index 0000000..f6bdd09 --- /dev/null +++ b/.github/scripts/cmake-freebsd-stage2 @@ -0,0 +1,52 @@ +#!/bin/bash +# Copyright (C) 2018-2021 nurupo + +# Toxcore building + +set -eux + +if [ "$PWD" != "/work" ]; then + cd .. + mv c-toxcore / + mkdir c-toxcore + cd /work +fi + +. cmake-freebsd-run.sh + +# === Get VM ready to build the code === + +gunzip "$IMAGE_NAME.gz" + +start_vm + +# Copy over toxcore code from host to qemu +scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -P "$SSH_PORT" -r /c-toxcore root@localhost:~ + +RUN ls -lh + +cd /c-toxcore +. ".github/scripts/flags-clang.sh" + +add_ld_flag -Wl,-z,defs + +# Make compilation error on a warning +add_flag -Werror + +RUN 'cmake -B_build -Hc-toxcore \ + -DCMAKE_C_FLAGS="$C_FLAGS" \ + -DCMAKE_CXX_FLAGS="$CXX_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_INSTALL_PREFIX:PATH="_install" \ + -DMIN_LOGGER_LEVEL=TRACE \ + -DMUST_BUILD_TOXAV=ON \ + -DNON_HERMETIC_TESTS=ON \ + -DSTRICT_ABI=ON \ + -DTEST_TIMEOUT_SECONDS=90 \ + -DUSE_IPV6=OFF \ + -DAUTOTEST=ON' + +# We created the VM with the same number of cores as the host, so the host-ran `nproc` here is fine +RUN 'gmake "-j$NPROC" -k install -C_build' +RUN 'gmake "-j$NPROC" test ARGS="-j50" -C_build || true' diff --git a/.github/scripts/cmake-osx b/.github/scripts/cmake-osx new file mode 100755 index 0000000..1ee1e9b --- /dev/null +++ b/.github/scripts/cmake-osx @@ -0,0 +1,45 @@ +#!/bin/bash + +set -eu + +NPROC=$(sysctl -n hw.physicalcpu) + +# Workaround for bug in Homebrew where it only finds an old Ruby version. +brew update + +brew install \ + libconfig \ + libsodium \ + libvpx \ + opus + +. ".github/scripts/flags-clang.sh" + +add_ld_flag -undefined error + +# Make compilation error on a warning +add_flag -Werror + +# Allow _Static_assert. Supported C11 extensions are fine, since we have several +# C99-only compilers we test against anyway. Anything that passes all the +# compilers we use is fine. +add_c_flag -Wno-c11-extensions + +cmake -B_build -H. \ + -DCMAKE_C_FLAGS="$C_FLAGS" \ + -DCMAKE_CXX_FLAGS="$CXX_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_INSTALL_PREFIX:PATH="$PWD/_install" \ + -DMIN_LOGGER_LEVEL=TRACE \ + -DMUST_BUILD_TOXAV=ON \ + -DNON_HERMETIC_TESTS=ON \ + -DTEST_TIMEOUT_SECONDS=120 \ + -DUSE_IPV6=OFF \ + -DAUTOTEST=ON + +cd _build # pushd +make "-j$NPROC" -k install +ctest -j50 --output-on-failure --rerun-failed --repeat until-pass:6 || + ctest -j50 --output-on-failure --rerun-failed --repeat until-pass:6 +cd - # popd diff --git a/.github/scripts/cmake-win32 b/.github/scripts/cmake-win32 new file mode 100755 index 0000000..389e3ae --- /dev/null +++ b/.github/scripts/cmake-win32 @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +i686=true +x86_64=false +WINDOWS_ARCH=win32 + +. .github/scripts/cmake-windows.sh diff --git a/.github/scripts/cmake-win64 b/.github/scripts/cmake-win64 new file mode 100755 index 0000000..1b4fa80 --- /dev/null +++ b/.github/scripts/cmake-win64 @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +i686=false +x86_64=true +WINDOWS_ARCH=win64 + +. .github/scripts/cmake-windows.sh diff --git a/.github/scripts/cmake-windows.sh b/.github/scripts/cmake-windows.sh new file mode 100644 index 0000000..ed2f677 --- /dev/null +++ b/.github/scripts/cmake-windows.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -eu + +NPROC=$(nproc) + +. ".github/scripts/flags-gcc.sh" + +# Allows wine to display source code file names and line numbers on crash in +# its backtrace. +add_flag -gdwarf-2 + +# Fix invalid register for .seh_savexmm error +add_flag -fno-asynchronous-unwind-tables + +docker run \ + -e ALLOW_TEST_FAILURE=true \ + -e ENABLE_ARCH_i686="$i686" \ + -e ENABLE_ARCH_x86_64="$x86_64" \ + -e ENABLE_TEST=true \ + -e EXTRA_CMAKE_FLAGS="-DBOOTSTRAP_DAEMON=OFF -DMIN_LOGGER_LEVEL=DEBUG -DTEST_TIMEOUT_SECONDS=90 -DAUTOTEST=ON" \ + -e CMAKE_C_FLAGS="$C_FLAGS" \ + -e CMAKE_CXX_FLAGS="$CXX_FLAGS" \ + -e CMAKE_EXE_LINKER_FLAGS="$LD_FLAGS" \ + -e CMAKE_SHARED_LINKER_FLAGS="$LD_FLAGS" \ + -v "$PWD:/toxcore" \ + -v "$PWD/result:/prefix" \ + --rm \ + "toxchat/windows:$WINDOWS_ARCH" diff --git a/.github/scripts/coverage-linux b/.github/scripts/coverage-linux new file mode 100755 index 0000000..35a1e75 --- /dev/null +++ b/.github/scripts/coverage-linux @@ -0,0 +1,61 @@ +#!/bin/bash + +set -eu + +NPROC=$(nproc) + +sudo apt-get install -y --no-install-recommends \ + libgtest-dev \ + libopus-dev \ + libsodium-dev \ + libvpx-dev \ + llvm-11 \ + ninja-build +git clone --depth=1 https://github.com/ralight/mallocfail /tmp/mallocfail +cd /tmp/mallocfail # pushd +make +sudo make install +cd - # popd + +export CC=clang +export CXX=clang++ + +sudo install other/docker/coverage/run_mallocfail /usr/local/bin/run_mallocfail +(cd other/proxy && go build) +other/proxy/proxy & + +. ".github/scripts/flags-coverage.sh" + +cmake -B_build -H. -GNinja \ + -DCMAKE_C_FLAGS="$C_FLAGS" \ + -DCMAKE_CXX_FLAGS="$CXX_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_INSTALL_PREFIX:PATH="$PWD/_install" \ + -DENABLE_SHARED=OFF \ + -DMIN_LOGGER_LEVEL=TRACE \ + -DMUST_BUILD_TOXAV=ON \ + -DNON_HERMETIC_TESTS=OFF \ + -DSTRICT_ABI=ON \ + -DTEST_TIMEOUT_SECONDS=120 \ + -DUSE_IPV6=OFF \ + -DAUTOTEST=ON \ + -DPROXY_TEST=ON + +cmake --build _build --parallel "$NPROC" --target install -- -k 0 + +cd _build # pushd +ctest -j50 --output-on-failure --rerun-failed --repeat until-pass:6 || + ctest -j50 --output-on-failure --rerun-failed --repeat until-pass:6 + +export PYTHONUNBUFFERED=1 +run_mallocfail --ctest=2 --jobs=8 +cd - # popd + +#coveralls \ +# --exclude auto_tests \ +# --exclude other \ +# --exclude testing \ +# --gcov-options '\-lp' + +bash <(curl -s https://codecov.io/bash) -x "llvm-cov-11 gcov" diff --git a/.github/scripts/flags-clang.sh b/.github/scripts/flags-clang.sh new file mode 100644 index 0000000..d083672 --- /dev/null +++ b/.github/scripts/flags-clang.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +. .github/scripts/flags.sh + +# Add all warning flags we can. +add_flag -Wall +add_flag -Wextra +add_flag -Weverything + +# Disable specific warning flags for both C and C++. + +# Very verbose, not very useful. This warns about things like int -> uint +# conversions that change sign without a cast and narrowing conversions. +add_flag -Wno-conversion +# TODO(iphydf): Check enum values when received from the user, then assume +# correctness and remove this suppression. +add_flag -Wno-covered-switch-default +# We use C99, so declarations can come after statements. +add_flag -Wno-declaration-after-statement +# Due to clang's tolower() macro being recursive +# https://github.com/TokTok/c-toxcore/pull/481 +add_flag -Wno-disabled-macro-expansion +# We don't put __attribute__ on the public API. +add_flag -Wno-documentation-deprecated-sync +# Bootstrap daemon does this. +add_flag -Wno-format-nonliteral +# struct Foo foo = {0}; is a common idiom. Missing braces means we'd need to +# write {{{0}}} in some cases, which is ugly and a maintenance burden. +add_flag -Wno-missing-braces +add_flag -Wno-missing-field-initializers +# We don't use this attribute. It appears in the non-NDEBUG stderr logger. +add_flag -Wno-missing-noreturn +# Useful sometimes, but we accept padding in structs for clarity. +# Reordering fields to avoid padding will reduce readability. +add_flag -Wno-padded +# This warns on things like _XOPEN_SOURCE, which we currently need (we +# probably won't need these in the future). +add_flag -Wno-reserved-id-macro +# TODO(iphydf): Clean these up. They are likely not bugs, but still +# potential issues and probably confusing. +add_flag -Wno-sign-compare +# __attribute__((nonnull)) causes this warning on defensive null checks. +add_flag -Wno-tautological-pointer-compare +# Our use of mutexes results in a false positive, see 1bbe446. +add_flag -Wno-thread-safety-analysis +# File transfer code has this. +add_flag -Wno-type-limits +# Callbacks often don't use all their parameters. +add_flag -Wno-unused-parameter +# cimple does this better +add_flag -Wno-unused-function +# libvpx uses __attribute__((unused)) for "potentially unused" static +# functions to avoid unused static function warnings. +add_flag -Wno-used-but-marked-unused +# We use variable length arrays a lot. +add_flag -Wno-vla +# Disable warnings about unknown Doxygen commands +add_flag -Wno-documentation-unknown-command + +# Disable specific warning flags for C++. + +# Comma at end of enum is supported everywhere we run. +add_cxx_flag -Wno-c++98-compat-pedantic +# TODO(iphydf): Stop using flexible array members. +add_cxx_flag -Wno-c99-extensions +# We're C-compatible, so use C style casts. +add_cxx_flag -Wno-old-style-cast + +# Downgrade to warning so we still see it. +add_flag -Wno-error=unreachable-code +add_flag -Wno-error=unused-variable diff --git a/.github/scripts/flags-coverage.sh b/.github/scripts/flags-coverage.sh new file mode 100644 index 0000000..3fa6b6b --- /dev/null +++ b/.github/scripts/flags-coverage.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +. .github/scripts/flags-clang.sh + +add_ld_flag -Wl,-z,defs + +# Make compilation error on a warning +add_flag -Werror + +# Coverage flags. +add_flag --coverage + +# Optimisation, but keep stack traces useful. +add_c_flag -fno-inline -fno-omit-frame-pointer + +# Show useful stack traces on crash. +add_flag -fsanitize=undefined -fno-sanitize-recover=all + +# In test code (_test.cc and libgtest), throw away all debug information. +# We only care about stack frames inside toxcore (which is C). Without this, +# mallocfail will spend a lot of time finding all the ways in which gtest can +# fail to allocate memory, which is not interesting to us. +add_cxx_flag -g0 + +# Continue executing code in error paths so we can see it cleanly exit (and the +# test code itself may abort). +add_flag -DABORT_ON_LOG_ERROR=false diff --git a/.github/scripts/flags-gcc.sh b/.github/scripts/flags-gcc.sh new file mode 100644 index 0000000..0cacdb4 --- /dev/null +++ b/.github/scripts/flags-gcc.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +. .github/scripts/flags.sh + +# Add all warning flags we can. +add_flag -Wall +add_flag -Wextra + +# Some additional warning flags not enabled by any of the above. +add_flag -Wbool-compare +add_flag -Wcast-align +add_flag -Wcast-qual +add_flag -Wchar-subscripts +add_flag -Wdouble-promotion +add_flag -Wduplicated-cond +add_flag -Wempty-body +add_flag -Wenum-compare +add_flag -Wfloat-equal +add_flag -Wformat=2 +add_flag -Wframe-address +add_flag -Wframe-larger-than=9000 +add_flag -Wignored-attributes +add_flag -Wignored-qualifiers +add_flag -Winit-self +add_flag -Winline +add_flag -Wlarger-than=530000 +add_flag -Wmaybe-uninitialized +add_flag -Wmemset-transposed-args +add_flag -Wmisleading-indentation +add_flag -Wmissing-declarations +add_flag -Wnonnull +add_flag -Wnull-dereference +add_flag -Wodr +add_flag -Wredundant-decls +add_flag -Wreturn-type +add_flag -Wshadow +add_flag -Wsuggest-attribute=format +add_flag -Wundef +add_flag -Wunsafe-loop-optimizations +add_flag -Wunused-but-set-parameter +add_flag -Wunused-but-set-variable +add_flag -Wunused-label +add_flag -Wunused-local-typedefs +add_flag -Wunused-value + +# Disable specific warning flags for both C and C++. + +# struct Foo foo = {0}; is a common idiom. +add_flag -Wno-missing-field-initializers +# TODO(iphydf): Clean these up. They are likely not bugs, but still +# potential issues and probably confusing. +add_flag -Wno-sign-compare +# File transfer code has this. +add_flag -Wno-type-limits +# Callbacks often don't use all their parameters. +add_flag -Wno-unused-parameter +# cimple does this better +add_flag -Wno-unused-function +# struct Foo foo = {0}; is a common idiom. Missing braces means we'd need to +# write {{{0}}} in some cases, which is ugly and a maintenance burden. +add_flag -Wno-missing-braces +# __attribute__((nonnull)) causes this warning on defensive null checks. +add_flag -Wno-nonnull-compare diff --git a/.github/scripts/flags.sh b/.github/scripts/flags.sh new file mode 100644 index 0000000..80a56c0 --- /dev/null +++ b/.github/scripts/flags.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +add_config_flag() { CONFIG_FLAGS+=("$@"); } +add_c_flag() { C_FLAGS="$C_FLAGS $@"; } +add_cxx_flag() { CXX_FLAGS="$CXX_FLAGS $@"; } +add_ld_flag() { LD_FLAGS="$LD_FLAGS $@"; } +add_flag() { + add_c_flag "$@" + add_cxx_flag "$@" +} + +# Our own flags which we can insert in the correct place. We don't use CFLAGS +# and friends here (we unset them below), because they influence config tests +# such as ./configure and cmake tests. Our warning flags break those tests, so +# we can't add them globally here. +CONFIG_FLAGS=() +C_FLAGS="" +CXX_FLAGS="" +LD_FLAGS="" + +unset CFLAGS +unset CXXFLAGS +unset CPPFLAGS +unset LDFLAGS + +# Optimisation flags. +add_flag -O3 -march=native + +# Warn on non-ISO C. +add_c_flag -pedantic +add_c_flag -std=c99 +add_cxx_flag -std=c++11 + +add_flag -g3 +add_flag -ftrapv diff --git a/.github/scripts/sonar-build b/.github/scripts/sonar-build new file mode 100755 index 0000000..53efa8d --- /dev/null +++ b/.github/scripts/sonar-build @@ -0,0 +1,7 @@ +#!/bin/bash + +set -eu + +. ".github/scripts/flags-gcc.sh" + +cmake --build _build --parallel "$(nproc)" --target install -- -k 0 diff --git a/.github/scripts/sonar-prepare b/.github/scripts/sonar-prepare new file mode 100755 index 0000000..6d7224c --- /dev/null +++ b/.github/scripts/sonar-prepare @@ -0,0 +1,32 @@ +#!/bin/bash + +set -eu + +sudo apt-get install -y --no-install-recommends \ + libconfig-dev \ + libopus-dev \ + libsodium-dev \ + libvpx-dev \ + ninja-build + +. ".github/scripts/flags-gcc.sh" + +add_ld_flag -Wl,-z,defs + +# Make compilation error on a warning +add_flag -Werror + +cmake -B_build -H. -GNinja \ + -DCMAKE_C_FLAGS="$C_FLAGS" \ + -DCMAKE_CXX_FLAGS="$CXX_FLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="$LD_FLAGS" \ + -DCMAKE_INSTALL_PREFIX:PATH="$PWD/_install" \ + -DMIN_LOGGER_LEVEL=TRACE \ + -DMUST_BUILD_TOXAV=ON \ + -DNON_HERMETIC_TESTS=OFF \ + -DSTRICT_ABI=ON \ + -DTEST_TIMEOUT_SECONDS=120 \ + -DUSE_IPV6=OFF \ + -DAUTOTEST=ON \ + -DENABLE_SHARED=OFF diff --git a/.github/scripts/tox-bootstrapd-docker b/.github/scripts/tox-bootstrapd-docker new file mode 100755 index 0000000..d6c1ee2 --- /dev/null +++ b/.github/scripts/tox-bootstrapd-docker @@ -0,0 +1,77 @@ +#!/bin/bash + +set -exu + +LOCAL="${1:-}" + +readarray -t FILES <<<"$(git ls-files)" + +tar c "${FILES[@]}" | docker build -f other/bootstrap_daemon/docker/Dockerfile -t toxchat/bootstrap-node - +docker tag toxchat/bootstrap-node:latest toxchat/bootstrap-node:"$(other/print-version)" + +sudo useradd \ + --home-dir /var/lib/tox-bootstrapd \ + --create-home \ + --system \ + --shell /sbin/nologin \ + --comment "Account to run Tox's DHT bootstrap daemon" \ + --user-group tox-bootstrapd +sudo chmod 700 /var/lib/tox-bootstrapd +docker run -d --name tox-bootstrapd \ + --user "$(id -u tox-bootstrapd):$(id -g tox-bootstrapd)" \ + -v /var/lib/tox-bootstrapd/:/var/lib/tox-bootstrapd/ \ + --ulimit nofile=32768:32768 \ + -p 443:443 \ + -p 3389:3389 \ + -p 33445:33445 \ + -p 33445:33445/udp \ + toxchat/bootstrap-node + +sudo ls -lbh /var/lib/tox-bootstrapd + +if sudo [ ! -f /var/lib/tox-bootstrapd/keys ]; then + echo "Error: File /var/lib/tox-bootstrapd/keys doesn't exist" + exit 1 +fi + +if [ "$LOCAL" != "local" ]; then + COUNTER=0 + COUNTER_END=120 + while [ "$COUNTER" -lt "$COUNTER_END" ]; do + if docker logs tox-bootstrapd | grep -q "Connected to another bootstrap node successfully"; then + break + fi + sleep 1 + COUNTER=$(($COUNTER + 1)) + done + + docker logs tox-bootstrapd + + if [ "$COUNTER" = "$COUNTER_END" ]; then + echo "Error: Didn't connect to any nodes" + exit 1 + fi +else + docker logs tox-bootstrapd +fi + +# Wait a bit before testing if the container is still running +sleep 30 + +docker ps -a + +if [ "$(docker inspect -f {{.State.Running}} tox-bootstrapd)" != "true" ]; then + echo "Error: Container is not running" + exit 1 +fi + +cat /proc/"$(pidof tox-bootstrapd)"/limits +if ! grep -P '^Max open files(\s+)32768(\s+)32768(\s+)files' /proc/"$(pidof tox-bootstrapd)"/limits; then + echo "Error: ulimit is not set to the expected value" + exit 1 +fi + +if ! other/fun/bootstrap_node_info.py ipv4 localhost 33445; then + echo "Error: Unable to get bootstrap node info" + exit 1 +fi diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..7f4f86a --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,70 @@ +--- +_extends: .github + +repository: + name: c-toxcore + description: The future of online communications. + homepage: https://tox.chat/ + topics: toxcore, network, p2p, security, encryption, cryptography + +branches: + - name: "master" + protection: + required_status_checks: + contexts: + - "bazel-asan" + - "bazel-dbg" + - "bazel-opt" + - "bazel-tsan" + - "build-compcert" + - "build-macos" + - "build-nacl" + - "build-tcc" + - "build-win32" + - "build-win64" + - "CodeFactor" + - "common / buildifier" + - "coverage-linux" + - "ci/circleci: asan" + - "ci/circleci: clang-analyze" + - "ci/circleci: clang-tidy" + - "ci/circleci: cpplint" + - "ci/circleci: infer" + - "ci/circleci: msan" + - "ci/circleci: static-analysis" + - "ci/circleci: tsan" + - "ci/circleci: ubsan" + - "cimple" + - "code-review/reviewable" + - "continuous-integration/appveyor/pr" + - "docker-bootstrap-node" + - "docker-bootstrap-node-websocket" + - "docker-toxcore-js" + - "mypy" + - "sonar-scan" + +# Labels specific to c-toxcore. +labels: + - name: "bootstrap" + color: "#01707f" + description: "Bootstrap" + + - name: "crypto" + color: "#1d76db" + description: "Crypto" + + - name: "file transfers" + color: "#e02abf" + description: "File Transfers" + + - name: "messenger" + color: "#d93f0b" + description: "Messenger" + + - name: "network" + color: "#d4c5f9" + description: "Network" + + - name: "toxav" + color: "#0052cc" + description: "Audio/video" diff --git a/.github/workflows/cflite_batch.yml b/.github/workflows/cflite_batch.yml new file mode 100644 index 0000000..d853507 --- /dev/null +++ b/.github/workflows/cflite_batch.yml @@ -0,0 +1,37 @@ +# Derived from: https://google.github.io/clusterfuzzlite/running-clusterfuzzlite/github-actions/ + +name: ClusterFuzzLite batch fuzzing +on: + schedule: + - cron: '0 6,8 * * *' # Run twice a day at low activity times + workflow_dispatch: # Manual trigger for testing +permissions: read-all +jobs: + BatchFuzzing: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: + - address + - undefined + - memory + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + sanitizer: ${{ matrix.sanitizer }} + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 3600 # 60min + mode: 'batch' + sanitizer: ${{ matrix.sanitizer }} + # Optional but recommended: For storing certain artifacts from fuzzing. + # See later section on "Git repo for storage". + storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/TokTok/toktok-fuzzer.git + storage-repo-branch: master # Optional. Defaults to "main" + storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". diff --git a/.github/workflows/cflite_cron.yml b/.github/workflows/cflite_cron.yml new file mode 100644 index 0000000..c127a67 --- /dev/null +++ b/.github/workflows/cflite_cron.yml @@ -0,0 +1,50 @@ +# Derived from: https://google.github.io/clusterfuzzlite/running-clusterfuzzlite/github-actions/ + +name: ClusterFuzzLite cron tasks +on: + schedule: + - cron: '0 10 * * *' # Once a day, after fuzzing run + workflow_dispatch: # Manual trigger for testing + +permissions: read-all +jobs: + Pruning: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + - name: Run Fuzzers + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 600 + mode: 'prune' + # Optional but recommended. + # See later section on "Git repo for storage". + storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/TokTok/toktok-fuzzer.git + storage-repo-branch: master # Optional. Defaults to "main" + storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". + Coverage: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + sanitizer: coverage + - name: Run Fuzzers + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 600 + mode: 'coverage' + sanitizer: 'coverage' + # Optional but recommended. + # See later section on "Git repo for storage". + storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/TokTok/toktok-fuzzer.git + storage-repo-branch: master # Optional. Defaults to "main" + storage-repo-branch-coverage: gh-pages # Optional. Defaults to "gh-pages". + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5fa4792 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,212 @@ +name: ci + +on: + pull_request: + branches: [master] + +# Cancel old PR builds when pushing new commits. +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + common: + uses: TokTok/ci-tools/.github/workflows/common-ci.yml@master + + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Set up Python 3.9 + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install mypy + run: pip install mypy + - name: Run mypy + run: | + (find . -name "*.py" -and -not -name "conanfile.py"; grep -lR '^#!.*python') \ + | xargs -n1 -P8 mypy --strict + + doxygen: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Docker Build + uses: docker/build-push-action@v2 + with: + file: other/docker/doxygen/Dockerfile + + tokstyle: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Docker Build + uses: docker/build-push-action@v2 + with: + file: other/docker/tokstyle/Dockerfile + + misra: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Docker Build + uses: docker/build-push-action@v2 + with: + file: other/docker/misra/Dockerfile + + cimplefmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Run cimplefmt + run: other/docker/cimplefmt/run -u $(find tox* -name "*.[ch]") + + build-nacl: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Docker Build + uses: docker/build-push-action@v2 + with: + file: other/docker/autotools/Dockerfile + + build-win32: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Cross compilation + run: .github/scripts/cmake-win32 script + + build-win64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Cross compilation + run: .github/scripts/cmake-win64 script + + build-freebsd: + runs-on: ubuntu-latest + container: toxchat/freebsd + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build on FreeBSD + run: .github/scripts/cmake-freebsd-stage2 + + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build and test + run: .github/scripts/cmake-osx + + coverage-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build, test, and upload coverage + run: .github/scripts/coverage-linux + + build-tcc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install dependencies + run: + sudo apt-get install -y --no-install-recommends + tcc + libconfig-dev + libopus-dev + libsodium-dev + libvpx-dev + - name: Build with TCC + run: + tcc + -Dinline=static + -o send_message_test + -Wall -Werror + -bench -g + auto_tests/auto_test_support.c + auto_tests/send_message_test.c + testing/misc_tools.c + toxav/*.c + toxcore/*.c + toxcore/*/*.c + toxencryptsave/*.c + third_party/cmp/*.c + $(pkg-config --cflags --libs libsodium opus vpx) + - name: Run the test + run: "./send_message_test | grep 'tox clients connected'" + - name: Build amalgamation file with TCC + run: + other/make_single_file + auto_tests/auto_test_support.c + auto_tests/send_message_test.c + testing/misc_tools.c | + tcc - + -o send_message_test + -Wall -Werror + -bench -g + $(pkg-config --cflags --libs libsodium opus vpx) + - name: Run the test again + run: "./send_message_test | grep 'tox clients connected'" + + build-compcert: + runs-on: ubuntu-latest + container: toxchat/compcert + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Build with CompCert + run: + ccomp + -o send_message_test + -Wall -Werror + -Wno-c11-extensions + -Wno-unknown-pragmas + -Wno-unused-variable + -fstruct-passing -fno-unprototyped -g + auto_tests/auto_test_support.c + auto_tests/send_message_test.c + testing/misc_tools.c + toxav/*.c + toxcore/*.c + toxcore/*/*.c + toxencryptsave/*.c + third_party/cmp/*.c + -D__COMPCERT__ -DDISABLE_VLA -Dinline= + -lpthread $(pkg-config --cflags --libs libsodium opus vpx) + - name: Run the test + run: "./send_message_test | grep 'tox clients connected'" + + build-android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - run: .github/scripts/cmake-android armeabi-v7a + - run: .github/scripts/cmake-android arm64-v8a + - run: .github/scripts/cmake-android x86 + - run: .github/scripts/cmake-android x86_64 diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml new file mode 100644 index 0000000..56776cb --- /dev/null +++ b/.github/workflows/coverity-scan.yml @@ -0,0 +1,51 @@ +name: coverity-scan + +on: + schedule: + - cron: '0 10 * * *' # Once a day + +jobs: + latest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Install libraries + run: + sudo apt-get update && + sudo apt-get install -y --no-install-recommends + libopus-dev + libsodium-dev + libvpx-dev + + - name: Download Coverity Build Tool + run: | + wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=TokTok/c-toxcore" -O cov-analysis-linux64.tar.gz + mkdir cov-analysis-linux64 + tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64 + env: + TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + + - name: Run autoreconf + run: autoreconf -fi + + - name: Configure + run: ./configure + + - name: Build with cov-build + run: cov-analysis-linux64/bin/cov-build --dir cov-int make + + - name: Submit the result to Coverity Scan + run: + tar czvf c-toxcore.tgz cov-int && + curl + --form project=TokTok/c-toxcore + --form token=$TOKEN + --form email=iphydf@gmail.com + --form file=@c-toxcore.tgz + --form version="$(git rev-list --count HEAD)" + --form description="CI build of $(git rev-parse --abbrev-ref HEAD) branch" + https://scan.coverity.com/builds + env: + TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..4921013 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,187 @@ +name: docker + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + docker-bootstrap-node: + runs-on: ubuntu-latest + steps: + - name: Login to DockerHub + if: ${{ github.event_name == 'push' }} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Docker Build + run: .github/scripts/tox-bootstrapd-docker local + - name: Push latest image to DockerHub + if: ${{ github.event_name == 'push' }} + run: docker push toxchat/bootstrap-node:latest + - name: Push versioned image to DockerHub + if: ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags/') }} + run: docker push toxchat/bootstrap-node:"$(other/print-version)" + + docker-bootstrap-node-websocket: + runs-on: ubuntu-latest + needs: [docker-bootstrap-node] + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + if: ${{ github.event_name == 'push' }} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: "{{defaultContext}}:other/bootstrap_daemon/websocket" + push: ${{ github.event_name == 'push' }} + tags: toxchat/bootstrap-node:latest-websocket + cache-from: type=registry,ref=toxchat/bootstrap-node:latest-websocket + cache-to: type=inline + + docker-clusterfuzz: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + if: ${{ github.event_name == 'push' }} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: "." + file: .clusterfuzzlite/Dockerfile + push: ${{ github.event_name == 'push' }} + tags: toxchat/c-toxcore:clusterfuzz + cache-from: type=registry,ref=toxchat/c-toxcore:clusterfuzz + cache-to: type=inline + + docker-fuzzer: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + if: ${{ github.event_name == 'push' }} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + file: testing/Dockerfile + push: ${{ github.event_name == 'push' }} + tags: toxchat/c-toxcore:fuzzer + cache-from: type=registry,ref=toxchat/c-toxcore:fuzzer + cache-to: type=inline + + docker-toxcore-js: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + if: ${{ github.event_name == 'push' }} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + file: other/emscripten/Dockerfile + push: ${{ github.event_name == 'push' }} + tags: toxchat/c-toxcore:wasm + cache-from: type=registry,ref=toxchat/c-toxcore:wasm + cache-to: type=inline + + docker-esp32: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + with: + driver: docker + - name: Login to DockerHub + if: ${{ github.event_name == 'push' }} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build toxchat/c-toxcore:sources + uses: docker/build-push-action@v2 + with: + file: other/docker/sources/Dockerfile + tags: toxchat/c-toxcore:sources + - name: Build and push + uses: docker/build-push-action@v2 + with: + file: other/docker/esp32/Dockerfile + push: ${{ github.event_name == 'push' }} + tags: toxchat/c-toxcore:esp32 + cache-from: type=registry,ref=toxchat/c-toxcore:esp32 + cache-to: type=inline + + docker-win32: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + if: ${{ github.event_name == 'push' }} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: "{{defaultContext}}:other/docker/windows" + push: ${{ github.event_name == 'push' }} + tags: toxchat/windows:win32 + cache-from: type=registry,ref=toxchat/windows:win32 + cache-to: type=inline + build-args: | + SUPPORT_ARCH_i686=true + SUPPORT_ARCH_x86_64=false + SUPPORT_TEST=true + + docker-win64: + runs-on: ubuntu-latest + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + if: ${{ github.event_name == 'push' }} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: "{{defaultContext}}:other/docker/windows" + push: ${{ github.event_name == 'push' }} + tags: toxchat/windows:win64 + cache-from: type=registry,ref=toxchat/windows:win64 + cache-to: type=inline + build-args: | + SUPPORT_ARCH_i686=false + SUPPORT_ARCH_x86_64=true + SUPPORT_TEST=true diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml new file mode 100644 index 0000000..45e32dd --- /dev/null +++ b/.github/workflows/sonar-scan.yml @@ -0,0 +1,51 @@ +name: sonar-scan + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + sonar-scan: + runs-on: ubuntu-latest + env: + SONAR_SCANNER_VERSION: 4.4.0.2170 + SONAR_SERVER_URL: "https://sonarcloud.io" + BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + submodules: recursive + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Download and set up sonar-scanner + env: + SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip + run: | + mkdir -p $HOME/.sonar + curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }} + unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ + echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH + - name: Download and set up build-wrapper + env: + BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip + run: | + curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }} + unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ + echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH + - name: Install dependencies and prepare build + run: | + .github/scripts/sonar-prepare + - name: Run build-wrapper + run: | + build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} .github/scripts/sonar-build + - name: Run sonar-scanner + if: github.event_name == 'push' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: 'sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}"' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc3cc5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,94 @@ +# OS files +.DS_Store +.DS_Store? +._* +.mypy_cache +.Spotlight-V100 +.Trash* +Icon? +ethumbs.db +Thumbs.db +*.tmp + +# Make +/_build +/_install +/tox-0.0.0* +CMakeCache.txt +CMakeFiles +Makefile +!/other/rpm/Makefile +cmake_install.cmake +install_manifest.txt +tags +Makefile.in +CMakeLists.txt.user +DartConfiguration.tcl +CTestTestfile.cmake +*.pc + +# Testing +/amalgamation.* +testing/data +*~ + +# Vim +*.swp + +# Object files +*.o +*.lo +*.a + +# Executables +*.exe +*.out +*.app +*.la + +# Libraries +*.so + +# Misc (?) +m4/* +configure +configure_aux +!m4/pkg.m4 +aclocal.m4 +config.h* +config.log +config.status +stamp-h1 +autom4te.cache +libtool +.deps +.libs +.dirstamp +build/ +*.nvim* +*.vim* + +#kdevelop +.kdev/ +*.kdev* + +# VScode +.vscode/ + +# Netbeans +nbproject + +# astyle +*.orig + +# Android buildscript +android-toolchain-* +toxcore-android-* + +# cscope files list +cscope.files + +# rpm +tox.spec + +.idea/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e2793dd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third_party/cmp"] + path = third_party/cmp + url = https://github.com/camgunz/cmp diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000..98122e6 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,13 @@ +--- +ignored: + # "cd" is sometimes useful when you want to run one command in one directory + # and then another command in another directory, but they should be executed + # in the same RUN instruction. + - DL3003 + - DL3007 + - DL3008 + - DL3013 + - DL3018 + - DL3059 + # $(pkg-config ...) needs this + - SC2046 diff --git a/.restyled.yaml b/.restyled.yaml new file mode 100644 index 0000000..aecd497 --- /dev/null +++ b/.restyled.yaml @@ -0,0 +1,22 @@ +--- +exclude: + - "**/*.api.h" + # shfmt doesn't support this file + - "other/analysis/run-clang-tidy" + +restylers: + - astyle: + arguments: ["--options=other/astyle/astylerc"] + include: + - "!**/*.cc" + - autopep8 + - black + - clang-format: + image: restyled/restyler-clang-format:13.0.1 + include: + - "**/*.cc" + - prettier-yaml + - reorder-python-imports + - shellharden + - shfmt + - yapf diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..4d39dab --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,38 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") +load("//tools/project:build_defs.bzl", "project") + +package(features = ["layering_check"]) + +project() + +genrule( + name = "public_headers", + srcs = [ + "//c-toxcore/toxav:toxav.h", + "//c-toxcore/toxcore:tox.h", + "//c-toxcore/toxencryptsave:toxencryptsave.h", + ], + outs = [ + "tox/toxav.h", + "tox/tox.h", + "tox/toxencryptsave.h", + ], + cmd = """ + 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/toxencryptsave:toxencryptsave.h) $(GENDIR)/c-toxcore/tox/toxencryptsave.h + """, + visibility = ["//visibility:public"], +) + +cc_library( + name = "c-toxcore", + hdrs = [":public_headers"], + includes = ["."], + visibility = ["//visibility:public"], + deps = [ + "//c-toxcore/toxav", + "//c-toxcore/toxcore", + "//c-toxcore/toxencryptsave", + ], +) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..082b788 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1669 @@ + + +## v0.2.18 + +### Merged PRs: + +- [#2299](https://github.com/TokTok/c-toxcore/pull/2299) fix: remove the assert because buffer can be larger than UINT16_MAX. +- [#2297](https://github.com/TokTok/c-toxcore/pull/2297) cleanup: remove unused field last_seen from Onion_Friend +- [#2289](https://github.com/TokTok/c-toxcore/pull/2289) test: Add a Null_System used in toxsave_harness. +- [#2288](https://github.com/TokTok/c-toxcore/pull/2288) test: enable additional sanitizers for fuzzing +- [#2287](https://github.com/TokTok/c-toxcore/pull/2287) fix: Don't allow onion paths to be built from real friends. +- [#2285](https://github.com/TokTok/c-toxcore/pull/2285) test: Don't abort fuzz test when tox_new fails. +- [#2284](https://github.com/TokTok/c-toxcore/pull/2284) refactor: Move crypto utilities from util to crypto_core. +- [#2283](https://github.com/TokTok/c-toxcore/pull/2283) fix: Allow onion paths to be built from more random nodes. +- [#2282](https://github.com/TokTok/c-toxcore/pull/2282) fix: Fix potential array out-of-bounds in DHT onion path building. +- [#2281](https://github.com/TokTok/c-toxcore/pull/2281) cleanup: Avoid goto in msi.c. +- [#2280](https://github.com/TokTok/c-toxcore/pull/2280) test: Improve test hermeticity by using local TCP relay. +- [#2279](https://github.com/TokTok/c-toxcore/pull/2279) test: Enable fuzzing for TCP. +- [#2277](https://github.com/TokTok/c-toxcore/pull/2277) refactor: Avoid `static_cast` in `Fuzz_System` functions. +- [#2275](https://github.com/TokTok/c-toxcore/pull/2275) chore: Downgrade C++ version to 17 +- [#2273](https://github.com/TokTok/c-toxcore/pull/2273) fix: Really fix overrun added in e49a477a +- [#2272](https://github.com/TokTok/c-toxcore/pull/2272) fix: Add missing return on error +- [#2270](https://github.com/TokTok/c-toxcore/pull/2270) test: Improve error messages in toxav_basic_test. +- [#2266](https://github.com/TokTok/c-toxcore/pull/2266) fix: Fixed leak and overrun added in e49a477a +- [#2263](https://github.com/TokTok/c-toxcore/pull/2263) chore: Upgrade to C++20 in CMake build. +- [#2261](https://github.com/TokTok/c-toxcore/pull/2261) refactor: Protect array unpacking against invalid lengths. +- [#2258](https://github.com/TokTok/c-toxcore/pull/2258) refactor: Rename announce functions into their own namespace. +- [#2257](https://github.com/TokTok/c-toxcore/pull/2257) fix: Format IP as string again in error log. +- [#2255](https://github.com/TokTok/c-toxcore/pull/2255) fix: Fix a stack overflow triggered by small DHT packets. +- [#2251](https://github.com/TokTok/c-toxcore/pull/2251) chore: Add MISRA-2012 check using cppcheck's misra addon. +- [#2250](https://github.com/TokTok/c-toxcore/pull/2250) cleanup: remove populate_path_nodes_tcp +- [#2249](https://github.com/TokTok/c-toxcore/pull/2249) test: Improve stability of forwarding_test. +- [#2248](https://github.com/TokTok/c-toxcore/pull/2248) refactor: Use a struct for the `ip_ntoa` buffer. +- [#2246](https://github.com/TokTok/c-toxcore/pull/2246) fix: Don't crash if RNG init failed. +- [#2244](https://github.com/TokTok/c-toxcore/pull/2244) refactor: Allow NULL logger; make it no-op in NDEBUG. +- [#2243](https://github.com/TokTok/c-toxcore/pull/2243) chore: Add initial ESP32 docker build. +- [#2242](https://github.com/TokTok/c-toxcore/pull/2242) cleanup: Use `static_assert` instead of `assert` where possible. +- [#2240](https://github.com/TokTok/c-toxcore/pull/2240) chore(deps): Use upstream cmp directly instead of our fork. +- [#2238](https://github.com/TokTok/c-toxcore/pull/2238) cleanup: Make `*_free` and `kill_*` functions nullable. +- [#2236](https://github.com/TokTok/c-toxcore/pull/2236) chore: Add dependabot config. +- [#2234](https://github.com/TokTok/c-toxcore/pull/2234) test: Add more functionality to the bootstrap fuzz harness. +- [#2233](https://github.com/TokTok/c-toxcore/pull/2233) cleanup: Replace a series of `if` statements with a `switch`. +- [#2232](https://github.com/TokTok/c-toxcore/pull/2232) test: Add fuzzer support functions for internal toxcore objects. +- [#2230](https://github.com/TokTok/c-toxcore/pull/2230) feat: Merge group announce portion of new groupchats implementation +- [#2229](https://github.com/TokTok/c-toxcore/pull/2229) cleanup: Remove layers in the cmake build. +- [#2228](https://github.com/TokTok/c-toxcore/pull/2228) chore: Fix path to fuzzer binaries in clusterfuzz build. +- [#2227](https://github.com/TokTok/c-toxcore/pull/2227) cleanup: Disallow stack frames of over 9000 bytes. +- [#2225](https://github.com/TokTok/c-toxcore/pull/2225) fix: fix typo in git command in INSTALL.md +- [#2224](https://github.com/TokTok/c-toxcore/pull/2224) cleanup: Add include for assert.h for the fuzzing build. +- [#2223](https://github.com/TokTok/c-toxcore/pull/2223) chore: Add `uint8_t` version of `bin_pack` for numbers. +- [#2219](https://github.com/TokTok/c-toxcore/pull/2219) refactor: Rename bin_pack/unpack functions the same as cmp funcs. +- [#2217](https://github.com/TokTok/c-toxcore/pull/2217) cleanup: Add Network object parameter for addr_resolve. +- [#2214](https://github.com/TokTok/c-toxcore/pull/2214) cleanup: Remove all uses of `TOX_*_MAX_SIZE` macros. +- [#2213](https://github.com/TokTok/c-toxcore/pull/2213) cleanup: Remove unused random_testing program. +- [#2212](https://github.com/TokTok/c-toxcore/pull/2212) cleanup: Expand `CONST_FUNCTION` and remove the macro. +- [#2211](https://github.com/TokTok/c-toxcore/pull/2211) cleanup: Move definitions of tox_private.h functions to tox_private.c. +- [#2210](https://github.com/TokTok/c-toxcore/pull/2210) chore: Clone submodules in clusterfuzzlite Dockerfile. +- [#2208](https://github.com/TokTok/c-toxcore/pull/2208) chore: Remove valgrind build. +- [#2206](https://github.com/TokTok/c-toxcore/pull/2206) feat: Allow overriding mono_time in tox_new. +- [#2204](https://github.com/TokTok/c-toxcore/pull/2204) chore: Support producing shared libraries on Windows +- [#2202](https://github.com/TokTok/c-toxcore/pull/2202) cleanup: Avoid name clash between struct field and function. +- [#2201](https://github.com/TokTok/c-toxcore/pull/2201) docs: Add information regarding git submodules, cmp to INSTALL.md +- [#2200](https://github.com/TokTok/c-toxcore/pull/2200) cleanup: Add more null checks in `tox_new`. +- [#2199](https://github.com/TokTok/c-toxcore/pull/2199) cleanup: Mark Mono_Time const where possible. +- [#2196](https://github.com/TokTok/c-toxcore/pull/2196) test: Remove save_load_test from autotools build. +- [#2195](https://github.com/TokTok/c-toxcore/pull/2195) fix: Correct calculation of packet sent time +- [#2193](https://github.com/TokTok/c-toxcore/pull/2193) perf: Make time move a lot faster in fuzzing runs. +- [#2190](https://github.com/TokTok/c-toxcore/pull/2190) feat: Add support for custom random number generator. +- [#2189](https://github.com/TokTok/c-toxcore/pull/2189) chore: Add third_party to toxcore-sources docker image. +- [#2188](https://github.com/TokTok/c-toxcore/pull/2188) cleanup: Make addr_resolve a private function. +- [#2187](https://github.com/TokTok/c-toxcore/pull/2187) test: Add DHT and tox_events fuzz tests to the cmake build. +- [#2186](https://github.com/TokTok/c-toxcore/pull/2186) cleanup: Use `_Static_assert` in gcc/clang. +- [#2184](https://github.com/TokTok/c-toxcore/pull/2184) test: Add some support functions to make writing fuzzers easier. +- [#2183](https://github.com/TokTok/c-toxcore/pull/2183) test: Enable more tests for msan. +- [#2182](https://github.com/TokTok/c-toxcore/pull/2182) test: Improve stability of tox_many_tcp_test. +- [#2177](https://github.com/TokTok/c-toxcore/pull/2177) feat: Add support for overriding network functions. +- [#2176](https://github.com/TokTok/c-toxcore/pull/2176) test: try to save the fuzzed save file again +- [#2175](https://github.com/TokTok/c-toxcore/pull/2175) refactor: Use cmp instead of msgpack-c for events packing. +- [#2173](https://github.com/TokTok/c-toxcore/pull/2173) chore: Use "master" as the branch in toktok-fuzzer. +- [#2170](https://github.com/TokTok/c-toxcore/pull/2170) test: Add fuzzer tests to the bazel build. +- [#2169](https://github.com/TokTok/c-toxcore/pull/2169) feat: Merge moderation portion of new groupchats codebase +- [#2167](https://github.com/TokTok/c-toxcore/pull/2167) cleanup: Reduce scope of array-typed variables where possible. +- [#2166](https://github.com/TokTok/c-toxcore/pull/2166) cleanup: Reduce scope of variables as requested by cimple. +- [#2164](https://github.com/TokTok/c-toxcore/pull/2164) feat: Merge onion_announce changes from new groupchats fork +- [#2163](https://github.com/TokTok/c-toxcore/pull/2163) chore: Enable memory sanitizer build. +- [#2160](https://github.com/TokTok/c-toxcore/pull/2160) test: Improve the stability of the save_load test +- [#2159](https://github.com/TokTok/c-toxcore/pull/2159) chore: Update the Appveyor build to VS2019 to work around conan issues +- [#2158](https://github.com/TokTok/c-toxcore/pull/2158) cleanup: Expose `struct Tox` to internal code. +- [#2157](https://github.com/TokTok/c-toxcore/pull/2157) cleanup: Split the huge TCP client packet handler. +- [#2156](https://github.com/TokTok/c-toxcore/pull/2156) cleanup: Sync doc comments between .h and .c files. +- [#2155](https://github.com/TokTok/c-toxcore/pull/2155) fix: Eliminate memory leak in toxav. +- [#2154](https://github.com/TokTok/c-toxcore/pull/2154) test: Speed up toxav_many_test by using fake mono_time. +- [#2153](https://github.com/TokTok/c-toxcore/pull/2153) docs: Put all the tox public api into a fake "tox" namespace. +- [#2150](https://github.com/TokTok/c-toxcore/pull/2150) cleanup: Move all the group.h structs into group.c. +- [#2149](https://github.com/TokTok/c-toxcore/pull/2149) cleanup: Avoid `memset` on structs. +- [#2147](https://github.com/TokTok/c-toxcore/pull/2147) feat: Store announcements +- [#2146](https://github.com/TokTok/c-toxcore/pull/2146) chore: Add testing/Dockerfile to the CI build. +- [#2145](https://github.com/TokTok/c-toxcore/pull/2145) chore: Be even more explicit about integer range bounds. +- [#2144](https://github.com/TokTok/c-toxcore/pull/2144) cleanup: Minor cleanups in TCP_connection.c. +- [#2143](https://github.com/TokTok/c-toxcore/pull/2143) fix: Allow port range in DHT_bootstrap.c. +- [#2142](https://github.com/TokTok/c-toxcore/pull/2142) cleanup: Count re-adding an existing bootstrap node as success. +- [#2141](https://github.com/TokTok/c-toxcore/pull/2141) cleanup: Disable LAN discovery in TCP-only mode. +- [#2140](https://github.com/TokTok/c-toxcore/pull/2140) cleanup: Add assertion for decrypted data length. +- [#2101](https://github.com/TokTok/c-toxcore/pull/2101) cleanup: improve CMakeLists.txt structure +- [#2100](https://github.com/TokTok/c-toxcore/pull/2100) chore: remove cpufeatures.c +- [#2098](https://github.com/TokTok/c-toxcore/pull/2098) chore: remove DHT_test.c since it's unused + +### Closed issues: + +- [#2256](https://github.com/TokTok/c-toxcore/issues/2256) New Defects reported by Coverity Scan for TokTok/c-toxcore +- [#2109](https://github.com/TokTok/c-toxcore/issues/2109) Assimilate `messenger_test.c`: replace with public API test +- [#2012](https://github.com/TokTok/c-toxcore/issues/2012) Support building a DLL on Windows +- [#907](https://github.com/TokTok/c-toxcore/issues/907) use dll abnormal System.AccessViolationException win7 x32 but x64 no problem + +## v0.2.17 + +### Merged PRs: + +- [#2138](https://github.com/TokTok/c-toxcore/pull/2138) chore: Release 0.2.17 +- [#2136](https://github.com/TokTok/c-toxcore/pull/2136) docs: Clean up doxygen comments to be more doxygen-like. +- [#2135](https://github.com/TokTok/c-toxcore/pull/2135) cleanup: Move test-only functions into tests. +- [#2134](https://github.com/TokTok/c-toxcore/pull/2134) cleanup: Add explicit callback setters for MSI callbacks. +- [#2133](https://github.com/TokTok/c-toxcore/pull/2133) cleanup: Remove redundant Messenger and DHT tests. +- [#2132](https://github.com/TokTok/c-toxcore/pull/2132) cleanup: Remove dependency from LAN_discovery onto DHT. +- [#2131](https://github.com/TokTok/c-toxcore/pull/2131) cleanup: Split util.c out of the network library. +- [#2130](https://github.com/TokTok/c-toxcore/pull/2130) cleanup: Remove redundant `()` around `return` expression. +- [#2128](https://github.com/TokTok/c-toxcore/pull/2128) test: Add unit test for create/handle request packets. +- [#2127](https://github.com/TokTok/c-toxcore/pull/2127) cleanup: Remove `EAGAIN` from the list of ignored errors. +- [#2126](https://github.com/TokTok/c-toxcore/pull/2126) cleanup: Remove port from `Broadcast_Info`. +- [#2125](https://github.com/TokTok/c-toxcore/pull/2125) cleanup: Don't reference local variables in macro bodies. +- [#2123](https://github.com/TokTok/c-toxcore/pull/2123) cleanup: Remove some more implicit bool conversions. +- [#2122](https://github.com/TokTok/c-toxcore/pull/2122) chore: Enable a bunch more warnings in GCC builds. +- [#2120](https://github.com/TokTok/c-toxcore/pull/2120) cleanup: Remove all boolean-int conversions. +- [#2117](https://github.com/TokTok/c-toxcore/pull/2117) cleanup: Comply with strict include ordering. +- [#2116](https://github.com/TokTok/c-toxcore/pull/2116) cleanup: Remove more boolean conversions (and a bugfix). +- [#2115](https://github.com/TokTok/c-toxcore/pull/2115) cleanup: Log at ERROR level when `connect()` fails. +- [#2114](https://github.com/TokTok/c-toxcore/pull/2114) cleanup: Avoid creating invalid enum values. +- [#2113](https://github.com/TokTok/c-toxcore/pull/2113) cleanup: Remove superfluous parentheses on the rhs of assignments. +- [#2108](https://github.com/TokTok/c-toxcore/pull/2108) test: Add mallocfail and proxy test to our coverage runs. +- [#2106](https://github.com/TokTok/c-toxcore/pull/2106) test: Add test coverage docker build for local tests. +- [#2105](https://github.com/TokTok/c-toxcore/pull/2105) cleanup: Change valid status of onion friend to bool. +- [#2104](https://github.com/TokTok/c-toxcore/pull/2104) fix: Report failure to DHT bootstrap back to the client. +- [#2103](https://github.com/TokTok/c-toxcore/pull/2103) cleanup: Remove more implicit bool conversions. +- [#2102](https://github.com/TokTok/c-toxcore/pull/2102) cleanup: Mark all local non-pointers as const where possible. +- [#2099](https://github.com/TokTok/c-toxcore/pull/2099) cleanup: Reduce name shadowing; remove ptr-to-bool conversions. +- [#2097](https://github.com/TokTok/c-toxcore/pull/2097) cleanup: Remove implicit conversions from `uint32_t` to `bool`. +- [#2096](https://github.com/TokTok/c-toxcore/pull/2096) cleanup: Return boolean constants, not ints from bool functions. +- [#2094](https://github.com/TokTok/c-toxcore/pull/2094) fix: Null function pointer dereference. +- [#2093](https://github.com/TokTok/c-toxcore/pull/2093) cleanup: Remove any disallowed casts. +- [#2092](https://github.com/TokTok/c-toxcore/pull/2092) perf: fix issue with friend finding taking too long after disconnects. +- [#2091](https://github.com/TokTok/c-toxcore/pull/2091) cleanup: Avoid casting function pointers. +- [#2090](https://github.com/TokTok/c-toxcore/pull/2090) refactor: Move `tox_new_log` to `auto_test_support`. +- [#2088](https://github.com/TokTok/c-toxcore/pull/2088) cleanup: Remove all implicit bool conversions in if-conditions. +- [#2086](https://github.com/TokTok/c-toxcore/pull/2086) cleanup: Enable most cppcheck warnings as errors. +- [#2084](https://github.com/TokTok/c-toxcore/pull/2084) fix: out-of-memory condition by corrupted save file +- [#2079](https://github.com/TokTok/c-toxcore/pull/2079) cleanup: Remove unused execution trace library. +- [#2078](https://github.com/TokTok/c-toxcore/pull/2078) perf: Refactor onion_client.c do_friends() to reduce network traffic +- [#2075](https://github.com/TokTok/c-toxcore/pull/2075) chore: Add a .clang-format that mostly agrees with astyle. +- [#2074](https://github.com/TokTok/c-toxcore/pull/2074) chore: Make coverity scan a nightly job. +- [#2073](https://github.com/TokTok/c-toxcore/pull/2073) chore: Fix build-args in win32/win64 docker build. +- [#2072](https://github.com/TokTok/c-toxcore/pull/2072) chore: Simplify and speed up windows builds using docker image. +- [#2070](https://github.com/TokTok/c-toxcore/pull/2070) chore: Add docker image builds for windows cross compiler. +- [#2064](https://github.com/TokTok/c-toxcore/pull/2064) fix: toxsave memory leak while loading groups + +### Closed issues: + +- [#2121](https://github.com/TokTok/c-toxcore/issues/2121) Improve speed and stability of LAN discovery test +- [#2083](https://github.com/TokTok/c-toxcore/issues/2083) Tox save file format/loader doesn't limit or check the number of frozen peers +- [#2080](https://github.com/TokTok/c-toxcore/issues/2080) speedup getting online after tox process has been suspended +- [#2050](https://github.com/TokTok/c-toxcore/issues/2050) Excessive onion friend lookups +- [#2034](https://github.com/TokTok/c-toxcore/issues/2034) Make coverity-scan workflow a nightly build instead of on master push +- [#2000](https://github.com/TokTok/c-toxcore/issues/2000) Move win32 docker image build out to dockerfiles +- [#1990](https://github.com/TokTok/c-toxcore/issues/1990) Add a .clang-format config that matches the astyle config as closely as possible. +- [#1115](https://github.com/TokTok/c-toxcore/issues/1115) Add empty handler for LAN discovery packets when LAN discovery is disabled +- [#647](https://github.com/TokTok/c-toxcore/issues/647) `tox_bootstrap` should return an error on unrecoverable `getnodes` failure +- [#169](https://github.com/TokTok/c-toxcore/issues/169) Get rid of TOX_USER_STATUS_BUSY + 1 + +## v0.2.16 + +### Merged PRs: + +- [#2071](https://github.com/TokTok/c-toxcore/pull/2071) chore: Release 0.2.16 +- [#2069](https://github.com/TokTok/c-toxcore/pull/2069) chore: Simplify and speed up nacl build using toxchat/nacl. +- [#2066](https://github.com/TokTok/c-toxcore/pull/2066) test: Add a profiling script and Dockerfile. +- [#2058](https://github.com/TokTok/c-toxcore/pull/2058) fix: properly deallocate frozen peers +- [#2056](https://github.com/TokTok/c-toxcore/pull/2056) cleanup: Avoid implicit boolean and floating point conversions in decls. +- [#2055](https://github.com/TokTok/c-toxcore/pull/2055) cleanup: Avoid implicit bool conversions in logical operators. +- [#2053](https://github.com/TokTok/c-toxcore/pull/2053) cleanup: Enable tokstyle's `-Wlarge-struct-params`. +- [#2052](https://github.com/TokTok/c-toxcore/pull/2052) fix: Fix return type of functions returning uint64_t. +- [#2049](https://github.com/TokTok/c-toxcore/pull/2049) cleanup: Apply stronger type checks and fix errors. +- [#2047](https://github.com/TokTok/c-toxcore/pull/2047) feat: Improve how we share TCP relays with friends +- [#2046](https://github.com/TokTok/c-toxcore/pull/2046) cleanup: Avoid implicit pointer-to-bool conversion in `if` in toxav. +- [#2043](https://github.com/TokTok/c-toxcore/pull/2043) refactor: Compare pointers in if conditions to nullptr. +- [#2041](https://github.com/TokTok/c-toxcore/pull/2041) fix: file transfer bug introduced in commit 2073d02 +- [#2039](https://github.com/TokTok/c-toxcore/pull/2039) refactor: Add a bit more logging; change WARNING to ERROR. +- [#2036](https://github.com/TokTok/c-toxcore/pull/2036) chore: Add BUILD file for websockify. +- [#2035](https://github.com/TokTok/c-toxcore/pull/2035) chore: fine tune fuzzing settings +- [#2033](https://github.com/TokTok/c-toxcore/pull/2033) cleanup: Add some more error path logging to TCP server code. +- [#2032](https://github.com/TokTok/c-toxcore/pull/2032) chore: update the list of CMake options & Windows Docker build deps +- [#2031](https://github.com/TokTok/c-toxcore/pull/2031) fix: remove bogus asserts in fuzzer harness +- [#2030](https://github.com/TokTok/c-toxcore/pull/2030) chore: expand fuzzing to toxsave +- [#2028](https://github.com/TokTok/c-toxcore/pull/2028) fix: syntax error introduced in 8bf37994fd12acec9e3010437502f478399b99b4 +- [#2027](https://github.com/TokTok/c-toxcore/pull/2027) fix: add continous fuzzing +- [#2026](https://github.com/TokTok/c-toxcore/pull/2026) chore: Fix implicit declaration warning in fuzz build +- [#2025](https://github.com/TokTok/c-toxcore/pull/2025) chore: add continous fuzzing to our CI +- [#2024](https://github.com/TokTok/c-toxcore/pull/2024) perf: Reduce minimal encoding size of packed events. +- [#2023](https://github.com/TokTok/c-toxcore/pull/2023) cleanup: Add wrapper library for msgpack pack functions +- [#2022](https://github.com/TokTok/c-toxcore/pull/2022) cleanup: Split tox_unpack into two smaller libs +- [#2021](https://github.com/TokTok/c-toxcore/pull/2021) chore: Disable non-null attributes by default. +- [#2019](https://github.com/TokTok/c-toxcore/pull/2019) chore: Silence clang compile warnings causing circle-ci/asan to fail +- [#2018](https://github.com/TokTok/c-toxcore/pull/2018) fix: Coverty scan +- [#2016](https://github.com/TokTok/c-toxcore/pull/2016) docs: Add libmsgpack dependency in INSTALL.md +- [#2015](https://github.com/TokTok/c-toxcore/pull/2015) fix: shared toxcore autotools build failing +- [#2013](https://github.com/TokTok/c-toxcore/pull/2013) cleanup: Don't use VLAs for huge allocations. +- [#2011](https://github.com/TokTok/c-toxcore/pull/2011) fix: Conan build link failures +- [#2010](https://github.com/TokTok/c-toxcore/pull/2010) chore: Remove duplicate source file in autotools build. +- [#2008](https://github.com/TokTok/c-toxcore/pull/2008) chore: get skeletons out of the closet +- [#2007](https://github.com/TokTok/c-toxcore/pull/2007) feat: add bash-completion for tox-bootstrapd +- [#2006](https://github.com/TokTok/c-toxcore/pull/2006) cleanup: Add more nonnull and nullable annotations. +- [#2002](https://github.com/TokTok/c-toxcore/pull/2002) cleanup: Add nonnull annotations to function definitions. +- [#2001](https://github.com/TokTok/c-toxcore/pull/2001) chore: Add an undefined behaviour/integer sanitizer build. +- [#1999](https://github.com/TokTok/c-toxcore/pull/1999) chore: Speed up cmake builds with `UNITY_BUILD`. +- [#1996](https://github.com/TokTok/c-toxcore/pull/1996) feat: Add unpacker functions for events structures. +- [#1993](https://github.com/TokTok/c-toxcore/pull/1993) feat: Add binary packing functions for tox events. +- [#1992](https://github.com/TokTok/c-toxcore/pull/1992) chore: Set up an Android CI job +- [#1988](https://github.com/TokTok/c-toxcore/pull/1988) cleanup: Make LAN discovery thread-safe without data races. +- [#1987](https://github.com/TokTok/c-toxcore/pull/1987) cleanup: Comply with new cimple callback rules. +- [#1985](https://github.com/TokTok/c-toxcore/pull/1985) cleanup: Split msi callback array into 1 member per callback +- [#1982](https://github.com/TokTok/c-toxcore/pull/1982) chore: Add an easy way to run autotools and circleci builds locally. +- [#1979](https://github.com/TokTok/c-toxcore/pull/1979) chore: Update readme header +- [#1952](https://github.com/TokTok/c-toxcore/pull/1952) feat: Add async event handling (callbacks) code. +- [#1935](https://github.com/TokTok/c-toxcore/pull/1935) feat: add DHT queries to private API +- [#1668](https://github.com/TokTok/c-toxcore/pull/1668) perf: Take advantage of fast networks for file transfers + +### Closed issues: + +- [#2009](https://github.com/TokTok/c-toxcore/issues/2009) Autotools build fails +- [#2004](https://github.com/TokTok/c-toxcore/issues/2004) Add `nullable` and `nonnull` annotations to all functions. +- [#1998](https://github.com/TokTok/c-toxcore/issues/1998) Large stack allocations +- [#1977](https://github.com/TokTok/c-toxcore/issues/1977) Turn array of callbacks in msi.h into separate callbacks. +- [#1670](https://github.com/TokTok/c-toxcore/issues/1670) Broken link in readme +- [#405](https://github.com/TokTok/c-toxcore/issues/405) Remove all¹ uses of global state in toxcore +- [#340](https://github.com/TokTok/c-toxcore/issues/340) Set up a continuous integration build for Android +- [#236](https://github.com/TokTok/c-toxcore/issues/236) Tox file transfers do not use available bandwidth +- [#128](https://github.com/TokTok/c-toxcore/issues/128) Toxcore should provide an easy, local method for making sure Travis checks will pass + +## v0.2.15 + +### Merged PRs: + +- [#1984](https://github.com/TokTok/c-toxcore/pull/1984) fix: Reduce logging verbosity even more. +- [#1983](https://github.com/TokTok/c-toxcore/pull/1983) chore: Release 0.2.15 +- [#1980](https://github.com/TokTok/c-toxcore/pull/1980) fix: Reduce logging verbosity in TCP server. + +## v0.2.14 + +### Merged PRs: + +- [#1978](https://github.com/TokTok/c-toxcore/pull/1978) chore: Release 0.2.14 +- [#1976](https://github.com/TokTok/c-toxcore/pull/1976) docs: Make crypto_core.h appear on doxygen. +- [#1975](https://github.com/TokTok/c-toxcore/pull/1975) refactor: use proper method for generating random numbers in a range +- [#1974](https://github.com/TokTok/c-toxcore/pull/1974) docs: Add doxygen configuration and netlify publishing. +- [#1972](https://github.com/TokTok/c-toxcore/pull/1972) chore: Make the last few remaining top level comments doxygen style. +- [#1971](https://github.com/TokTok/c-toxcore/pull/1971) chore: Sync all comments between header and source files. +- [#1968](https://github.com/TokTok/c-toxcore/pull/1968) cleanup: Ensure we limit the system headers included in .h files. +- [#1964](https://github.com/TokTok/c-toxcore/pull/1964) cleanup: Don't pass the whole DHT object to lan discovery. +- [#1958](https://github.com/TokTok/c-toxcore/pull/1958) chore: Make run-infer script use docker. +- [#1956](https://github.com/TokTok/c-toxcore/pull/1956) chore: Disable some cimple warnings for now. +- [#1955](https://github.com/TokTok/c-toxcore/pull/1955) cleanup: Properly copy Node_format using serialized format +- [#1954](https://github.com/TokTok/c-toxcore/pull/1954) cleanup: make functions take const pointer to IP_Port wherever possible +- [#1950](https://github.com/TokTok/c-toxcore/pull/1950) feat: Add WASM build for toxcore and websocket bootstrap node. +- [#1948](https://github.com/TokTok/c-toxcore/pull/1948) fix: potential freeing of an immutable static buffer +- [#1945](https://github.com/TokTok/c-toxcore/pull/1945) fix: Fix bootstrap on emscripten/wasm. +- [#1943](https://github.com/TokTok/c-toxcore/pull/1943) chore: use latest toktok-stack msan version +- [#1942](https://github.com/TokTok/c-toxcore/pull/1942) cleanup: Add some toxav bounds/sanity checks +- [#1940](https://github.com/TokTok/c-toxcore/pull/1940) chore: Use latest instead of versioned toktok-stack image. +- [#1939](https://github.com/TokTok/c-toxcore/pull/1939) chore: Rename bazel-release to -opt and -debug to -dbg. +- [#1938](https://github.com/TokTok/c-toxcore/pull/1938) cleanup: small refactor of DHT getnodes function +- [#1937](https://github.com/TokTok/c-toxcore/pull/1937) cleanup: remove brackets from ip_ntoa ipv6 formatting +- [#1933](https://github.com/TokTok/c-toxcore/pull/1933) chore: Add a Bazel Buildifier CI job +- [#1932](https://github.com/TokTok/c-toxcore/pull/1932) test: separate run_auto_tests into a library (revival of #1505) +- [#1929](https://github.com/TokTok/c-toxcore/pull/1929) cleanup: make some non-const pointers const +- [#1928](https://github.com/TokTok/c-toxcore/pull/1928) fix: unintentional integer down-casts +- [#1926](https://github.com/TokTok/c-toxcore/pull/1926) fix: group av memory leak +- [#1924](https://github.com/TokTok/c-toxcore/pull/1924) test: refactor autotest live network bootstrapping +- [#1923](https://github.com/TokTok/c-toxcore/pull/1923) fix: corruption in key files, making it unable to load when node starts. +- [#1922](https://github.com/TokTok/c-toxcore/pull/1922) chore: Don't run sonar scan on pull requests. +- [#1920](https://github.com/TokTok/c-toxcore/pull/1920) cleanup: refactor group audio packet data handling +- [#1918](https://github.com/TokTok/c-toxcore/pull/1918) chore: Run sonar scan on pull requests. +- [#1917](https://github.com/TokTok/c-toxcore/pull/1917) fix: buffer overwrite in bootstrap config +- [#1916](https://github.com/TokTok/c-toxcore/pull/1916) chore: Add a make_single_file script, used for CI. +- [#1915](https://github.com/TokTok/c-toxcore/pull/1915) cleanup: replace magic numbers with appropriately named constants +- [#1914](https://github.com/TokTok/c-toxcore/pull/1914) chore: Add cpplint to the CI. +- [#1912](https://github.com/TokTok/c-toxcore/pull/1912) cleanup: Remove uses of `strcpy` and `sprintf`. +- [#1910](https://github.com/TokTok/c-toxcore/pull/1910) cleanup: Remove our only use of flexible array members in toxcore. +- [#1909](https://github.com/TokTok/c-toxcore/pull/1909) chore: Expose public API headers as files in bazel. +- [#1906](https://github.com/TokTok/c-toxcore/pull/1906) cleanup: Split large switch statement into functions. +- [#1905](https://github.com/TokTok/c-toxcore/pull/1905) chore: Mark unsafe code as testonly. +- [#1903](https://github.com/TokTok/c-toxcore/pull/1903) cleanup: Even more pointer-to-const parameters. +- [#1901](https://github.com/TokTok/c-toxcore/pull/1901) cleanup: Make parameters pointers-to-const where possible. +- [#1900](https://github.com/TokTok/c-toxcore/pull/1900) cleanup: Remove old check Suite compat layer. +- [#1899](https://github.com/TokTok/c-toxcore/pull/1899) cleanup: Make `Networking_Core` pointer-to-const where possible. +- [#1898](https://github.com/TokTok/c-toxcore/pull/1898) cleanup: Use pointer cast instead of memcpy in qsort callback. +- [#1897](https://github.com/TokTok/c-toxcore/pull/1897) refactor: Deduplicate a bunch of code in TCP client/server. +- [#1894](https://github.com/TokTok/c-toxcore/pull/1894) cleanup: Deduplicate a somewhat complex loop in DHT.c. +- [#1891](https://github.com/TokTok/c-toxcore/pull/1891) cleanup: Remove our only use of sprintf. +- [#1889](https://github.com/TokTok/c-toxcore/pull/1889) cleanup: Stop using `strerror` directly. +- [#1887](https://github.com/TokTok/c-toxcore/pull/1887) test: Add two more bootstrap/TCP nodes to autotests +- [#1884](https://github.com/TokTok/c-toxcore/pull/1884) chore: Add mypy Python type check. +- [#1883](https://github.com/TokTok/c-toxcore/pull/1883) chore: Add sonar-scan analysis on pushes. +- [#1881](https://github.com/TokTok/c-toxcore/pull/1881) cleanup: Merge crypto_core and crypto_core_mem. +- [#1880](https://github.com/TokTok/c-toxcore/pull/1880) chore: Run static analysers in multiple variants. +- [#1879](https://github.com/TokTok/c-toxcore/pull/1879) fix: Fix `toxav_basic_test` buffer overflow. +- [#1878](https://github.com/TokTok/c-toxcore/pull/1878) fix: don't count filetransfer as sending until accepted +- [#1877](https://github.com/TokTok/c-toxcore/pull/1877) fix: Fix some uninitialised memory errors found by valgrind. +- [#1876](https://github.com/TokTok/c-toxcore/pull/1876) chore: Ignore some failures in bazel-tsan. +- [#1875](https://github.com/TokTok/c-toxcore/pull/1875) chore: Add asan/tsan bazel builds. +- [#1874](https://github.com/TokTok/c-toxcore/pull/1874) cleanup: replace all instances of atoi with strtol +- [#1873](https://github.com/TokTok/c-toxcore/pull/1873) chore: Enable layering check in all c-toxcore build files. +- [#1871](https://github.com/TokTok/c-toxcore/pull/1871) chore: Enable compiler layering check. +- [#1870](https://github.com/TokTok/c-toxcore/pull/1870) chore: Disable the OpenMP cracker in bazel for now. +- [#1867](https://github.com/TokTok/c-toxcore/pull/1867) chore: Retry asan/tsan tests a few more times. +- [#1866](https://github.com/TokTok/c-toxcore/pull/1866) chore: Run tokstyle with 3 cores. +- [#1865](https://github.com/TokTok/c-toxcore/pull/1865) cleanup: Remove extra parens around function arguments. +- [#1864](https://github.com/TokTok/c-toxcore/pull/1864) cleanup: Don't use memcpy where assignment can be used. +- [#1862](https://github.com/TokTok/c-toxcore/pull/1862) chore: Remove all references to Travis CI. +- [#1861](https://github.com/TokTok/c-toxcore/pull/1861) cleanup: Use `calloc` instead of `malloc` for struct allocations. +- [#1860](https://github.com/TokTok/c-toxcore/pull/1860) cleanup: Fix `calloc` argument order. +- [#1857](https://github.com/TokTok/c-toxcore/pull/1857) chore: Get all* autotests working with MSVC +- [#1853](https://github.com/TokTok/c-toxcore/pull/1853) cleanup: Remove useless parentheses. +- [#1850](https://github.com/TokTok/c-toxcore/pull/1850) chore: Add a GH Actions code coverage job +- [#1845](https://github.com/TokTok/c-toxcore/pull/1845) fix: use correct sample size in toxav_basic_test +- [#1844](https://github.com/TokTok/c-toxcore/pull/1844) cleanup: make struct typedefs have the same name as their struct +- [#1841](https://github.com/TokTok/c-toxcore/pull/1841) cleanup: refactor toxav_call_control +- [#1840](https://github.com/TokTok/c-toxcore/pull/1840) chore: Remove old travis docker scripts. +- [#1837](https://github.com/TokTok/c-toxcore/pull/1837) chore: Add bazel-debug build. +- [#1836](https://github.com/TokTok/c-toxcore/pull/1836) fix: possible unintended negative loop bound +- [#1835](https://github.com/TokTok/c-toxcore/pull/1835) cleanup: remove dead code +- [#1834](https://github.com/TokTok/c-toxcore/pull/1834) cleanup: Reduce the scope of for-loop iterator variables. +- [#1832](https://github.com/TokTok/c-toxcore/pull/1832) fix: a double-unlocking mutex in toxav +- [#1830](https://github.com/TokTok/c-toxcore/pull/1830) chore: Add "tcc" and "compcert" compiler targets. +- [#1820](https://github.com/TokTok/c-toxcore/pull/1820) chore: Add macOS build. +- [#1819](https://github.com/TokTok/c-toxcore/pull/1819) refactor: Extract some functions from the big run_auto_test function. +- [#1818](https://github.com/TokTok/c-toxcore/pull/1818) feat: Add programs for creating savedata & bootstrap keys +- [#1816](https://github.com/TokTok/c-toxcore/pull/1816) cleanup: put breaks inside case braces +- [#1815](https://github.com/TokTok/c-toxcore/pull/1815) test: add ability for autotests to use TCP connections +- [#1813](https://github.com/TokTok/c-toxcore/pull/1813) chore: Login to dockerhub before trying to push to dockerhub. +- [#1812](https://github.com/TokTok/c-toxcore/pull/1812) chore: Only push versioned docker image on tag builds. +- [#1811](https://github.com/TokTok/c-toxcore/pull/1811) chore: Add bootstrap daemon docker image build. +- [#1810](https://github.com/TokTok/c-toxcore/pull/1810) chore: Remove apidsl comment from tox.h. +- [#1807](https://github.com/TokTok/c-toxcore/pull/1807) chore: Don't run CI on master branch pushes. +- [#1802](https://github.com/TokTok/c-toxcore/pull/1802) cleanup: Sync doc comments in a few more .c/.h files. +- [#1801](https://github.com/TokTok/c-toxcore/pull/1801) chore: Fix up a few source code comment and style issues. +- [#1798](https://github.com/TokTok/c-toxcore/pull/1798) chore: merge friend_connections from NGC fork +- [#1797](https://github.com/TokTok/c-toxcore/pull/1797) cleanup: Move `sodium.h` include to front of network.c. +- [#1794](https://github.com/TokTok/c-toxcore/pull/1794) chore: Move cmake-freebsd-stage2 back into the toxcore repo. +- [#1793](https://github.com/TokTok/c-toxcore/pull/1793) chore: Add FreeBSD build to CI. +- [#1792](https://github.com/TokTok/c-toxcore/pull/1792) chore: Add cross compilation CI targets. +- [#1790](https://github.com/TokTok/c-toxcore/pull/1790) cleanup: remove redundant (and incorrect) comments +- [#1789](https://github.com/TokTok/c-toxcore/pull/1789) refactor: rename variable to clarify purpose +- [#1786](https://github.com/TokTok/c-toxcore/pull/1786) cleanup: Remove apidsl; remove `crypto_memcmp`. +- [#1783](https://github.com/TokTok/c-toxcore/pull/1783) cleanup: fix format-source +- [#1779](https://github.com/TokTok/c-toxcore/pull/1779) chore: Update toktok-stack version. +- [#1778](https://github.com/TokTok/c-toxcore/pull/1778) chore: Tie down the use of doxygen comments. +- [#1777](https://github.com/TokTok/c-toxcore/pull/1777) cleanup: Remove crypto_pwhash import. +- [#1776](https://github.com/TokTok/c-toxcore/pull/1776) cleanup: remove unused function argument from set_tcp_connection_status() +- [#1775](https://github.com/TokTok/c-toxcore/pull/1775) cleanup: Remove apidsl for everything except the public API. +- [#1774](https://github.com/TokTok/c-toxcore/pull/1774) chore: Remove config.h. +- [#1773](https://github.com/TokTok/c-toxcore/pull/1773) chore: Fix gen-file.sh: it wasn't globbing properly. +- [#1772](https://github.com/TokTok/c-toxcore/pull/1772) chore: Add .cc files to the static analysis. +- [#1770](https://github.com/TokTok/c-toxcore/pull/1770) cleanup: merge onion.c changes from new groupchats fork +- [#1769](https://github.com/TokTok/c-toxcore/pull/1769) chore: merge tcp_connection changes from new groupchats fork +- [#1768](https://github.com/TokTok/c-toxcore/pull/1768) chore: merge DHT changes from new groupchats fork +- [#1766](https://github.com/TokTok/c-toxcore/pull/1766) chore: Use docker for the autotools ci build. +- [#1765](https://github.com/TokTok/c-toxcore/pull/1765) fix: Fix file permission issue with toxchat/bootstrap-node Docker container +- [#1762](https://github.com/TokTok/c-toxcore/pull/1762) chore: Add autotools build; exempt crypto_pwhash from tokstyle. +- [#1761](https://github.com/TokTok/c-toxcore/pull/1761) cleanup: Don't include `"config.h"` unless needed. +- [#1759](https://github.com/TokTok/c-toxcore/pull/1759) cleanup: address some unused return values +- [#1758](https://github.com/TokTok/c-toxcore/pull/1758) test: Make ERROR logging fatal in tests. +- [#1754](https://github.com/TokTok/c-toxcore/pull/1754) fix: off-by-one error caused by integer division without proper cast +- [#1753](https://github.com/TokTok/c-toxcore/pull/1753) cleanup: use crypto_memzero to wipe secret keys when no longer in use +- [#1752](https://github.com/TokTok/c-toxcore/pull/1752) chore: Use an incrementing version number for coverity scans. +- [#1751](https://github.com/TokTok/c-toxcore/pull/1751) fix: Fixed uninitialised value copy. +- [#1747](https://github.com/TokTok/c-toxcore/pull/1747) cleanup: Fix some clang-tidy warnings and make them errors. +- [#1746](https://github.com/TokTok/c-toxcore/pull/1746) chore: Add clang-tidy review github workflow. +- [#1744](https://github.com/TokTok/c-toxcore/pull/1744) cleanup: Enforce for loop consistency. +- [#1743](https://github.com/TokTok/c-toxcore/pull/1743) chore: Minor cleanups of warnings given by cppcheck. +- [#1742](https://github.com/TokTok/c-toxcore/pull/1742) test: Add a simple test for `ip_ntoa`. +- [#1740](https://github.com/TokTok/c-toxcore/pull/1740) cleanup: Put fatal errors where API return values should be impossible +- [#1738](https://github.com/TokTok/c-toxcore/pull/1738) chore: Add missing `sudo` to coverity apt-get calls. +- [#1737](https://github.com/TokTok/c-toxcore/pull/1737) refactor: Fix previous refactor +- [#1736](https://github.com/TokTok/c-toxcore/pull/1736) chore: Add workflow for running coverity scan. +- [#1735](https://github.com/TokTok/c-toxcore/pull/1735) cleanup: Use `static_assert` instead of preprocessor `#error`. +- [#1734](https://github.com/TokTok/c-toxcore/pull/1734) chore: Add logger to onion and onion announce objects +- [#1733](https://github.com/TokTok/c-toxcore/pull/1733) cleanup: Minor fixes in test code. +- [#1732](https://github.com/TokTok/c-toxcore/pull/1732) cleanup: Refactor kill_nonused_tcp() +- [#1730](https://github.com/TokTok/c-toxcore/pull/1730) cleanup: Fix last instance of `-Wcast-align` and enable the warning. +- [#1729](https://github.com/TokTok/c-toxcore/pull/1729) cleanup: Ensure that error codes are always initialised. +- [#1727](https://github.com/TokTok/c-toxcore/pull/1727) cleanup: Avoid endian-specific code in `crypto_core`. +- [#1720](https://github.com/TokTok/c-toxcore/pull/1720) feat: Add automatic fuzz testing for c-toxcore +- [#1673](https://github.com/TokTok/c-toxcore/pull/1673) cleanup: Remove hardening code from DHT +- [#1622](https://github.com/TokTok/c-toxcore/pull/1622) perf: Make the key cracker a bit faster +- [#1333](https://github.com/TokTok/c-toxcore/pull/1333) refactor: Clean up friend loading. +- [#1307](https://github.com/TokTok/c-toxcore/pull/1307) refactor: Split toxav_iterate into audio and video part + +### Closed issues: + +- [#1967](https://github.com/TokTok/c-toxcore/issues/1967) Potential freeing of an immutable static buffer +- [#1788](https://github.com/TokTok/c-toxcore/issues/1788) Rename dht->last_run +- [#1719](https://github.com/TokTok/c-toxcore/issues/1719) Enforce braces around macros that compute a value +- [#1694](https://github.com/TokTok/c-toxcore/issues/1694) Double unlocking in the ac_iterate +- [#1332](https://github.com/TokTok/c-toxcore/issues/1332) Padding bytes in Tox save format are not specified +- [#1217](https://github.com/TokTok/c-toxcore/issues/1217) valgrind reports "Conditional jump or move depends on uninitialised value(s)" +- [#1118](https://github.com/TokTok/c-toxcore/issues/1118) Fix threading issues in tests caught by tsan (data race etc) +- [#1087](https://github.com/TokTok/c-toxcore/issues/1087) Remove all uses of `%zu` in printf formats. +- [#1040](https://github.com/TokTok/c-toxcore/issues/1040) Random numbers should not be produced using `rng() % max`. +- [#540](https://github.com/TokTok/c-toxcore/issues/540) Stop deleting source files +- [#501](https://github.com/TokTok/c-toxcore/issues/501) Testsuite fails and hangs on FreeBSD +- [#451](https://github.com/TokTok/c-toxcore/issues/451) Don't fail when building toxcore on windows with `cmake .` +- [#350](https://github.com/TokTok/c-toxcore/issues/350) Configure coverity runs for nightly builds +- [#349](https://github.com/TokTok/c-toxcore/issues/349) Run clang-tidy on Travis with specific warnings as errors. +- [#348](https://github.com/TokTok/c-toxcore/issues/348) Run cppcheck on Travis and push the results to toktok.github.io. +- [#323](https://github.com/TokTok/c-toxcore/issues/323) Set library version on future releases +- [#235](https://github.com/TokTok/c-toxcore/issues/235) Video corruption: Don't drop video keyframes +- [#203](https://github.com/TokTok/c-toxcore/issues/203) ToxAV is still on the old API style +- [#198](https://github.com/TokTok/c-toxcore/issues/198) Crash on call while peer calling you +- [#167](https://github.com/TokTok/c-toxcore/issues/167) Const-ify pointers +- [#124](https://github.com/TokTok/c-toxcore/issues/124) Don't include OS specific headers in .h files +- [#106](https://github.com/TokTok/c-toxcore/issues/106) Sometimes Toxcore reports the wrong connection status for both the DHT, and friends. +- [#85](https://github.com/TokTok/c-toxcore/issues/85) Reproducible Builds // OBS a bad Idea + +## v0.2.13 + +### Merged PRs: + +- [#1725](https://github.com/TokTok/c-toxcore/pull/1725) cleanup: add some missing null checks +- [#1723](https://github.com/TokTok/c-toxcore/pull/1723) chore: Run infer static analyser on circle ci builds. +- [#1722](https://github.com/TokTok/c-toxcore/pull/1722) chore: Release 0.2.13 +- [#1718](https://github.com/TokTok/c-toxcore/pull/1718) fix: Sec/fix crypto size compute +- [#1716](https://github.com/TokTok/c-toxcore/pull/1716) chore: Use toktok-stack docker image with built third_party. +- [#1713](https://github.com/TokTok/c-toxcore/pull/1713) test: Add some unit tests for important internal DHT functions. +- [#1708](https://github.com/TokTok/c-toxcore/pull/1708) perf: reduce calling into Mono_Time in DHT +- [#1706](https://github.com/TokTok/c-toxcore/pull/1706) chore: Enable cimple tests on cirrus build. +- [#1705](https://github.com/TokTok/c-toxcore/pull/1705) fix: issue with save_load autotest +- [#1703](https://github.com/TokTok/c-toxcore/pull/1703) chore: Upgrade to toktok-stack:0.0.11. +- [#1699](https://github.com/TokTok/c-toxcore/pull/1699) fix: some friend connection issues +- [#1698](https://github.com/TokTok/c-toxcore/pull/1698) fix: bug causing API to report wrong self connection status +- [#1693](https://github.com/TokTok/c-toxcore/pull/1693) chore: Update IRC info +- [#1691](https://github.com/TokTok/c-toxcore/pull/1691) chore: Fix Appveyor and add workarounds for Cirrus CI +- [#1686](https://github.com/TokTok/c-toxcore/pull/1686) chore: Enable c-toxcore conan packaging +- [#1684](https://github.com/TokTok/c-toxcore/pull/1684) cleanup: Update INSTALL.md instructions +- [#1679](https://github.com/TokTok/c-toxcore/pull/1679) cleanup: Trivial cleanup +- [#1674](https://github.com/TokTok/c-toxcore/pull/1674) cleanup: filetransfer code +- [#1672](https://github.com/TokTok/c-toxcore/pull/1672) docs: Add instructions for building unit tests to INSTALL.md +- [#1667](https://github.com/TokTok/c-toxcore/pull/1667) chore: Update tox-bootstrapd checksum due to newer packages in Alpine +- [#1664](https://github.com/TokTok/c-toxcore/pull/1664) cleanup: use heap memory instead of stack for large variables +- [#1663](https://github.com/TokTok/c-toxcore/pull/1663) fix: Fix file tests on windows +- [#1633](https://github.com/TokTok/c-toxcore/pull/1633) fix: AppVeyor failing due to conan remote being added twice +- [#1602](https://github.com/TokTok/c-toxcore/pull/1602) fix: Fix buffer over-read when a peer leaves a conference +- [#1586](https://github.com/TokTok/c-toxcore/pull/1586) test: Fix tcp_relay_test by adding a second bootstrap node. +- [#1580](https://github.com/TokTok/c-toxcore/pull/1580) style: Format comments according to tokstyle's requirements. +- [#1557](https://github.com/TokTok/c-toxcore/pull/1557) chore: Add conan support +- [#1537](https://github.com/TokTok/c-toxcore/pull/1537) chore: Cygwin build +- [#1516](https://github.com/TokTok/c-toxcore/pull/1516) cleanup: Make pylint and mypy happy with bootstrap_node_info.py. +- [#1515](https://github.com/TokTok/c-toxcore/pull/1515) style: Run restyled on Travis and Circle CI scripts. +- [#1514](https://github.com/TokTok/c-toxcore/pull/1514) refactor: Remove multi-declarators entirely. +- [#1513](https://github.com/TokTok/c-toxcore/pull/1513) refactor: Disallow multiple initialised declarators per decl. +- [#1510](https://github.com/TokTok/c-toxcore/pull/1510) chore: Don't build pushes to branches, only to tags. +- [#1504](https://github.com/TokTok/c-toxcore/pull/1504) chore: Remove release-drafter configuration in favour of global one. +- [#1498](https://github.com/TokTok/c-toxcore/pull/1498) refactor: Limit scope of loop iterators. +- [#1497](https://github.com/TokTok/c-toxcore/pull/1497) refactor: Use bash arrays instead of strings for static analysis scripts. +- [#1496](https://github.com/TokTok/c-toxcore/pull/1496) cleanup: Stop hard-coding packet IDs in tests. +- [#1495](https://github.com/TokTok/c-toxcore/pull/1495) chore: Exclude imported libsodium sources from restyled. +- [#1493](https://github.com/TokTok/c-toxcore/pull/1493) feat: Add logging to TCP and onion client. +- [#1489](https://github.com/TokTok/c-toxcore/pull/1489) cleanup: `NAC_LIBS` -> `NACL_LIBS`. +- [#1487](https://github.com/TokTok/c-toxcore/pull/1487) chore: Add autotools build to localbuild docker images. +- [#1473](https://github.com/TokTok/c-toxcore/pull/1473) chore: Add a script to run Travis CI locally. +- [#1467](https://github.com/TokTok/c-toxcore/pull/1467) fix: Fix a bug in savedata loading when malloc fails. +- [#1464](https://github.com/TokTok/c-toxcore/pull/1464) fix: Fix errors on error paths found by oomer. +- [#1463](https://github.com/TokTok/c-toxcore/pull/1463) cleanup: Add a check that we don't have any unused functions. +- [#1462](https://github.com/TokTok/c-toxcore/pull/1462) cleanup: Include `` for `explicit_bzero`. +- [#1436](https://github.com/TokTok/c-toxcore/pull/1436) chore: Enable cimple tests by default but allow disabling them. + +### Closed issues: + +- [#1598](https://github.com/TokTok/c-toxcore/issues/1598) ERROR: heap-buffer-overflow in group.c found with AddressSanitizer +- [#1326](https://github.com/TokTok/c-toxcore/issues/1326) the cause is great, but this thing is completely unusable +- [#1319](https://github.com/TokTok/c-toxcore/issues/1319) Is this new application is safe & trusted ?? +- [#1236](https://github.com/TokTok/c-toxcore/issues/1236) Ruby Extension? +- [#1149](https://github.com/TokTok/c-toxcore/issues/1149) uTox aborts on toxcore restart +- [#886](https://github.com/TokTok/c-toxcore/issues/886) Maybe need to set the stacksize for musl-libc + +## v0.2.12 + +### Merged PRs: + +- [#1458](https://github.com/TokTok/c-toxcore/pull/1458) Release 0.2.12 +- [#1457](https://github.com/TokTok/c-toxcore/pull/1457) Disable non-hermetic tests by default. +- [#1456](https://github.com/TokTok/c-toxcore/pull/1456) Limit the number of friends you can have to ~4 billion. +- [#1452](https://github.com/TokTok/c-toxcore/pull/1452) Add execution trace option for debugging. +- [#1444](https://github.com/TokTok/c-toxcore/pull/1444) Set up release-drafter to automatically draft the next release. +- [#1443](https://github.com/TokTok/c-toxcore/pull/1443) Allow test coverage to fluctuate 2% up and down, but not below 80%. +- [#1442](https://github.com/TokTok/c-toxcore/pull/1442) Add CODEOWNERS and settings.yml files. +- [#1441](https://github.com/TokTok/c-toxcore/pull/1441) [ImgBot] Optimize images +- [#1439](https://github.com/TokTok/c-toxcore/pull/1439) Fix continuous integration builds. +- [#1437](https://github.com/TokTok/c-toxcore/pull/1437) Rework the toxchat/bootstrap-node Docker image. +- [#1435](https://github.com/TokTok/c-toxcore/pull/1435) Enable TCP relay test in Bazel and autotools build. +- [#1434](https://github.com/TokTok/c-toxcore/pull/1434) Skip invalid TCP relays and DHT nodes when loading save data. +- [#1433](https://github.com/TokTok/c-toxcore/pull/1433) Fix saving of combination of loaded and connected TCP relays +- [#1430](https://github.com/TokTok/c-toxcore/pull/1430) Invert `not_valid` functions and name them `is_valid`. +- [#1429](https://github.com/TokTok/c-toxcore/pull/1429) Fix things not being initialized if creating a TCP-only network +- [#1426](https://github.com/TokTok/c-toxcore/pull/1426) Remove tokstyle exemptions from build files. +- [#1425](https://github.com/TokTok/c-toxcore/pull/1425) Stop using the "inline namespace" feature of apidsl. +- [#1424](https://github.com/TokTok/c-toxcore/pull/1424) Add new semi-private API functions to set per-packet-id custom handlers. +- [#1423](https://github.com/TokTok/c-toxcore/pull/1423) Give CI workflow a better name: clang-sanitizers +- [#1422](https://github.com/TokTok/c-toxcore/pull/1422) Use public API for sending in RTP +- [#1421](https://github.com/TokTok/c-toxcore/pull/1421) Install ci-tools and get tokstyle via the script it provides. +- [#1420](https://github.com/TokTok/c-toxcore/pull/1420) Use tox public API for sending packets in toxav BWController +- [#1419](https://github.com/TokTok/c-toxcore/pull/1419) Remove newlines from the end of LOGGER format strings. +- [#1418](https://github.com/TokTok/c-toxcore/pull/1418) Change ToxAVCall struct mutex to a more distinct name +- [#1417](https://github.com/TokTok/c-toxcore/pull/1417) Create own instance of Mono_Time for ToxAV +- [#1416](https://github.com/TokTok/c-toxcore/pull/1416) Stop using Messenger's mono_time in bandwidth controller. +- [#1415](https://github.com/TokTok/c-toxcore/pull/1415) Fix 2 memory leaks in ToxAV. +- [#1414](https://github.com/TokTok/c-toxcore/pull/1414) Show function names in asan/tsan stack traces on CircleCI. +- [#1413](https://github.com/TokTok/c-toxcore/pull/1413) Make afl_toxsave.c a bit more portable; fix memory leak. +- [#1411](https://github.com/TokTok/c-toxcore/pull/1411) Fixes towards building on MSVC. +- [#1409](https://github.com/TokTok/c-toxcore/pull/1409) Mark conference test as small. +- [#1407](https://github.com/TokTok/c-toxcore/pull/1407) Add minimal save generator +- [#1406](https://github.com/TokTok/c-toxcore/pull/1406) Migrate format-source script to new apidsl web app. +- [#1404](https://github.com/TokTok/c-toxcore/pull/1404) Smarter setup of bazel remote cache on Cirrus CI. +- [#1331](https://github.com/TokTok/c-toxcore/pull/1331) Add basic test adapter for AFL + +### Closed issues: + +- [#1365](https://github.com/TokTok/c-toxcore/issues/1365) Add the option to use LAN discovery even when using a proxy for remote connections +- [#1353](https://github.com/TokTok/c-toxcore/issues/1353) libtoxdns.a and libtoxav.a +- [#86](https://github.com/TokTok/c-toxcore/issues/86) Freenet as Offline Messaging Backend + +## v0.2.11 + +### Merged PRs: + +- [#1405](https://github.com/TokTok/c-toxcore/pull/1405) Release 0.2.11 +- [#1403](https://github.com/TokTok/c-toxcore/pull/1403) Install libsodium from apt instead of from source. +- [#1402](https://github.com/TokTok/c-toxcore/pull/1402) Remove bazel build from Travis. +- [#1400](https://github.com/TokTok/c-toxcore/pull/1400) Disable bazel remote cache on CI. +- [#1399](https://github.com/TokTok/c-toxcore/pull/1399) Periodically try to send direct packets when connected by TCP. +- [#1398](https://github.com/TokTok/c-toxcore/pull/1398) Minor cleanup: use `assoc_timeout` function where possible. +- [#1397](https://github.com/TokTok/c-toxcore/pull/1397) Check that LOGGER macros are only called with string literals. +- [#1396](https://github.com/TokTok/c-toxcore/pull/1396) Make function defns match their decls regarding storage class. +- [#1395](https://github.com/TokTok/c-toxcore/pull/1395) Mark file-local function definitions as `static`. +- [#1394](https://github.com/TokTok/c-toxcore/pull/1394) Enable remote cache for bazel builds. +- [#1393](https://github.com/TokTok/c-toxcore/pull/1393) Add another bootstrap node to the bootstrap test. +- [#1392](https://github.com/TokTok/c-toxcore/pull/1392) Clear out old conference connections. +- [#1391](https://github.com/TokTok/c-toxcore/pull/1391) Minor cleanups in network code. +- [#1390](https://github.com/TokTok/c-toxcore/pull/1390) Avoid casting back and forth between void-ptr. +- [#1389](https://github.com/TokTok/c-toxcore/pull/1389) Standardise on having a comma at the end of enums. +- [#1388](https://github.com/TokTok/c-toxcore/pull/1388) Fix up comments a bit to start being more uniform. +- [#1387](https://github.com/TokTok/c-toxcore/pull/1387) Use rules_cc instead of native cc_library rules. +- [#1386](https://github.com/TokTok/c-toxcore/pull/1386) Use spdx license identifier instead of GPL blurb. +- [#1383](https://github.com/TokTok/c-toxcore/pull/1383) Pass packet ID to custom packet handlers. +- [#1382](https://github.com/TokTok/c-toxcore/pull/1382) Add a mutex lock/unlock inside every public API function. +- [#1381](https://github.com/TokTok/c-toxcore/pull/1381) Use `net_pack` instead of casting bytes to ints. +- [#1380](https://github.com/TokTok/c-toxcore/pull/1380) Disable FreeBSD travis build until it is fixed. +- [#1379](https://github.com/TokTok/c-toxcore/pull/1379) Update and fix FreeBSD setup on Travis-CI +- [#1378](https://github.com/TokTok/c-toxcore/pull/1378) Use ninja build system for the cmake-linux build. +- [#1376](https://github.com/TokTok/c-toxcore/pull/1376) Remove testing/av_test.c. +- [#1375](https://github.com/TokTok/c-toxcore/pull/1375) Add "cimple_test" to the bazel build. +- [#1374](https://github.com/TokTok/c-toxcore/pull/1374) Handle invite to existing conference +- [#1372](https://github.com/TokTok/c-toxcore/pull/1372) Upgrade bazel to 2.1.1. +- [#1371](https://github.com/TokTok/c-toxcore/pull/1371) Bump to astyle-3.1 in travis build. +- [#1370](https://github.com/TokTok/c-toxcore/pull/1370) use -1 rather than ~0 in unsigned integer types +- [#1362](https://github.com/TokTok/c-toxcore/pull/1362) Workaround for message number saving +- [#1358](https://github.com/TokTok/c-toxcore/pull/1358) Allow Bazel to rerun tests marked as flaky +- [#1352](https://github.com/TokTok/c-toxcore/pull/1352) Update tests to use a working bootstrap node +- [#1349](https://github.com/TokTok/c-toxcore/pull/1349) Fix tox-bootstrapd's README and update Dockerfile +- [#1347](https://github.com/TokTok/c-toxcore/pull/1347) Fix pthread_mutex_destroy getting too many arguments +- [#1346](https://github.com/TokTok/c-toxcore/pull/1346) Fix most TSAN failures +- [#1345](https://github.com/TokTok/c-toxcore/pull/1345) fix concurrency issues in mono_time +- [#1343](https://github.com/TokTok/c-toxcore/pull/1343) Fix TSAN failures in tests +- [#1334](https://github.com/TokTok/c-toxcore/pull/1334) fix missing group title length check +- [#1330](https://github.com/TokTok/c-toxcore/pull/1330) Force IPv4 for cirrus-ci tests +- [#1329](https://github.com/TokTok/c-toxcore/pull/1329) bump libsodium version in appveyor.yml +- [#1322](https://github.com/TokTok/c-toxcore/pull/1322) Clean-up of group.c code +- [#1321](https://github.com/TokTok/c-toxcore/pull/1321) Some small fixes to groups. +- [#1299](https://github.com/TokTok/c-toxcore/pull/1299) Add VScode folder to .gitignore +- [#1297](https://github.com/TokTok/c-toxcore/pull/1297) Use net_pack/unpack instead of host_to_net. + +### Closed issues: + +- [#1373](https://github.com/TokTok/c-toxcore/issues/1373) handle crashes after group invites +- [#1368](https://github.com/TokTok/c-toxcore/issues/1368) Are tox clients also open source +- [#1366](https://github.com/TokTok/c-toxcore/issues/1366) Generate a link for websites (Friendship and proxy) +- [#1354](https://github.com/TokTok/c-toxcore/issues/1354) Unstable Tests +- [#1316](https://github.com/TokTok/c-toxcore/issues/1316) Documentation claims toxav_iteration_interval is threadsafe but it's not +- [#1274](https://github.com/TokTok/c-toxcore/issues/1274) build error +- [#850](https://github.com/TokTok/c-toxcore/issues/850) GPG App Usage + +## v0.2.10 + +### Merged PRs: + +- [#1324](https://github.com/TokTok/c-toxcore/pull/1324) Release 0.2.10 +- [#1320](https://github.com/TokTok/c-toxcore/pull/1320) add undef guard in tox_many_tcp_test +- [#1314](https://github.com/TokTok/c-toxcore/pull/1314) Fix bazel build version at 0.22.0 for CI. +- [#1311](https://github.com/TokTok/c-toxcore/pull/1311) Disable failing TCP server test +- [#1310](https://github.com/TokTok/c-toxcore/pull/1310) Do not send the same packet to the same node twice +- [#1309](https://github.com/TokTok/c-toxcore/pull/1309) add configurable limit on number of stored frozen peers +- [#1305](https://github.com/TokTok/c-toxcore/pull/1305) Expose api functions for enabling and disabling AV in AV groups +- [#1302](https://github.com/TokTok/c-toxcore/pull/1302) Specify that buffer size for tox_conference_peer_get_name is given by $size + +### Closed issues: + +- [#1325](https://github.com/TokTok/c-toxcore/issues/1325) Question: ETA of v0.2.10? +- [#1313](https://github.com/TokTok/c-toxcore/issues/1313) CirrusCI is failing and blocking PRs +- [#1312](https://github.com/TokTok/c-toxcore/issues/1312) Onion client review +- [#1306](https://github.com/TokTok/c-toxcore/issues/1306) Persistent conference's offline peer list always grows and never decreases +- [#1303](https://github.com/TokTok/c-toxcore/issues/1303) Loaded persistent groups fail to send audio +- [#1298](https://github.com/TokTok/c-toxcore/issues/1298) How to make libtox4j-c.so for android? +- [#1261](https://github.com/TokTok/c-toxcore/issues/1261) Bump so version +- [#1116](https://github.com/TokTok/c-toxcore/issues/1116) Message length is too large log spam + +## v0.2.9 + +### Merged PRs: + +- [#1296](https://github.com/TokTok/c-toxcore/pull/1296) Add some const qualifiers +- [#1295](https://github.com/TokTok/c-toxcore/pull/1295) Implement all min/max functions for (un)signed int types. +- [#1293](https://github.com/TokTok/c-toxcore/pull/1293) Fix misaligned 4-byte access in trace logging. +- [#1291](https://github.com/TokTok/c-toxcore/pull/1291) Use correct path to test log to cat on error. +- [#1290](https://github.com/TokTok/c-toxcore/pull/1290) Display build log for autotools build on failure. +- [#1289](https://github.com/TokTok/c-toxcore/pull/1289) Enable auto tests under STRICT_ABI if static libs are available. +- [#1288](https://github.com/TokTok/c-toxcore/pull/1288) Add MIN_LOGGER_LEVEL to the Circle CI builds. +- [#1287](https://github.com/TokTok/c-toxcore/pull/1287) Avoid sending group messages to a peer before we have its group number +- [#1284](https://github.com/TokTok/c-toxcore/pull/1284) Use new WineHQ Debian package repository key +- [#1283](https://github.com/TokTok/c-toxcore/pull/1283) Release 0.2.9 +- [#1282](https://github.com/TokTok/c-toxcore/pull/1282) Merge irungentoo/master back into toktok/master. +- [#1281](https://github.com/TokTok/c-toxcore/pull/1281) Allow unauthenticated wine packages in the Windows build. +- [#1278](https://github.com/TokTok/c-toxcore/pull/1278) Add cmake option for building additional tests +- [#1277](https://github.com/TokTok/c-toxcore/pull/1277) Define tox_conference_id_size and tox_conference_uid_size +- [#1273](https://github.com/TokTok/c-toxcore/pull/1273) Avoid format truncation in save compatibility test +- [#1272](https://github.com/TokTok/c-toxcore/pull/1272) Upgrade bazel to 0.19.0 to fix the bazel build. +- [#1271](https://github.com/TokTok/c-toxcore/pull/1271) Return TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION if we are not connected to any peers +- [#1268](https://github.com/TokTok/c-toxcore/pull/1268) Fix indices calculation for congestion control. +- [#1267](https://github.com/TokTok/c-toxcore/pull/1267) Improve handling of peers entering and leaving conferences +- [#1266](https://github.com/TokTok/c-toxcore/pull/1266) Expose offline conference peers in API +- [#1242](https://github.com/TokTok/c-toxcore/pull/1242) Fix critical stack overflow arising from VLA usage +- [#1239](https://github.com/TokTok/c-toxcore/pull/1239) Add some hopefully helpful documentation to the functions in mono_time.h +- [#1235](https://github.com/TokTok/c-toxcore/pull/1235) Change method of PK production for FAKE friend in DHT +- [#1234](https://github.com/TokTok/c-toxcore/pull/1234) Increase NOFILE limit for tox-bootstrapd +- [#1231](https://github.com/TokTok/c-toxcore/pull/1231) Use `bool` for IPv6 flag in test programs and `DHT_bootstrap`. +- [#1230](https://github.com/TokTok/c-toxcore/pull/1230) Add `LOGGER_ASSERT` for checking fatal error conditions. +- [#1229](https://github.com/TokTok/c-toxcore/pull/1229) Include `CTest` in CMakeLists.txt to get valgrind support. +- [#1228](https://github.com/TokTok/c-toxcore/pull/1228) Consistently use camel case enum names. +- [#1223](https://github.com/TokTok/c-toxcore/pull/1223) Add AUTOTEST option to CMakeLists.txt +- [#1221](https://github.com/TokTok/c-toxcore/pull/1221) Make tox-bootstrapd free memory on SIGINT and SIGTERM +- [#1218](https://github.com/TokTok/c-toxcore/pull/1218) Support DragonFlyBSD and prune unused variables. +- [#1215](https://github.com/TokTok/c-toxcore/pull/1215) Ensure save data unchanged after save and load +- [#1213](https://github.com/TokTok/c-toxcore/pull/1213) Make saving and loading the responsibility of Tox rather than Messenger +- [#1211](https://github.com/TokTok/c-toxcore/pull/1211) Some improvements to tox-bootstrapd's Dockerfile +- [#1210](https://github.com/TokTok/c-toxcore/pull/1210) Remove Alpine Linux bootstrap daemon dockerfile. +- [#1209](https://github.com/TokTok/c-toxcore/pull/1209) Improve Windows compatibility of toxav code. +- [#1206](https://github.com/TokTok/c-toxcore/pull/1206) Add LAN_discovery to the list of apidsl-generated files. +- [#1156](https://github.com/TokTok/c-toxcore/pull/1156) conferences saving + +### Closed issues: + +- [#1285](https://github.com/TokTok/c-toxcore/issues/1285) Persistent group titles get mixed up +- [#1276](https://github.com/TokTok/c-toxcore/issues/1276) How to run test case? +- [#1275](https://github.com/TokTok/c-toxcore/issues/1275) Save file corruption +- [#1269](https://github.com/TokTok/c-toxcore/issues/1269) Tox conference connected callback isn't triggered +- [#1264](https://github.com/TokTok/c-toxcore/issues/1264) tox_conference_id_size() symbol missing +- [#1262](https://github.com/TokTok/c-toxcore/issues/1262) Fails to build with STRICT_ABI option +- [#1169](https://github.com/TokTok/c-toxcore/issues/1169) PK should not be generated with random_bytes +- [#1143](https://github.com/TokTok/c-toxcore/issues/1143) Test #1081 +- [#956](https://github.com/TokTok/c-toxcore/issues/956) friendlist access (add, delete, ...) causes crashes sometimes +- [#777](https://github.com/TokTok/c-toxcore/issues/777) Proposal: TFCL [Tox Friend Capabilities Level] +- [#762](https://github.com/TokTok/c-toxcore/issues/762) packet ranges not very clearly explained +- [#743](https://github.com/TokTok/c-toxcore/issues/743) Proposal: reduce Video corruption even more by negotating the reference frame between sender and receiver +- [#735](https://github.com/TokTok/c-toxcore/issues/735) Proposal: Tox MessageV2 +- [#663](https://github.com/TokTok/c-toxcore/issues/663) libvpx vulnerability +- [#626](https://github.com/TokTok/c-toxcore/issues/626) please add documentation to: bwcontroller.c +- [#625](https://github.com/TokTok/c-toxcore/issues/625) function names misleading in ToxAV +- [#617](https://github.com/TokTok/c-toxcore/issues/617) WIP: ToxIdenticon - howto +- [#610](https://github.com/TokTok/c-toxcore/issues/610) PACKET_LOSSY_AV_RESERVED 8 # why? +- [#609](https://github.com/TokTok/c-toxcore/issues/609) payload_type hardcoded +- [#589](https://github.com/TokTok/c-toxcore/issues/589) running a normal tox node as tcp relay is not possible +- [#584](https://github.com/TokTok/c-toxcore/issues/584) [INFO]: network graphs 0.1.9 vs 0.1.10 +- [#548](https://github.com/TokTok/c-toxcore/issues/548) toxcore removes message receipts and filetransfers from memory, when client has short network outage +- [#375](https://github.com/TokTok/c-toxcore/issues/375) Invalid bit rate prevents call + +## v0.2.8 + +### Merged PRs: + +- [#1225](https://github.com/TokTok/c-toxcore/pull/1225) Release 0.2.8 +- [#1224](https://github.com/TokTok/c-toxcore/pull/1224) Avoid use of IPv6 in tests if not supported (e.g. on Travis). +- [#1216](https://github.com/TokTok/c-toxcore/pull/1216) Fix memory leak in tcp server by wiping priority queues on deletion. +- [#1212](https://github.com/TokTok/c-toxcore/pull/1212) Fix logger level defaulting to TRACE in CMake +- [#1208](https://github.com/TokTok/c-toxcore/pull/1208) Remove a function-like macro and replace it with a function. +- [#1205](https://github.com/TokTok/c-toxcore/pull/1205) Use a working DHT node for bootstrap tests. +- [#1203](https://github.com/TokTok/c-toxcore/pull/1203) Revert "Improve cmake build for MSVC." +- [#1202](https://github.com/TokTok/c-toxcore/pull/1202) Reset onion search rate for a friend when we see evidence that they are online +- [#1199](https://github.com/TokTok/c-toxcore/pull/1199) Run tests on Appveyor (Windows native build). +- [#1198](https://github.com/TokTok/c-toxcore/pull/1198) Add Cirrus CI configuration. +- [#1197](https://github.com/TokTok/c-toxcore/pull/1197) Use new `@pthread` library from toktok-stack for Windows compat. +- [#1196](https://github.com/TokTok/c-toxcore/pull/1196) Run UBSAN (undefined behaviour sanitizer) on Circle CI. +- [#1195](https://github.com/TokTok/c-toxcore/pull/1195) Fix using uninitialized mutex on call end +- [#1192](https://github.com/TokTok/c-toxcore/pull/1192) Send rejoin packets on conference disconnection +- [#1191](https://github.com/TokTok/c-toxcore/pull/1191) Improve cmake build for MSVC +- [#1188](https://github.com/TokTok/c-toxcore/pull/1188) Fix yamllint test (it's gone from bazel, add a new one). +- [#1187](https://github.com/TokTok/c-toxcore/pull/1187) Fix typos in comments and log and test assertion messages. +- [#1165](https://github.com/TokTok/c-toxcore/pull/1165) Fixed a silly boolean practice using uint8_t +- [#1164](https://github.com/TokTok/c-toxcore/pull/1164) Format yaml files according to yamllint's requirements and remove branch filter for appveyor. +- [#1161](https://github.com/TokTok/c-toxcore/pull/1161) Use most recent version of Bazel (0.17.1). +- [#1158](https://github.com/TokTok/c-toxcore/pull/1158) Use C++ style casts in C++ code. +- [#1157](https://github.com/TokTok/c-toxcore/pull/1157) Use run_auto_test fixture in typing_test.c +- [#1155](https://github.com/TokTok/c-toxcore/pull/1155) Standardise header guards. +- [#1154](https://github.com/TokTok/c-toxcore/pull/1154) Assert that we don't kill tox before killing toxav. +- [#1153](https://github.com/TokTok/c-toxcore/pull/1153) Always use the passed logger (from Messenger) in msi_kill. +- [#1151](https://github.com/TokTok/c-toxcore/pull/1151) Fix typo: tcp_replays -> tcp_relays. +- [#1150](https://github.com/TokTok/c-toxcore/pull/1150) Use `(void)` for empty parameter lists in C. +- [#1147](https://github.com/TokTok/c-toxcore/pull/1147) Ignore "unused-result" warning in super_donators code. +- [#1145](https://github.com/TokTok/c-toxcore/pull/1145) Fix login issue on Travis-CI FreeBSD build +- [#1141](https://github.com/TokTok/c-toxcore/pull/1141) Include necessary opencv2 header on OSX. +- [#1140](https://github.com/TokTok/c-toxcore/pull/1140) Clean up `add_to_list` function a bit. +- [#1139](https://github.com/TokTok/c-toxcore/pull/1139) Avoid recursion in `ip_is_lan` and `ip_is_local`. +- [#1138](https://github.com/TokTok/c-toxcore/pull/1138) Add tool to find directly recursive calls in toxcore. +- [#1136](https://github.com/TokTok/c-toxcore/pull/1136) Remove the use of `CLOCK_MONOTONIC_RAW`. +- [#1135](https://github.com/TokTok/c-toxcore/pull/1135) Avoid use of global mutable state in mono_time on win32. +- [#1134](https://github.com/TokTok/c-toxcore/pull/1134) Use `code font` for tool names and flags in INSTALL.md. +- [#1126](https://github.com/TokTok/c-toxcore/pull/1126) Simplify configure.ac for faster autotools build. +- [#1095](https://github.com/TokTok/c-toxcore/pull/1095) Use test clock in run_auto_test tests and dht test +- [#1069](https://github.com/TokTok/c-toxcore/pull/1069) Add mechanism for recovering from disconnections in conferences +- [#1046](https://github.com/TokTok/c-toxcore/pull/1046) Finish the messenger state plugin system +- [#895](https://github.com/TokTok/c-toxcore/pull/895) Feature bootstrap trace/debug log output + +### Closed issues: + +- [#1214](https://github.com/TokTok/c-toxcore/issues/1214) Massive red shutdown of nodes +- [#1201](https://github.com/TokTok/c-toxcore/issues/1201) Windows cross-compilation is broken +- [#1194](https://github.com/TokTok/c-toxcore/issues/1194) Cancelling unanswered toxav call locks uninitialied mutex +- [#961](https://github.com/TokTok/c-toxcore/issues/961) Can't send messages in persistent group chat +- [#960](https://github.com/TokTok/c-toxcore/issues/960) Persistent groups don't work properly when using toxync bot +- [#838](https://github.com/TokTok/c-toxcore/issues/838) How to get groupchat identifier? + +## v0.2.7 + +### Merged PRs: + +- [#1142](https://github.com/TokTok/c-toxcore/pull/1142) Release 0.2.7 +- [#1137](https://github.com/TokTok/c-toxcore/pull/1137) Make `ip_is_lan` return bool instead of 0/-1. +- [#1133](https://github.com/TokTok/c-toxcore/pull/1133) Make the tsan build fail instead of swallowing its errors. +- [#1132](https://github.com/TokTok/c-toxcore/pull/1132) Use `bool` in place of 0/1 int values. +- [#1131](https://github.com/TokTok/c-toxcore/pull/1131) Format crypto_core.c. +- [#1130](https://github.com/TokTok/c-toxcore/pull/1130) Fix test class name for mono_time_test. +- [#1129](https://github.com/TokTok/c-toxcore/pull/1129) Call `abort` instead of `exit` on test failure. +- [#1128](https://github.com/TokTok/c-toxcore/pull/1128) Add some tests for `ping_array`. +- [#1127](https://github.com/TokTok/c-toxcore/pull/1127) Update copyright to 2018. +- [#1125](https://github.com/TokTok/c-toxcore/pull/1125) Run save_compatibility_test in the autotools build. +- [#1124](https://github.com/TokTok/c-toxcore/pull/1124) Fix the `PORT_ALLOC` failure of `save_compatibility_test`. +- [#1123](https://github.com/TokTok/c-toxcore/pull/1123) Add support for setting a custom monotonic time function in mono_time +- [#1122](https://github.com/TokTok/c-toxcore/pull/1122) Run all tests (and compilation) in parallel with autotools. +- [#1120](https://github.com/TokTok/c-toxcore/pull/1120) Stop using massive macros in `toxav_basic_test`. +- [#1119](https://github.com/TokTok/c-toxcore/pull/1119) Use do-while instead of while in tests. +- [#1117](https://github.com/TokTok/c-toxcore/pull/1117) Fix leave callback calling in del_groupchat +- [#1112](https://github.com/TokTok/c-toxcore/pull/1112) Fix auto_tests Makefile +- [#1110](https://github.com/TokTok/c-toxcore/pull/1110) Add check to make sure tox was created successfully +- [#1109](https://github.com/TokTok/c-toxcore/pull/1109) Consistently use 'mono_time' rather than 'monotime' +- [#1107](https://github.com/TokTok/c-toxcore/pull/1107) Always print output on failure in cmake tests on Travis. +- [#1106](https://github.com/TokTok/c-toxcore/pull/1106) Fix libmisc_tools building +- [#1104](https://github.com/TokTok/c-toxcore/pull/1104) Avoid redefining macros from different files. +- [#1103](https://github.com/TokTok/c-toxcore/pull/1103) Upload coverage to codecov as well as coveralls. +- [#1102](https://github.com/TokTok/c-toxcore/pull/1102) Enable color diagnostics on circleci. +- [#1101](https://github.com/TokTok/c-toxcore/pull/1101) Make the save_compatibility_test work with bazel. +- [#1100](https://github.com/TokTok/c-toxcore/pull/1100) Make Mono_Time an argument to current_time_monotonic +- [#1099](https://github.com/TokTok/c-toxcore/pull/1099) Fix const cast in save-generator. +- [#1098](https://github.com/TokTok/c-toxcore/pull/1098) Run both asan and tsan on Circle CI. +- [#1097](https://github.com/TokTok/c-toxcore/pull/1097) Run project tests like yamllint_test. +- [#1096](https://github.com/TokTok/c-toxcore/pull/1096) Enable .travis.yml check and use non-markdown license. +- [#1094](https://github.com/TokTok/c-toxcore/pull/1094) Set `_POSIX_C_SOURCE` to 200112L. We need it for C99 compat. +- [#1092](https://github.com/TokTok/c-toxcore/pull/1092) Install the `DHT_bootstrap` binary on `make install`. +- [#1086](https://github.com/TokTok/c-toxcore/pull/1086) Try ipv6 connections even after udp timeout +- [#1081](https://github.com/TokTok/c-toxcore/pull/1081) Change while-loop to for-loop to express for-each-frame. +- [#1075](https://github.com/TokTok/c-toxcore/pull/1075) Fix FreeBSD VM on Travis not shutting down +- [#1061](https://github.com/TokTok/c-toxcore/pull/1061) Force storing the result of crypto_memcmp in the test. +- [#1057](https://github.com/TokTok/c-toxcore/pull/1057) Reduce the number of times `unix_time_update` is called. +- [#1051](https://github.com/TokTok/c-toxcore/pull/1051) Add save file generator, compatibility test, and generate a savefile +- [#1038](https://github.com/TokTok/c-toxcore/pull/1038) Use per-instance `Mono_Time` instead of a global `unix_time` + +### Closed issues: + +- [#1114](https://github.com/TokTok/c-toxcore/issues/1114) Segfault on group quit, free of invalid audio_decoder +- [#1105](https://github.com/TokTok/c-toxcore/issues/1105) Sodium.h missing? + +## v0.2.6 + +### Merged PRs: + +- [#1093](https://github.com/TokTok/c-toxcore/pull/1093) Release 0.2.6 +- [#1090](https://github.com/TokTok/c-toxcore/pull/1090) Fix possible resource leaks in test +- [#1089](https://github.com/TokTok/c-toxcore/pull/1089) Limit the size of a save file in file_saving_test. +- [#1088](https://github.com/TokTok/c-toxcore/pull/1088) Use `--config` to tell bazel about the environment. +- [#1085](https://github.com/TokTok/c-toxcore/pull/1085) Prune long long warnings. +- [#1084](https://github.com/TokTok/c-toxcore/pull/1084) Fix style in toxav.c. +- [#1083](https://github.com/TokTok/c-toxcore/pull/1083) Fix coding style in rtp module. +- [#1082](https://github.com/TokTok/c-toxcore/pull/1082) Fix groupav.c style and avoid casts in toxav_old.c. +- [#1080](https://github.com/TokTok/c-toxcore/pull/1080) Fix memory leak in error path in group A/V. +- [#1079](https://github.com/TokTok/c-toxcore/pull/1079) Fix style in video.c. +- [#1078](https://github.com/TokTok/c-toxcore/pull/1078) Fix style in msi.c. +- [#1077](https://github.com/TokTok/c-toxcore/pull/1077) Make `conferences_object` properly typed. +- [#1076](https://github.com/TokTok/c-toxcore/pull/1076) Fix style in bwcontroller module. +- [#1074](https://github.com/TokTok/c-toxcore/pull/1074) Move OSX to stage 1 of Travis. +- [#1073](https://github.com/TokTok/c-toxcore/pull/1073) Stop running tests in the bazel build. +- [#1072](https://github.com/TokTok/c-toxcore/pull/1072) Avoid forward declaration of rtp structs. +- [#1071](https://github.com/TokTok/c-toxcore/pull/1071) Temporarily disable FreeBSD build, since it times out. +- [#1070](https://github.com/TokTok/c-toxcore/pull/1070) Fix enumerator names in toxav to comply with toxcore naming standards. +- [#1068](https://github.com/TokTok/c-toxcore/pull/1068) Fix a few warnings from clang. +- [#1067](https://github.com/TokTok/c-toxcore/pull/1067) Remove last use of the `MIN` macro. +- [#1066](https://github.com/TokTok/c-toxcore/pull/1066) Remove all uses of the PAIR macro in toxav. +- [#1064](https://github.com/TokTok/c-toxcore/pull/1064) Fix ToxAv's use of `struct Tox`. +- [#1063](https://github.com/TokTok/c-toxcore/pull/1063) Avoid passing -1 as friend connection to new groups. +- [#1062](https://github.com/TokTok/c-toxcore/pull/1062) Check that the save file size isn't larger than our address space. +- [#1060](https://github.com/TokTok/c-toxcore/pull/1060) Avoid implicit conversion of negative value to uint32_t. +- [#1059](https://github.com/TokTok/c-toxcore/pull/1059) Assert that we don't divide by 0 in random_testing.cc. +- [#1056](https://github.com/TokTok/c-toxcore/pull/1056) Fix typo in loop over assocs. +- [#1053](https://github.com/TokTok/c-toxcore/pull/1053) Use tokstyle in the cmake travis build. +- [#1049](https://github.com/TokTok/c-toxcore/pull/1049) Fix some printf format specifiers. +- [#1043](https://github.com/TokTok/c-toxcore/pull/1043) Add simple deterministic random number generator for tests +- [#1042](https://github.com/TokTok/c-toxcore/pull/1042) Add callback for successful connection to a conference +- [#1039](https://github.com/TokTok/c-toxcore/pull/1039) Use the crypto random functions instead of `rand()`. +- [#1036](https://github.com/TokTok/c-toxcore/pull/1036) Add deprecation notice to some UPPER_CASE enums. +- [#1016](https://github.com/TokTok/c-toxcore/pull/1016) Split out conference type (text/av) from identifier. + +## v0.2.5 + +### Merged PRs: + +- [#1054](https://github.com/TokTok/c-toxcore/pull/1054) Release 0.2.5 +- [#1048](https://github.com/TokTok/c-toxcore/pull/1048) Fix error message in m_send_generic_message +- [#1047](https://github.com/TokTok/c-toxcore/pull/1047) Remove unused `m_callback_log` function. +- [#1041](https://github.com/TokTok/c-toxcore/pull/1041) Avoid multiple for-next expressions. +- [#1037](https://github.com/TokTok/c-toxcore/pull/1037) Run all tests in the Autotools build +- [#1035](https://github.com/TokTok/c-toxcore/pull/1035) Fix problems with initial connections and name-setting in conferences +- [#1032](https://github.com/TokTok/c-toxcore/pull/1032) Use auto_test fixture in some tests and standardise filenames +- [#1030](https://github.com/TokTok/c-toxcore/pull/1030) Make a separate `struct Tox` containing the Messenger. +- [#1029](https://github.com/TokTok/c-toxcore/pull/1029) Add `by_id` and `get_id` functions, renaming from `*_uid`. +- [#1025](https://github.com/TokTok/c-toxcore/pull/1025) More fixed_width ints and incorporating file_saving_test.c +- [#1023](https://github.com/TokTok/c-toxcore/pull/1023) Run buildifier on c-toxcore BUILD files. +- [#1022](https://github.com/TokTok/c-toxcore/pull/1022) Make `resize` in `list.c` return bool instead of 0/1. +- [#1021](https://github.com/TokTok/c-toxcore/pull/1021) Remove redundant casts to the same type. +- [#1020](https://github.com/TokTok/c-toxcore/pull/1020) Add github usernames to TODOs. +- [#1019](https://github.com/TokTok/c-toxcore/pull/1019) Synchronise parameter names in headers with those in the implementation. +- [#1018](https://github.com/TokTok/c-toxcore/pull/1018) Reduce nesting by doing more early returns on error. +- [#1017](https://github.com/TokTok/c-toxcore/pull/1017) Add missing braces in dht_test.c. +- [#1011](https://github.com/TokTok/c-toxcore/pull/1011) Run Clang global static analysis on Travis. +- [#1010](https://github.com/TokTok/c-toxcore/pull/1010) Avoid implementations in .h files or #including .c files. + +### Closed issues: + +- [#1028](https://github.com/TokTok/c-toxcore/issues/1028) qTox crashes 1-2 times a day after update to 0.2.4 +- [#1002](https://github.com/TokTok/c-toxcore/issues/1002) Implement an abstraction over pthread and windows thread synchronisation primitives + +## v0.2.4 + +### Merged PRs: + +- [#1024](https://github.com/TokTok/c-toxcore/pull/1024) Release v0.2.4 +- [#1014](https://github.com/TokTok/c-toxcore/pull/1014) Use string comparison operator in configure.ac. +- [#1013](https://github.com/TokTok/c-toxcore/pull/1013) Link -lsocket and -lnsl for socket functions on Solaris. +- [#1012](https://github.com/TokTok/c-toxcore/pull/1012) Correct the max hostname length constant. +- [#1009](https://github.com/TokTok/c-toxcore/pull/1009) Using stdint instead of int/long +- [#1008](https://github.com/TokTok/c-toxcore/pull/1008) Set `_XOPEN_SOURCE` to 700 for FreeBSD. +- [#1007](https://github.com/TokTok/c-toxcore/pull/1007) Use enums for group packet types. +- [#1006](https://github.com/TokTok/c-toxcore/pull/1006) Set C++11/C99 flag manually in older cmake on not-msvc. +- [#1005](https://github.com/TokTok/c-toxcore/pull/1005) Use the correct repository name in the coverage badge. +- [#1003](https://github.com/TokTok/c-toxcore/pull/1003) Remove LOGGER_ERROR for harmless send failure. +- [#1001](https://github.com/TokTok/c-toxcore/pull/1001) Add conference_by_uid and conference_get_uid functions. +- [#1000](https://github.com/TokTok/c-toxcore/pull/1000) Limit number of group chats to 65536. +- [#998](https://github.com/TokTok/c-toxcore/pull/998) Use named function types for group callbacks. +- [#997](https://github.com/TokTok/c-toxcore/pull/997) Style fixes in TCP code; remove MIN and PAIR from util.h. +- [#996](https://github.com/TokTok/c-toxcore/pull/996) Add the bazel build as one of the PR blocking builds. +- [#995](https://github.com/TokTok/c-toxcore/pull/995) Fix style in some header files. +- [#994](https://github.com/TokTok/c-toxcore/pull/994) Fix style in DHT.c. +- [#993](https://github.com/TokTok/c-toxcore/pull/993) Move `load_state` and its helper functions to their own module. +- [#991](https://github.com/TokTok/c-toxcore/pull/991) Use named function types for friend_connection callbacks. +- [#990](https://github.com/TokTok/c-toxcore/pull/990) Use named function types for friend_requests callbacks. +- [#989](https://github.com/TokTok/c-toxcore/pull/989) Use named function types for callbacks in net_crypto. +- [#987](https://github.com/TokTok/c-toxcore/pull/987) Use named types for onion callbacks. +- [#986](https://github.com/TokTok/c-toxcore/pull/986) Simplify Travis-CI FreeBSD build +- [#985](https://github.com/TokTok/c-toxcore/pull/985) Clarify the intent of "file kinds" in the API. +- [#984](https://github.com/TokTok/c-toxcore/pull/984) Avoid side-effectful assignments in conditionals. +- [#981](https://github.com/TokTok/c-toxcore/pull/981) Factor out time keeping code into its own module: mono_time.c. +- [#979](https://github.com/TokTok/c-toxcore/pull/979) Add a thread-safe version of unix_time and friends. +- [#978](https://github.com/TokTok/c-toxcore/pull/978) Rename `BS_LIST` to `BS_List` to follow the naming conventions. +- [#977](https://github.com/TokTok/c-toxcore/pull/977) Remove VLA usage from `send_audio_packet`. +- [#976](https://github.com/TokTok/c-toxcore/pull/976) Call the "peer leaves" callback only once on group delete. +- [#975](https://github.com/TokTok/c-toxcore/pull/975) Factor out the actual test code from conference_test. +- [#972](https://github.com/TokTok/c-toxcore/pull/972) Add a test that reproduces the NULL peer nick bug. +- [#968](https://github.com/TokTok/c-toxcore/pull/968) Make tox.c unambiguously parseable. +- [#967](https://github.com/TokTok/c-toxcore/pull/967) lan_discovery_test and version_test cleanup +- [#966](https://github.com/TokTok/c-toxcore/pull/966) Use run_auto_test.h test fixture for some auto-tests. +- [#965](https://github.com/TokTok/c-toxcore/pull/965) Add `#include ` for `std::printf`. +- [#964](https://github.com/TokTok/c-toxcore/pull/964) Add some tests for our ring_buffer implementation. +- [#962](https://github.com/TokTok/c-toxcore/pull/962) Collect `PACKET_ID*` constants in `net_crypto.h`, cleanup their uses +- [#958](https://github.com/TokTok/c-toxcore/pull/958) Fix leak of Logger instances in dht_test. +- [#957](https://github.com/TokTok/c-toxcore/pull/957) Remove broken conference tests. +- [#955](https://github.com/TokTok/c-toxcore/pull/955) Another TCP_test upgrade +- [#954](https://github.com/TokTok/c-toxcore/pull/954) Proposal: Make arg `host` understand clearly. +- [#953](https://github.com/TokTok/c-toxcore/pull/953) Add missing MAX_HOSTNAME_LENGTH doc. +- [#945](https://github.com/TokTok/c-toxcore/pull/945) Add a test to try and overflow the send queue in net_crypto. +- [#943](https://github.com/TokTok/c-toxcore/pull/943) Correct check for net_crypto packet index. +- [#942](https://github.com/TokTok/c-toxcore/pull/942) Simplify Travis CI builds. +- [#932](https://github.com/TokTok/c-toxcore/pull/932) Various minor cleanups in `net_crypto`. + +### Closed issues: + +- [#1015](https://github.com/TokTok/c-toxcore/issues/1015) Improve Solaris support +- [#1004](https://github.com/TokTok/c-toxcore/issues/1004) qTox: "Program received signal SIGPIPE, Broken pipe." with TokTok-c-toxcore-v0.2.3_GH0 on FreeBSD 11.x. +- [#988](https://github.com/TokTok/c-toxcore/issues/988) Registration on https://nodes.tox.chat (question) +- [#982](https://github.com/TokTok/c-toxcore/issues/982) Merge the two travis stages for freebsd back into one +- [#970](https://github.com/TokTok/c-toxcore/issues/970) Crash with persistent groups +- [#963](https://github.com/TokTok/c-toxcore/issues/963) ToxAV's `rb_write` function is written in a strange way +- [#946](https://github.com/TokTok/c-toxcore/issues/946) [API] for max proxy address length +- [#944](https://github.com/TokTok/c-toxcore/issues/944) How detect that friend is busy +- [#936](https://github.com/TokTok/c-toxcore/issues/936) Help needed in testing a tox client,I need some dummy toxids to test . +- [#923](https://github.com/TokTok/c-toxcore/issues/923) Crash on malloc in ping_array_add +- [#911](https://github.com/TokTok/c-toxcore/issues/911) Weekly Tox Dev Meeting +- [#910](https://github.com/TokTok/c-toxcore/issues/910) Crash in clear_entry in ping_array.c +- [#903](https://github.com/TokTok/c-toxcore/issues/903) c-toxcore and LGPL (question) +- [#528](https://github.com/TokTok/c-toxcore/issues/528) c-toxcore on Windows can be compiled using MSYS2 (with modern MinGW-w64) +- [#472](https://github.com/TokTok/c-toxcore/issues/472) Crash in ping_array.c:35 +- [#450](https://github.com/TokTok/c-toxcore/issues/450) Run format-test earlier in the build +- [#429](https://github.com/TokTok/c-toxcore/issues/429) Cannot build on Windows using MinGW + +## v0.2.3 + +### Merged PRs: + +- [#952](https://github.com/TokTok/c-toxcore/pull/952) Release v0.2.3 +- [#951](https://github.com/TokTok/c-toxcore/pull/951) Only run astyle if the astyle binary exists. +- [#950](https://github.com/TokTok/c-toxcore/pull/950) Remove utils.c and utils.h from toxencryptsave build. +- [#949](https://github.com/TokTok/c-toxcore/pull/949) Fixes to the imported sodium sources to compile without warnings. +- [#948](https://github.com/TokTok/c-toxcore/pull/948) Add a MAX_HOSTNAME_LENGTH constant. +- [#947](https://github.com/TokTok/c-toxcore/pull/947) Remove the format test. +- [#937](https://github.com/TokTok/c-toxcore/pull/937) Add new Circle CI configuration. +- [#935](https://github.com/TokTok/c-toxcore/pull/935) Add a test for double conference invite. +- [#933](https://github.com/TokTok/c-toxcore/pull/933) Add Logger to various net_crypto functions, and add `const` to Logger where possible. +- [#931](https://github.com/TokTok/c-toxcore/pull/931) Avoid conditional-uninitialised warning for tcp test. +- [#930](https://github.com/TokTok/c-toxcore/pull/930) Disable UDP when proxy is enabled. +- [#928](https://github.com/TokTok/c-toxcore/pull/928) Use clang-format for C++ code. +- [#927](https://github.com/TokTok/c-toxcore/pull/927) Add assertions to bootstrap tests for correct connection type. +- [#926](https://github.com/TokTok/c-toxcore/pull/926) Make NULL options behave the same as default options. +- [#925](https://github.com/TokTok/c-toxcore/pull/925) Add tests for what happens when passing an invalid proxy host. +- [#924](https://github.com/TokTok/c-toxcore/pull/924) Make the net_crypto connection state an enum. +- [#922](https://github.com/TokTok/c-toxcore/pull/922) Clarify/Improve test_some test +- [#921](https://github.com/TokTok/c-toxcore/pull/921) Beginnings of a TCP_test.c overhaul +- [#920](https://github.com/TokTok/c-toxcore/pull/920) Add test for creating multiple conferences in one tox. +- [#918](https://github.com/TokTok/c-toxcore/pull/918) Merge irungentoo/master into toktok +- [#917](https://github.com/TokTok/c-toxcore/pull/917) Add random testing program. +- [#916](https://github.com/TokTok/c-toxcore/pull/916) Fix linking with address sanitizer. +- [#915](https://github.com/TokTok/c-toxcore/pull/915) Remove resource_leak_test. +- [#914](https://github.com/TokTok/c-toxcore/pull/914) Make dht_test more stable. +- [#913](https://github.com/TokTok/c-toxcore/pull/913) Minor cleanup: return early on error condition. +- [#906](https://github.com/TokTok/c-toxcore/pull/906) Sort bazel build file according to buildifier standard. +- [#905](https://github.com/TokTok/c-toxcore/pull/905) In DEBUG mode, make toxcore crash on signed integer overflow. +- [#902](https://github.com/TokTok/c-toxcore/pull/902) Log only the filename, not the full path in LOGGER. +- [#899](https://github.com/TokTok/c-toxcore/pull/899) Fix macOS macro because of GNU Mach +- [#898](https://github.com/TokTok/c-toxcore/pull/898) Fix enumeration of Crypto_Connection instances +- [#897](https://github.com/TokTok/c-toxcore/pull/897) Fix ipport_isset: port 0 is not a valid port. +- [#894](https://github.com/TokTok/c-toxcore/pull/894) Fix logging related crash in bootstrap node +- [#893](https://github.com/TokTok/c-toxcore/pull/893) Fix bootstrap crashes, still +- [#892](https://github.com/TokTok/c-toxcore/pull/892) Add empty logger to DHT bootstrap daemons. +- [#887](https://github.com/TokTok/c-toxcore/pull/887) Fix FreeBSD build on Travis +- [#884](https://github.com/TokTok/c-toxcore/pull/884) Fix the often call of event tox_friend_connection_status +- [#883](https://github.com/TokTok/c-toxcore/pull/883) Make toxcore compile on BSD +- [#878](https://github.com/TokTok/c-toxcore/pull/878) fix DHT_bootstrap key loading +- [#877](https://github.com/TokTok/c-toxcore/pull/877) Add minitox to under "Other resources" section in the README +- [#875](https://github.com/TokTok/c-toxcore/pull/875) Make bootstrap daemon use toxcore's version +- [#867](https://github.com/TokTok/c-toxcore/pull/867) Improve network error reporting on Windows +- [#841](https://github.com/TokTok/c-toxcore/pull/841) Only check full rtp offset if RTP_LARGE_FRAME is set +- [#823](https://github.com/TokTok/c-toxcore/pull/823) Finish @Diadlo's network Family abstraction. +- [#822](https://github.com/TokTok/c-toxcore/pull/822) Move system header includes from network.h to network.c + +### Closed issues: + +- [#900](https://github.com/TokTok/c-toxcore/issues/900) Log messages include the full build path +- [#881](https://github.com/TokTok/c-toxcore/issues/881) Toxcore does not build with cmake on OpenBSD. +- [#879](https://github.com/TokTok/c-toxcore/issues/879) DHT_bootstrap asserts due to no default logger +- [#868](https://github.com/TokTok/c-toxcore/issues/868) A tox_friend_connection_status event often occurs + +## v0.2.2 + +### Merged PRs: + +- [#872](https://github.com/TokTok/c-toxcore/pull/872) Restrict packet kinds that can be sent through onion path. +- [#864](https://github.com/TokTok/c-toxcore/pull/864) CMake warn if libconfig not found +- [#863](https://github.com/TokTok/c-toxcore/pull/863) Remove broken and unmaintained scripts. +- [#862](https://github.com/TokTok/c-toxcore/pull/862) Release v0.2.2 +- [#859](https://github.com/TokTok/c-toxcore/pull/859) Add clarifying comment to cryptpacket_received function. +- [#857](https://github.com/TokTok/c-toxcore/pull/857) Avoid the use of rand() in tests. +- [#852](https://github.com/TokTok/c-toxcore/pull/852) bugfix build error on MacOS +- [#846](https://github.com/TokTok/c-toxcore/pull/846) Disallow stderr logger by default. +- [#845](https://github.com/TokTok/c-toxcore/pull/845) Fix coveralls reporting. +- [#844](https://github.com/TokTok/c-toxcore/pull/844) Add COVERAGE cmake flag for clang. +- [#825](https://github.com/TokTok/c-toxcore/pull/825) Add default stderr logger for logging to nullptr. +- [#824](https://github.com/TokTok/c-toxcore/pull/824) Simplify sendpacket function, deduplicate some logic. +- [#809](https://github.com/TokTok/c-toxcore/pull/809) Remove the use of the 'hh' format specifier. +- [#801](https://github.com/TokTok/c-toxcore/pull/801) Add logging to the onion_test. +- [#797](https://github.com/TokTok/c-toxcore/pull/797) Move struct DHT_Friend into DHT.c. + +### Closed issues: + +- [#873](https://github.com/TokTok/c-toxcore/issues/873) Onion vulnerability +- [#786](https://github.com/TokTok/c-toxcore/issues/786) Make format strings msvc/mingw-happy + +## v0.2.1 + +### Merged PRs: + +- [#839](https://github.com/TokTok/c-toxcore/pull/839) Update changelog for 0.2.1 +- [#837](https://github.com/TokTok/c-toxcore/pull/837) Update version to 0.2.1. +- [#833](https://github.com/TokTok/c-toxcore/pull/833) Add missing tox_nospam_size() function +- [#832](https://github.com/TokTok/c-toxcore/pull/832) Don't set RTP_LARGE_FRAME on rtp audio packets +- [#831](https://github.com/TokTok/c-toxcore/pull/831) Don't throw away rtp packets from old Toxcore +- [#828](https://github.com/TokTok/c-toxcore/pull/828) Make file transfers 50% faster. + +## v0.2.0 + +### Merged PRs: + +- [#821](https://github.com/TokTok/c-toxcore/pull/821) Remove deprecated conference namelist change callback. +- [#820](https://github.com/TokTok/c-toxcore/pull/820) Fix auto_tests to stop using the deprecated conference API. +- [#819](https://github.com/TokTok/c-toxcore/pull/819) Change default username to empty string +- [#818](https://github.com/TokTok/c-toxcore/pull/818) Change README to talk about cmake instead of autoreconf. +- [#817](https://github.com/TokTok/c-toxcore/pull/817) Fix warning on Mac OS X and FreeBSD. +- [#815](https://github.com/TokTok/c-toxcore/pull/815) Some minor cleanups suggested by cppcheck. +- [#814](https://github.com/TokTok/c-toxcore/pull/814) Fix memory leak of Logger instance on error paths. +- [#813](https://github.com/TokTok/c-toxcore/pull/813) Minor cleanups: dead stores and avoiding complex macros. +- [#811](https://github.com/TokTok/c-toxcore/pull/811) Update changelog for 0.2.0 +- [#808](https://github.com/TokTok/c-toxcore/pull/808) Fix a bunch of compiler warnings and remove suppressions. +- [#807](https://github.com/TokTok/c-toxcore/pull/807) Link all tests to the android cpufeatures library if available. +- [#806](https://github.com/TokTok/c-toxcore/pull/806) Fix toxcore.pc generation. +- [#805](https://github.com/TokTok/c-toxcore/pull/805) Add an option that allows us to specify that we require toxav. +- [#804](https://github.com/TokTok/c-toxcore/pull/804) Fix OSX tests: find(1) doesn't work like on Linux. +- [#803](https://github.com/TokTok/c-toxcore/pull/803) Fix the windows build: pthread needs to be linked after vpx. +- [#800](https://github.com/TokTok/c-toxcore/pull/800) Make group number in the toxav public API uint32_t +- [#799](https://github.com/TokTok/c-toxcore/pull/799) Implement the "persistent conference" callback changes as new functions. +- [#798](https://github.com/TokTok/c-toxcore/pull/798) Add deprecation notices to functions that will go away in v0.3.0. +- [#796](https://github.com/TokTok/c-toxcore/pull/796) Make some sizeof tests linux-only. +- [#794](https://github.com/TokTok/c-toxcore/pull/794) Remove apidsl from the build. +- [#793](https://github.com/TokTok/c-toxcore/pull/793) Add a bazel test that ensures all our projects are GPL-3.0. +- [#792](https://github.com/TokTok/c-toxcore/pull/792) Increase range of ports available to Toxes during tests +- [#791](https://github.com/TokTok/c-toxcore/pull/791) Run all tests in parallel on Travis. +- [#790](https://github.com/TokTok/c-toxcore/pull/790) Disable lan discovery in most tests. +- [#789](https://github.com/TokTok/c-toxcore/pull/789) Remove tox_test from autotools build. +- [#788](https://github.com/TokTok/c-toxcore/pull/788) Don't print trace level logging in tests. +- [#787](https://github.com/TokTok/c-toxcore/pull/787) Split up tox_test into multiple smaller tests +- [#784](https://github.com/TokTok/c-toxcore/pull/784) Use Wine Devel instead of Wine Staging +- [#783](https://github.com/TokTok/c-toxcore/pull/783) Send 0 as peer number in CHANGE_OCCURRED group event. +- [#782](https://github.com/TokTok/c-toxcore/pull/782) Use `const` more in C code. +- [#781](https://github.com/TokTok/c-toxcore/pull/781) Don't build all the small sub-libraries. +- [#780](https://github.com/TokTok/c-toxcore/pull/780) Get rid of the only GNU extension we used. +- [#779](https://github.com/TokTok/c-toxcore/pull/779) Remove leftover symmetric key from DHT struct. +- [#778](https://github.com/TokTok/c-toxcore/pull/778) Add static asserts for all the struct sizes in toxcore. +- [#776](https://github.com/TokTok/c-toxcore/pull/776) Optionally use newer cmake features. +- [#775](https://github.com/TokTok/c-toxcore/pull/775) Look for dependencies in third_party/ +- [#774](https://github.com/TokTok/c-toxcore/pull/774) Improve gtest finding, support local checkout. +- [#773](https://github.com/TokTok/c-toxcore/pull/773) Add gtest include directory to -I flags if found. +- [#772](https://github.com/TokTok/c-toxcore/pull/772) Reject discovery packets coming from outside the "LAN". +- [#771](https://github.com/TokTok/c-toxcore/pull/771) Adopt the "change occurred" API change from isotoxin-groupchat. +- [#770](https://github.com/TokTok/c-toxcore/pull/770) Add MSVC compilation instructions +- [#767](https://github.com/TokTok/c-toxcore/pull/767) Build toxcore with libsodium.dll instead of libsodium.lib. +- [#766](https://github.com/TokTok/c-toxcore/pull/766) Remove libcheck from the dependencies. +- [#765](https://github.com/TokTok/c-toxcore/pull/765) Make outgoing Filetransfers round-robin. +- [#764](https://github.com/TokTok/c-toxcore/pull/764) Fix LAN discovery on FreeBSD. +- [#761](https://github.com/TokTok/c-toxcore/pull/761) use official debian domain +- [#760](https://github.com/TokTok/c-toxcore/pull/760) Make cmake script more forgiving. +- [#759](https://github.com/TokTok/c-toxcore/pull/759) Use more ubuntu packages; remove hstox for now. +- [#757](https://github.com/TokTok/c-toxcore/pull/757) Improve stability of crypto_memcmp test. +- [#756](https://github.com/TokTok/c-toxcore/pull/756) Format .cpp files with format-source. +- [#755](https://github.com/TokTok/c-toxcore/pull/755) Add some unit tests for util.h. +- [#754](https://github.com/TokTok/c-toxcore/pull/754) Move the tox_sync tool to the toxins repository. +- [#753](https://github.com/TokTok/c-toxcore/pull/753) Move irc_syncbot to the toxins repository. +- [#752](https://github.com/TokTok/c-toxcore/pull/752) Move tox_shell program to the toxins repository. +- [#751](https://github.com/TokTok/c-toxcore/pull/751) Use the markdown GPLv3 license in the c-toxcore repo. +- [#750](https://github.com/TokTok/c-toxcore/pull/750) Remove csrc from the RTPHeader struct. +- [#748](https://github.com/TokTok/c-toxcore/pull/748) Revert "Add correction message type" +- [#745](https://github.com/TokTok/c-toxcore/pull/745) Change the "capabilities" field to a "flags" field. +- [#742](https://github.com/TokTok/c-toxcore/pull/742) Improve conference test stability. +- [#741](https://github.com/TokTok/c-toxcore/pull/741) Add `-D__STDC_LIMIT_MACROS=1` for C++ code. +- [#739](https://github.com/TokTok/c-toxcore/pull/739) Add RTP header fields for the full frame length and offset. +- [#737](https://github.com/TokTok/c-toxcore/pull/737) Use nullptr as NULL pointer constant instead of NULL or 0. +- [#736](https://github.com/TokTok/c-toxcore/pull/736) Avoid clashes with "build" directories on case-insensitive file systems. +- [#734](https://github.com/TokTok/c-toxcore/pull/734) Make audio/video bit rates "properties" +- [#733](https://github.com/TokTok/c-toxcore/pull/733) Fix link in README.md +- [#730](https://github.com/TokTok/c-toxcore/pull/730) Fix out of bounds read in error case in messenger_test. +- [#729](https://github.com/TokTok/c-toxcore/pull/729) Remove dead return statement. +- [#728](https://github.com/TokTok/c-toxcore/pull/728) Disable the autotools build in PR builds. +- [#727](https://github.com/TokTok/c-toxcore/pull/727) Rename some rtp header struct members to be clearer. +- [#725](https://github.com/TokTok/c-toxcore/pull/725) Publish a single public BUILD target for c-toxcore. +- [#723](https://github.com/TokTok/c-toxcore/pull/723) Use for alloca on FreeBSD. +- [#722](https://github.com/TokTok/c-toxcore/pull/722) Use self-built portaudio instead of system-provided. +- [#721](https://github.com/TokTok/c-toxcore/pull/721) Manually serialise RTPHeader struct instead of memcpy. +- [#718](https://github.com/TokTok/c-toxcore/pull/718) Improve sending of large video frames in toxav. +- [#716](https://github.com/TokTok/c-toxcore/pull/716) Add comment from #629 in ring_buffer.c. +- [#714](https://github.com/TokTok/c-toxcore/pull/714) Make BUILD files more finely-grained. +- [#713](https://github.com/TokTok/c-toxcore/pull/713) Add BUILD files for all the little tools in the repo. +- [#712](https://github.com/TokTok/c-toxcore/pull/712) Fix high quality video sending (backport to 0.1.x). +- [#711](https://github.com/TokTok/c-toxcore/pull/711) Make the monolith test a C++ binary. +- [#710](https://github.com/TokTok/c-toxcore/pull/710) Don't allocate or dereference Tox_Options in tests. +- [#709](https://github.com/TokTok/c-toxcore/pull/709) Remove nTox from the repo. +- [#708](https://github.com/TokTok/c-toxcore/pull/708) Add testing/*.c (except av_test) to bazel build. +- [#707](https://github.com/TokTok/c-toxcore/pull/707) Fix log message in simple_conference_test: invite -> message. +- [#705](https://github.com/TokTok/c-toxcore/pull/705) Add correction support for conference +- [#703](https://github.com/TokTok/c-toxcore/pull/703) Add a simple conference test with 3 friends. +- [#702](https://github.com/TokTok/c-toxcore/pull/702) Update to astyle 2.04 on CircleCI to get the correct result +- [#701](https://github.com/TokTok/c-toxcore/pull/701) Add astyle to Circle CI build. +- [#700](https://github.com/TokTok/c-toxcore/pull/700) Use more descriptive names in bwcontroller. +- [#699](https://github.com/TokTok/c-toxcore/pull/699) Add some explanatory comments to the toxav audio code. +- [#698](https://github.com/TokTok/c-toxcore/pull/698) Extract named constants from magic numbers in toxav/audio.c. +- [#697](https://github.com/TokTok/c-toxcore/pull/697) Use C99 standard in bazel builds. +- [#694](https://github.com/TokTok/c-toxcore/pull/694) Add bazel build scripts for c-toxcore. +- [#693](https://github.com/TokTok/c-toxcore/pull/693) Make libcheck optional for windows builds. +- [#691](https://github.com/TokTok/c-toxcore/pull/691) Don't install packages needlessly on Travis +- [#690](https://github.com/TokTok/c-toxcore/pull/690) Run fewer Travis jobs during Pull Requests. +- [#689](https://github.com/TokTok/c-toxcore/pull/689) Make Net_Crypto a module-private type. +- [#688](https://github.com/TokTok/c-toxcore/pull/688) Make DHT a module-private type. +- [#687](https://github.com/TokTok/c-toxcore/pull/687) Use apidsl to generate LAN_discovery.h. +- [#686](https://github.com/TokTok/c-toxcore/pull/686) Remove hstox test for now. +- [#685](https://github.com/TokTok/c-toxcore/pull/685) Add message type for correction +- [#684](https://github.com/TokTok/c-toxcore/pull/684) Add random_u16 function and rename the others to match. +- [#682](https://github.com/TokTok/c-toxcore/pull/682) Use larger arrays in crypto timing tests. +- [#681](https://github.com/TokTok/c-toxcore/pull/681) Fix some memory or file descriptor leaks in test code. +- [#680](https://github.com/TokTok/c-toxcore/pull/680) Filter out annoying log statements in unit tests. +- [#679](https://github.com/TokTok/c-toxcore/pull/679) Use apidsl to generate ping.h. +- [#678](https://github.com/TokTok/c-toxcore/pull/678) Sort monolith.h according to ls(1): uppercase first. +- [#677](https://github.com/TokTok/c-toxcore/pull/677) Make pack/unpack_ip_port public DHT functions. +- [#675](https://github.com/TokTok/c-toxcore/pull/675) Make Onion_Announce a module-private type. +- [#674](https://github.com/TokTok/c-toxcore/pull/674) Make TCP_Client_Connection a module-private type. +- [#673](https://github.com/TokTok/c-toxcore/pull/673) Move TCP_Secure_Connection from .h to .c file. +- [#672](https://github.com/TokTok/c-toxcore/pull/672) Make Friend_Connections a module-private type. +- [#670](https://github.com/TokTok/c-toxcore/pull/670) Make Friend_Requests a module-private type. +- [#669](https://github.com/TokTok/c-toxcore/pull/669) Make Onion_Client a module-private type. +- [#668](https://github.com/TokTok/c-toxcore/pull/668) Make Ping_Array a module-private type. +- [#667](https://github.com/TokTok/c-toxcore/pull/667) pkg-config .pc files: added .private versions of Libs and Required +- [#666](https://github.com/TokTok/c-toxcore/pull/666) Fix some typos in code and cmake comments +- [#665](https://github.com/TokTok/c-toxcore/pull/665) Remove useless if statement +- [#662](https://github.com/TokTok/c-toxcore/pull/662) Move Networking_Core struct into the .c file. +- [#661](https://github.com/TokTok/c-toxcore/pull/661) Disable asan, since it seems to break on travis. +- [#660](https://github.com/TokTok/c-toxcore/pull/660) Increase test retries to 10 (basically infinite). +- [#659](https://github.com/TokTok/c-toxcore/pull/659) Fix formatting in some C files. +- [#658](https://github.com/TokTok/c-toxcore/pull/658) Call freeaddrinfo on error paths in net_getipport. +- [#657](https://github.com/TokTok/c-toxcore/pull/657) Zero-initialise stack-allocated objects in hstox driver. +- [#656](https://github.com/TokTok/c-toxcore/pull/656) Fix file descriptor leak in hstox test. +- [#654](https://github.com/TokTok/c-toxcore/pull/654) Bump toxcore version to 0.2.0. +- [#652](https://github.com/TokTok/c-toxcore/pull/652) Add support for building the monolith test on android. +- [#650](https://github.com/TokTok/c-toxcore/pull/650) Remove deprecated ToxDNS +- [#648](https://github.com/TokTok/c-toxcore/pull/648) Make hstox compile on FreeBSD +- [#624](https://github.com/TokTok/c-toxcore/pull/624) Update rpm spec and use variables in cmake instead of hardcoded paths +- [#616](https://github.com/TokTok/c-toxcore/pull/616) Add projects link to Readme. +- [#613](https://github.com/TokTok/c-toxcore/pull/613) Fix travis +- [#605](https://github.com/TokTok/c-toxcore/pull/605) Fix OS X Travis. +- [#598](https://github.com/TokTok/c-toxcore/pull/598) Fix typos in docs +- [#578](https://github.com/TokTok/c-toxcore/pull/578) Split toxav_bit_rate_set() into two functions to hold the maximum bitrates libvpx supports +- [#477](https://github.com/TokTok/c-toxcore/pull/477) Update install instructions to use CMake +- [#465](https://github.com/TokTok/c-toxcore/pull/465) Add Alpine linux Dockerfile in addition to the existing Debian one +- [#442](https://github.com/TokTok/c-toxcore/pull/442) Generate only one large library "libtoxcore". +- [#334](https://github.com/TokTok/c-toxcore/pull/334) Change toxencryptsave API to never overwrite pass keys. + +### Closed issues: + +- [#810](https://github.com/TokTok/c-toxcore/issues/810) Release 0.2.0 +- [#704](https://github.com/TokTok/c-toxcore/issues/704) Add CORRECTION support to group chats +- [#620](https://github.com/TokTok/c-toxcore/issues/620) Video bug: large video frames are not sent correctly +- [#606](https://github.com/TokTok/c-toxcore/issues/606) groupId is int whereas friendId is uint32_t, reason? +- [#599](https://github.com/TokTok/c-toxcore/issues/599) Error when linking against libtoxcore: undefined reference to symbol 'crypto_hash_sha256' +- [#572](https://github.com/TokTok/c-toxcore/issues/572) int32_t may be not large enough as a argument for video_bit_rate of vp8/9 codec +- [#566](https://github.com/TokTok/c-toxcore/issues/566) LAYER #: modules for static linking - build issue +- [#383](https://github.com/TokTok/c-toxcore/issues/383) TODO: add cmake instructions in README.md +- [#42](https://github.com/TokTok/c-toxcore/issues/42) Remove ToxDNS and related stuff from toxcore + +## v0.1.11 + +### Merged PRs: + +- [#643](https://github.com/TokTok/c-toxcore/pull/643) Add .editorconfig +- [#638](https://github.com/TokTok/c-toxcore/pull/638) Release v0.1.11 +- [#637](https://github.com/TokTok/c-toxcore/pull/637) Update tox-bootstrapd Dockerfile +- [#635](https://github.com/TokTok/c-toxcore/pull/635) Separate FreeBSD Travis build in 2 stages +- [#632](https://github.com/TokTok/c-toxcore/pull/632) Lift libconfig to v1.7.1 +- [#631](https://github.com/TokTok/c-toxcore/pull/631) Add aspcud for Opam +- [#630](https://github.com/TokTok/c-toxcore/pull/630) Fix for Travis fail on addr_resolve testing +- [#623](https://github.com/TokTok/c-toxcore/pull/623) Split video payload into multiple RTP messages when too big to fit into one +- [#615](https://github.com/TokTok/c-toxcore/pull/615) forget DHT pubkey of offline friend after DHT timeout +- [#611](https://github.com/TokTok/c-toxcore/pull/611) Fix typo +- [#607](https://github.com/TokTok/c-toxcore/pull/607) set onion pingid timeout to announce timeout (300s) +- [#592](https://github.com/TokTok/c-toxcore/pull/592) Adjust docs of few toxencrypt function to the code +- [#587](https://github.com/TokTok/c-toxcore/pull/587) Fix tox test +- [#586](https://github.com/TokTok/c-toxcore/pull/586) Improve LAN discovery +- [#576](https://github.com/TokTok/c-toxcore/pull/576) Replace include(CTest) on enable_testing() +- [#574](https://github.com/TokTok/c-toxcore/pull/574) Reset hole-punching parameters after not punching for a while +- [#571](https://github.com/TokTok/c-toxcore/pull/571) Configure needs to find libsodium headers. +- [#515](https://github.com/TokTok/c-toxcore/pull/515) Network cleanup: reduce dependency on system-defined constants +- [#505](https://github.com/TokTok/c-toxcore/pull/505) Add FreeBSD Travis +- [#500](https://github.com/TokTok/c-toxcore/pull/500) Fixed the bug when receipts for messages sent from the receipt callback never arrived. + +### Closed issues: + +- [#493](https://github.com/TokTok/c-toxcore/issues/493) Receipts for messages sent from the receipt callback never arrive +- [#240](https://github.com/TokTok/c-toxcore/issues/240) Tox doesn't reconnect after internet connection interruption +- [#237](https://github.com/TokTok/c-toxcore/issues/237) Contacts are shown offline when they are online + +## v0.1.10 + +### Merged PRs: + +- [#575](https://github.com/TokTok/c-toxcore/pull/575) Release v0.1.10 +- [#564](https://github.com/TokTok/c-toxcore/pull/564) Fix Windows build +- [#542](https://github.com/TokTok/c-toxcore/pull/542) Save bandwidth by moderating onion pinging + +## v0.1.9 + +### Merged PRs: + +- [#563](https://github.com/TokTok/c-toxcore/pull/563) Release v0.1.9 +- [#561](https://github.com/TokTok/c-toxcore/pull/561) Remove unused variable +- [#560](https://github.com/TokTok/c-toxcore/pull/560) Fix non-portable zeroing out of doubles +- [#559](https://github.com/TokTok/c-toxcore/pull/559) Fix theoretical memory leaks +- [#557](https://github.com/TokTok/c-toxcore/pull/557) Document inverted mutex lock/unlock. +- [#556](https://github.com/TokTok/c-toxcore/pull/556) Build tests on appveyor, the MSVC build, but don't run them yet. +- [#555](https://github.com/TokTok/c-toxcore/pull/555) Fold hstox tests into the general linux test. +- [#554](https://github.com/TokTok/c-toxcore/pull/554) Add a monolith_test that includes all toxcore sources. +- [#553](https://github.com/TokTok/c-toxcore/pull/553) Factor out strict_abi cmake code into a separate module. +- [#552](https://github.com/TokTok/c-toxcore/pull/552) Fix formatting and spelling in version-sync script. +- [#551](https://github.com/TokTok/c-toxcore/pull/551) Forbid undefined symbols in shared libraries. +- [#546](https://github.com/TokTok/c-toxcore/pull/546) Make variable names in file saving test less cryptic +- [#539](https://github.com/TokTok/c-toxcore/pull/539) Make OSX test failures fail the Travis CI build. +- [#537](https://github.com/TokTok/c-toxcore/pull/537) Fix TokTok/c-toxcore#535 +- [#534](https://github.com/TokTok/c-toxcore/pull/534) Fix markdown formatting +- [#530](https://github.com/TokTok/c-toxcore/pull/530) Implement missing TES constant functions. +- [#511](https://github.com/TokTok/c-toxcore/pull/511) Save bandwidth by avoiding superfluous Nodes Requests to peers already on the Close List +- [#506](https://github.com/TokTok/c-toxcore/pull/506) Add test case for title change +- [#498](https://github.com/TokTok/c-toxcore/pull/498) DHT refactoring +- [#487](https://github.com/TokTok/c-toxcore/pull/487) Split daemon's logging backends in separate modules +- [#468](https://github.com/TokTok/c-toxcore/pull/468) Test for memberlist not changing after changing own name +- [#449](https://github.com/TokTok/c-toxcore/pull/449) Use new encoding of `Maybe` in msgpack results. + +### Closed issues: + +- [#482](https://github.com/TokTok/c-toxcore/issues/482) CMake can't detect and compile ToxAV on OSX + +## v0.1.8 + +### Merged PRs: + +- [#538](https://github.com/TokTok/c-toxcore/pull/538) Reverting tox_loop PR changes +- [#536](https://github.com/TokTok/c-toxcore/pull/536) Release v0.1.8 +- [#526](https://github.com/TokTok/c-toxcore/pull/526) Add TOX_NOSPAM_SIZE to the public API. +- [#525](https://github.com/TokTok/c-toxcore/pull/525) Retry autotools tests the same way as cmake tests. +- [#524](https://github.com/TokTok/c-toxcore/pull/524) Reduce ctest timeout to 2 minutes from 5 minutes. +- [#512](https://github.com/TokTok/c-toxcore/pull/512) Add test for DHT pack_nodes and unpack_nodes +- [#504](https://github.com/TokTok/c-toxcore/pull/504) CMake: install bootstrapd if it is built +- [#488](https://github.com/TokTok/c-toxcore/pull/488) Save compiled Android artifacts after CircleCI builds. +- [#473](https://github.com/TokTok/c-toxcore/pull/473) Added missing includes: and +- [#335](https://github.com/TokTok/c-toxcore/pull/335) Implement tox_loop + +### Closed issues: + +- [#535](https://github.com/TokTok/c-toxcore/issues/535) OS X tests failing +- [#503](https://github.com/TokTok/c-toxcore/issues/503) Undefined functions: tox_pass_salt_length, tox_pass_key_length, tox_pass_encryption_extra_length +- [#456](https://github.com/TokTok/c-toxcore/issues/456) Tox.h doesn't expose the size of the nospam. +- [#411](https://github.com/TokTok/c-toxcore/issues/411) Reduce CTest timeout to 2 minutes + +## v0.1.7 + +### Merged PRs: + +- [#523](https://github.com/TokTok/c-toxcore/pull/523) Release v0.1.7 +- [#521](https://github.com/TokTok/c-toxcore/pull/521) Fix appveyor script: install curl from chocolatey. +- [#510](https://github.com/TokTok/c-toxcore/pull/510) Fix list malloc(0) bug +- [#509](https://github.com/TokTok/c-toxcore/pull/509) Fix network malloc(0) bug +- [#497](https://github.com/TokTok/c-toxcore/pull/497) Fix network +- [#496](https://github.com/TokTok/c-toxcore/pull/496) Fix Travis always succeeding despite tests failing +- [#491](https://github.com/TokTok/c-toxcore/pull/491) Add crypto_memzero for temp buffer +- [#490](https://github.com/TokTok/c-toxcore/pull/490) Move c_sleep to helpers.h and misc_tools.h +- [#486](https://github.com/TokTok/c-toxcore/pull/486) Remove empty line in Messenger.c +- [#483](https://github.com/TokTok/c-toxcore/pull/483) Make BUILD_TOXAV an option and fail if dependencies are missing +- [#481](https://github.com/TokTok/c-toxcore/pull/481) Remove dependency on strings.h +- [#480](https://github.com/TokTok/c-toxcore/pull/480) Use VLA macro +- [#479](https://github.com/TokTok/c-toxcore/pull/479) Fix pthreads in AppVeyor build +- [#471](https://github.com/TokTok/c-toxcore/pull/471) Remove statics used in onion comparison functions. +- [#461](https://github.com/TokTok/c-toxcore/pull/461) Replace part of network functions on platform-independent implementation +- [#452](https://github.com/TokTok/c-toxcore/pull/452) Add VLA compatibility macro for C89-ish compilers. + +### Closed issues: + +- [#495](https://github.com/TokTok/c-toxcore/issues/495) Fix heap buffer overflow introduced by #461 +- [#494](https://github.com/TokTok/c-toxcore/issues/494) Format networking code introduced by #461 +- [#474](https://github.com/TokTok/c-toxcore/issues/474) TOX_VERSION_PATCH isn't in sync with the version + +## v0.1.6 + +### Merged PRs: + +- [#460](https://github.com/TokTok/c-toxcore/pull/460) Release v0.1.6. +- [#459](https://github.com/TokTok/c-toxcore/pull/459) Add Android build to CI. +- [#454](https://github.com/TokTok/c-toxcore/pull/454) Add appveyor build for native windows tests. +- [#448](https://github.com/TokTok/c-toxcore/pull/448) Only retry failed tests on Circle CI instead of all. +- [#434](https://github.com/TokTok/c-toxcore/pull/434) Replace redundant packet type check in handler with assert. +- [#432](https://github.com/TokTok/c-toxcore/pull/432) Remove some static variables +- [#385](https://github.com/TokTok/c-toxcore/pull/385) Add platform-independent Socket and IP implementation + +### Closed issues: + +- [#457](https://github.com/TokTok/c-toxcore/issues/457) EPOLLRDHUP not defined in android ndk on lower API that 21 +- [#415](https://github.com/TokTok/c-toxcore/issues/415) Set up a native windows build on appveyor + +## v0.1.5 + +### Merged PRs: + +- [#447](https://github.com/TokTok/c-toxcore/pull/447) Release v0.1.5. +- [#446](https://github.com/TokTok/c-toxcore/pull/446) Limit number of retries to 3. +- [#445](https://github.com/TokTok/c-toxcore/pull/445) Make Travis tests slightly more robust by re-running them. +- [#443](https://github.com/TokTok/c-toxcore/pull/443) Make building `DHT_bootstrap` in cmake optional. +- [#433](https://github.com/TokTok/c-toxcore/pull/433) Add tutorial and "danger: experimental" banner to README. +- [#431](https://github.com/TokTok/c-toxcore/pull/431) Update license headers and remove redundant file name comment. +- [#424](https://github.com/TokTok/c-toxcore/pull/424) Fixed the FreeBSD build failure due to the undefined MSG_NOSIGNAL. +- [#420](https://github.com/TokTok/c-toxcore/pull/420) Setup autotools to read .so version info from a separate file +- [#418](https://github.com/TokTok/c-toxcore/pull/418) Clarify how the autotools build is done on Travis. +- [#414](https://github.com/TokTok/c-toxcore/pull/414) Explicitly check if compiler supports C99 + +### Closed issues: + +- [#413](https://github.com/TokTok/c-toxcore/issues/413) Support C compilation with `-std=c99` in autotools + +## v0.1.4 + +### Merged PRs: + +- [#422](https://github.com/TokTok/c-toxcore/pull/422) Release v0.1.4. +- [#410](https://github.com/TokTok/c-toxcore/pull/410) Fix NaCl build: tar was called incorrectly. +- [#409](https://github.com/TokTok/c-toxcore/pull/409) Clarify that the pass key `new` function can fail. +- [#407](https://github.com/TokTok/c-toxcore/pull/407) Don't use `git.depth=1` anymore. +- [#404](https://github.com/TokTok/c-toxcore/pull/404) Issue 404: semicolon not found +- [#403](https://github.com/TokTok/c-toxcore/pull/403) Warn on -pedantic, don't error yet. +- [#401](https://github.com/TokTok/c-toxcore/pull/401) Add logging callback to messenger_test. +- [#400](https://github.com/TokTok/c-toxcore/pull/400) Run windows tests but ignore their failures. +- [#398](https://github.com/TokTok/c-toxcore/pull/398) Portability Fixes +- [#397](https://github.com/TokTok/c-toxcore/pull/397) Replace make_quick_sort with qsort +- [#396](https://github.com/TokTok/c-toxcore/pull/396) Add an OSX build that doesn't run tests. +- [#394](https://github.com/TokTok/c-toxcore/pull/394) CMake: Add soversion to library files to generate proper symlinks +- [#393](https://github.com/TokTok/c-toxcore/pull/393) Set up autotools build to build against vanilla NaCl. +- [#392](https://github.com/TokTok/c-toxcore/pull/392) Check that TCP connections aren't dropped in callbacks. +- [#391](https://github.com/TokTok/c-toxcore/pull/391) Minor simplification in `file_seek` code. +- [#390](https://github.com/TokTok/c-toxcore/pull/390) Always kill invalid file transfers when receiving file controls. +- [#388](https://github.com/TokTok/c-toxcore/pull/388) Fix logging condition for IPv6 client timestamp updates. +- [#387](https://github.com/TokTok/c-toxcore/pull/387) Eliminate dead return statement. +- [#386](https://github.com/TokTok/c-toxcore/pull/386) Avoid accessing uninitialised memory in `net_crypto`. +- [#381](https://github.com/TokTok/c-toxcore/pull/381) Remove `TOX_DEBUG` and have asserts always enabled. + +### Closed issues: + +- [#378](https://github.com/TokTok/c-toxcore/issues/378) Replace all uses of `make_quick_sort` with `qsort` +- [#364](https://github.com/TokTok/c-toxcore/issues/364) Delete misc_tools.h after replacing its use by qsort. +- [#363](https://github.com/TokTok/c-toxcore/issues/363) Test against NaCl in addition to libsodium on Travis. + +## v0.1.3 + +### Merged PRs: + +- [#395](https://github.com/TokTok/c-toxcore/pull/395) Revert "Portability fixes" +- [#380](https://github.com/TokTok/c-toxcore/pull/380) Test a few cmake option combinations before the build. +- [#377](https://github.com/TokTok/c-toxcore/pull/377) Fix SSL verification in coveralls. +- [#376](https://github.com/TokTok/c-toxcore/pull/376) Bring back autotools instructions +- [#373](https://github.com/TokTok/c-toxcore/pull/373) Only fetch 1 revision from git during Travis builds. +- [#369](https://github.com/TokTok/c-toxcore/pull/369) Integrate with CircleCI to build artifacts in the future +- [#366](https://github.com/TokTok/c-toxcore/pull/366) Release v0.1.3. +- [#362](https://github.com/TokTok/c-toxcore/pull/362) Remove .cabal-sandbox option from tox-spectest find line. +- [#361](https://github.com/TokTok/c-toxcore/pull/361) Simplify integration as a third-party lib in cmake projects +- [#354](https://github.com/TokTok/c-toxcore/pull/354) Add secure memcmp and memzero implementation. +- [#324](https://github.com/TokTok/c-toxcore/pull/324) Do not compile and install DHT_bootstrap if it was disabled in configure +- [#297](https://github.com/TokTok/c-toxcore/pull/297) Portability fixes + +### Closed issues: + +- [#347](https://github.com/TokTok/c-toxcore/issues/347) Implement our own secure `memcmp` and `memzero` if libsodium isn't available +- [#319](https://github.com/TokTok/c-toxcore/issues/319) toxcore installs `DHT_bootstrap` even though `--disable-daemon` is passed to `./configure` + +## v0.1.2 + +### Merged PRs: + +- [#355](https://github.com/TokTok/c-toxcore/pull/355) Release v0.1.2 +- [#353](https://github.com/TokTok/c-toxcore/pull/353) Fix toxav use after free caused by premature MSI destruction +- [#346](https://github.com/TokTok/c-toxcore/pull/346) Avoid array out of bounds read in friend saving. +- [#344](https://github.com/TokTok/c-toxcore/pull/344) Remove unused get/set salt/key functions from toxencryptsave. +- [#343](https://github.com/TokTok/c-toxcore/pull/343) Wrap all sodium/nacl functions in crypto_core.c. +- [#341](https://github.com/TokTok/c-toxcore/pull/341) Add test to check if tox_new/tox_kill leaks. +- [#336](https://github.com/TokTok/c-toxcore/pull/336) Correct TES docs to reflect how many bytes functions actually require. +- [#333](https://github.com/TokTok/c-toxcore/pull/333) Use `tox_options_set_*` instead of direct member access. + +### Closed issues: + +- [#345](https://github.com/TokTok/c-toxcore/issues/345) Array out of bounds read in "save" function +- [#342](https://github.com/TokTok/c-toxcore/issues/342) Wrap all libsodium functions we use in toxcore in `crypto_core`. +- [#278](https://github.com/TokTok/c-toxcore/issues/278) ToxAV use-after-free bug + +## v0.1.1 + +### Merged PRs: + +- [#337](https://github.com/TokTok/c-toxcore/pull/337) Release v0.1.1 +- [#332](https://github.com/TokTok/c-toxcore/pull/332) Add test for encrypted savedata. +- [#330](https://github.com/TokTok/c-toxcore/pull/330) Strengthen the note about ABI compatibility in tox.h. +- [#328](https://github.com/TokTok/c-toxcore/pull/328) Drop the broken `TOX_VERSION_REQUIRE` macro. +- [#326](https://github.com/TokTok/c-toxcore/pull/326) Fix unresolved reference in toxencryptsave API docs. +- [#309](https://github.com/TokTok/c-toxcore/pull/309) Fixed attempt to join detached threads (fixes toxav test crash) +- [#306](https://github.com/TokTok/c-toxcore/pull/306) Add option to disable local peer discovery + +### Closed issues: + +- [#327](https://github.com/TokTok/c-toxcore/issues/327) The `TOX_VERSION_REQUIRE` macro is broken. +- [#221](https://github.com/TokTok/c-toxcore/issues/221) Option to disable local peer detection + +## v0.1.0 + +### Merged PRs: + +- [#325](https://github.com/TokTok/c-toxcore/pull/325) Fix Libs line in toxcore.pc pkg-config file. +- [#322](https://github.com/TokTok/c-toxcore/pull/322) Add compatibility pkg-config modules: libtoxcore, libtoxav. +- [#318](https://github.com/TokTok/c-toxcore/pull/318) Fix `--enable-logging` flag in autotools configure script. +- [#316](https://github.com/TokTok/c-toxcore/pull/316) Release 0.1.0. +- [#315](https://github.com/TokTok/c-toxcore/pull/315) Fix version compatibility test. +- [#314](https://github.com/TokTok/c-toxcore/pull/314) Fix off by one error in saving our own status message. +- [#313](https://github.com/TokTok/c-toxcore/pull/313) Fix padding being in the wrong place in `SAVED_FRIEND` struct +- [#312](https://github.com/TokTok/c-toxcore/pull/312) Conditionally enable non-portable assert on LP64. +- [#310](https://github.com/TokTok/c-toxcore/pull/310) Add apidsl file for toxencryptsave. +- [#307](https://github.com/TokTok/c-toxcore/pull/307) Clarify toxencryptsave documentation regarding buffer sizes +- [#305](https://github.com/TokTok/c-toxcore/pull/305) Fix static builds +- [#303](https://github.com/TokTok/c-toxcore/pull/303) Don't build nTox by default. +- [#301](https://github.com/TokTok/c-toxcore/pull/301) Renamed messenger functions, prepend `m_`. +- [#299](https://github.com/TokTok/c-toxcore/pull/299) net_crypto give handle_data_packet_helper a better name +- [#294](https://github.com/TokTok/c-toxcore/pull/294) Don't error on warnings by default + +### Closed issues: + +- [#317](https://github.com/TokTok/c-toxcore/issues/317) toxcore fails to build with autotools and debugging level enabled +- [#311](https://github.com/TokTok/c-toxcore/issues/311) Incorrect padding +- [#308](https://github.com/TokTok/c-toxcore/issues/308) Review TES and port it to APIDSL +- [#293](https://github.com/TokTok/c-toxcore/issues/293) error building on ubuntu 14.04 +- [#292](https://github.com/TokTok/c-toxcore/issues/292) Don't build nTox by default with CMake +- [#290](https://github.com/TokTok/c-toxcore/issues/290) User Feed +- [#266](https://github.com/TokTok/c-toxcore/issues/266) Support all levels listed in TOX_DHT_NAT_LEVEL +- [#216](https://github.com/TokTok/c-toxcore/issues/216) When v0.1 release? + +## v0.0.5 + +### Merged PRs: + +- [#289](https://github.com/TokTok/c-toxcore/pull/289) Version Patch v0.0.4 => v0.0.5 +- [#287](https://github.com/TokTok/c-toxcore/pull/287) Add CMake knobs to suppress building tests +- [#286](https://github.com/TokTok/c-toxcore/pull/286) Support float32 and float64 in msgpack type printer. +- [#285](https://github.com/TokTok/c-toxcore/pull/285) Mark `Tox_Options` struct as deprecated. +- [#284](https://github.com/TokTok/c-toxcore/pull/284) Add NONE enumerator to bit mask. +- [#281](https://github.com/TokTok/c-toxcore/pull/281) Made save format platform-independent +- [#277](https://github.com/TokTok/c-toxcore/pull/277) Fix a memory leak in hstox interface +- [#276](https://github.com/TokTok/c-toxcore/pull/276) Fix NULL pointer dereference in log calls +- [#275](https://github.com/TokTok/c-toxcore/pull/275) Fix a memory leak in GroupAV +- [#274](https://github.com/TokTok/c-toxcore/pull/274) Options in `new_messenger()` must never be null. +- [#271](https://github.com/TokTok/c-toxcore/pull/271) Convert to and from network byte order in set/get nospam. +- [#262](https://github.com/TokTok/c-toxcore/pull/262) Add ability to disable UDP hole punching + +### Closed issues: + +- [#254](https://github.com/TokTok/c-toxcore/issues/254) Add option to disable UDP hole punching +- [#215](https://github.com/TokTok/c-toxcore/issues/215) The current tox save format is non-portable +- [#205](https://github.com/TokTok/c-toxcore/issues/205) nospam value is reversed in array returned by `tox_self_get_address()` + +## v0.0.4 + +### Merged PRs: + +- [#272](https://github.com/TokTok/c-toxcore/pull/272) v0.0.4 +- [#265](https://github.com/TokTok/c-toxcore/pull/265) Disable -Wunused-but-set-variable compiler warning flag. +- [#261](https://github.com/TokTok/c-toxcore/pull/261) Work around Travis issue that causes build failures. +- [#260](https://github.com/TokTok/c-toxcore/pull/260) Support arbitrary video resolutions in av_test +- [#257](https://github.com/TokTok/c-toxcore/pull/257) Add decode/encode PlainText test support. +- [#256](https://github.com/TokTok/c-toxcore/pull/256) Add spectest to the cmake test suite. +- [#255](https://github.com/TokTok/c-toxcore/pull/255) Disable some gcc-specific warnings. +- [#249](https://github.com/TokTok/c-toxcore/pull/249) Use apidsl for the crypto_core API. +- [#248](https://github.com/TokTok/c-toxcore/pull/248) Remove new_nonce function in favour of random_nonce. +- [#224](https://github.com/TokTok/c-toxcore/pull/224) Add DHT_create_packet, an abstraction for DHT RPC packets + +## v0.0.3 + +### Merged PRs: + +- [#251](https://github.com/TokTok/c-toxcore/pull/251) Rename log levels to remove the extra "LOG" prefix. +- [#250](https://github.com/TokTok/c-toxcore/pull/250) Release v0.0.3. +- [#245](https://github.com/TokTok/c-toxcore/pull/245) Change packet kind enum to use hex constants. +- [#243](https://github.com/TokTok/c-toxcore/pull/243) Enable address sanitizer on the cmake build. +- [#242](https://github.com/TokTok/c-toxcore/pull/242) Remove assoc +- [#241](https://github.com/TokTok/c-toxcore/pull/241) Move log callback to options. +- [#233](https://github.com/TokTok/c-toxcore/pull/233) Enable all possible C compiler warning flags. +- [#230](https://github.com/TokTok/c-toxcore/pull/230) Move packing and unpacking DHT request packets to DHT module. +- [#228](https://github.com/TokTok/c-toxcore/pull/228) Remove unimplemented "time delta" parameter. +- [#227](https://github.com/TokTok/c-toxcore/pull/227) Compile as C++ for windows builds. +- [#223](https://github.com/TokTok/c-toxcore/pull/223) TravisCI shorten IRC message +- [#220](https://github.com/TokTok/c-toxcore/pull/220) toxav renaming: group.{h,c} -> groupav.{h,c} +- [#218](https://github.com/TokTok/c-toxcore/pull/218) Rename some internal "group chat" thing to "conference". +- [#212](https://github.com/TokTok/c-toxcore/pull/212) Convert series of `NET_PACKET_*` defines into a typedef enum +- [#196](https://github.com/TokTok/c-toxcore/pull/196) Update readme, moved the roadmap to a higher position +- [#193](https://github.com/TokTok/c-toxcore/pull/193) Remove duplicate tests: split tests part 2. + +### Closed issues: + +- [#40](https://github.com/TokTok/c-toxcore/issues/40) Stateless callbacks in toxcore's public API + +## v0.0.2 + +### Merged PRs: + +- [#207](https://github.com/TokTok/c-toxcore/pull/207) docs: correct instructions for cloning & harden against repo name changes +- [#206](https://github.com/TokTok/c-toxcore/pull/206) Corrected libsodium tag +- [#204](https://github.com/TokTok/c-toxcore/pull/204) Error if format_test can't be executed. +- [#202](https://github.com/TokTok/c-toxcore/pull/202) Version Patch v0.0.2 +- [#190](https://github.com/TokTok/c-toxcore/pull/190) Install libraries with RPATH. +- [#189](https://github.com/TokTok/c-toxcore/pull/189) Use `socklen_t` instead of `unsigned int` in call to `accept`. +- [#188](https://github.com/TokTok/c-toxcore/pull/188) Add option to set test timeout +- [#187](https://github.com/TokTok/c-toxcore/pull/187) Add option to build tox-bootstrapd +- [#185](https://github.com/TokTok/c-toxcore/pull/185) Import the hstox SUT interface from hstox. +- [#183](https://github.com/TokTok/c-toxcore/pull/183) Set log level for DEBUG=ON to LOG_DEBUG. +- [#182](https://github.com/TokTok/c-toxcore/pull/182) Remove return after no-return situation. +- [#181](https://github.com/TokTok/c-toxcore/pull/181) Minor documentation fixes. +- [#180](https://github.com/TokTok/c-toxcore/pull/180) Add the 'Tox' context object to the logger. +- [#179](https://github.com/TokTok/c-toxcore/pull/179) Remove the `_test` suffix in `auto_test` calls. +- [#178](https://github.com/TokTok/c-toxcore/pull/178) Rebuild apidsl'd headers in cmake. +- [#177](https://github.com/TokTok/c-toxcore/pull/177) docs(INSTALL): update compiling instructions for Linux +- [#176](https://github.com/TokTok/c-toxcore/pull/176) Merge irungentoo/toxcore into TokTok/c-toxcore. +- [#173](https://github.com/TokTok/c-toxcore/pull/173) Duplicate tox_test to 4 other files. + +### Closed issues: + +- [#201](https://github.com/TokTok/c-toxcore/issues/201) Logging callback was broken + +## v0.0.1 + +### Merged PRs: + +- [#174](https://github.com/TokTok/c-toxcore/pull/174) Remove redundant callback objects. +- [#171](https://github.com/TokTok/c-toxcore/pull/171) Simple Version tick to v0.0.1 +- [#170](https://github.com/TokTok/c-toxcore/pull/170) C++ the second round. +- [#166](https://github.com/TokTok/c-toxcore/pull/166) Add version-sync script. +- [#164](https://github.com/TokTok/c-toxcore/pull/164) Replace `void*` with `RingBuffer*` to avoid conversions. +- [#163](https://github.com/TokTok/c-toxcore/pull/163) Move ring buffer out of toxcore/util into toxav. +- [#162](https://github.com/TokTok/c-toxcore/pull/162) Allow the OSX build to fail on travis. +- [#161](https://github.com/TokTok/c-toxcore/pull/161) Minor cleanups: unused vars, unreachable code, static globals. +- [#160](https://github.com/TokTok/c-toxcore/pull/160) Work around bug in opencv3 headers. +- [#157](https://github.com/TokTok/c-toxcore/pull/157) Make TCP_Connections module-private. +- [#156](https://github.com/TokTok/c-toxcore/pull/156) Make TCP_Server opaque. +- [#153](https://github.com/TokTok/c-toxcore/pull/153) Fix strict-ld grep expressions to include digits. +- [#151](https://github.com/TokTok/c-toxcore/pull/151) Revert #130 "Make ToxAV stateless" +- [#148](https://github.com/TokTok/c-toxcore/pull/148) Added UB comment r/t deleting a friend w/ active call +- [#146](https://github.com/TokTok/c-toxcore/pull/146) Make group callbacks stateless +- [#145](https://github.com/TokTok/c-toxcore/pull/145) Make internal chat list function take uint32_t* as well. +- [#144](https://github.com/TokTok/c-toxcore/pull/144) Only build toxav if opus and vpx are found. +- [#143](https://github.com/TokTok/c-toxcore/pull/143) Make toxcore code C++ compatible. +- [#142](https://github.com/TokTok/c-toxcore/pull/142) Fix for windows dynamic libraries. +- [#141](https://github.com/TokTok/c-toxcore/pull/141) const-correctness in windows code. +- [#140](https://github.com/TokTok/c-toxcore/pull/140) Use C99 %zu format conversion in printf for size_t. +- [#139](https://github.com/TokTok/c-toxcore/pull/139) Clean up Travis build a bit in preparation for osx/win. +- [#138](https://github.com/TokTok/c-toxcore/pull/138) Remove format-source from travis script. +- [#135](https://github.com/TokTok/c-toxcore/pull/135) Convert old groupchats to new API format +- [#134](https://github.com/TokTok/c-toxcore/pull/134) Add some astyle options to make it do more. +- [#133](https://github.com/TokTok/c-toxcore/pull/133) Ensure that all TODOs have an owner. +- [#132](https://github.com/TokTok/c-toxcore/pull/132) Remove `else` directly after `return`. +- [#130](https://github.com/TokTok/c-toxcore/pull/130) Make ToxAV stateless +- [#129](https://github.com/TokTok/c-toxcore/pull/129) Use TokTok's apidsl instead of the iphydf one. +- [#127](https://github.com/TokTok/c-toxcore/pull/127) Use "phase" script for travis build phases. +- [#126](https://github.com/TokTok/c-toxcore/pull/126) Add option to build static libraries. +- [#125](https://github.com/TokTok/c-toxcore/pull/125) Group #include directives in 3-4 groups. +- [#123](https://github.com/TokTok/c-toxcore/pull/123) Use correct logical operator for tox_test +- [#120](https://github.com/TokTok/c-toxcore/pull/120) make the majority of the callbacks stateless and add some status to a testcase +- [#118](https://github.com/TokTok/c-toxcore/pull/118) Use `const` for version numbers. +- [#117](https://github.com/TokTok/c-toxcore/pull/117) Add STRICT_ABI cmake flag to generate export lists. +- [#116](https://github.com/TokTok/c-toxcore/pull/116) Fix potential null pointer dereference. +- [#115](https://github.com/TokTok/c-toxcore/pull/115) Fix memory leak on error paths in tox_new. +- [#114](https://github.com/TokTok/c-toxcore/pull/114) Fix compilation for Windows. +- [#111](https://github.com/TokTok/c-toxcore/pull/111) Add debugging option to autotools configuration +- [#110](https://github.com/TokTok/c-toxcore/pull/110) Comment intentional switch fallthroughs +- [#109](https://github.com/TokTok/c-toxcore/pull/109) Separate ip_port packing from pack_nodes() and unpack_nodes() +- [#108](https://github.com/TokTok/c-toxcore/pull/108) Prevent `` inclusion by ``. +- [#107](https://github.com/TokTok/c-toxcore/pull/107) Print a message about missing astyle in format-source. +- [#104](https://github.com/TokTok/c-toxcore/pull/104) Merge with irungentoo/master +- [#103](https://github.com/TokTok/c-toxcore/pull/103) Allocate `sizeof(IP_ADAPTER_INFO)` bytes instead of `sizeof(T*)`. +- [#101](https://github.com/TokTok/c-toxcore/pull/101) Add TODO for @mannol. +- [#100](https://github.com/TokTok/c-toxcore/pull/100) Remove the packet mutation in toxav's bwcontroller. +- [#99](https://github.com/TokTok/c-toxcore/pull/99) Make packet data a ptr-to-const. +- [#97](https://github.com/TokTok/c-toxcore/pull/97) Improve static and const correctness. +- [#96](https://github.com/TokTok/c-toxcore/pull/96) Improve C standard compliance. +- [#94](https://github.com/TokTok/c-toxcore/pull/94) Rearrange fields to decrease size of structure +- [#84](https://github.com/TokTok/c-toxcore/pull/84) Remove useless casts. +- [#82](https://github.com/TokTok/c-toxcore/pull/82) Add missing #include to av_test.c. +- [#81](https://github.com/TokTok/c-toxcore/pull/81) Match parameter names in declarations with their definitions. +- [#80](https://github.com/TokTok/c-toxcore/pull/80) Sort #includes in all source files. +- [#79](https://github.com/TokTok/c-toxcore/pull/79) Remove redundant `return` statements. +- [#78](https://github.com/TokTok/c-toxcore/pull/78) Do not use `else` after `return`. +- [#77](https://github.com/TokTok/c-toxcore/pull/77) Add OSX and Windows build to travis config. +- [#76](https://github.com/TokTok/c-toxcore/pull/76) Remove unused and bit-rotten friends_test. +- [#75](https://github.com/TokTok/c-toxcore/pull/75) Enable build of av_test. +- [#74](https://github.com/TokTok/c-toxcore/pull/74) Add missing #includes to headers and rename tox_old to tox_group. +- [#73](https://github.com/TokTok/c-toxcore/pull/73) Add braces to all if statements. +- [#72](https://github.com/TokTok/c-toxcore/pull/72) Add getters/setters for options. +- [#70](https://github.com/TokTok/c-toxcore/pull/70) Expose constants as functions. +- [#68](https://github.com/TokTok/c-toxcore/pull/68) Add address sanitizer option to cmake file. +- [#66](https://github.com/TokTok/c-toxcore/pull/66) Fix plane size calculation in test +- [#65](https://github.com/TokTok/c-toxcore/pull/65) Avoid large stack allocations on thread stacks. +- [#64](https://github.com/TokTok/c-toxcore/pull/64) Comment out useless TODO'd if block. +- [#63](https://github.com/TokTok/c-toxcore/pull/63) Initialise the id in assoc_test. +- [#62](https://github.com/TokTok/c-toxcore/pull/62) Reduce the timeout on travis to something much more reasonable +- [#60](https://github.com/TokTok/c-toxcore/pull/60) Make friend requests stateless +- [#59](https://github.com/TokTok/c-toxcore/pull/59) Replace uint with unsigned int in assoc.c. +- [#58](https://github.com/TokTok/c-toxcore/pull/58) Make Message received receipts stateless +- [#57](https://github.com/TokTok/c-toxcore/pull/57) Make Friend User Status stateless +- [#55](https://github.com/TokTok/c-toxcore/pull/55) docs(INSTALL.md): update instructions for Gentoo +- [#54](https://github.com/TokTok/c-toxcore/pull/54) Make typing change callback stateless +- [#53](https://github.com/TokTok/c-toxcore/pull/53) Add format-source script. +- [#52](https://github.com/TokTok/c-toxcore/pull/52) Build assoc DHT code on travis. +- [#51](https://github.com/TokTok/c-toxcore/pull/51) Fix operation sequencing in TCP_test. +- [#49](https://github.com/TokTok/c-toxcore/pull/49) Apidsl test +- [#48](https://github.com/TokTok/c-toxcore/pull/48) Make friend message callback stateless +- [#46](https://github.com/TokTok/c-toxcore/pull/46) Move logging to a callback. +- [#45](https://github.com/TokTok/c-toxcore/pull/45) Stateless friend status message +- [#43](https://github.com/TokTok/c-toxcore/pull/43) Allow NULL as argument to tox_kill. +- [#41](https://github.com/TokTok/c-toxcore/pull/41) Fix warnings +- [#39](https://github.com/TokTok/c-toxcore/pull/39) Merge irungentoo/toxcore into TokTok/c-toxcore. +- [#38](https://github.com/TokTok/c-toxcore/pull/38) Try searching for libsodium with pkg-config in ./configure. +- [#37](https://github.com/TokTok/c-toxcore/pull/37) Add missing DHT_bootstrap to CMakeLists.txt. +- [#36](https://github.com/TokTok/c-toxcore/pull/36) Make tox_callback_friend_name stateless. +- [#33](https://github.com/TokTok/c-toxcore/pull/33) Update readme with tentative roadmap, removed old todo.md +- [#32](https://github.com/TokTok/c-toxcore/pull/32) Fix a bug I introduced that would make toxcore fail to initialise a second time +- [#31](https://github.com/TokTok/c-toxcore/pull/31) 7. Travis envs +- [#30](https://github.com/TokTok/c-toxcore/pull/30) 2. Hstox test +- [#29](https://github.com/TokTok/c-toxcore/pull/29) 1. Move toxcore travis build scripts out of .travis.yml. +- [#27](https://github.com/TokTok/c-toxcore/pull/27) 8. Stateless +- [#26](https://github.com/TokTok/c-toxcore/pull/26) 6. Cmake bootstrapd +- [#25](https://github.com/TokTok/c-toxcore/pull/25) 5. Coverage clang +- [#24](https://github.com/TokTok/c-toxcore/pull/24) Silence/fix some compiler warnings. +- [#23](https://github.com/TokTok/c-toxcore/pull/23) 4. Cmake +- [#20](https://github.com/TokTok/c-toxcore/pull/20) 3. Travis astyle +- [#13](https://github.com/TokTok/c-toxcore/pull/13) Enable, and report test status +- [#12](https://github.com/TokTok/c-toxcore/pull/12) Fix readme for TokTok +- [#11](https://github.com/TokTok/c-toxcore/pull/11) Documentation: SysVInit workaround for <1024 ports +- [#2](https://github.com/TokTok/c-toxcore/pull/2) Enable toxcore logging when building on Travis. +- [#1](https://github.com/TokTok/c-toxcore/pull/1) Apidsl fixes and start tracking test coverage + +### Closed issues: + +- [#158](https://github.com/TokTok/c-toxcore/issues/158) Error while build with OpenCV 3.1 +- [#147](https://github.com/TokTok/c-toxcore/issues/147) Add comment to m_delfriend about the NULL passing to the internal conn status cb +- [#136](https://github.com/TokTok/c-toxcore/issues/136) Replace astyle by clang-format +- [#113](https://github.com/TokTok/c-toxcore/issues/113) Toxcore tests fail +- [#83](https://github.com/TokTok/c-toxcore/issues/83) Travis tests are hard to quickly parse from their output. +- [#22](https://github.com/TokTok/c-toxcore/issues/22) Make the current tests exercise both ipv4 and ipv6. +- [#9](https://github.com/TokTok/c-toxcore/issues/9) Fix the failing test +- [#8](https://github.com/TokTok/c-toxcore/issues/8) Toxcore should make more liberal use of assertions +- [#4](https://github.com/TokTok/c-toxcore/issues/4) Integrate hstox tests with toxcore Travis build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6f7e6a5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,501 @@ +################################################################################ +# +# The main toxcore CMake build file. +# +# This file when processed with cmake produces: +# - A number of small libraries (.a/.so/...) containing independent components +# of toxcore. E.g. the DHT has its own library, and the system/network +# abstractions are in their own library as well. These libraries are not +# installed on `make install`. The toxav, and toxencryptsave libraries are +# also not installed. +# - A number of small programs, statically linked if possible. +# - One big library containing all of the toxcore, toxav, and toxencryptsave +# code. +# +################################################################################ + +cmake_minimum_required(VERSION 2.8.12) +cmake_policy(VERSION 2.8.12) +project(toxcore) + +list(APPEND CMAKE_MODULE_PATH ${toxcore_SOURCE_DIR}/cmake) + +option(FLAT_OUTPUT_STRUCTURE "Whether to produce output artifacts in ${CMAKE_BINARY_DIR}/{bin,lib}" OFF) +if(FLAT_OUTPUT_STRUCTURE) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +endif() + +set_source_files_properties( + toxcore/mono_time.c + toxcore/network.c + toxcore/tox.c + toxcore/util.c + PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE) + +################################################################################ +# +# :: Version management +# +################################################################################ + +# This version is for the entire project. All libraries (core, av, ...) move in +# versions in a synchronised way. +set(PROJECT_VERSION_MAJOR "0") +set(PROJECT_VERSION_MINOR "2") +set(PROJECT_VERSION_PATCH "18") +set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + +# set .so library version / following libtool scheme +# https://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info +file(STRINGS ${toxcore_SOURCE_DIR}/so.version SOVERSION_CURRENT REGEX "^CURRENT=[0-9]+$") +string(SUBSTRING "${SOVERSION_CURRENT}" 8 -1 SOVERSION_CURRENT) +file(STRINGS ${toxcore_SOURCE_DIR}/so.version SOVERSION_REVISION REGEX "^REVISION=[0-9]+$") +string(SUBSTRING "${SOVERSION_REVISION}" 9 -1 SOVERSION_REVISION) +file(STRINGS ${toxcore_SOURCE_DIR}/so.version SOVERSION_AGE REGEX "^AGE=[0-9]+$") +string(SUBSTRING "${SOVERSION_AGE}" 4 -1 SOVERSION_AGE) +# account for some libtool magic, see other/version-sync script for details +math(EXPR SOVERSION_MAJOR ${SOVERSION_CURRENT}-${SOVERSION_AGE}) +set(SOVERSION "${SOVERSION_MAJOR}.${SOVERSION_AGE}.${SOVERSION_REVISION}") +message("SOVERSION: ${SOVERSION}") + +################################################################################ +# +# :: Dependencies and configuration +# +################################################################################ + +include(CTest) +include(ModulePackage) +include(StrictAbi) +include(GNUInstallDirs) + +if(APPLE) + include(MacRpath) +endif() + +enable_testing() + +set(CMAKE_MACOSX_RPATH ON) + +if(${CMAKE_VERSION} VERSION_LESS "3.1.0") + if(NOT MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") + endif() +else() + # Set standard version for compiler. + set(CMAKE_C_STANDARD 99) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_C_EXTENSIONS OFF) + set(CMAKE_CXX_EXTENSIONS OFF) + + message(STATUS "Supported C compiler features = ${CMAKE_C_COMPILE_FEATURES}") + message(STATUS "Supported C++ compiler features = ${CMAKE_CXX_COMPILE_FEATURES}") +endif() + +set(MIN_LOGGER_LEVEL "" CACHE STRING "Logging level to use (TRACE, DEBUG, INFO, WARNING, ERROR)") +if(MIN_LOGGER_LEVEL) + if(("${MIN_LOGGER_LEVEL}" STREQUAL "TRACE") OR + ("${MIN_LOGGER_LEVEL}" STREQUAL "DEBUG") OR + ("${MIN_LOGGER_LEVEL}" STREQUAL "INFO") OR + ("${MIN_LOGGER_LEVEL}" STREQUAL "WARNING") OR + ("${MIN_LOGGER_LEVEL}" STREQUAL "ERROR")) + add_definitions(-DMIN_LOGGER_LEVEL=LOGGER_LEVEL_${MIN_LOGGER_LEVEL}) + else() + message(FATAL_ERROR "Unknown value provided for MIN_LOGGER_LEVEL: \"${MIN_LOGGER_LEVEL}\", must be one of TRACE, DEBUG, INFO, WARNING or ERROR") + endif() +endif() + +option(USE_IPV6 "Use IPv6 in tests" ON) +if(NOT USE_IPV6) + add_definitions(-DUSE_IPV6=0) +endif() + +option(USE_TEST_NETWORK "Use a separate test network with different packet IDs" OFF) +if(USE_TEST_NETWORK) + add_definitions(-DUSE_TEST_NETWORK=1) +endif() + +option(BUILD_MISC_TESTS "Build additional tests and utilities" OFF) +option(BUILD_FUN_UTILS "Build additional just for fun utilities" OFF) + +option(AUTOTEST "Enable autotests (mainly for CI)" OFF) +if (AUTOTEST) + option(NON_HERMETIC_TESTS "Whether to build and run tests that depend on an internet connection" OFF) + option(PROXY_TEST "Enable proxy test (needs HTTP/SOCKS5 proxy on port 8080/8081)" OFF) +endif() + +option(BUILD_TOXAV "Whether to build the tox AV library" ON) +option(MUST_BUILD_TOXAV "Fail the build if toxav cannot be built" OFF) + +option(DHT_BOOTSTRAP "Enable building of DHT_bootstrap" ON) +option(BOOTSTRAP_DAEMON "Enable building of tox-bootstrapd" ON) +if(BOOTSTRAP_DAEMON AND WIN32) + message(WARNING "Building tox-bootstrapd for Windows is not supported, disabling") + set(BOOTSTRAP_DAEMON OFF) +endif() + +# Enabling this breaks all other tests and no network connections will be possible +option(BUILD_FUZZ_TESTS "Build fuzzing harnesses" OFF) +if(BUILD_FUZZ_TESTS) + message(STATUS "Building in fuzz testing mode, no network connection will be possible") + # Disable everything we can + set(AUTOTEST OFF) + set(BUILD_MISC_TESTS OFF) + set(BUILD_FUN_UTILS OFF) + set(ENABLE_SHARED OFF) + set(MUST_BUILD_TOXAV OFF) + set(BUILD_TOXAV OFF) + set(BOOTSTRAP_DAEMON OFF) + set(DHT_BOOTSTRAP OFF) +endif() + + +if(MSVC) + option(MSVC_STATIC_SODIUM "Whether to link libsodium statically for MSVC" OFF) + if(MSVC_STATIC_SODIUM) + add_definitions(-DSODIUM_STATIC=1 -DSODIUM_EXPORT) + endif() +endif() + +include(Dependencies) + +if(MUST_BUILD_TOXAV) + set(NO_TOXAV_ERROR_TYPE SEND_ERROR) +else() + set(NO_TOXAV_ERROR_TYPE WARNING) +endif() + +if(BUILD_TOXAV) + if(NOT OPUS_FOUND) + message(${NO_TOXAV_ERROR_TYPE} "Option BUILD_TOXAV is enabled but required library OPUS was not found.") + set(BUILD_TOXAV OFF) + endif() + if(NOT VPX_FOUND) + message(${NO_TOXAV_ERROR_TYPE} "Option BUILD_TOXAV is enabled but required library VPX was not found.") + set(BUILD_TOXAV OFF) + endif() +endif() + +# Disable float/double packing in CMP (C MessagePack library). +# We don't transfer floats over the network, so we disable this functionality. +add_definitions(-DCMP_NO_FLOAT=1) + +################################################################################ +# +# :: Tox Core Library +# +################################################################################ + +# toxcore_PKGCONFIG_LIBS is what's added to the Libs: line in toxcore.pc. It +# needs to contain all the libraries a program using toxcore should link against +# if it's statically linked. If it's dynamically linked, there is no need to +# explicitly link against all the dependencies, but it doesn't harm much(*) +# either. +# +# (*) It allows client code to use symbols from our dependencies without +# explicitly linking against them. +set(toxcore_PKGCONFIG_LIBS) +# Requires: in pkg-config file. +set(toxcore_PKGCONFIG_REQUIRES) + +set(toxcore_SOURCES + third_party/cmp/cmp.c + third_party/cmp/cmp.h + toxcore/announce.c + toxcore/announce.h + toxcore/bin_pack.c + toxcore/bin_pack.h + toxcore/bin_unpack.c + toxcore/bin_unpack.h + toxcore/ccompat.c + toxcore/ccompat.h + toxcore/crypto_core.c + toxcore/crypto_core.h + toxcore/DHT.c + toxcore/DHT.h + toxcore/events/conference_connected.c + toxcore/events/conference_invite.c + toxcore/events/conference_message.c + toxcore/events/conference_peer_list_changed.c + toxcore/events/conference_peer_name.c + toxcore/events/conference_title.c + toxcore/events/events_alloc.c + toxcore/events/events_alloc.h + toxcore/events/file_chunk_request.c + toxcore/events/file_recv.c + toxcore/events/file_recv_chunk.c + toxcore/events/file_recv_control.c + toxcore/events/friend_connection_status.c + toxcore/events/friend_lossless_packet.c + toxcore/events/friend_lossy_packet.c + toxcore/events/friend_message.c + toxcore/events/friend_name.c + toxcore/events/friend_read_receipt.c + toxcore/events/friend_request.c + toxcore/events/friend_status.c + toxcore/events/friend_status_message.c + toxcore/events/friend_typing.c + toxcore/events/self_connection_status.c + toxcore/events/group_custom_packet.c + toxcore/events/group_custom_private_packet.c + toxcore/events/group_invite.c + toxcore/events/group_join_fail.c + toxcore/events/group_message.c + toxcore/events/group_moderation.c + toxcore/events/group_password.c + toxcore/events/group_peer_exit.c + toxcore/events/group_peer_join.c + toxcore/events/group_peer_limit.c + toxcore/events/group_peer_name.c + toxcore/events/group_peer_status.c + toxcore/events/group_privacy_state.c + toxcore/events/group_private_message.c + toxcore/events/group_self_join.c + toxcore/events/group_topic.c + toxcore/events/group_topic_lock.c + toxcore/events/group_voice_state.c + toxcore/forwarding.c + toxcore/forwarding.h + toxcore/friend_connection.c + toxcore/friend_connection.h + toxcore/friend_requests.c + toxcore/friend_requests.h + toxcore/group.c + toxcore/group_chats.c + toxcore/group_chats.h + toxcore/group_common.h + toxcore/group_connection.c + toxcore/group_connection.h + toxcore/group.h + toxcore/group_announce.c + toxcore/group_announce.h + toxcore/group_moderation.c + toxcore/group_moderation.h + toxcore/group_onion_announce.c + toxcore/group_onion_announce.h + toxcore/group_pack.c + toxcore/group_pack.h + toxcore/LAN_discovery.c + toxcore/LAN_discovery.h + toxcore/list.c + toxcore/list.h + toxcore/logger.c + toxcore/logger.h + toxcore/Messenger.c + toxcore/Messenger.h + toxcore/mono_time.c + toxcore/mono_time.h + toxcore/net_crypto.c + toxcore/net_crypto.h + toxcore/network.c + toxcore/network.h + toxcore/onion_announce.c + toxcore/onion_announce.h + toxcore/onion.c + toxcore/onion_client.c + toxcore/onion_client.h + toxcore/onion.h + toxcore/ping_array.c + toxcore/ping_array.h + toxcore/ping.c + toxcore/ping.h + toxcore/shared_key_cache.c + toxcore/shared_key_cache.h + toxcore/state.c + toxcore/state.h + toxcore/TCP_client.c + toxcore/TCP_client.h + toxcore/TCP_common.c + toxcore/TCP_common.h + toxcore/TCP_connection.c + toxcore/TCP_connection.h + toxcore/TCP_server.c + toxcore/TCP_server.h + toxcore/timed_auth.c + toxcore/timed_auth.h + toxcore/tox_api.c + toxcore/tox.c + toxcore/tox_dispatch.c + toxcore/tox_dispatch.h + toxcore/tox_events.c + toxcore/tox_events.h + toxcore/tox.h + toxcore/tox_private.c + toxcore/tox_private.h + toxcore/tox_unpack.c + toxcore/tox_unpack.h + toxcore/util.c + toxcore/util.h) +set(toxcore_LINK_MODULES ${toxcore_LINK_MODULES} ${LIBSODIUM_LIBRARIES}) +set(toxcore_PKGCONFIG_REQUIRES ${toxcore_PKGCONFIG_REQUIRES} libsodium) +set(toxcore_API_HEADERS + ${toxcore_SOURCE_DIR}/toxcore/tox.h^tox + ${toxcore_SOURCE_DIR}/toxcore/tox_events.h^tox + ${toxcore_SOURCE_DIR}/toxcore/tox_dispatch.h^tox) + +################################################################################ +# +# :: Audio/Video Library +# +################################################################################ + +if(BUILD_TOXAV) + set(toxcore_SOURCES ${toxcore_SOURCES} + toxav/audio.c + toxav/audio.h + toxav/bwcontroller.c + toxav/bwcontroller.h + toxav/groupav.c + toxav/groupav.h + toxav/msi.c + toxav/msi.h + toxav/ring_buffer.c + toxav/ring_buffer.h + toxav/rtp.c + toxav/rtp.h + toxav/toxav.c + toxav/toxav.h + toxav/toxav_old.c + toxav/video.c + toxav/video.h) + set(toxcore_API_HEADERS ${toxcore_API_HEADERS} + ${toxcore_SOURCE_DIR}/toxav/toxav.h^toxav) + + set(toxcore_LINK_MODULES ${toxcore_LINK_MODULES} ${OPUS_LIBRARIES} ${VPX_LIBRARIES}) + set(toxcore_PKGCONFIG_REQUIRES ${toxcore_PKGCONFIG_REQUIRES} opus vpx) +endif() + +################################################################################ +# +# :: Block encryption libraries +# +################################################################################ + +set(toxcore_SOURCES ${toxcore_SOURCES} + toxencryptsave/toxencryptsave.c + toxencryptsave/toxencryptsave.h) +set(toxcore_API_HEADERS ${toxcore_API_HEADERS} + ${toxcore_SOURCE_DIR}/toxencryptsave/toxencryptsave.h^tox) + +################################################################################ +# +# :: System dependencies +# +################################################################################ + +# These need to come after other dependencies, since e.g. libvpx may depend on +# pthread, but doesn't list it in VPX_LIBRARIES. We're adding it here, after +# any potential libvpx linking. +message("CMAKE_THREAD_LIBS_INIT: ${CMAKE_THREAD_LIBS_INIT}") +if(CMAKE_THREAD_LIBS_INIT) + set(toxcore_LINK_MODULES ${toxcore_LINK_MODULES} ${CMAKE_THREAD_LIBS_INIT}) + set(toxcore_PKGCONFIG_LIBS ${toxcore_PKGCONFIG_LIBS} ${CMAKE_THREAD_LIBS_INIT}) +endif() + + +if(NSL_LIBRARIES) + set(toxcore_LINK_MODULES ${toxcore_LINK_MODULES} ${NSL_LIBRARIES}) + set(toxcore_PKGCONFIG_LIBS ${toxcore_PKGCONFIG_LIBS} -lnsl) +endif() + +if(RT_LIBRARIES) + set(toxcore_LINK_MODULES ${toxcore_LINK_MODULES} ${RT_LIBRARIES}) + set(toxcore_PKGCONFIG_LIBS ${toxcore_PKGCONFIG_LIBS} -lrt) +endif() + +if(SOCKET_LIBRARIES) + set(toxcore_LINK_MODULES ${toxcore_LINK_MODULES} ${SOCKET_LIBRARIES}) + set(toxcore_PKGCONFIG_LIBS ${toxcore_PKGCONFIG_LIBS} -lsocket) +endif() + +if(WIN32) + set(toxcore_LINK_MODULES ${toxcore_LINK_MODULES} ws2_32 iphlpapi) + set(toxcore_PKGCONFIG_LIBS ${toxcore_PKGCONFIG_LIBS} -lws2_32 -liphlpapi) +endif() + +################################################################################ +# +# :: All layers together in one library for ease of use +# +################################################################################ + +# Create combined library from all the sources. +add_module(toxcore ${toxcore_SOURCES}) + +# Link it to all dependencies. +target_link_modules(toxcore ${toxcore_LINK_MODULES}) + +# Make version script (on systems that support it) to limit symbol visibility. +make_version_script(toxcore ${toxcore_API_HEADERS}) + +# Generate pkg-config file, install library to "${CMAKE_INSTALL_LIBDIR}" and install headers to +# "${CMAKE_INSTALL_INCLUDEDIR}/tox". +install_module(toxcore DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tox) + +################################################################################ +# +# :: Unit tests: no networking, just pure function calls. +# +################################################################################ + +include(CompileGTest) + +# The actual unit tests follow. +# +unit_test(toxav ring_buffer) +unit_test(toxav rtp) +unit_test(toxcore DHT) +unit_test(toxcore bin_pack) +unit_test(toxcore crypto_core) +unit_test(toxcore group_announce) +unit_test(toxcore group_moderation) +unit_test(toxcore mono_time) +unit_test(toxcore ping_array) +unit_test(toxcore tox) +unit_test(toxcore util) + +add_subdirectory(testing) + +################################################################################ +# +# :: Automated regression tests: create a tox network and run integration tests +# +################################################################################ + +if(AUTOTEST) + add_subdirectory(auto_tests) +endif() + +################################################################################ +# +# :: Bootstrap daemon +# +################################################################################ + +if(DHT_BOOTSTRAP) + add_executable(DHT_bootstrap + other/DHT_bootstrap.c + other/bootstrap_node_packets.c) + target_link_modules(DHT_bootstrap toxcore misc_tools) + install(TARGETS DHT_bootstrap RUNTIME DESTINATION bin) +endif() + +if(BOOTSTRAP_DAEMON) + if(LIBCONFIG_FOUND) + add_subdirectory(other/bootstrap_daemon) + else() + message(WARNING "Option BOOTSTRAP_DAEMON is enabled but required library LIBCONFIG was not found.") + set(BOOTSTRAP_DAEMON OFF) + endif() +endif() + +if(BUILD_FUN_UTILS) + add_subdirectory(other/fun) +endif() + +if (BUILD_FUZZ_TESTS) + add_subdirectory(testing/fuzzing) +endif() diff --git a/DONATORS b/DONATORS new file mode 100644 index 0000000..dcce32c --- /dev/null +++ b/DONATORS @@ -0,0 +1,7 @@ +Minnesota > Florida +vdo +Spitfire is best technicolor horse. +if bad people don't hate you, you're doing something wrong +Pinkie Pie is best pony. +JRS was here +qTox is so bloated it doesn't even fit in a 64-bit address space diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..f54a161 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,297 @@ +# Installation instructions + +These instructions will guide you through the process of building and installing the toxcore library and its components, as well as getting already pre-built binaries. + +## Table of contents + +- [Overview](#overview) + - [Components](#components) + - [Main](#main) + - [Secondary](#secondary) +- [Building](#building) + - [Requirements](#requirements) + - [Library dependencies](#library-dependencies) + - [Compiler requirements](#compiler-requirements) + - [Build system requirements](#build-system-requirements) + - [CMake options](#cmake-options) + - [Build process](#build-process) + - [Unix-like](#unix-like) + - [Windows](#windows) + - [Building on Windows host](#building-on-windows-host) + - [Microsoft Visual Studio's Developer Command Prompt](#microsoft-visual-studios-developer-command-prompt) + - [MSYS/Cygwin](#msyscygwin) + - [Cross-compiling from Linux](#cross-compiling-from-linux) +- [Pre-built binaries](#pre-built-binaries) + - [Linux](#linux) + +## Overview + +### Components + +#### Main + +This repository, although called `toxcore`, in fact contains several libraries besides `toxcore` which complement it, as well as several executables. However, note that although these are separate libraries, at the moment, when building the libraries, they are all merged into a single `toxcore` library. Here is the full list of the main components that can be built using the CMake, their dependencies and descriptions. + +| Name | Type | Dependencies | Platform | Description | +|------------------|------------|-----------------------------------------------|----------------|----------------------------------------------------------------------------| +| `toxcore` | Library | libnacl or libsodium, libm, libpthread, librt | Cross-platform | The main Tox library that provides the messenger functionality. | +| `toxav` | Library | libtoxcore, libopus, libvpx | Cross-platform | Provides audio/video functionality. | +| `toxencryptsave` | Library | libtoxcore, libnacl or libsodium | Cross-platform | Provides encryption of Tox profiles (savedata), as well as arbitrary data. | +| `DHT_bootstrap` | Executable | libtoxcore | Cross-platform | A simple DHT bootstrap node. | +| `tox-bootstrapd` | Executable | libtoxcore, libconfig | Unix-like | Highly configurable DHT bootstrap node daemon (systemd, SysVinit, Docker). | +| `cmp` | Library | | Cross-platform | C implementation of the MessagePack serialization format. [https://github.com/camgunz/cmp](https://github.com/camgunz/cmp) | + +#### Secondary + +There are some programs that are not built by default which you might find interesting. You need to pass `-DBUILD_FUN_UTILS=ON` to cmake to build them. + +##### Vanity key generators + +Can be used to generate vanity Tox Ids or DHT bootstrap node public keys. + +| Name | Type | Dependencies | Platform | Description | +|---------------------------|------------|-----------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `cracker` | Executable | libsodium, OpenMP | Cross-platform | Tries to find a curve25519 key pair, hex representation of the public key of which starts with a specified byte sequence. Multi-threaded. | +| `cracker_simple` | Executable | libsodium | Cross-platform | Tries to find a curve25519 key pair, hex representation of the public key of which starts with a specified byte sequence. Single-threaded. | +| `strkey` | Executable | libsodium | Cross-platform | Tries to find a curve25519 key pair, hex representation of the public key of which contains a specified byte sequence at a specified or any position at all. Single-threaded. | + +##### Key file generators + +Useful for generating Tox profiles from the output of the vanity key generators, as well as generating random Tox profiles. + +| Name | Type | Dependencies | Platform | Description | +|---------------------------|------------|-----------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `make-funny-savefile` | Script | python | Cross-platform | Generates a Tox profile file (savedata file) with the provided key pair. | +| `create_bootstrap_keys` | Executable | libsodium | Cross-platform | Generates a keys file for tox-bootstrapd with either the provided or a random key pair. | +| `create_minimal_savedata` | Executable | libsodium | Cross-platform | Generates a minimal Tox profile file (savedata file) with either the provided or a random key pair, printing the generated Tox Id and secret & public key information. | +| `create_savedata` | Executable | libsodium, libtoxcore | Cross-platform | Generates a Tox profile file (savedata file) with either the provided or a random key pair using libtoxcore, printing the generated Tox Id and secret & public key information. | +| `save-generator` | Executable | libtoxcore | Cross-platform | Generates a Tox profile file (savedata file) with a random key pair using libtoxcore, setting the specified user name, going online and adding specified Tox Ids as friends. | + +##### Other + +| Name | Type | Dependencies | Platform | Description | +|---------------------------|------------|-----------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `bootstrap_node_info` | Script | python3 | Cross-platform | Prints version and Message Of The Day (MOTD) information of the specified DHT bootstrap node, given the node doesn't have those disabled. | +| `sign` | Executable | libsodium | Cross-platform | Signs a file with a ed25519 key. | + +## Building + +### Requirements + +#### Library dependencies + +Library dependencies are listed in the [components](#components) table. The dependencies need to be satisfied for the components to be built. Note that if you don't have a dependency for some component, e.g. you don't have `libopus` installed required for building `toxav` component, building of that component is silently disabled. + + +Be advised that due to the addition of `cmp` as a submodule, you now also need to initialize the git submodules required by toxcore. This can be done by cloning the repo with the addition of `--recurse-submodules` or by running `git submodule update --init` in the root directory of the repo. + +#### Compiler requirements + +The supported compilers are GCC, Clang and MinGW. + +In theory, any compiler that fully supports C99 and accepts GCC flags should work. + +There is a partial and experimental support of Microsoft Visual C++ compiler. We welcome any patches that help improve it. + +You should have a C99 compatible compiler in order to build the main components. The secondary components might require the compiler to support GNU extensions. + +#### Build system requirements + +To build the main components you need to have CMake of at least 2.8.6 version installed. You also need to have pkg-config installed, the build system uses it to find dependency libraries. + +There is some experimental accommodation for building natively on Windows, i.e. without having to use MSYS/Cygwin and pkg-config, but it uses exact hardcoded paths for finding libraries and supports building only of some of toxcore components, so your mileage might vary. + +### CMake options + +There are some options that are available to configure the build. + +| Name | Description | Expected Value | Default Value | +|------------------------|-----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------| +| `AUTOTEST` | Enable autotests (mainly for CI). | ON or OFF | OFF | +| `BOOTSTRAP_DAEMON` | Enable building of tox-bootstrapd, the DHT bootstrap node daemon. For Unix-like systems only. | ON or OFF | ON | +| `BUILD_FUZZ_TESTS` | Build fuzzing harnesses. | ON or OFF | OFF | +| `BUILD_MISC_TESTS` | Build additional tests. | ON or OFF | OFF | +| `BUILD_FUN_UTILS` | Build additional funny utilities. | ON or OFF | OFF | +| `BUILD_TOXAV` | Whether to build the toxav library. | ON or OFF | ON | +| `CMAKE_INSTALL_PREFIX` | Path to where everything should be installed. | Directory path. | Platform-dependent. Refer to CMake documentation. | +| `CMAKE_BUILD_TYPE` | Specifies the build type on single-configuration generators (e.g. make or ninja). | Debug, Release, RelWithDebInfo, MinSizeRel | Empty string. | +| `DHT_BOOTSTRAP` | Enable building of `DHT_bootstrap` | ON or OFF | ON | +| `ENABLE_SHARED` | Build shared (dynamic) libraries for all modules. | ON or OFF | ON | +| `ENABLE_STATIC` | Build static libraries for all modules. | ON or OFF | ON | +| `EXECUTION_TRACE` | Print a function trace during execution (for debugging). | ON or OFF | OFF | +| `FULLY_STATIC` | Build fully static executables. | ON or OFF | OFF | +| `MIN_LOGGER_LEVEL` | Logging level to use. | TRACE, DEBUG, INFO, WARNING, ERROR or nothing (empty string) for default. | Empty string. | +| `MSVC_STATIC_SODIUM` | Whether to link libsodium statically for MSVC. | ON or OFF | OFF | +| `MUST_BUILD_TOXAV` | Fail the build if toxav cannot be built. | ON or OFF | OFF | +| `NON_HERMETIC_TESTS` | Whether to build and run tests that depend on an internet connection. | ON or OFF | OFF | +| `STRICT_ABI` | Enforce strict ABI export in dynamic libraries. | ON or OFF | OFF | +| `TEST_TIMEOUT_SECONDS` | Limit runtime of each test to the number of seconds specified. | Positive number or nothing (empty string). | Empty string. | +| `USE_IPV6` | Use IPv6 in tests. | ON or OFF | ON | + +You can get this list of option using the following commands + +```sh +grep "option(" CMakeLists.txt cmake/* +grep "set(.* CACHE" CMakeLists.txt cmake/* +``` + +Note that some options might be considered only if other options are enabled. + +Example of calling cmake with options + +```sh +cmake \ + -D ENABLE_STATIC=OFF \ + -D ENABLE_SHARED=ON \ + -D CMAKE_INSTALL_PREFIX="${PWD}/prefix" \ + -D CMAKE_BUILD_TYPE=Release \ + -D TEST_TIMEOUT_SECONDS=120 \ + .. +``` + +### Building tests +In addition to the integration tests ("autotests") and miscellaneous tests +enabled by cmake variables described above, there are unit tests which will be +built if the source distribution of gtest (the Google Unit Test framework) is +found by cmake in `c-toxcore/third_party`. This can be achieved by running +'git clone https://github.com/google/googletest` from that directory. + +### Build process + +#### Unix-like + +Assuming all the [requirements](#requirements) are met, just run + +```sh +mkdir _build +cd _build +cmake .. +make +make install +``` + +#### Windows + +##### Building on Windows host + +###### Microsoft Visual Studio's Developer Command Prompt + +In addition to meeting the [requirements](#requirements), you need a version of Visual Studio (the [community edition](https://www.visualstudio.com/vs/visual-studio-express/) is enough) and a CMake version that's compatible with the Visual Studio version you're using. + +You must also ensure that the msvc versions of dependencies you're using are placed in the correct folders. + +For libsodium that is `c-toxcore/third_party/libsodium`, and for pthreads-w32, it's `c-toxcore/third_party/pthreads-win32` + +Once all of this is done, from the **Developer Command Prompt for VS**, simply run + +``` +mkdir _build +cd _build +cmake .. +msbuild ALL_BUILD.vcxproj +``` + +###### MSYS/Cygwin + +Download Cygwin ([32-bit](https://cygwin.com/setup-x86.exe)/[64-bit](https://cygwin.com/setup-x86_64.exe)) + +Search and select exactly these packages in Devel category: + + - mingw64-i686-gcc-core (32-bit) / mingw64-x86_64-gcc-core (64-bit) + - mingw64-i686-gcc-g++ (32-bit) / mingw64-x86_64-gcc-g++ (64-bit) + - make + - cmake + - libtool + - autoconf + - automake + - tree + - curl + - perl + - yasm + - pkg-config + +To handle Windows EOL correctly run the following in the Cygwin Terminal: + +```sh +echo ' +export SHELLOPTS +set -o igncr +' > ~/.bash_profile +``` + +Download toxcore source code and extract it to a folder. + +Open Cygwin Terminal in the toxcore folder and run `./other/windows_build_script_toxcore.sh` to start the build process. + +Toxcore build result files will appear in `/root/prefix/` relatively to Cygwin folder (default `C:\cygwin64`). + +Dependency versions can be customized in `./other/windows_build_script_toxcore.sh` and described in the section below. + + +##### Cross-compiling from Linux + +These cross-compilation instructions were tested on and written for 64-bit Ubuntu 16.04. You could generalize them for any Linux system, the only requirements are that you have Docker version of >= 1.9.0 and you are running 64-bit system. + +The cross-compilation is fully automated by a parameterized [Dockerfile](/other/docker/windows/Dockerfile). + +Install Docker + +```sh +apt-get update +apt-get install docker.io +``` + +Get the toxcore source code and navigate to `other/docker/windows`. + +Build the container image based on the Dockerfile. The following options are available to customize the building of the container image. + +| Name | Description | Expected Value | Default Value | +|-----------------------|----------------------------------------------------------------|-------------------------------------|---------------| +| `SUPPORT_ARCH_i686` | Support building 32-bit toxcore. | "true" or "false" (case sensitive). | true | +| `SUPPORT_ARCH_x86_64` | Support building 64-bit toxcore. | "true" or "false" (case sensitive). | true | +| `SUPPORT_TEST` | Support running toxcore automated tests. | "true" or "false" (case sensitive). | false | +| `CROSS_COMPILE` | Cross-compiling. True for Docker, false for Cygwin. | "true" or "false" (case sensitive). | true | +| `VERSION_OPUS` | Version of libopus to build toxcore with. | Numeric version number. | 1.3.1 | +| `VERSION_SODIUM` | Version of libsodium to build toxcore with. | Numeric version number. | 1.0.18 | +| `VERSION_VPX` | Version of libvpx to build toxcore with. | Numeric version number. | 1.11.0 | + +Example of building a container image with options + +```sh +cd other/docker/windows +docker build \ + --build-arg SUPPORT_TEST=true \ + -t toxcore \ + . +``` + +Run the container to build toxcore. The following options are available to customize the running of the container image. + +| Name | Description | Expected Value | Default Value | +|----------------------|--------------------------------------------------------------------------------------------|-------------------------------------|--------------------------------------------------------------------| +| `ALLOW_TEST_FAILURE` | Don't stop if a test suite fails. | "true" or "false" (case sensitive). | `false` | +| `ENABLE_ARCH_i686` | Build 32-bit toxcore. The image should have been built with `SUPPORT_ARCH_i686` enabled. | "true" or "false" (case sensitive). | `true` | +| `ENABLE_ARCH_x86_64` | Build 64-bit toxcore. The image should have been built with `SUPPORT_ARCH_x86_64` enabled. | "true" or "false" (case sensitive). | `true` | +| `ENABLE_TEST` | Run the test suite. The image should have been built with `SUPPORT_TEST` enabled. | "true" or "false" (case sensitive). | `false` | +| `EXTRA_CMAKE_FLAGS` | Extra arguments to pass to the CMake command when building toxcore. | CMake options. | `-DTEST_TIMEOUT_SECONDS=90` | +| `CROSS_COMPILE` | Cross-compiling. True for Docker, false for Cygwin. | "true" or "false" (case sensitive). | `true` | + +Example of running the container with options + +```sh +docker run \ + -e ENABLE_TEST=true \ + -e ALLOW_TEST_FAILURE=true \ + -v /path/to/toxcore/sourcecode:/toxcore \ + -v /path/to/where/output/build/result:/prefix \ + --rm \ + toxcore +``` + +After the build succeeds, you should see the built toxcore libraries in `/path/to/where/output/build/result`. + +## Pre-built binaries + +### Linux + +Toxcore is packaged by at least by the following distributions: ALT Linux, [Arch Linux](https://www.archlinux.org/packages/?q=toxcore), [Fedora](https://apps.fedoraproject.org/packages/toxcore), Mageia, openSUSE, PCLinuxOS, ROSA and Slackware, [according to the information from pkgs.org](https://pkgs.org/download/toxcore). Note that this list might be incomplete and some other distributions might package it too. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..10926e8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..97bad77 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,33 @@ +SUBDIRS = build + +ACLOCAL_AMFLAGS = -I m4 + +pkgconfigdir = $(libdir)/pkgconfig + + +pkgconfig_DATA = $(top_builddir)/libtoxcore.pc +BUILT_SOURCES = $(top_builddir)/libtoxcore.pc +CLEANFILES = $(top_builddir)/libtoxcore.pc + + +EXTRA_DIST = \ + README.md \ + libtoxcore.pc.in \ + tox.spec \ + so.version \ + $(top_srcdir)/docs/updates/Crypto.md \ + $(top_srcdir)/docs/updates/Spam-Prevention.md \ + $(top_srcdir)/docs/updates/Symmetric-NAT-Transversal.md \ + $(top_srcdir)/other/astyle/README.md \ + $(top_srcdir)/other/astyle/astylerc \ + $(top_srcdir)/other/astyle/pre-commit + +if BUILD_AV + +pkgconfig_DATA += $(top_builddir)/libtoxav.pc +BUILT_SOURCES += $(top_builddir)/libtoxav.pc +CLEANFILES += $(top_builddir)/libtoxav.pc + +EXTRA_DIST += libtoxav.pc.in + +endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..9082406 --- /dev/null +++ b/README.md @@ -0,0 +1,175 @@ +# ![Project Tox](https://raw.github.com/TokTok/c-toxcore/master/other/tox.png "Project Tox") + +**Current Coverage:** [![coverage](https://codecov.io/gh/TokTok/c-toxcore/branch/master/graph/badge.svg?token=BRfCKo02De)](https://codecov.io/gh/TokTok/c-toxcore) + +[**Website**](https://tox.chat) **|** [**Wiki**](https://wiki.tox.chat/) **|** [**Blog**](https://blog.tox.chat/) **|** [**FAQ**](https://wiki.tox.chat/doku.php?id=users:faq) **|** [**Binaries/Downloads**](https://tox.chat/download.html) **|** [**Clients**](https://wiki.tox.chat/doku.php?id=clients) **|** [**Compiling**](/INSTALL.md) + +**IRC Channels:** Users: [#tox@libera.chat](https://web.libera.chat/#tox), Developers: [#toktok@libera.chat](https://web.libera.chat/#toktok) + +## What is Tox + +Tox is a peer to peer (serverless) instant messenger aimed at making security +and privacy easy to obtain for regular users. It uses +[NaCl](https://nacl.cr.yp.to/) for its encryption and authentication. + +## IMPORTANT! + +### ![Danger: Experimental](other/tox-warning.png) + +This is an **experimental** cryptographic network library. It has not been +formally audited by an independent third party that specializes in +cryptography or cryptanalysis. **Use this library at your own risk.** + +The underlying crypto library [NaCl](https://nacl.cr.yp.to/install.html) +provides reliable encryption, but the security model has not yet been fully +specified. See [issue 210](https://github.com/TokTok/c-toxcore/issues/210) for +a discussion on developing a threat model. See other issues for known +weaknesses (e.g. [issue 426](https://github.com/TokTok/c-toxcore/issues/426) +describes what can happen if your secret key is stolen). + +## Toxcore Development Roadmap + +The roadmap and changelog are generated from GitHub issues. You may view them +on the website, where they are updated at least once every 24 hours: + +- Changelog: https://toktok.ltd/changelog/c-toxcore +- Roadmap: https://toktok.ltd/roadmap/c-toxcore + +## Installing toxcore + +Detailed installation instructions can be found in [INSTALL.md](INSTALL.md). + +Be advised that due to the addition of `cmp` as a submodule, you now also need to initialize the git submodules required by toxcore. This can be done by cloning the repo with the following command: `git clone --recurse-submodules https://github.com/Toktok/c-toxcore` or by running `git submodule update --init` in the root directory of the repo. + +In a nutshell, if you have [libsodium](https://github.com/jedisct1/libsodium) +installed, run: + +```sh +mkdir _build && cd _build +cmake .. +make +sudo make install +``` + +If you have [libvpx](https://github.com/webmproject/libvpx) and +[opus](https://github.com/xiph/opus) installed, the above will also build the +A/V library for multimedia chats. + +## Using toxcore + +The simplest "hello world" example could be an echo bot. Here we will walk +through the implementation of a simple bot. + +### Creating the tox instance + +All toxcore API functions work with error parameters. They are enums with one +`OK` value and several error codes that describe the different situations in +which the function might fail. + +```c +TOX_ERR_NEW err_new; +Tox *tox = tox_new(NULL, &err_new); +if (err_new != TOX_ERR_NEW_OK) { + fprintf(stderr, "tox_new failed with error code %d\n", err_new); + exit(1); +} +``` + +Here, we simply exit the program, but in a real client you will probably want +to do some error handling and proper error reporting to the user. The `NULL` +argument given to the first parameter of `tox_new` is the `Tox_Options`. It +contains various write-once network settings and allows you to load a +previously serialised instance. See [toxcore/tox.h](tox.h) for details. + +### Setting up callbacks + +Toxcore works with callbacks that you can register to listen for certain +events. Examples of such events are "friend request received" or "friend sent +a message". Search the API for `tox_callback_*` to find all of them. + +Here, we will set up callbacks for receiving friend requests and receiving +messages. We will always accept any friend request (because we're a bot), and +when we receive a message, we send it back to the sender. + +```c +tox_callback_friend_request(tox, handle_friend_request); +tox_callback_friend_message(tox, handle_friend_message); +``` + +These two function calls set up the callbacks. Now we also need to implement +these "handle" functions. + +### Handle friend requests + +```c +static void handle_friend_request( + Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, + void *user_data) { + // Accept the friend request: + TOX_ERR_FRIEND_ADD err_friend_add; + tox_friend_add_norequest(tox, public_key, &err_friend_add); + if (err_friend_add != TOX_ERR_FRIEND_ADD_OK) { + fprintf(stderr, "unable to add friend: %d\n", err_friend_add); + } +} +``` + +The `tox_friend_add_norequest` function adds the friend without sending them a +friend request. Since we already got a friend request, this is the right thing +to do. If you wanted to send a friend request yourself, you would use +`tox_friend_add`, which has an extra parameter for the message. + +### Handle messages + +Now, when the friend sends us a message, we want to respond to them by sending +them the same message back. This will be our "echo". + +```c +static void handle_friend_message( + Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, + const uint8_t *message, size_t length, + void *user_data) { + TOX_ERR_FRIEND_SEND_MESSAGE err_send; + tox_friend_send_message(tox, friend_number, type, message, length, + &err_send); + if (err_send != TOX_ERR_FRIEND_SEND_MESSAGE_OK) { + fprintf(stderr, "unable to send message back to friend %d: %d\n", + friend_number, err_send); + } +} +``` + +That's it for the setup. Now we want to actually run the bot. + +### Main event loop + +Toxcore works with a main event loop function `tox_iterate` that you need to +call at a certain frequency dictated by `tox_iteration_interval`. This is a +polling function that receives new network messages and processes them. + +```c +while (true) { + usleep(1000 * tox_iteration_interval(tox)); + tox_iterate(tox, NULL); +} +``` + +That's it! Now you have a working echo bot. The only problem is that since Tox +works with public keys, and you can't really guess your bot's public key, you +can't add it as a friend in your client. For this, we need to call another API +function: `tox_self_get_address(tox, address)`. This will fill the 38 byte +friend address into the `address` buffer. You can then display that binary +string as hex and input it into your client. Writing a `bin2hex` function is +left as exercise for the reader. + +We glossed over a lot of details, such as the user data which we passed to +`tox_iterate` (passing `NULL`), bootstrapping into an actual network (this bot +will work in the LAN, but not on an internet server) and the fact that we now +have no clean way of stopping the bot (`while (true)`). If you want to write a +real bot, you will probably want to read up on all the API functions. Consult +the API documentation in [toxcore/tox.h](toxcore/tox.h) for more information. + +### Other resources + +- [Another echo bot](https://wiki.tox.chat/developers/client_examples/echo_bot) +- [minitox](https://github.com/hqwrong/minitox) (A minimal tox client) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..6929217 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,32 @@ +--- +image: Visual Studio 2019 + +cache: + - '%USERPROFILE%\.conan -> conanfile.py' + +environment: + matrix: + - job_name: static + - job_name: shared + +install: + - set PATH=C:\Python311-x64\Scripts;%PATH% + - py -3 -m pip install conan==1.59.0 + - git submodule update --init --recursive + +for: + - matrix: + only: + - job_name: static + before_build: + - conan install -if _build -o with_tests=False . + - matrix: + only: + - job_name: shared + before_build: + - conan install -if _build -o with_tests=False -o shared=True . + +build_script: + - set CONAN_CPU_COUNT=50 + - set CTEST_OUTPUT_ON_FAILURE=1 + - conan build -bf _build -if _build . diff --git a/auto_tests/BUILD.bazel b/auto_tests/BUILD.bazel new file mode 100644 index 0000000..babd3ac --- /dev/null +++ b/auto_tests/BUILD.bazel @@ -0,0 +1,72 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +package(features = ["layering_check"]) + +cc_library( + name = "check_compat", + testonly = True, + hdrs = ["check_compat.h"], +) + +cc_library( + name = "auto_test_support", + testonly = True, + srcs = ["auto_test_support.c"], + hdrs = ["auto_test_support.h"], + deps = [ + ":check_compat", + "//c-toxcore/testing:misc_tools", + "//c-toxcore/toxcore:Messenger", + "//c-toxcore/toxcore:mono_time", + "//c-toxcore/toxcore:tox", + ], +) + +flaky_tests = { + "crypto_core_test": True, + "lan_discovery_test": True, + "save_load_test": True, + "tox_many_tcp_test": True, +} + +[cc_test( + name = src[:-2], + size = "small", + srcs = [src], + args = ["$(location %s)" % src] + ["$(location //c-toxcore/other/proxy)"], + data = glob(["data/*"]) + ["//c-toxcore/other/proxy"], + flaky = flaky_tests.get( + src[:-2], + False, + ), + deps = [ + ":auto_test_support", + ":check_compat", + "//c-toxcore/testing:misc_tools", + "//c-toxcore/toxav", + "//c-toxcore/toxcore:Messenger", + "//c-toxcore/toxcore:TCP_client", + "//c-toxcore/toxcore:TCP_common", + "//c-toxcore/toxcore:TCP_connection", + "//c-toxcore/toxcore:TCP_server", + "//c-toxcore/toxcore:announce", + "//c-toxcore/toxcore:ccompat", + "//c-toxcore/toxcore:crypto_core", + "//c-toxcore/toxcore:forwarding", + "//c-toxcore/toxcore:friend_connection", + "//c-toxcore/toxcore:logger", + "//c-toxcore/toxcore:mono_time", + "//c-toxcore/toxcore:net_crypto", + "//c-toxcore/toxcore:network", + "//c-toxcore/toxcore:onion", + "//c-toxcore/toxcore:onion_announce", + "//c-toxcore/toxcore:onion_client", + "//c-toxcore/toxcore:tox", + "//c-toxcore/toxcore:tox_dispatch", + "//c-toxcore/toxcore:tox_events", + "//c-toxcore/toxcore:util", + "//c-toxcore/toxencryptsave", + "@libsodium", + "@libvpx", + ], +) for src in glob(["*_test.c"])] diff --git a/auto_tests/CMakeLists.txt b/auto_tests/CMakeLists.txt new file mode 100644 index 0000000..774e3e3 --- /dev/null +++ b/auto_tests/CMakeLists.txt @@ -0,0 +1,86 @@ +set(TEST_TIMEOUT_SECONDS "" CACHE STRING "Limit runtime of each test to the number of seconds specified") + +add_library(auto_test_support + auto_test_support.c + auto_test_support.h) +target_link_modules(auto_test_support toxcore misc_tools) + +function(auto_test target) + if(AUTOTEST AND NOT (MSVC AND ARGV1 STREQUAL "MSVC_DONT_BUILD")) + add_executable(auto_${target}_test ${target}_test.c) + target_link_modules(auto_${target}_test toxcore misc_tools auto_test_support) + if(NOT ARGV1 STREQUAL "DONT_RUN") + add_test(NAME ${target} COMMAND ${CROSSCOMPILING_EMULATOR} auto_${target}_test) + set_tests_properties(${target} PROPERTIES TIMEOUT "${TEST_TIMEOUT_SECONDS}") + # add the source dir as environment variable, so the testdata can be found + set_tests_properties(${target} PROPERTIES ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw;srcdir=${CMAKE_CURRENT_SOURCE_DIR}") + endif() + endif() +endfunction() + +auto_test(TCP) +auto_test(announce) +auto_test(conference) +auto_test(conference_double_invite) +auto_test(conference_invite_merge) +auto_test(conference_peer_nick) +auto_test(conference_simple) +auto_test(conference_two) +auto_test(crypto) +#auto_test(dht) # Doesn't work with UNITY_BUILD. +auto_test(dht_getnodes_api) +auto_test(encryptsave) +auto_test(file_transfer) +auto_test(file_saving) +auto_test(forwarding) +auto_test(friend_connection) +auto_test(friend_request) +auto_test(friend_request_spam) +auto_test(group_general) +auto_test(group_invite) +auto_test(group_message) +auto_test(group_moderation) +auto_test(group_save) +auto_test(group_state) +auto_test(group_sync) +auto_test(group_tcp) +auto_test(group_topic) +auto_test(invalid_tcp_proxy) +auto_test(invalid_udp_proxy) +auto_test(lan_discovery) +auto_test(lossless_packet) +auto_test(lossy_packet) +auto_test(network) +auto_test(onion) +auto_test(overflow_recvq) +auto_test(overflow_sendq) +auto_test(reconnect) +auto_test(save_friend) +auto_test(save_load) +auto_test(send_message) +auto_test(set_name) +auto_test(set_status_message) +auto_test(tox_dispatch) +auto_test(tox_events) +auto_test(tox_many) +auto_test(tox_many_tcp) +auto_test(tox_strncasecmp) +auto_test(typing) +auto_test(version) +auto_test(save_compatibility) + +if(NON_HERMETIC_TESTS) + auto_test(bootstrap) + auto_test(tcp_relay) +endif() + +if(BUILD_TOXAV) + auto_test(conference_av MSVC_DONT_BUILD) + auto_test(toxav_basic) + auto_test(toxav_many) +endif() + + +if(PROXY_TEST) + auto_test(proxy) +endif() diff --git a/auto_tests/Makefile.inc b/auto_tests/Makefile.inc new file mode 100644 index 0000000..6b73c2c --- /dev/null +++ b/auto_tests/Makefile.inc @@ -0,0 +1,248 @@ +if BUILD_TESTS + +noinst_LTLIBRARIES += libauto_test_support.la +libauto_test_support_la_SOURCES = ../auto_tests/auto_test_support.c ../auto_tests/auto_test_support.h +libauto_test_support_la_LIBADD = libmisc_tools.la libtoxcore.la + +TESTS = \ + announce_test \ + conference_double_invite_test \ + conference_invite_merge_test \ + conference_peer_nick_test \ + conference_simple_test \ + conference_test \ + conference_two_test \ + crypto_test \ + file_transfer_test \ + forwarding_test \ + friend_connection_test \ + friend_request_test \ + group_state_test \ + invalid_tcp_proxy_test \ + invalid_udp_proxy_test \ + lan_discovery_test \ + lossless_packet_test \ + lossy_packet_test \ + network_test \ + onion_test \ + overflow_recvq_test \ + overflow_sendq_test \ + reconnect_test \ + save_compatibility_test \ + save_friend_test \ + send_message_test \ + set_name_test \ + set_status_message_test \ + TCP_test \ + tox_events_test \ + tox_dispatch_test \ + tox_many_tcp_test \ + tox_many_test \ + tox_strncasecmp_test \ + typing_test \ + version_test + +if !WITH_NACL +TESTS += \ + encryptsave_test \ + file_saving_test +endif + +AUTOTEST_CFLAGS = \ + $(LIBSODIUM_CFLAGS) \ + $(NACL_CFLAGS) + +AUTOTEST_LDADD = \ + $(LIBSODIUM_LDFLAGS) \ + $(NACL_LDFLAGS) \ + libmisc_tools.la \ + libauto_test_support.la \ + libtoxcore.la \ + libtoxencryptsave.la \ + $(LIBSODIUM_LIBS) \ + $(NACL_OBJECTS) \ + $(NACL_LIBS) + + +if BUILD_AV +TESTS += conference_av_test toxav_basic_test toxav_many_test +AUTOTEST_LDADD += libtoxav.la +endif + +check_PROGRAMS = $(TESTS) + +announce_test_SOURCES = ../auto_tests/announce_test.c +announce_test_CFLAGS = $(AUTOTEST_CFLAGS) +announce_test_LDADD = $(AUTOTEST_LDADD) + +conference_double_invite_test_SOURCES = ../auto_tests/conference_double_invite_test.c +conference_double_invite_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_double_invite_test_LDADD = $(AUTOTEST_LDADD) + +conference_invite_merge_test_SOURCES = ../auto_tests/conference_invite_merge_test.c +conference_invite_merge_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_invite_merge_test_LDADD = $(AUTOTEST_LDADD) + +conference_peer_nick_test_SOURCES = ../auto_tests/conference_peer_nick_test.c +conference_peer_nick_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_peer_nick_test_LDADD = $(AUTOTEST_LDADD) + +conference_simple_test_SOURCES = ../auto_tests/conference_simple_test.c +conference_simple_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_simple_test_LDADD = $(AUTOTEST_LDADD) + +conference_test_SOURCES = ../auto_tests/conference_test.c +conference_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_test_LDADD = $(AUTOTEST_LDADD) + +conference_two_test_SOURCES = ../auto_tests/conference_two_test.c +conference_two_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_two_test_LDADD = $(AUTOTEST_LDADD) + +crypto_test_SOURCES = ../auto_tests/crypto_test.c +crypto_test_CFLAGS = $(AUTOTEST_CFLAGS) +crypto_test_LDADD = $(AUTOTEST_LDADD) + +encryptsave_test_SOURCES = ../auto_tests/encryptsave_test.c +encryptsave_test_CFLAGS = $(AUTOTEST_CFLAGS) +encryptsave_test_LDADD = $(AUTOTEST_LDADD) + +file_saving_test_SOURCES = ../auto_tests/file_saving_test.c +file_saving_test_CFLAGS = $(AUTOTEST_CFLAGS) +file_saving_test_LDADD = $(AUTOTEST_LDADD) + +file_transfer_test_SOURCES = ../auto_tests/file_transfer_test.c +file_transfer_test_CFLAGS = $(AUTOTEST_CFLAGS) +file_transfer_test_LDADD = $(AUTOTEST_LDADD) + +forwarding_test_SOURCES = ../auto_tests/forwarding_test.c +forwarding_test_CFLAGS = $(AUTOTEST_CFLAGS) +forwarding_test_LDADD = $(AUTOTEST_LDADD) + +friend_connection_test_SOURCES = ../auto_tests/friend_connection_test.c +friend_connection_test_CFLAGS = $(AUTOTEST_CFLAGS) +friend_connection_test_LDADD = $(AUTOTEST_LDADD) + +friend_request_test_SOURCES = ../auto_tests/friend_request_test.c +friend_request_test_CFLAGS = $(AUTOTEST_CFLAGS) +friend_request_test_LDADD = $(AUTOTEST_LDADD) + +group_state_test_SOURCES = ../auto_tests/group_state_test.c +group_state_test_CFLAGS = $(AUTOTEST_CFLAGS) +group_state_test_LDADD = $(AUTOTEST_LDADD) + +invalid_tcp_proxy_test_SOURCES = ../auto_tests/invalid_tcp_proxy_test.c +invalid_tcp_proxy_test_CFLAGS = $(AUTOTEST_CFLAGS) +invalid_tcp_proxy_test_LDADD = $(AUTOTEST_LDADD) + +invalid_udp_proxy_test_SOURCES = ../auto_tests/invalid_udp_proxy_test.c +invalid_udp_proxy_test_CFLAGS = $(AUTOTEST_CFLAGS) +invalid_udp_proxy_test_LDADD = $(AUTOTEST_LDADD) + +lan_discovery_test_SOURCES = ../auto_tests/lan_discovery_test.c +lan_discovery_test_CFLAGS = $(AUTOTEST_CFLAGS) +lan_discovery_test_LDADD = $(AUTOTEST_LDADD) + +lossless_packet_test_SOURCES = ../auto_tests/lossless_packet_test.c +lossless_packet_test_CFLAGS = $(AUTOTEST_CFLAGS) +lossless_packet_test_LDADD = $(AUTOTEST_LDADD) + +lossy_packet_test_SOURCES = ../auto_tests/lossy_packet_test.c +lossy_packet_test_CFLAGS = $(AUTOTEST_CFLAGS) +lossy_packet_test_LDADD = $(AUTOTEST_LDADD) + +network_test_SOURCES = ../auto_tests/network_test.c +network_test_CFLAGS = $(AUTOTEST_CFLAGS) +network_test_LDADD = $(AUTOTEST_LDADD) + +onion_test_SOURCES = ../auto_tests/onion_test.c +onion_test_CFLAGS = $(AUTOTEST_CFLAGS) +onion_test_LDADD = $(AUTOTEST_LDADD) + +overflow_recvq_test_SOURCES = ../auto_tests/overflow_recvq_test.c +overflow_recvq_test_CFLAGS = $(AUTOTEST_CFLAGS) +overflow_recvq_test_LDADD = $(AUTOTEST_LDADD) + +overflow_sendq_test_SOURCES = ../auto_tests/overflow_sendq_test.c +overflow_sendq_test_CFLAGS = $(AUTOTEST_CFLAGS) +overflow_sendq_test_LDADD = $(AUTOTEST_LDADD) + +reconnect_test_SOURCES = ../auto_tests/reconnect_test.c +reconnect_test_CFLAGS = $(AUTO_TEST_CFLAGS) +reconnect_test_LDADD = $(AUTOTEST_LDADD) + +save_compatibility_test_SOURCES = ../auto_tests/save_compatibility_test.c +save_compatibility_test_CFLAGS = $(AUTOTEST_CFLAGS) +save_compatibility_test_LDADD = $(AUTOTEST_LDADD) + +save_friend_test_SOURCES = ../auto_tests/save_friend_test.c +save_friend_test_CFLAGS = $(AUTOTEST_CFLAGS) +save_friend_test_LDADD = $(AUTOTEST_LDADD) + +send_message_test_SOURCES = ../auto_tests/send_message_test.c +send_message_test_CFLAGS = $(AUTOTEST_CFLAGS) +send_message_test_LDADD = $(AUTOTEST_LDADD) + +set_name_test_SOURCES = ../auto_tests/set_name_test.c +set_name_test_CFLAGS = $(AUTOTEST_CFLAGS) +set_name_test_LDADD = $(AUTOTEST_LDADD) + +set_status_message_test_SOURCES = ../auto_tests/set_status_message_test.c +set_status_message_test_CFLAGS = $(AUTOTEST_CFLAGS) +set_status_message_test_LDADD = $(AUTOTEST_LDADD) + +TCP_test_SOURCES = ../auto_tests/TCP_test.c +TCP_test_CFLAGS = $(AUTOTEST_CFLAGS) +TCP_test_LDADD = $(AUTOTEST_LDADD) + +tox_events_test_SOURCES = ../auto_tests/tox_events_test.c +tox_events_test_CFLAGS = $(AUTOTEST_CFLAGS) +tox_events_test_LDADD = $(AUTOTEST_LDADD) + +tox_dispatch_test_SOURCES = ../auto_tests/tox_dispatch_test.c +tox_dispatch_test_CFLAGS = $(AUTOTEST_CFLAGS) +tox_dispatch_test_LDADD = $(AUTOTEST_LDADD) + +tox_many_tcp_test_SOURCES = ../auto_tests/tox_many_tcp_test.c +tox_many_tcp_test_CFLAGS = $(AUTOTEST_CFLAGS) +tox_many_tcp_test_LDADD = $(AUTOTEST_LDADD) + +tox_many_test_SOURCES = ../auto_tests/tox_many_test.c +tox_many_test_CFLAGS = $(AUTOTEST_CFLAGS) +tox_many_test_LDADD = $(AUTOTEST_LDADD) + +tox_strncasecmp_test_SOURCES = ../auto_tests/tox_strncasecmp_test.c +tox_strncasecmp_test_CFLAGS = $(AUTOTEST_CFLAGS) +tox_strncasecmp_test_LDADD = $(AUTOTEST_LDADD) + +typing_test_SOURCES = ../auto_tests/typing_test.c +typing_test_CFLAGS = $(AUTOTEST_CFLAGS) +typing_test_LDADD = $(AUTOTEST_LDADD) + +version_test_SOURCES = ../auto_tests/version_test.c +version_test_CFLAGS = $(AUTOTEST_CFLAGS) +version_test_LDADD = $(AUTOTEST_LDADD) + +if BUILD_AV + +conference_av_test_SOURCES = ../auto_tests/conference_av_test.c +conference_av_test_CFLAGS = $(AUTOTEST_CFLAGS) +conference_av_test_LDADD = $(AUTOTEST_LDADD) + +toxav_basic_test_SOURCES = ../auto_tests/toxav_basic_test.c +toxav_basic_test_CFLAGS = $(AUTOTEST_CFLAGS) +toxav_basic_test_LDADD = $(AUTOTEST_LDADD) $(AV_LIBS) + +toxav_many_test_SOURCES = ../auto_tests/toxav_many_test.c +toxav_many_test_CFLAGS = $(AUTOTEST_CFLAGS) +toxav_many_test_LDADD = $(AUTOTEST_LDADD) + +endif + +endif + + +EXTRA_DIST += \ + $(top_srcdir)/auto_tests/data/save.tox \ + $(top_srcdir)/auto_tests/check_compat.h \ + $(top_srcdir)/auto_tests/auto_test_support.h diff --git a/auto_tests/TCP_test.c b/auto_tests/TCP_test.c new file mode 100644 index 0000000..33bbe0e --- /dev/null +++ b/auto_tests/TCP_test.c @@ -0,0 +1,902 @@ +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/TCP_client.h" +#include "../toxcore/TCP_common.h" +#include "../toxcore/TCP_server.h" +#include "../toxcore/crypto_core.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" + +#define NUM_PORTS 3 + +#ifndef USE_IPV6 +#define USE_IPV6 1 +#endif + +#if !USE_IPV6 +#define net_family_ipv6 net_family_ipv4 +#endif + +static IP get_loopback(void) +{ + IP ip; +#if USE_IPV6 + ip.family = net_family_ipv6(); + ip.ip.v6 = get_ip6_loopback(); +#else + ip.family = net_family_ipv4(); + ip.ip.v4 = get_ip4_loopback(); +#endif + return ip; +} + +static void do_TCP_server_delay(TCP_Server *tcp_s, Mono_Time *mono_time, int delay) +{ + c_sleep(delay); + mono_time_update(mono_time); + do_TCP_server(tcp_s, mono_time); + c_sleep(delay); +} +static uint16_t ports[NUM_PORTS] = {13215, 33445, 25643}; + +static void test_basic(void) +{ + Mono_Time *mono_time = mono_time_new(nullptr, nullptr); + const Random *rng = system_random(); + ck_assert(rng != nullptr); + Logger *logger = logger_new(); + logger_callback_log(logger, print_debug_logger, nullptr, nullptr); + + // Attempt to create a new TCP_Server instance. + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, self_public_key, self_secret_key); + const Network *ns = system_network(); + TCP_Server *tcp_s = new_TCP_server(logger, rng, ns, USE_IPV6, NUM_PORTS, ports, self_secret_key, nullptr, nullptr); + ck_assert_msg(tcp_s != nullptr, "Failed to create a TCP relay server."); + ck_assert_msg(tcp_server_listen_count(tcp_s) == NUM_PORTS, + "Failed to bind a TCP relay server to all %d attempted ports.", NUM_PORTS); + + Socket sock = {0}; + IP_Port localhost; + localhost.ip = get_loopback(); + localhost.port = 0; + + // Check all opened ports for connectivity. + for (uint8_t i = 0; i < NUM_PORTS; i++) { + sock = net_socket(ns, net_family_ipv6(), TOX_SOCK_STREAM, TOX_PROTO_TCP); + localhost.port = net_htons(ports[i]); + bool ret = net_connect(logger, sock, &localhost); + ck_assert_msg(ret, "Failed to connect to created TCP relay server on port %d (%d).", ports[i], errno); + + // Leave open one connection for the next test. + if (i + 1 < NUM_PORTS) { + kill_sock(ns, sock); + } + } + + // Key creation. + uint8_t f_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t f_secret_key[CRYPTO_SECRET_KEY_SIZE]; + uint8_t f_nonce[CRYPTO_NONCE_SIZE]; + crypto_new_keypair(rng, f_public_key, f_secret_key); + random_nonce(rng, f_nonce); + + // Generation of the initial handshake. + uint8_t t_secret_key[CRYPTO_SECRET_KEY_SIZE]; + uint8_t *handshake_plain = (uint8_t *)malloc(TCP_HANDSHAKE_PLAIN_SIZE); + ck_assert(handshake_plain != nullptr); + crypto_new_keypair(rng, handshake_plain, t_secret_key); + memcpy(handshake_plain + CRYPTO_PUBLIC_KEY_SIZE, f_nonce, CRYPTO_NONCE_SIZE); + uint8_t *handshake = (uint8_t *)malloc(TCP_CLIENT_HANDSHAKE_SIZE); + ck_assert(handshake != nullptr); + memcpy(handshake, f_public_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(rng, handshake + CRYPTO_PUBLIC_KEY_SIZE); + + // Encrypting handshake + int ret = encrypt_data(self_public_key, f_secret_key, handshake + CRYPTO_PUBLIC_KEY_SIZE, handshake_plain, + TCP_HANDSHAKE_PLAIN_SIZE, handshake + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + ck_assert_msg(ret == TCP_CLIENT_HANDSHAKE_SIZE - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE), + "encrypt_data() call failed."); + + free(handshake_plain); + + // Sending the handshake + ck_assert_msg(net_send(ns, logger, sock, handshake, TCP_CLIENT_HANDSHAKE_SIZE - 1, + &localhost) == TCP_CLIENT_HANDSHAKE_SIZE - 1, + "An attempt to send the initial handshake minus last byte failed."); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + ck_assert_msg(net_send(ns, logger, sock, handshake + (TCP_CLIENT_HANDSHAKE_SIZE - 1), 1, &localhost) == 1, + "The attempt to send the last byte of handshake failed."); + + free(handshake); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + // Receiving server response and decrypting it + uint8_t response[TCP_SERVER_HANDSHAKE_SIZE]; + uint8_t response_plain[TCP_HANDSHAKE_PLAIN_SIZE]; + ck_assert_msg(net_recv(ns, logger, sock, response, TCP_SERVER_HANDSHAKE_SIZE, &localhost) == TCP_SERVER_HANDSHAKE_SIZE, + "Could/did not receive a server response to the initial handshake."); + ret = decrypt_data(self_public_key, f_secret_key, response, response + CRYPTO_NONCE_SIZE, + TCP_SERVER_HANDSHAKE_SIZE - CRYPTO_NONCE_SIZE, response_plain); + ck_assert_msg(ret == TCP_HANDSHAKE_PLAIN_SIZE, "Failed to decrypt handshake response."); + uint8_t f_nonce_r[CRYPTO_NONCE_SIZE]; + uint8_t f_shared_key[CRYPTO_SHARED_KEY_SIZE]; + encrypt_precompute(response_plain, t_secret_key, f_shared_key); + memcpy(f_nonce_r, response_plain + CRYPTO_SHARED_KEY_SIZE, CRYPTO_NONCE_SIZE); + + // Building a request + uint8_t r_req_p[1 + CRYPTO_PUBLIC_KEY_SIZE]; + r_req_p[0] = TCP_PACKET_ROUTING_REQUEST; + memcpy(r_req_p + 1, f_public_key, CRYPTO_PUBLIC_KEY_SIZE); + uint8_t r_req[2 + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE]; + uint16_t size = 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE; + size = net_htons(size); + encrypt_data_symmetric(f_shared_key, f_nonce, r_req_p, 1 + CRYPTO_PUBLIC_KEY_SIZE, r_req + 2); + increment_nonce(f_nonce); + memcpy(r_req, &size, 2); + + // Sending the request at random intervals in random pieces. + for (uint32_t i = 0; i < sizeof(r_req);) { + uint8_t msg_length = rand() % 5 + 1; // msg_length = 1 to 5 + + if (i + msg_length >= sizeof(r_req)) { + msg_length = sizeof(r_req) - i; + } + + ck_assert_msg(net_send(ns, logger, sock, r_req + i, msg_length, &localhost) == msg_length, + "Failed to send request after completing the handshake."); + i += msg_length; + + c_sleep(50); + mono_time_update(mono_time); + do_TCP_server(tcp_s, mono_time); + } + + // Receiving the second response and verifying its validity + uint8_t packet_resp[4096]; + int recv_data_len = net_recv(ns, logger, sock, packet_resp, 2 + 2 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE, &localhost); + ck_assert_msg(recv_data_len == 2 + 2 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE, + "Failed to receive server response to request. %d", recv_data_len); + memcpy(&size, packet_resp, 2); + ck_assert_msg(net_ntohs(size) == 2 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE, + "Wrong packet size for request response."); + + uint8_t packet_resp_plain[4096]; + ret = decrypt_data_symmetric(f_shared_key, f_nonce_r, packet_resp + 2, recv_data_len - 2, packet_resp_plain); + ck_assert_msg(ret != -1, "Failed to decrypt the TCP server's response."); + increment_nonce(f_nonce_r); + + ck_assert_msg(packet_resp_plain[0] == TCP_PACKET_ROUTING_RESPONSE, "Server sent the wrong packet id: %u", + packet_resp_plain[0]); + ck_assert_msg(packet_resp_plain[1] == 0, "Server did not refuse the connection."); + ck_assert_msg(pk_equal(packet_resp_plain + 2, f_public_key), "Server sent the wrong public key."); + + // Closing connections. + kill_sock(ns, sock); + kill_TCP_server(tcp_s); + + logger_kill(logger); + mono_time_free(mono_time); +} + +struct sec_TCP_con { + Socket sock; + const Network *ns; + uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; + uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; +}; + +static struct sec_TCP_con *new_TCP_con(const Logger *logger, const Random *rng, const Network *ns, TCP_Server *tcp_s, Mono_Time *mono_time) +{ + struct sec_TCP_con *sec_c = (struct sec_TCP_con *)malloc(sizeof(struct sec_TCP_con)); + ck_assert(sec_c != nullptr); + sec_c->ns = ns; + Socket sock = net_socket(ns, net_family_ipv6(), TOX_SOCK_STREAM, TOX_PROTO_TCP); + + IP_Port localhost; + localhost.ip = get_loopback(); + localhost.port = net_htons(ports[random_u32(rng) % NUM_PORTS]); + + bool ok = net_connect(logger, sock, &localhost); + ck_assert_msg(ok, "Failed to connect to the test TCP relay server."); + + uint8_t f_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, sec_c->public_key, f_secret_key); + random_nonce(rng, sec_c->sent_nonce); + + uint8_t t_secret_key[CRYPTO_SECRET_KEY_SIZE]; + uint8_t handshake_plain[TCP_HANDSHAKE_PLAIN_SIZE]; + crypto_new_keypair(rng, handshake_plain, t_secret_key); + memcpy(handshake_plain + CRYPTO_PUBLIC_KEY_SIZE, sec_c->sent_nonce, CRYPTO_NONCE_SIZE); + uint8_t handshake[TCP_CLIENT_HANDSHAKE_SIZE]; + memcpy(handshake, sec_c->public_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(rng, handshake + CRYPTO_PUBLIC_KEY_SIZE); + + int ret = encrypt_data(tcp_server_public_key(tcp_s), f_secret_key, handshake + CRYPTO_PUBLIC_KEY_SIZE, handshake_plain, + TCP_HANDSHAKE_PLAIN_SIZE, handshake + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); + ck_assert_msg(ret == TCP_CLIENT_HANDSHAKE_SIZE - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE), + "Failed to encrypt the outgoing handshake."); + + ck_assert_msg(net_send(ns, logger, sock, handshake, TCP_CLIENT_HANDSHAKE_SIZE - 1, + &localhost) == TCP_CLIENT_HANDSHAKE_SIZE - 1, + "Failed to send the first portion of the handshake to the TCP relay server."); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + ck_assert_msg(net_send(ns, logger, sock, handshake + (TCP_CLIENT_HANDSHAKE_SIZE - 1), 1, &localhost) == 1, + "Failed to send last byte of handshake."); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + uint8_t response[TCP_SERVER_HANDSHAKE_SIZE]; + uint8_t response_plain[TCP_HANDSHAKE_PLAIN_SIZE]; + ck_assert_msg(net_recv(sec_c->ns, logger, sock, response, TCP_SERVER_HANDSHAKE_SIZE, &localhost) == TCP_SERVER_HANDSHAKE_SIZE, + "Failed to receive server handshake response."); + ret = decrypt_data(tcp_server_public_key(tcp_s), f_secret_key, response, response + CRYPTO_NONCE_SIZE, + TCP_SERVER_HANDSHAKE_SIZE - CRYPTO_NONCE_SIZE, response_plain); + ck_assert_msg(ret == TCP_HANDSHAKE_PLAIN_SIZE, "Failed to decrypt server handshake response."); + encrypt_precompute(response_plain, t_secret_key, sec_c->shared_key); + memcpy(sec_c->recv_nonce, response_plain + CRYPTO_SHARED_KEY_SIZE, CRYPTO_NONCE_SIZE); + sec_c->sock = sock; + return sec_c; +} + +static void kill_TCP_con(struct sec_TCP_con *con) +{ + kill_sock(con->ns, con->sock); + free(con); +} + +static int write_packet_TCP_test_connection(const Logger *logger, struct sec_TCP_con *con, const uint8_t *data, + uint16_t length) +{ + VLA(uint8_t, packet, sizeof(uint16_t) + length + CRYPTO_MAC_SIZE); + + uint16_t c_length = net_htons(length + CRYPTO_MAC_SIZE); + memcpy(packet, &c_length, sizeof(uint16_t)); + int len = encrypt_data_symmetric(con->shared_key, con->sent_nonce, data, length, packet + sizeof(uint16_t)); + + if ((unsigned int)len != (SIZEOF_VLA(packet) - sizeof(uint16_t))) { + return -1; + } + + increment_nonce(con->sent_nonce); + + IP_Port localhost; + localhost.ip = get_loopback(); + localhost.port = 0; + + ck_assert_msg(net_send(con->ns, logger, con->sock, packet, SIZEOF_VLA(packet), &localhost) == SIZEOF_VLA(packet), + "Failed to send a packet."); + return 0; +} + +static int read_packet_sec_TCP(const Logger *logger, struct sec_TCP_con *con, uint8_t *data, uint16_t length) +{ + IP_Port localhost; + localhost.ip = get_loopback(); + localhost.port = 0; + + int rlen = net_recv(con->ns, logger, con->sock, data, length, &localhost); + ck_assert_msg(rlen == length, "Did not receive packet of correct length. Wanted %i, instead got %i", length, rlen); + rlen = decrypt_data_symmetric(con->shared_key, con->recv_nonce, data + 2, length - 2, data); + ck_assert_msg(rlen != -1, "Failed to decrypt a received packet from the Relay server."); + increment_nonce(con->recv_nonce); + return rlen; +} + +static void test_some(void) +{ + Mono_Time *mono_time = mono_time_new(nullptr, nullptr); + const Random *rng = system_random(); + ck_assert(rng != nullptr); + Logger *logger = logger_new(); + const Network *ns = system_network(); + + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, self_public_key, self_secret_key); + TCP_Server *tcp_s = new_TCP_server(logger, rng, ns, USE_IPV6, NUM_PORTS, ports, self_secret_key, nullptr, nullptr); + ck_assert_msg(tcp_s != nullptr, "Failed to create TCP relay server"); + ck_assert_msg(tcp_server_listen_count(tcp_s) == NUM_PORTS, "Failed to bind to all ports."); + + struct sec_TCP_con *con1 = new_TCP_con(logger, rng, ns, tcp_s, mono_time); + struct sec_TCP_con *con2 = new_TCP_con(logger, rng, ns, tcp_s, mono_time); + struct sec_TCP_con *con3 = new_TCP_con(logger, rng, ns, tcp_s, mono_time); + + uint8_t requ_p[1 + CRYPTO_PUBLIC_KEY_SIZE]; + requ_p[0] = TCP_PACKET_ROUTING_REQUEST; + + // Sending wrong public keys to test server response. + memcpy(requ_p + 1, con3->public_key, CRYPTO_PUBLIC_KEY_SIZE); + write_packet_TCP_test_connection(logger, con1, requ_p, sizeof(requ_p)); + memcpy(requ_p + 1, con1->public_key, CRYPTO_PUBLIC_KEY_SIZE); + write_packet_TCP_test_connection(logger, con3, requ_p, sizeof(requ_p)); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + // Testing response from connection 1 + uint8_t data[2048]; + int len = read_packet_sec_TCP(logger, con1, data, 2 + 1 + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE); + ck_assert_msg(len == 1 + 1 + CRYPTO_PUBLIC_KEY_SIZE, "Wrong response packet length of %d.", len); + ck_assert_msg(data[0] == TCP_PACKET_ROUTING_RESPONSE, "Wrong response packet id of %d.", data[0]); + ck_assert_msg(data[1] == 16, "Server didn't refuse connection using wrong public key."); + ck_assert_msg(pk_equal(data + 2, con3->public_key), "Key in response packet wrong."); + + // Connection 3 + len = read_packet_sec_TCP(logger, con3, data, 2 + 1 + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE); + ck_assert_msg(len == 1 + 1 + CRYPTO_PUBLIC_KEY_SIZE, "Wrong response packet length of %d.", len); + ck_assert_msg(data[0] == TCP_PACKET_ROUTING_RESPONSE, "Wrong response packet id of %d.", data[0]); + ck_assert_msg(data[1] == 16, "Server didn't refuse connection using wrong public key."); + ck_assert_msg(pk_equal(data + 2, con1->public_key), "Key in response packet wrong."); + + uint8_t test_packet[512] = {16, 17, 16, 86, 99, 127, 255, 189, 78}; // What is this packet???? + + write_packet_TCP_test_connection(logger, con3, test_packet, sizeof(test_packet)); + write_packet_TCP_test_connection(logger, con3, test_packet, sizeof(test_packet)); + write_packet_TCP_test_connection(logger, con3, test_packet, sizeof(test_packet)); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + len = read_packet_sec_TCP(logger, con1, data, 2 + 2 + CRYPTO_MAC_SIZE); + ck_assert_msg(len == 2, "wrong len %d", len); + ck_assert_msg(data[0] == TCP_PACKET_CONNECTION_NOTIFICATION, "wrong packet id %u", data[0]); + ck_assert_msg(data[1] == 16, "wrong peer id %u", data[1]); + len = read_packet_sec_TCP(logger, con3, data, 2 + 2 + CRYPTO_MAC_SIZE); + ck_assert_msg(len == 2, "wrong len %d", len); + ck_assert_msg(data[0] == TCP_PACKET_CONNECTION_NOTIFICATION, "wrong packet id %u", data[0]); + ck_assert_msg(data[1] == 16, "wrong peer id %u", data[1]); + len = read_packet_sec_TCP(logger, con1, data, 2 + sizeof(test_packet) + CRYPTO_MAC_SIZE); + ck_assert_msg(len == sizeof(test_packet), "wrong len %d", len); + ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1], + data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]); + len = read_packet_sec_TCP(logger, con1, data, 2 + sizeof(test_packet) + CRYPTO_MAC_SIZE); + ck_assert_msg(len == sizeof(test_packet), "wrong len %d", len); + ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1], + data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]); + len = read_packet_sec_TCP(logger, con1, data, 2 + sizeof(test_packet) + CRYPTO_MAC_SIZE); + ck_assert_msg(len == sizeof(test_packet), "wrong len %d", len); + ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1], + data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]); + write_packet_TCP_test_connection(logger, con1, test_packet, sizeof(test_packet)); + write_packet_TCP_test_connection(logger, con1, test_packet, sizeof(test_packet)); + write_packet_TCP_test_connection(logger, con1, test_packet, sizeof(test_packet)); + do_TCP_server_delay(tcp_s, mono_time, 50); + len = read_packet_sec_TCP(logger, con3, data, 2 + sizeof(test_packet) + CRYPTO_MAC_SIZE); + ck_assert_msg(len == sizeof(test_packet), "wrong len %d", len); + ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1], + data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]); + len = read_packet_sec_TCP(logger, con3, data, 2 + sizeof(test_packet) + CRYPTO_MAC_SIZE); + ck_assert_msg(len == sizeof(test_packet), "wrong len %d", len); + ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1], + data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]); + len = read_packet_sec_TCP(logger, con3, data, 2 + sizeof(test_packet) + CRYPTO_MAC_SIZE); + ck_assert_msg(len == sizeof(test_packet), "wrong len %d", len); + ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1], + data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]); + + uint8_t ping_packet[1 + sizeof(uint64_t)] = {TCP_PACKET_PING, 8, 6, 9, 67}; + write_packet_TCP_test_connection(logger, con1, ping_packet, sizeof(ping_packet)); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + len = read_packet_sec_TCP(logger, con1, data, 2 + sizeof(ping_packet) + CRYPTO_MAC_SIZE); + ck_assert_msg(len == sizeof(ping_packet), "wrong len %d", len); + ck_assert_msg(data[0] == TCP_PACKET_PONG, "wrong packet id %u", data[0]); + ck_assert_msg(memcmp(ping_packet + 1, data + 1, sizeof(uint64_t)) == 0, "wrong packet data"); + + // Kill off the connections + kill_TCP_server(tcp_s); + kill_TCP_con(con1); + kill_TCP_con(con2); + kill_TCP_con(con3); + + logger_kill(logger); + mono_time_free(mono_time); +} + +static int response_callback_good; +static uint8_t response_callback_connection_id; +static uint8_t response_callback_public_key[CRYPTO_PUBLIC_KEY_SIZE]; +static int response_callback(void *object, uint8_t connection_id, const uint8_t *public_key) +{ + if (set_tcp_connection_number((TCP_Client_Connection *)(void *)((char *)object - 2), connection_id, 7) != 0) { + return 1; + } + + response_callback_connection_id = connection_id; + memcpy(response_callback_public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); + response_callback_good++; + return 0; +} +static int status_callback_good; +static uint8_t status_callback_connection_id; +static uint8_t status_callback_status; +static int status_callback(void *object, uint32_t number, uint8_t connection_id, uint8_t status) +{ + if (object != (void *)2) { + return 1; + } + + if (number != 7) { + return 1; + } + + status_callback_connection_id = connection_id; + status_callback_status = status; + status_callback_good++; + return 0; +} +static int data_callback_good; +static int data_callback(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, uint16_t length, + void *userdata) +{ + if (object != (void *)3) { + return 1; + } + + if (number != 7) { + return 1; + } + + if (length != 5) { + return 1; + } + + if (data[0] == 1 && data[1] == 2 && data[2] == 3 && data[3] == 4 && data[4] == 5) { + data_callback_good++; + return 0; + } + + return 1; +} + +static int oob_data_callback_good; +static uint8_t oob_pubkey[CRYPTO_PUBLIC_KEY_SIZE]; +static int oob_data_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length, + void *userdata) +{ + if (object != (void *)4) { + return 1; + } + + if (length != 5) { + return 1; + } + + if (!pk_equal(public_key, oob_pubkey)) { + return 1; + } + + if (data[0] == 1 && data[1] == 2 && data[2] == 3 && data[3] == 4 && data[4] == 5) { + oob_data_callback_good++; + return 0; + } + + return 1; +} + +static void test_client(void) +{ + Mono_Time *mono_time = mono_time_new(nullptr, nullptr); + const Random *rng = system_random(); + ck_assert(rng != nullptr); + Logger *logger = logger_new(); + + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, self_public_key, self_secret_key); + const Network *ns = system_network(); + TCP_Server *tcp_s = new_TCP_server(logger, rng, ns, USE_IPV6, NUM_PORTS, ports, self_secret_key, nullptr, nullptr); + ck_assert_msg(tcp_s != nullptr, "Failed to create a TCP relay server."); + ck_assert_msg(tcp_server_listen_count(tcp_s) == NUM_PORTS, "Failed to bind the relay server to all ports."); + + uint8_t f_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t f_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, f_public_key, f_secret_key); + IP_Port ip_port_tcp_s; + + ip_port_tcp_s.port = net_htons(ports[random_u32(rng) % NUM_PORTS]); + ip_port_tcp_s.ip = get_loopback(); + + TCP_Client_Connection *conn = new_TCP_connection(logger, mono_time, rng, ns, &ip_port_tcp_s, self_public_key, f_public_key, + f_secret_key, nullptr); + do_TCP_connection(logger, mono_time, conn, nullptr); + c_sleep(50); + + // The connection status should be unconfirmed here because we have finished + // sending our data and are awaiting a response. + ck_assert_msg(tcp_con_status(conn) == TCP_CLIENT_UNCONFIRMED, "Wrong connection status. Expected: %d, is: %d.", + TCP_CLIENT_UNCONFIRMED, tcp_con_status(conn)); + + do_TCP_server_delay(tcp_s, mono_time, 50); // Now let the server handle requests... + + const uint8_t LOOP_SIZE = 3; + + for (uint8_t i = 0; i < LOOP_SIZE; i++) { + mono_time_update(mono_time); + do_TCP_connection(logger, mono_time, conn, nullptr); // Run the connection loop. + + // The status of the connection should continue to be TCP_CLIENT_CONFIRMED after multiple subsequent do_TCP_connection() calls. + ck_assert_msg(tcp_con_status(conn) == TCP_CLIENT_CONFIRMED, "Wrong connection status. Expected: %d, is: %d", + TCP_CLIENT_CONFIRMED, tcp_con_status(conn)); + + c_sleep(i == LOOP_SIZE - 1 ? 0 : 500); // Sleep for 500ms on all except third loop. + } + + do_TCP_server_delay(tcp_s, mono_time, 50); + + // And still after the server runs again. + ck_assert_msg(tcp_con_status(conn) == TCP_CLIENT_CONFIRMED, "Wrong status. Expected: %d, is: %d", TCP_CLIENT_CONFIRMED, + tcp_con_status(conn)); + + uint8_t f2_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t f2_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, f2_public_key, f2_secret_key); + ip_port_tcp_s.port = net_htons(ports[random_u32(rng) % NUM_PORTS]); + TCP_Client_Connection *conn2 = new_TCP_connection(logger, mono_time, rng, ns, &ip_port_tcp_s, self_public_key, f2_public_key, + f2_secret_key, nullptr); + + // The client should call this function (defined earlier) during the routing process. + routing_response_handler(conn, response_callback, (char *)conn + 2); + // The client should call this function when it receives a connection notification. + routing_status_handler(conn, status_callback, (void *)2); + // The client should call this function when + routing_data_handler(conn, data_callback, (void *)3); + // The client should call this function when sending out of band packets. + oob_data_handler(conn, oob_data_callback, (void *)4); + + // These integers will increment per successful callback. + oob_data_callback_good = response_callback_good = status_callback_good = data_callback_good = 0; + + do_TCP_connection(logger, mono_time, conn, nullptr); + do_TCP_connection(logger, mono_time, conn2, nullptr); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_TCP_connection(logger, mono_time, conn, nullptr); + do_TCP_connection(logger, mono_time, conn2, nullptr); + c_sleep(50); + + uint8_t data[5] = {1, 2, 3, 4, 5}; + memcpy(oob_pubkey, f2_public_key, CRYPTO_PUBLIC_KEY_SIZE); + send_oob_packet(logger, conn2, f_public_key, data, 5); + send_routing_request(logger, conn, f2_public_key); + send_routing_request(logger, conn2, f_public_key); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_TCP_connection(logger, mono_time, conn, nullptr); + do_TCP_connection(logger, mono_time, conn2, nullptr); + + // All callback methods save data should have run during the above network prodding. + ck_assert_msg(oob_data_callback_good == 1, "OOB callback not called"); + ck_assert_msg(response_callback_good == 1, "Response callback not called."); + ck_assert_msg(pk_equal(response_callback_public_key, f2_public_key), "Wrong public key."); + ck_assert_msg(status_callback_good == 1, "Status callback not called."); + ck_assert_msg(status_callback_status == 2, "Wrong status callback status."); + ck_assert_msg(status_callback_connection_id == response_callback_connection_id, + "Status and response callback connection IDs are not equal."); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + ck_assert_msg(send_data(logger, conn2, 0, data, 5) == 1, "Failed a send_data() call."); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_TCP_connection(logger, mono_time, conn, nullptr); + do_TCP_connection(logger, mono_time, conn2, nullptr); + ck_assert_msg(data_callback_good == 1, "Data callback was not called."); + status_callback_good = 0; + send_disconnect_request(logger, conn2, 0); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_TCP_connection(logger, mono_time, conn, nullptr); + do_TCP_connection(logger, mono_time, conn2, nullptr); + ck_assert_msg(status_callback_good == 1, "Status callback not called"); + ck_assert_msg(status_callback_status == 1, "Wrong status callback status."); + + // Kill off all connections and servers. + kill_TCP_server(tcp_s); + kill_TCP_connection(conn); + kill_TCP_connection(conn2); + + logger_kill(logger); + mono_time_free(mono_time); +} + +// Test how the client handles servers that don't respond. +static void test_client_invalid(void) +{ + Mono_Time *mono_time = mono_time_new(nullptr, nullptr); + const Random *rng = system_random(); + ck_assert(rng != nullptr); + Logger *logger = logger_new(); + const Network *ns = system_network(); + + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, self_public_key, self_secret_key); + + uint8_t f_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t f_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, f_public_key, f_secret_key); + IP_Port ip_port_tcp_s; + + ip_port_tcp_s.port = net_htons(ports[random_u32(rng) % NUM_PORTS]); + ip_port_tcp_s.ip = get_loopback(); + TCP_Client_Connection *conn = new_TCP_connection(logger, mono_time, rng, ns, &ip_port_tcp_s, + self_public_key, f_public_key, f_secret_key, nullptr); + + // Run the client's main loop but not the server. + mono_time_update(mono_time); + do_TCP_connection(logger, mono_time, conn, nullptr); + c_sleep(50); + + // After 50ms of no response... + ck_assert_msg(tcp_con_status(conn) == TCP_CLIENT_CONNECTING, "Wrong status. Expected: %d, is: %d.", + TCP_CLIENT_CONNECTING, tcp_con_status(conn)); + // After 5s... + c_sleep(5000); + mono_time_update(mono_time); + do_TCP_connection(logger, mono_time, conn, nullptr); + ck_assert_msg(tcp_con_status(conn) == TCP_CLIENT_CONNECTING, "Wrong status. Expected: %d, is: %d.", + TCP_CLIENT_CONNECTING, tcp_con_status(conn)); + // 11s... (Should wait for 10 before giving up.) + c_sleep(6000); + mono_time_update(mono_time); + do_TCP_connection(logger, mono_time, conn, nullptr); + ck_assert_msg(tcp_con_status(conn) == TCP_CLIENT_DISCONNECTED, "Wrong status. Expected: %d, is: %d.", + TCP_CLIENT_DISCONNECTED, tcp_con_status(conn)); + + kill_TCP_connection(conn); + + logger_kill(logger); + mono_time_free(mono_time); +} + +#include "../toxcore/TCP_connection.h" + +static bool tcp_data_callback_called; +static int tcp_data_callback(void *object, int id, const uint8_t *data, uint16_t length, void *userdata) +{ + if (object != (void *)120397) { + return -1; + } + + if (id != 123) { + return -1; + } + + if (length != 6) { + return -1; + } + + if (memcmp(data, "Gentoo", length) != 0) { + return -1; + } + + tcp_data_callback_called = 1; + return 0; +} + + +static void test_tcp_connection(void) +{ + Mono_Time *mono_time = mono_time_new(nullptr, nullptr); + Logger *logger = logger_new(); + const Random *rng = system_random(); + ck_assert(rng != nullptr); + const Network *ns = system_network(); + + tcp_data_callback_called = 0; + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, self_public_key, self_secret_key); + TCP_Server *tcp_s = new_TCP_server(logger, rng, ns, USE_IPV6, NUM_PORTS, ports, self_secret_key, nullptr, nullptr); + ck_assert_msg(pk_equal(tcp_server_public_key(tcp_s), self_public_key), "Wrong public key"); + + TCP_Proxy_Info proxy_info; + proxy_info.proxy_type = TCP_PROXY_NONE; + crypto_new_keypair(rng, self_public_key, self_secret_key); + TCP_Connections *tc_1 = new_tcp_connections(logger, rng, ns, mono_time, self_secret_key, &proxy_info); + ck_assert_msg(pk_equal(tcp_connections_public_key(tc_1), self_public_key), "Wrong public key"); + + crypto_new_keypair(rng, self_public_key, self_secret_key); + TCP_Connections *tc_2 = new_tcp_connections(logger, rng, ns, mono_time, self_secret_key, &proxy_info); + ck_assert_msg(pk_equal(tcp_connections_public_key(tc_2), self_public_key), "Wrong public key"); + + IP_Port ip_port_tcp_s; + + ip_port_tcp_s.port = net_htons(ports[random_u32(rng) % NUM_PORTS]); + ip_port_tcp_s.ip = get_loopback(); + + int connection = new_tcp_connection_to(tc_1, tcp_connections_public_key(tc_2), 123); + ck_assert_msg(connection == 0, "Connection id wrong"); + ck_assert_msg(add_tcp_relay_connection(tc_1, connection, &ip_port_tcp_s, tcp_server_public_key(tcp_s)) == 0, + "Could not add tcp relay to connection\n"); + + ip_port_tcp_s.port = net_htons(ports[random_u32(rng) % NUM_PORTS]); + connection = new_tcp_connection_to(tc_2, tcp_connections_public_key(tc_1), 123); + ck_assert_msg(connection == 0, "Connection id wrong"); + ck_assert_msg(add_tcp_relay_connection(tc_2, connection, &ip_port_tcp_s, tcp_server_public_key(tcp_s)) == 0, + "Could not add tcp relay to connection\n"); + + ck_assert_msg(new_tcp_connection_to(tc_2, tcp_connections_public_key(tc_1), 123) == -1, + "Managed to read same connection\n"); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + int ret = send_packet_tcp_connection(tc_1, 0, (const uint8_t *)"Gentoo", 6); + ck_assert_msg(ret == 0, "could not send packet."); + set_packet_tcp_connection_callback(tc_2, &tcp_data_callback, (void *) 120397); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + ck_assert_msg(tcp_data_callback_called, "could not recv packet."); + ck_assert_msg(tcp_connection_to_online_tcp_relays(tc_1, 0) == 1, "Wrong number of connected relays"); + ck_assert_msg(kill_tcp_connection_to(tc_1, 0) == 0, "could not kill connection to\n"); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + ck_assert_msg(send_packet_tcp_connection(tc_1, 0, (const uint8_t *)"Gentoo", 6) == -1, "could send packet."); + ck_assert_msg(kill_tcp_connection_to(tc_2, 0) == 0, "could not kill connection to\n"); + + kill_TCP_server(tcp_s); + kill_tcp_connections(tc_1); + kill_tcp_connections(tc_2); + + logger_kill(logger); + mono_time_free(mono_time); +} + +static bool tcp_oobdata_callback_called; +static int tcp_oobdata_callback(void *object, const uint8_t *public_key, unsigned int id, const uint8_t *data, + uint16_t length, void *userdata) +{ + const TCP_Connections *tcp_c = (const TCP_Connections *)object; + + if (length != 6) { + return -1; + } + + if (memcmp(data, "Gentoo", length) != 0) { + return -1; + } + + if (tcp_send_oob_packet(tcp_c, id, public_key, data, length) == 0) { + tcp_oobdata_callback_called = 1; + } + + return 0; +} + +static void test_tcp_connection2(void) +{ + Mono_Time *mono_time = mono_time_new(nullptr, nullptr); + Logger *logger = logger_new(); + const Random *rng = system_random(); + ck_assert(rng != nullptr); + const Network *ns = system_network(); + + tcp_oobdata_callback_called = 0; + tcp_data_callback_called = 0; + + uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + crypto_new_keypair(rng, self_public_key, self_secret_key); + TCP_Server *tcp_s = new_TCP_server(logger, rng, ns, USE_IPV6, NUM_PORTS, ports, self_secret_key, nullptr, nullptr); + ck_assert_msg(pk_equal(tcp_server_public_key(tcp_s), self_public_key), "Wrong public key"); + + TCP_Proxy_Info proxy_info; + proxy_info.proxy_type = TCP_PROXY_NONE; + crypto_new_keypair(rng, self_public_key, self_secret_key); + TCP_Connections *tc_1 = new_tcp_connections(logger, rng, ns, mono_time, self_secret_key, &proxy_info); + ck_assert_msg(pk_equal(tcp_connections_public_key(tc_1), self_public_key), "Wrong public key"); + + crypto_new_keypair(rng, self_public_key, self_secret_key); + TCP_Connections *tc_2 = new_tcp_connections(logger, rng, ns, mono_time, self_secret_key, &proxy_info); + ck_assert_msg(pk_equal(tcp_connections_public_key(tc_2), self_public_key), "Wrong public key"); + + IP_Port ip_port_tcp_s; + + ip_port_tcp_s.port = net_htons(ports[random_u32(rng) % NUM_PORTS]); + ip_port_tcp_s.ip = get_loopback(); + + int connection = new_tcp_connection_to(tc_1, tcp_connections_public_key(tc_2), 123); + ck_assert_msg(connection == 0, "Connection id wrong"); + ck_assert_msg(add_tcp_relay_connection(tc_1, connection, &ip_port_tcp_s, tcp_server_public_key(tcp_s)) == 0, + "Could not add tcp relay to connection\n"); + + ck_assert_msg(add_tcp_relay_global(tc_2, &ip_port_tcp_s, tcp_server_public_key(tcp_s)) == 0, + "Could not add global relay"); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + int ret = send_packet_tcp_connection(tc_1, 0, (const uint8_t *)"Gentoo", 6); + ck_assert_msg(ret == 0, "could not send packet."); + set_oob_packet_tcp_connection_callback(tc_2, &tcp_oobdata_callback, tc_2); + set_packet_tcp_connection_callback(tc_1, &tcp_data_callback, (void *) 120397); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + ck_assert_msg(tcp_oobdata_callback_called, "could not recv packet."); + + do_TCP_server_delay(tcp_s, mono_time, 50); + + do_tcp_connections(logger, tc_1, nullptr); + do_tcp_connections(logger, tc_2, nullptr); + + ck_assert_msg(tcp_data_callback_called, "could not recv packet."); + ck_assert_msg(kill_tcp_connection_to(tc_1, 0) == 0, "could not kill connection to\n"); + + kill_TCP_server(tcp_s); + kill_tcp_connections(tc_1); + kill_tcp_connections(tc_2); + + logger_kill(logger); + mono_time_free(mono_time); +} + +static void TCP_suite(void) +{ + test_basic(); + test_some(); + test_client(); + test_client_invalid(); + test_tcp_connection(); + test_tcp_connection2(); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + TCP_suite(); + return 0; +} diff --git a/auto_tests/announce_test.c b/auto_tests/announce_test.c new file mode 100644 index 0000000..c31d5ca --- /dev/null +++ b/auto_tests/announce_test.c @@ -0,0 +1,123 @@ +#include +#include + +#include "../toxcore/announce.h" +#include "../toxcore/tox.h" +#include "../testing/misc_tools.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/forwarding.h" +#include "../toxcore/net_crypto.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +static void test_bucketnum(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + uint8_t key1[CRYPTO_PUBLIC_KEY_SIZE], key2[CRYPTO_PUBLIC_KEY_SIZE]; + random_bytes(rng, key1, sizeof(key1)); + memcpy(key2, key1, CRYPTO_PUBLIC_KEY_SIZE); + + ck_assert_msg(announce_get_bucketnum(key1, key2) == 0, "Bad bucketnum"); + + key2[4] ^= 0x09; + key2[5] ^= 0xc5; + + ck_assert_msg(announce_get_bucketnum(key1, key2) == 7, "Bad bucketnum"); + + key2[4] ^= 0x09; + + ck_assert_msg(announce_get_bucketnum(key1, key2) == 17, "Bad bucketnum"); + + key2[5] ^= 0xc5; + key2[31] ^= 0x09; + + ck_assert_msg(announce_get_bucketnum(key1, key2) == 4, "Bad bucketnum"); +} + +typedef struct Announce_Test_Data { + uint8_t data[MAX_ANNOUNCEMENT_SIZE]; + uint16_t length; + bool passed; +} Announce_Test_Data; + +static void test_announce_data(void *object, const uint8_t *data, uint16_t length) +{ + Announce_Test_Data *test_data = (Announce_Test_Data *) object; + test_data->passed = test_data->length == length && memcmp(test_data->data, data, length) == 0; +} + +static void test_store_data(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + const Network *ns = system_network(); + ck_assert(ns != nullptr); + Logger *log = logger_new(); + ck_assert(log != nullptr); + logger_callback_log(log, print_debug_logger, nullptr, nullptr); + Mono_Time *mono_time = mono_time_new(nullptr, nullptr); + Networking_Core *net = new_networking_no_udp(log, ns); + DHT *dht = new_dht(log, rng, ns, mono_time, net, true, true); + Forwarding *forwarding = new_forwarding(log, rng, mono_time, dht); + Announcements *announce = new_announcements(log, rng, mono_time, forwarding); + ck_assert(announce != nullptr); + + /* Just to prevent CI from complaining that set_synch_offset is unused: */ + announce_set_synch_offset(announce, 0); + + Announce_Test_Data test_data; + random_bytes(rng, test_data.data, sizeof(test_data.data)); + test_data.length = sizeof(test_data.data); + + uint8_t key[CRYPTO_PUBLIC_KEY_SIZE]; + random_bytes(rng, key, sizeof(key)); + + ck_assert_msg(!announce_on_stored(announce, key, nullptr, nullptr), "Unstored announcement exists"); + + ck_assert_msg(announce_store_data(announce, key, test_data.data, sizeof(test_data.data), + MAX_MAX_ANNOUNCEMENT_TIMEOUT), "Failed to store announcement"); + + ck_assert_msg(announce_on_stored(announce, key, test_announce_data, &test_data), "Failed to get stored announcement"); + + ck_assert_msg(test_data.passed, "Bad stored announcement data"); + + const uint8_t *const base = dht_get_self_public_key(dht); + ck_assert_msg(announce_store_data(announce, base, test_data.data, sizeof(test_data.data), 1), "failed to store base"); + + uint8_t test_keys[ANNOUNCE_BUCKET_SIZE + 1][CRYPTO_PUBLIC_KEY_SIZE]; + + for (uint8_t i = 0; i < ANNOUNCE_BUCKET_SIZE + 1; ++i) { + memcpy(test_keys[i], base, CRYPTO_PUBLIC_KEY_SIZE); + test_keys[i][i] ^= 1; + ck_assert_msg(announce_store_data(announce, test_keys[i], test_data.data, sizeof(test_data.data), 1), + "Failed to store announcement %d", i); + } + + ck_assert_msg(announce_on_stored(announce, base, nullptr, nullptr), "base was evicted"); + ck_assert_msg(!announce_on_stored(announce, test_keys[0], nullptr, nullptr), "furthest was not evicted"); + ck_assert_msg(!announce_store_data(announce, test_keys[0], nullptr, 0, 1), "furthest evicted closer"); + + kill_announcements(announce); + kill_forwarding(forwarding); + kill_dht(dht); + kill_networking(net); + mono_time_free(mono_time); + logger_kill(log); +} + +static void basic_announce_tests(void) +{ + test_bucketnum(); + test_store_data(); +} + + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + basic_announce_tests(); + return 0; +} diff --git a/auto_tests/auto_test_support.c b/auto_tests/auto_test_support.c new file mode 100644 index 0000000..cb40973 --- /dev/null +++ b/auto_tests/auto_test_support.c @@ -0,0 +1,465 @@ +#include // assert +#include // calloc, free + +#include "check_compat.h" +#include "../testing/misc_tools.h" +#include "../toxcore/Messenger.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/tox_struct.h" + +#include "auto_test_support.h" + +#ifndef ABORT_ON_LOG_ERROR +#define ABORT_ON_LOG_ERROR true +#endif + +Run_Auto_Options default_run_auto_options() +{ + return (Run_Auto_Options) { + .graph = GRAPH_COMPLETE, + .init_autotox = nullptr, + .tcp_port = 33188, + }; +} + +// List of live bootstrap nodes. These nodes should have TCP server enabled. +static const struct BootstrapNodes { + const char *ip; + uint16_t port; + const uint8_t key[32]; +} BootstrapNodes[] = { +#ifndef USE_TEST_NETWORK + { + "tox.abilinski.com", 33445, + 0x10, 0xC0, 0x0E, 0xB2, 0x50, 0xC3, 0x23, 0x3E, + 0x34, 0x3E, 0x2A, 0xEB, 0xA0, 0x71, 0x15, 0xA5, + 0xC2, 0x89, 0x20, 0xE9, 0xC8, 0xD2, 0x94, 0x92, + 0xF6, 0xD0, 0x0B, 0x29, 0x04, 0x9E, 0xDC, 0x7E, + }, + { + "tox.initramfs.io", 33445, + 0x02, 0x80, 0x7C, 0xF4, 0xF8, 0xBB, 0x8F, 0xB3, + 0x90, 0xCC, 0x37, 0x94, 0xBD, 0xF1, 0xE8, 0x44, + 0x9E, 0x9A, 0x83, 0x92, 0xC5, 0xD3, 0xF2, 0x20, + 0x00, 0x19, 0xDA, 0x9F, 0x1E, 0x81, 0x2E, 0x46, + }, + { + "tox.plastiras.org", 33445, + 0x8E, 0x8B, 0x63, 0x29, 0x9B, 0x3D, 0x52, 0x0F, + 0xB3, 0x77, 0xFE, 0x51, 0x00, 0xE6, 0x5E, 0x33, + 0x22, 0xF7, 0xAE, 0x5B, 0x20, 0xA0, 0xAC, 0xED, + 0x29, 0x81, 0x76, 0x9F, 0xC5, 0xB4, 0x37, 0x25, + }, + { + "tox.novg.net", 33445, + 0xD5, 0x27, 0xE5, 0x84, 0x7F, 0x83, 0x30, 0xD6, + 0x28, 0xDA, 0xB1, 0x81, 0x4F, 0x0A, 0x42, 0x2F, + 0x6D, 0xC9, 0xD0, 0xA3, 0x00, 0xE6, 0xC3, 0x57, + 0x63, 0x4E, 0xE2, 0xDA, 0x88, 0xC3, 0x54, 0x63, + }, +#else + { + "172.93.52.70", 33445, + 0x79, 0xCA, 0xDA, 0x49, 0x74, 0xB0, 0x92, 0x6F, + 0x28, 0x6F, 0x02, 0x5C, 0xD5, 0xFF, 0xDF, 0x3E, + 0x65, 0x4A, 0x37, 0x58, 0xC5, 0x3E, 0x02, 0x73, + 0xEC, 0xFC, 0x4D, 0x12, 0xC2, 0x1D, 0xCA, 0x48, + }, + { + "tox.plastiras.org", 38445, + 0x5E, 0x47, 0xBA, 0x1D, 0xC3, 0x91, 0x3E, 0xB2, + 0xCB, 0xF2, 0xD6, 0x4C, 0xE4, 0xF2, 0x3D, 0x8B, + 0xFE, 0x53, 0x91, 0xBF, 0xAB, 0xE5, 0xC4, 0x3C, + 0x5B, 0xAD, 0x13, 0xF0, 0xA4, 0x14, 0xCD, 0x77, + }, +#endif // USE_TEST_NETWORK + { nullptr, 0, 0 }, +}; + +void bootstrap_tox_live_network(Tox *tox, bool enable_tcp) +{ + ck_assert(tox != nullptr); + + for (size_t j = 0; BootstrapNodes[j].ip != nullptr; ++j) { + const char *ip = BootstrapNodes[j].ip; + uint16_t port = BootstrapNodes[j].port; + const uint8_t *key = BootstrapNodes[j].key; + + Tox_Err_Bootstrap err; + tox_bootstrap(tox, ip, port, key, &err); + + if (err != TOX_ERR_BOOTSTRAP_OK) { + fprintf(stderr, "Failed to bootstrap node %zu (%s): error %d\n", j, ip, err); + } + + if (enable_tcp) { + tox_add_tcp_relay(tox, ip, port, key, &err); + + if (err != TOX_ERR_BOOTSTRAP_OK) { + fprintf(stderr, "Failed to add TCP relay %zu (%s): error %d\n", j, ip, err); + } + } + } +} + +bool all_connected(const AutoTox *autotoxes, uint32_t tox_count) +{ + if (tox_count) { + ck_assert(autotoxes != nullptr); + } + + for (uint32_t i = 0; i < tox_count; ++i) { + if (tox_self_get_connection_status(autotoxes[i].tox) == TOX_CONNECTION_NONE) { + return false; + } + } + + return true; +} + +bool all_friends_connected(const AutoTox *autotoxes, uint32_t tox_count) +{ + if (tox_count) { + ck_assert(autotoxes != nullptr); + } + + for (uint32_t i = 0; i < tox_count; ++i) { + const size_t friend_count = tox_self_get_friend_list_size(autotoxes[i].tox); + + for (size_t j = 0; j < friend_count; ++j) { + if (tox_friend_get_connection_status(autotoxes[i].tox, j, nullptr) == TOX_CONNECTION_NONE) { + return false; + } + } + } + + return true; +} + +void iterate_all_wait(AutoTox *autotoxes, uint32_t tox_count, uint32_t wait) +{ + if (tox_count) { + ck_assert(autotoxes != nullptr); + } + + for (uint32_t i = 0; i < tox_count; ++i) { + if (autotoxes[i].alive) { + tox_iterate(autotoxes[i].tox, &autotoxes[i]); + autotoxes[i].clock += wait; + } + } + + /* Also actually sleep a little, to allow for local network processing */ + c_sleep(5); +} + +static uint64_t get_state_clock_callback(void *user_data) +{ + const uint64_t *clock = (const uint64_t *)user_data; + return *clock; +} + +void set_mono_time_callback(AutoTox *autotox) +{ + ck_assert(autotox != nullptr); + + Mono_Time *mono_time = autotox->tox->mono_time; + + autotox->clock = current_time_monotonic(mono_time); + mono_time_set_current_time_callback(mono_time, nullptr, nullptr); // set to default first + mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &autotox->clock); +} + +void save_autotox(AutoTox *autotox) +{ + ck_assert(autotox != nullptr); + + fprintf(stderr, "Saving #%u\n", autotox->index); + + free(autotox->save_state); + autotox->save_state = nullptr; + + autotox->save_size = tox_get_savedata_size(autotox->tox); + ck_assert_msg(autotox->save_size > 0, "save is invalid size %u", (unsigned)autotox->save_size); + autotox->save_state = (uint8_t *)malloc(autotox->save_size); + ck_assert_msg(autotox->save_state != nullptr, "malloc failed"); + tox_get_savedata(autotox->tox, autotox->save_state); +} + +void kill_autotox(AutoTox *autotox) +{ + ck_assert(autotox != nullptr); + ck_assert(autotox->alive); + fprintf(stderr, "Killing #%u\n", autotox->index); + autotox->alive = false; + tox_kill(autotox->tox); +} + +void reload(AutoTox *autotox) +{ + ck_assert(autotox != nullptr); + + if (autotox->alive) { + kill_autotox(autotox); + } + + fprintf(stderr, "Reloading #%u\n", autotox->index); + ck_assert(autotox->save_state != nullptr); + + struct Tox_Options *const options = tox_options_new(nullptr); + ck_assert(options != nullptr); + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + tox_options_set_savedata_data(options, autotox->save_state, autotox->save_size); + autotox->tox = tox_new_log(options, nullptr, &autotox->index); + ck_assert(autotox->tox != nullptr); + tox_options_free(options); + + set_mono_time_callback(autotox); + autotox->alive = true; +} + +static void initialise_autotox(struct Tox_Options *options, AutoTox *autotox, uint32_t index, uint32_t state_size, + Run_Auto_Options *autotest_opts) +{ + autotox->index = index; + + Tox_Err_New err = TOX_ERR_NEW_OK; + + if (index == 0) { + struct Tox_Options *default_opts = tox_options_new(nullptr); + ck_assert(default_opts != nullptr); + + if (options == nullptr) { + options = default_opts; + } + + if (tox_options_get_udp_enabled(options)) { + tox_options_set_tcp_port(options, 0); + autotest_opts->tcp_port = 0; + autotox->tox = tox_new_log(options, &err, &autotox->index); + ck_assert_msg(err == TOX_ERR_NEW_OK, "unexpected tox_new error: %d", err); + } else { + // Try a few ports for the TCP relay. + for (uint16_t tcp_port = autotest_opts->tcp_port; tcp_port < autotest_opts->tcp_port + 200; ++tcp_port) { + tox_options_set_tcp_port(options, tcp_port); + autotox->tox = tox_new_log(options, &err, &autotox->index); + + if (autotox->tox != nullptr) { + autotest_opts->tcp_port = tcp_port; + break; + } + + ck_assert_msg(err == TOX_ERR_NEW_PORT_ALLOC, "unexpected tox_new error (expected PORT_ALLOC): %d", err); + } + } + + tox_options_free(default_opts); + } else { + // No TCP relay enabled for all the other toxes. + if (options != nullptr) { + tox_options_set_tcp_port(options, 0); + } + + autotox->tox = tox_new_log(options, &err, &autotox->index); + } + + ck_assert_msg(autotox->tox != nullptr, "failed to create tox instance #%u (error = %d)", index, err); + + set_mono_time_callback(autotox); + + autotox->alive = true; + autotox->save_state = nullptr; + + if (state_size > 0) { + autotox->state = calloc(1, state_size); + ck_assert(autotox->state != nullptr); + ck_assert_msg(autotox->state != nullptr, "failed to allocate state"); + } else { + autotox->state = nullptr; + } + + if (autotest_opts->init_autotox != nullptr) { + autotest_opts->init_autotox(autotox, index); + } +} + +static void autotox_add_friend(AutoTox *autotoxes, uint32_t adding, uint32_t added) +{ + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(autotoxes[added].tox, public_key); + Tox_Err_Friend_Add err; + tox_friend_add_norequest(autotoxes[adding].tox, public_key, &err); + ck_assert(err == TOX_ERR_FRIEND_ADD_OK); +} + +static void initialise_friend_graph(Graph_Type graph, uint32_t num_toxes, AutoTox *autotoxes) +{ + if (graph == GRAPH_LINEAR) { + printf("toxes #%d-#%u each add adjacent toxes as friends\n", 0, num_toxes - 1); + + for (uint32_t i = 0; i < num_toxes; ++i) { + for (uint32_t j = i - 1; j != i + 3; j += 2) { + if (j < num_toxes) { + autotox_add_friend(autotoxes, i, j); + } + } + } + } else if (graph == GRAPH_COMPLETE) { + printf("toxes #%d-#%u add each other as friends\n", 0, num_toxes - 1); + + for (uint32_t i = 0; i < num_toxes; ++i) { + for (uint32_t j = 0; j < num_toxes; ++j) { + if (i != j) { + autotox_add_friend(autotoxes, i, j); + } + } + } + } else { + ck_abort_msg("Unknown graph type"); + } +} + +static void bootstrap_autotoxes(struct Tox_Options *options, uint32_t tox_count, const Run_Auto_Options *autotest_opts, + AutoTox *autotoxes) +{ + const bool udp_enabled = options != nullptr ? tox_options_get_udp_enabled(options) : true; + + printf("bootstrapping all toxes off tox 0\n"); + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(autotoxes[0].tox, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(autotoxes[0].tox, nullptr); + + for (uint32_t i = 1; i < tox_count; ++i) { + Tox_Err_Bootstrap err; + tox_bootstrap(autotoxes[i].tox, "localhost", dht_port, dht_key, &err); + ck_assert(err == TOX_ERR_BOOTSTRAP_OK); + } + + if (!udp_enabled) { + ck_assert(autotest_opts->tcp_port != 0); + printf("bootstrapping all toxes to local TCP relay running on port %d\n", autotest_opts->tcp_port); + + for (uint32_t i = 0; i < tox_count; ++i) { + Tox_Err_Bootstrap err; + tox_add_tcp_relay(autotoxes[i].tox, "localhost", autotest_opts->tcp_port, dht_key, &err); + ck_assert(err == TOX_ERR_BOOTSTRAP_OK); + } + } +} + +void run_auto_test(struct Tox_Options *options, uint32_t tox_count, void test(AutoTox *autotoxes), + uint32_t state_size, Run_Auto_Options *autotest_opts) +{ + printf("initialising %u toxes\n", tox_count); + + AutoTox *autotoxes = (AutoTox *)calloc(tox_count, sizeof(AutoTox)); + + ck_assert(autotoxes != nullptr); + + for (uint32_t i = 0; i < tox_count; ++i) { + initialise_autotox(options, &autotoxes[i], i, state_size, autotest_opts); + } + + initialise_friend_graph(autotest_opts->graph, tox_count, autotoxes); + + bootstrap_autotoxes(options, tox_count, autotest_opts, autotoxes); + + do { + iterate_all_wait(autotoxes, tox_count, ITERATION_INTERVAL); + } while (!all_connected(autotoxes, tox_count)); + + printf("toxes are online\n"); + + do { + iterate_all_wait(autotoxes, tox_count, ITERATION_INTERVAL); + } while (!all_friends_connected(autotoxes, tox_count)); + + printf("tox clients connected\n"); + + test(autotoxes); + + for (uint32_t i = 0; i < tox_count; ++i) { + tox_kill(autotoxes[i].tox); + free(autotoxes[i].state); + free(autotoxes[i].save_state); + } + + free(autotoxes); +} + +static const char *tox_log_level_name(Tox_Log_Level level) +{ + switch (level) { + case TOX_LOG_LEVEL_TRACE: + return "TRACE"; + + case TOX_LOG_LEVEL_DEBUG: + return "DEBUG"; + + case TOX_LOG_LEVEL_INFO: + return "INFO"; + + case TOX_LOG_LEVEL_WARNING: + return "WARNING"; + + case TOX_LOG_LEVEL_ERROR: + return "ERROR"; + } + + return ""; +} + +void print_debug_log(Tox *m, Tox_Log_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *user_data) +{ + if (level == TOX_LOG_LEVEL_TRACE) { + return; + } + + const uint32_t index = user_data ? *(uint32_t *)user_data : 0; + fprintf(stderr, "[#%u] %s %s:%u\t%s:\t%s\n", index, tox_log_level_name(level), file, line, func, message); + + if (level == TOX_LOG_LEVEL_ERROR && ABORT_ON_LOG_ERROR) { + fputs("Aborting test program\n", stderr); + abort(); + } +} + + +void print_debug_logger(void *context, Logger_Level level, const char *file, int line, const char *func, const char *message, void *userdata) +{ + print_debug_log(nullptr, (Tox_Log_Level) level, file, (uint32_t) line, func, message, userdata); +} + +Tox *tox_new_log_lan(struct Tox_Options *options, Tox_Err_New *err, void *log_user_data, bool lan_discovery) +{ + struct Tox_Options *log_options = options; + + if (log_options == nullptr) { + log_options = tox_options_new(nullptr); + } + + assert(log_options != nullptr); + + tox_options_set_local_discovery_enabled(log_options, lan_discovery); + // Use a higher start port for non-LAN-discovery tests so it's more likely for the LAN discovery + // test to get the default port 33445. + const uint16_t start_port = lan_discovery ? 33445 : 33545; + tox_options_set_start_port(log_options, start_port); + tox_options_set_end_port(log_options, start_port + 2000); + tox_options_set_log_callback(log_options, &print_debug_log); + tox_options_set_log_user_data(log_options, log_user_data); + Tox *tox = tox_new(log_options, err); + + if (options == nullptr) { + tox_options_free(log_options); + } + + return tox; +} + +Tox *tox_new_log(struct Tox_Options *options, Tox_Err_New *err, void *log_user_data) +{ + return tox_new_log_lan(options, err, log_user_data, false); +} + diff --git a/auto_tests/auto_test_support.h b/auto_tests/auto_test_support.h new file mode 100644 index 0000000..eab121a --- /dev/null +++ b/auto_tests/auto_test_support.h @@ -0,0 +1,65 @@ +#ifndef RUN_AUTO_TEST_H +#define RUN_AUTO_TEST_H + +#include // calloc, free + +#include "check_compat.h" +#include "../testing/misc_tools.h" +#include "../toxcore/Messenger.h" +#include "../toxcore/mono_time.h" + +typedef struct AutoTox { + Tox *tox; + + uint32_t index; + uint64_t clock; + + size_t save_size; + uint8_t *save_state; + bool alive; + + void *state; +} AutoTox; + +bool all_connected(const AutoTox *autotoxes, uint32_t tox_count); + +bool all_friends_connected(const AutoTox *autotoxes, uint32_t tox_count); + +void iterate_all_wait(AutoTox *autotoxes, uint32_t tox_count, uint32_t wait); + +void save_autotox(AutoTox *autotox); +void kill_autotox(AutoTox *autotox); +void reload(AutoTox *autotox); + +void set_mono_time_callback(AutoTox *autotox); + +typedef enum Graph_Type { + GRAPH_COMPLETE = 0, + GRAPH_LINEAR, +} Graph_Type; + +typedef struct Run_Auto_Options { + Graph_Type graph; + void (*init_autotox)(AutoTox *autotox, uint32_t n); + uint16_t tcp_port; +} Run_Auto_Options; + +Run_Auto_Options default_run_auto_options(void); + +void run_auto_test(struct Tox_Options *options, uint32_t tox_count, void test(AutoTox *autotoxes), + uint32_t state_size, Run_Auto_Options *autotest_opts); + +void bootstrap_tox_live_network(Tox *tox, bool enable_tcp); + +// Use this function when setting the log callback on a Tox* object +void print_debug_log(Tox *m, Tox_Log_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *user_data); + +// Use this function when setting the log callback on a Logger object +void print_debug_logger(void *context, Logger_Level level, const char *file, int line, + const char *func, const char *message, void *userdata); + +Tox *tox_new_log(struct Tox_Options *options, Tox_Err_New *err, void *log_user_data); +Tox *tox_new_log_lan(struct Tox_Options *options, Tox_Err_New *err, void *log_user_data, bool lan_discovery); + +#endif diff --git a/auto_tests/bootstrap_test.c b/auto_tests/bootstrap_test.c new file mode 100644 index 0000000..b9b1965 --- /dev/null +++ b/auto_tests/bootstrap_test.c @@ -0,0 +1,34 @@ +#include + +#include "../testing/misc_tools.h" +#include "check_compat.h" + +#include "auto_test_support.h" + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Tox *tox_udp = tox_new_log(nullptr, nullptr, nullptr); + + bootstrap_tox_live_network(tox_udp, false); + + printf("Waiting for connection"); + + do { + printf("."); + fflush(stdout); + + tox_iterate(tox_udp, nullptr); + c_sleep(ITERATION_INTERVAL); + } while (tox_self_get_connection_status(tox_udp) == TOX_CONNECTION_NONE); + + const Tox_Connection status = tox_self_get_connection_status(tox_udp); + ck_assert_msg(status == TOX_CONNECTION_UDP, + "expected connection status to be UDP, but got %d", status); + printf("Connection (UDP): %d\n", tox_self_get_connection_status(tox_udp)); + + tox_kill(tox_udp); + + return 0; +} diff --git a/auto_tests/check_compat.h b/auto_tests/check_compat.h new file mode 100644 index 0000000..72fa55b --- /dev/null +++ b/auto_tests/check_compat.h @@ -0,0 +1,32 @@ +#ifndef C_TOXCORE_AUTO_TESTS_CHECK_COMPAT_H +#define C_TOXCORE_AUTO_TESTS_CHECK_COMPAT_H + +#include "../toxcore/ccompat.h" + +#include +#include + +#define ck_assert(ok) do { \ + if (!(ok)) { \ + fprintf(stderr, "%s:%d: failed `%s'\n", __FILE__, __LINE__, #ok); \ + abort(); \ + } \ +} 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"); \ + abort(); \ + } \ +} while (0) + +#define ck_abort_msg(...) do { \ + fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + abort(); \ +} while (0) + +#endif // C_TOXCORE_AUTO_TESTS_CHECK_COMPAT_H diff --git a/auto_tests/conference_av_test.c b/auto_tests/conference_av_test.c new file mode 100644 index 0000000..9bbc418 --- /dev/null +++ b/auto_tests/conference_av_test.c @@ -0,0 +1,464 @@ +/* Auto Tests: Conferences AV. + */ + +#include +#include +#include +#include + +#include "../toxav/toxav.h" +#include "check_compat.h" + +#define NUM_AV_GROUP_TOX 16 +#define NUM_AV_DISCONNECT (NUM_AV_GROUP_TOX / 2) +#define NUM_AV_DISABLE (NUM_AV_GROUP_TOX / 2) + +#include "auto_test_support.h" + +typedef struct State { + bool invited_next; + + uint32_t received_audio_peers[NUM_AV_GROUP_TOX]; + uint32_t received_audio_num; +} State; + +static void handle_self_connection_status( + Tox *tox, Tox_Connection connection_status, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + + if (connection_status != TOX_CONNECTION_NONE) { + printf("tox #%u: is now connected\n", autotox->index); + } else { + printf("tox #%u: is now disconnected\n", autotox->index); + } +} + +static void handle_friend_connection_status( + Tox *tox, uint32_t friendnumber, Tox_Connection connection_status, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + + if (connection_status != TOX_CONNECTION_NONE) { + printf("tox #%u: is now connected to friend %u\n", autotox->index, friendnumber); + } else { + printf("tox #%u: is now disconnected from friend %u\n", autotox->index, friendnumber); + } +} + +static void audio_callback(void *tox, uint32_t groupnumber, uint32_t peernumber, + const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t + sample_rate, void *user_data) +{ + if (samples == 0) { + return; + } + + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + for (uint32_t i = 0; i < state->received_audio_num; ++i) { + if (state->received_audio_peers[i] == peernumber) { + return; + } + } + + ck_assert(state->received_audio_num < NUM_AV_GROUP_TOX); + + state->received_audio_peers[state->received_audio_num] = peernumber; + ++state->received_audio_num; +} + +static void handle_conference_invite( + Tox *tox, uint32_t friendnumber, Tox_Conference_Type type, + const uint8_t *data, size_t length, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + ck_assert_msg(type == TOX_CONFERENCE_TYPE_AV, "tox #%u: wrong conference type: %d", autotox->index, type); + + ck_assert_msg(toxav_join_av_groupchat(tox, friendnumber, data, length, audio_callback, user_data) == 0, + "tox #%u: failed to join group", autotox->index); +} + +static void handle_conference_connected( + Tox *tox, uint32_t conference_number, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + if (state->invited_next || tox_self_get_friend_list_size(tox) <= 1) { + return; + } + + Tox_Err_Conference_Invite err; + tox_conference_invite(tox, 1, 0, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %d", autotox->index, + err); + printf("tox #%u: invited next friend\n", autotox->index); + state->invited_next = true; +} + +static bool toxes_are_disconnected_from_group(uint32_t tox_count, AutoTox *autotoxes, + bool *disconnected) +{ + uint32_t num_disconnected = 0; + + for (uint32_t i = 0; i < tox_count; ++i) { + num_disconnected += disconnected[i]; + } + + for (uint32_t i = 0; i < tox_count; ++i) { + if (disconnected[i]) { + continue; + } + + if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) > tox_count - num_disconnected) { + return false; + } + } + + return true; +} + +static void disconnect_toxes(uint32_t tox_count, AutoTox *autotoxes, + const bool *disconnect, const bool *exclude) +{ + /* Fake a network outage for a set of peers D by iterating only the other + * peers D' until the connections time out according to D', then iterating + * only D until the connections time out according to D. */ + + VLA(bool, disconnect_now, tox_count); + bool invert = false; + + do { + for (uint32_t i = 0; i < tox_count; ++i) { + disconnect_now[i] = exclude[i] || (invert ^ disconnect[i]); + } + + do { + for (uint32_t i = 0; i < tox_count; ++i) { + if (!disconnect_now[i]) { + tox_iterate(autotoxes[i].tox, &autotoxes[i]); + autotoxes[i].clock += 1000; + } + } + + c_sleep(20); + } while (!toxes_are_disconnected_from_group(tox_count, autotoxes, disconnect_now)); + + invert = !invert; + } while (invert); +} + +static bool all_connected_to_group(uint32_t tox_count, AutoTox *autotoxes) +{ + for (uint32_t i = 0; i < tox_count; ++i) { + if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) < tox_count) { + return false; + } + } + + return true; +} + +/** + * returns a random index at which a list of booleans is false + * (some such index is required to exist) + */ +static uint32_t random_false_index(const Random *rng, bool *list, const uint32_t length) +{ + uint32_t index; + + do { + index = random_u32(rng) % length; + } while (list[index]); + + return index; +} + +static bool all_got_audio(AutoTox *autotoxes, const bool *disabled) +{ + uint32_t num_disabled = 0; + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + num_disabled += disabled[i]; + } + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + State *state = (State *)autotoxes[i].state; + + if (disabled[i] ^ (state->received_audio_num + != NUM_AV_GROUP_TOX - num_disabled - 1)) { + return false; + } + } + + return true; +} + +static void reset_received_audio(AutoTox *autotoxes) +{ + for (uint32_t j = 0; j < NUM_AV_GROUP_TOX; ++j) { + ((State *)autotoxes[j].state)->received_audio_num = 0; + } +} + +#define GROUP_AV_TEST_SAMPLES 960 + +/* must have + * GROUP_AV_AUDIO_ITERATIONS - NUM_AV_GROUP_TOX >= 2^n >= GROUP_JBUF_SIZE + * for some n, to give messages time to be relayed and to let the jitter + * buffers fill up. */ +#define GROUP_AV_AUDIO_ITERATIONS (8 + NUM_AV_GROUP_TOX) + +static bool test_audio(AutoTox *autotoxes, const bool *disabled, bool quiet) +{ + if (!quiet) { + printf("testing sending and receiving audio\n"); + } + + const int16_t PCM[GROUP_AV_TEST_SAMPLES] = {0}; + + reset_received_audio(autotoxes); + + for (uint32_t n = 0; n < GROUP_AV_AUDIO_ITERATIONS; ++n) { + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (disabled[i]) { + continue; + } + + if (toxav_group_send_audio(autotoxes[i].tox, 0, PCM, GROUP_AV_TEST_SAMPLES, 1, 48000) != 0) { + if (!quiet) { + ck_abort_msg("#%u failed to send audio", autotoxes[i].index); + } + + return false; + } + } + + iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); + + if (all_got_audio(autotoxes, disabled)) { + return true; + } + } + + if (!quiet) { + ck_abort_msg("group failed to receive audio"); + } + + return false; +} + +static void test_eventual_audio(AutoTox *autotoxes, const bool *disabled, uint64_t timeout) +{ + uint64_t start = autotoxes[0].clock; + + while (autotoxes[0].clock < start + timeout) { + if (test_audio(autotoxes, disabled, true) + && test_audio(autotoxes, disabled, true)) { + printf("audio test successful after %d seconds\n", (int)((autotoxes[0].clock - start) / 1000)); + return; + } + } + + printf("audio seems not to be getting through: testing again with errors.\n"); + test_audio(autotoxes, disabled, false); +} + +static void do_audio(AutoTox *autotoxes, uint32_t iterations) +{ + const int16_t PCM[GROUP_AV_TEST_SAMPLES] = {0}; + printf("running audio for %u iterations\n", iterations); + + for (uint32_t f = 0; f < iterations; ++f) { + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + ck_assert_msg(toxav_group_send_audio(autotoxes[i].tox, 0, PCM, GROUP_AV_TEST_SAMPLES, 1, 48000) == 0, + "#%u failed to send audio", autotoxes[i].index); + iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); + } + } +} + +// should agree with value in groupav.c +#define GROUP_JBUF_DEAD_SECONDS 4 + +#define JITTER_SETTLE_TIME (GROUP_JBUF_DEAD_SECONDS*1000 + NUM_AV_GROUP_TOX*ITERATION_INTERVAL*(GROUP_AV_AUDIO_ITERATIONS+1)) + +static void run_conference_tests(AutoTox *autotoxes) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + bool disabled[NUM_AV_GROUP_TOX] = {0}; + + test_audio(autotoxes, disabled, false); + + /* have everyone send audio for a bit so we can test that the audio + * sequnums dropping to 0 on restart isn't a problem */ + do_audio(autotoxes, 20); + + printf("letting random toxes timeout\n"); + bool disconnected[NUM_AV_GROUP_TOX] = {0}; + bool restarting[NUM_AV_GROUP_TOX] = {0}; + + ck_assert(NUM_AV_DISCONNECT < NUM_AV_GROUP_TOX); + + for (uint32_t i = 0; i < NUM_AV_DISCONNECT; ++i) { + uint32_t disconnect = random_false_index(rng, disconnected, NUM_AV_GROUP_TOX); + disconnected[disconnect] = true; + + if (i < NUM_AV_DISCONNECT / 2) { + restarting[disconnect] = true; + printf("Restarting #%u\n", autotoxes[disconnect].index); + } else { + printf("Disconnecting #%u\n", autotoxes[disconnect].index); + } + } + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (restarting[i]) { + save_autotox(&autotoxes[i]); + kill_autotox(&autotoxes[i]); + } + } + + disconnect_toxes(NUM_AV_GROUP_TOX, autotoxes, disconnected, restarting); + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (restarting[i]) { + reload(&autotoxes[i]); + } + } + + printf("reconnecting toxes\n"); + + do { + iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); + } while (!all_connected_to_group(NUM_AV_GROUP_TOX, autotoxes)); + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + if (restarting[i]) { + ck_assert_msg(!toxav_groupchat_av_enabled(autotoxes[i].tox, 0), + "#%u restarted but av enabled", autotoxes[i].index); + ck_assert_msg(toxav_groupchat_enable_av(autotoxes[i].tox, 0, audio_callback, &autotoxes[i]) == 0, + "#%u failed to re-enable av", autotoxes[i].index); + ck_assert_msg(toxav_groupchat_av_enabled(autotoxes[i].tox, 0), + "#%u av not enabled even after enabling", autotoxes[i].index); + } + } + + printf("testing audio\n"); + + /* Allow time for the jitter buffers to reset and for the group to become + * connected enough for lossy messages to get through + * (all_connected_to_group() only checks lossless connectivity, which is a + * looser condition). */ + test_eventual_audio(autotoxes, disabled, JITTER_SETTLE_TIME + NUM_AV_GROUP_TOX * 1000); + + printf("testing disabling av\n"); + + ck_assert(NUM_AV_DISABLE < NUM_AV_GROUP_TOX); + + for (uint32_t i = 0; i < NUM_AV_DISABLE; ++i) { + uint32_t disable = random_false_index(rng, disabled, NUM_AV_GROUP_TOX); + disabled[disable] = true; + printf("Disabling #%u\n", autotoxes[disable].index); + ck_assert_msg(toxav_groupchat_enable_av(autotoxes[disable].tox, 0, audio_callback, &autotoxes[disable]) != 0, + "#%u could enable already enabled av!", autotoxes[i].index); + ck_assert_msg(toxav_groupchat_disable_av(autotoxes[disable].tox, 0) == 0, + "#%u failed to disable av", autotoxes[i].index); + } + + // Run test without error to clear out messages from now-disabled peers. + test_audio(autotoxes, disabled, true); + + printf("testing audio with some peers having disabled their av\n"); + test_audio(autotoxes, disabled, false); + + for (uint32_t i = 0; i < NUM_AV_DISABLE; ++i) { + if (!disabled[i]) { + continue; + } + + disabled[i] = false; + ck_assert_msg(toxav_groupchat_disable_av(autotoxes[i].tox, 0) != 0, + "#%u could disable already disabled av!", autotoxes[i].index); + ck_assert_msg(!toxav_groupchat_av_enabled(autotoxes[i].tox, 0), + "#%u av enabled after disabling", autotoxes[i].index); + ck_assert_msg(toxav_groupchat_enable_av(autotoxes[i].tox, 0, audio_callback, &autotoxes[i]) == 0, + "#%u failed to re-enable av", autotoxes[i].index); + } + + printf("testing audio after re-enabling all av\n"); + test_eventual_audio(autotoxes, disabled, JITTER_SETTLE_TIME); +} + +static void test_groupav(AutoTox *autotoxes) +{ + const time_t test_start_time = time(nullptr); + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + tox_callback_self_connection_status(autotoxes[i].tox, &handle_self_connection_status); + tox_callback_friend_connection_status(autotoxes[i].tox, &handle_friend_connection_status); + tox_callback_conference_invite(autotoxes[i].tox, &handle_conference_invite); + tox_callback_conference_connected(autotoxes[i].tox, &handle_conference_connected); + } + + ck_assert_msg(toxav_add_av_groupchat(autotoxes[0].tox, audio_callback, &autotoxes[0]) != UINT32_MAX, + "failed to create group"); + printf("tox #%u: inviting its first friend\n", autotoxes[0].index); + ck_assert_msg(tox_conference_invite(autotoxes[0].tox, 0, 0, nullptr) != 0, "failed to invite friend"); + ((State *)autotoxes[0].state)->invited_next = true; + + + printf("waiting for invitations to be made\n"); + uint32_t invited_count = 0; + + do { + iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); + + invited_count = 0; + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + invited_count += ((State *)autotoxes[i].state)->invited_next; + } + } while (invited_count != NUM_AV_GROUP_TOX - 1); + + uint64_t pregroup_clock = autotoxes[0].clock; + printf("waiting for all toxes to be in the group\n"); + uint32_t fully_connected_count = 0; + + do { + fully_connected_count = 0; + iterate_all_wait(autotoxes, NUM_AV_GROUP_TOX, ITERATION_INTERVAL); + + for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { + Tox_Err_Conference_Peer_Query err; + uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, &err); + + if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { + peer_count = 0; + } + + fully_connected_count += peer_count == NUM_AV_GROUP_TOX; + } + } while (fully_connected_count != NUM_AV_GROUP_TOX); + + printf("group connected, took %d seconds\n", (int)((autotoxes[0].clock - pregroup_clock) / 1000)); + + run_conference_tests(autotoxes); + + printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + + run_auto_test(nullptr, NUM_AV_GROUP_TOX, test_groupav, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/conference_double_invite_test.c b/auto_tests/conference_double_invite_test.c new file mode 100644 index 0000000..201dd8f --- /dev/null +++ b/auto_tests/conference_double_invite_test.c @@ -0,0 +1,90 @@ +#include +#include + +typedef struct State { + bool self_online; + bool friend_online; + + bool joined; + uint32_t conference; +} State; + +#include "auto_test_support.h" + +static void handle_conference_invite( + Tox *tox, uint32_t friend_number, Tox_Conference_Type type, + const uint8_t *cookie, size_t length, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + fprintf(stderr, "handle_conference_invite(#%u, %u, %d, uint8_t[%u], _)\n", + autotox->index, friend_number, type, (unsigned)length); + fprintf(stderr, "tox%u joining conference\n", autotox->index); + + ck_assert_msg(!state->joined, "invitation callback generated for already joined conference"); + + if (friend_number != -1) { + Tox_Err_Conference_Join err; + state->conference = tox_conference_join(tox, friend_number, cookie, length, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, + "attempting to join the conference returned with an error: %d", err); + fprintf(stderr, "tox%u joined conference %u\n", autotox->index, state->conference); + state->joined = true; + } +} + +static void conference_double_invite_test(AutoTox *autotoxes) +{ + // Conference callbacks. + tox_callback_conference_invite(autotoxes[0].tox, handle_conference_invite); + tox_callback_conference_invite(autotoxes[1].tox, handle_conference_invite); + + State *state[2]; + state[0] = (State *)autotoxes[0].state; + state[1] = (State *)autotoxes[1].state; + + { + // Create new conference, tox0 is the founder. + Tox_Err_Conference_New err; + state[0]->conference = tox_conference_new(autotoxes[0].tox, &err); + state[0]->joined = true; + ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, + "attempting to create a new conference returned with an error: %d", err); + fprintf(stderr, "Created conference: index=%u\n", state[0]->conference); + } + + { + // Invite friend. + Tox_Err_Conference_Invite err; + tox_conference_invite(autotoxes[0].tox, 0, state[0]->conference, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, + "attempting to invite a friend returned with an error: %d", err); + fprintf(stderr, "tox0 invited tox1\n"); + } + + fprintf(stderr, "Waiting for invitation to arrive\n"); + + do { + iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); + } while (!state[0]->joined || !state[1]->joined); + + fprintf(stderr, "Invitations accepted\n"); + + fprintf(stderr, "Sending second invitation; should be ignored\n"); + tox_conference_invite(autotoxes[0].tox, 0, state[0]->conference, nullptr); + + iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + + run_auto_test(nullptr, 2, conference_double_invite_test, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/conference_invite_merge_test.c b/auto_tests/conference_invite_merge_test.c new file mode 100644 index 0000000..c02c59b --- /dev/null +++ b/auto_tests/conference_invite_merge_test.c @@ -0,0 +1,179 @@ +#include +#include +#include + +typedef struct State { + bool connected; + uint32_t conference; +} State; + +#define NUM_INVITE_MERGE_TOX 5 + +#include "auto_test_support.h" + +static void handle_conference_invite( + Tox *tox, uint32_t friend_number, Tox_Conference_Type type, + const uint8_t *cookie, size_t length, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + if (friend_number != -1) { + Tox_Err_Conference_Join err; + state->conference = tox_conference_join(tox, friend_number, cookie, length, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, + "attempting to join the conference returned with an error: %d", err); + fprintf(stderr, "#%u accepted invite to conference %u\n", autotox->index, state->conference); + } +} + +static void handle_conference_connected( + Tox *tox, uint32_t conference_number, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + fprintf(stderr, "#%u connected to conference %u\n", autotox->index, state->conference); + state->connected = true; +} + +static void wait_connected(AutoTox *autotoxes, AutoTox *autotox, uint32_t friendnumber) +{ + do { + iterate_all_wait(autotoxes, NUM_INVITE_MERGE_TOX, ITERATION_INTERVAL); + } while (tox_friend_get_connection_status(autotox->tox, friendnumber, nullptr) == TOX_CONNECTION_NONE); +} + +static void do_invite(AutoTox *autotoxes, AutoTox *inviter, AutoTox *invitee, uint32_t friendnum) +{ + fprintf(stderr, "#%u inviting #%u\n", inviter->index, invitee->index); + + Tox_Err_Conference_Invite err; + tox_conference_invite(inviter->tox, friendnum, ((State *)inviter->state)->conference, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, + "#%u attempting to invite #%u (friendnumber %u) returned with an error: %d", inviter->index, invitee->index, + friendnum, err); + + do { + iterate_all_wait(autotoxes, NUM_INVITE_MERGE_TOX, ITERATION_INTERVAL); + } while (!((State *)invitee->state)->connected); +} + +static bool group_complete(AutoTox *autotoxes) +{ + int c = -1, size = 0; + + for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { + if (!autotoxes[i].alive) { + continue; + } + + const int ct = tox_conference_peer_count(autotoxes[i].tox, ((State *)autotoxes[i].state)->conference, nullptr); + + if (c == -1) { + c = ct; + } else if (c != ct) { + return false; + } + + ++size; + } + + return (c == size); +} + +static void wait_group_complete(AutoTox *autotoxes) +{ + do { + iterate_all_wait(autotoxes, NUM_INVITE_MERGE_TOX, ITERATION_INTERVAL); + } while (!group_complete(autotoxes)); +} + +static void conference_invite_merge_test(AutoTox *autotoxes) +{ + // Test that an explicit invite between peers in different connected + // components will cause a split group to merge + + for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { + tox_callback_conference_invite(autotoxes[i].tox, &handle_conference_invite); + tox_callback_conference_connected(autotoxes[i].tox, &handle_conference_connected); + } + + State *state2 = (State *)autotoxes[2].state; + + { + // Create new conference, tox 2 is the founder. + Tox_Err_Conference_New err; + state2->conference = tox_conference_new(autotoxes[2].tox, &err); + state2->connected = true; + ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, + "attempting to create a new conference returned with an error: %d", err); + fprintf(stderr, "Created conference: index=%u\n", state2->conference); + } + + save_autotox(&autotoxes[2]); + + do_invite(autotoxes, &autotoxes[2], &autotoxes[1], 0); + do_invite(autotoxes, &autotoxes[1], &autotoxes[0], 0); + + save_autotox(&autotoxes[1]); + kill_autotox(&autotoxes[1]); + + do { + iterate_all_wait(autotoxes, NUM_INVITE_MERGE_TOX, ITERATION_INTERVAL); + } while (tox_conference_peer_count(autotoxes[2].tox, state2->conference, nullptr) != 1); + + do_invite(autotoxes, &autotoxes[2], &autotoxes[3], 1); + do_invite(autotoxes, &autotoxes[3], &autotoxes[4], 1); + + kill_autotox(&autotoxes[2]); + + reload(&autotoxes[1]); + + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(autotoxes[1].tox, public_key); + tox_friend_add_norequest(autotoxes[3].tox, public_key, nullptr); + tox_self_get_public_key(autotoxes[3].tox, public_key); + tox_friend_add_norequest(autotoxes[1].tox, public_key, nullptr); + wait_connected(autotoxes, &autotoxes[1], 2); + + do_invite(autotoxes, &autotoxes[1], &autotoxes[3], 2); + + fprintf(stderr, "Waiting for group to merge\n"); + + wait_group_complete(autotoxes); + + fprintf(stderr, "Group merged\n"); + + reload(&autotoxes[2]); + wait_connected(autotoxes, &autotoxes[2], 0); + do_invite(autotoxes, &autotoxes[2], &autotoxes[1], 0); + + fprintf(stderr, "Waiting for #2 to rejoin\n"); + + wait_group_complete(autotoxes); + + kill_autotox(&autotoxes[2]); + wait_group_complete(autotoxes); + reload(&autotoxes[2]); + wait_connected(autotoxes, &autotoxes[2], 0); + wait_connected(autotoxes, &autotoxes[1], 1); + + do_invite(autotoxes, &autotoxes[1], &autotoxes[2], 1); + + fprintf(stderr, "Waiting for #2 to rejoin\n"); + + wait_group_complete(autotoxes); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + + run_auto_test(nullptr, NUM_INVITE_MERGE_TOX, conference_invite_merge_test, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/conference_peer_nick_test.c b/auto_tests/conference_peer_nick_test.c new file mode 100644 index 0000000..d1d1297 --- /dev/null +++ b/auto_tests/conference_peer_nick_test.c @@ -0,0 +1,137 @@ +#include +#include + +typedef struct State { + bool self_online; + bool friend_online; + bool friend_in_group; + + bool joined; + uint32_t conference; +} State; + +#include "auto_test_support.h" + +static void handle_conference_invite( + Tox *tox, uint32_t friend_number, Tox_Conference_Type type, + const uint8_t *cookie, size_t length, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + fprintf(stderr, "handle_conference_invite(#%u, %u, %d, uint8_t[%u], _)\n", + autotox->index, friend_number, type, (unsigned)length); + fprintf(stderr, "tox%u joining conference\n", autotox->index); + + Tox_Err_Conference_Join err; + state->conference = tox_conference_join(tox, friend_number, cookie, length, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, + "attempting to join the conference returned with an error: %d", err); + fprintf(stderr, "tox%u joined conference %u\n", autotox->index, state->conference); + state->joined = true; +} + +static void handle_peer_list_changed(Tox *tox, uint32_t conference_number, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + fprintf(stderr, "handle_peer_list_changed(#%u, %u, _)\n", + autotox->index, conference_number); + + Tox_Err_Conference_Peer_Query err; + uint32_t const count = tox_conference_peer_count(tox, conference_number, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK, + "failed to get conference peer count: err = %d", err); + printf("tox%u has %u peers\n", autotox->index, count); + state->friend_in_group = count == 2; +} + +static void rebuild_peer_list(Tox *tox) +{ + for (uint32_t conference_number = 0; + conference_number < tox_conference_get_chatlist_size(tox); + ++conference_number) { + Tox_Err_Conference_Peer_Query err; + uint32_t const count = tox_conference_peer_count(tox, conference_number, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK, + "failed to get conference peer count for conference %u: err = %d", conference_number, err); + + for (uint32_t peer_number = 0; peer_number < count; peer_number++) { + size_t size = tox_conference_peer_get_name_size(tox, conference_number, peer_number, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK, + "failed to get conference peer %u's name size (conference = %u): err = %d", peer_number, conference_number, err); + + uint8_t *const name = (uint8_t *)malloc(size); + ck_assert(name != nullptr); + tox_conference_peer_get_name(tox, conference_number, peer_number, name, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK, + "failed to get conference peer %u's name (conference = %u): err = %d", peer_number, conference_number, err); + free(name); + } + } +} + +static void conference_peer_nick_test(AutoTox *autotoxes) +{ + // Conference callbacks. + tox_callback_conference_invite(autotoxes[0].tox, handle_conference_invite); + tox_callback_conference_invite(autotoxes[1].tox, handle_conference_invite); + tox_callback_conference_peer_list_changed(autotoxes[0].tox, handle_peer_list_changed); + tox_callback_conference_peer_list_changed(autotoxes[1].tox, handle_peer_list_changed); + + // Set the names of the toxes. + tox_self_set_name(autotoxes[0].tox, (const uint8_t *)"test-tox-0", 10, nullptr); + tox_self_set_name(autotoxes[1].tox, (const uint8_t *)"test-tox-1", 10, nullptr); + + State *state[2]; + state[0] = (State *)autotoxes[0].state; + state[1] = (State *)autotoxes[1].state; + + { + // Create new conference, tox0 is the founder. + Tox_Err_Conference_New err; + state[0]->conference = tox_conference_new(autotoxes[0].tox, &err); + state[0]->joined = true; + ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, + "attempting to create a new conference returned with an error: %d", err); + fprintf(stderr, "Created conference: index=%u\n", state[0]->conference); + } + + { + // Invite friend. + Tox_Err_Conference_Invite err; + tox_conference_invite(autotoxes[0].tox, 0, state[0]->conference, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, + "attempting to invite a friend returned with an error: %d", err); + fprintf(stderr, "tox0 invited tox1\n"); + } + + fprintf(stderr, "Waiting for invitation to arrive and peers to be in the group\n"); + + do { + iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); + } while (!state[0]->joined || !state[1]->joined || !state[0]->friend_in_group || !state[1]->friend_in_group); + + fprintf(stderr, "Running tox0, but not tox1, waiting for tox1 to drop out\n"); + + do { + iterate_all_wait(autotoxes, 1, 1000); + + // Rebuild peer list after every iteration. + rebuild_peer_list(autotoxes[0].tox); + } while (state[0]->friend_in_group); + + fprintf(stderr, "Invitations accepted\n"); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + run_auto_test(nullptr, 2, conference_peer_nick_test, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/conference_simple_test.c b/auto_tests/conference_simple_test.c new file mode 100644 index 0000000..2c445d4 --- /dev/null +++ b/auto_tests/conference_simple_test.c @@ -0,0 +1,259 @@ +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/tox.h" +#include "auto_test_support.h" +#include "check_compat.h" + +typedef struct State { + uint32_t id; + bool self_online; + bool friend_online; + bool invited_next; + + bool joined; + uint32_t conference; + + bool received; + + uint32_t peers; +} State; + +static void handle_self_connection_status(Tox *tox, Tox_Connection connection_status, void *user_data) +{ + State *state = (State *)user_data; + + fprintf(stderr, "self_connection_status(#%u, %d, _)\n", state->id, connection_status); + state->self_online = connection_status != TOX_CONNECTION_NONE; +} + +static void handle_friend_connection_status(Tox *tox, uint32_t friend_number, Tox_Connection connection_status, + void *user_data) +{ + State *state = (State *)user_data; + + fprintf(stderr, "handle_friend_connection_status(#%u, %u, %d, _)\n", state->id, friend_number, connection_status); + state->friend_online = connection_status != TOX_CONNECTION_NONE; +} + +static void handle_conference_invite(Tox *tox, uint32_t friend_number, Tox_Conference_Type type, const uint8_t *cookie, + size_t length, void *user_data) +{ + State *state = (State *)user_data; + + fprintf(stderr, "handle_conference_invite(#%u, %u, %d, uint8_t[%u], _)\n", + state->id, friend_number, type, (unsigned)length); + fprintf(stderr, "tox%u joining conference\n", state->id); + + { + Tox_Err_Conference_Join err; + state->conference = tox_conference_join(tox, friend_number, cookie, length, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "failed to join a conference: err = %d", err); + fprintf(stderr, "tox%u Joined conference %u\n", state->id, state->conference); + state->joined = true; + } +} + +static void handle_conference_message(Tox *tox, uint32_t conference_number, uint32_t peer_number, + Tox_Message_Type type, const uint8_t *message, size_t length, void *user_data) +{ + State *state = (State *)user_data; + + fprintf(stderr, "handle_conference_message(#%u, %u, %u, %d, uint8_t[%u], _)\n", + state->id, conference_number, peer_number, type, (unsigned)length); + + fprintf(stderr, "tox%u got message: %s\n", state->id, (const char *)message); + state->received = true; +} + +static void handle_conference_peer_list_changed(Tox *tox, uint32_t conference_number, void *user_data) +{ + State *state = (State *)user_data; + + fprintf(stderr, "handle_conference_peer_list_changed(#%u, %u, _)\n", + state->id, conference_number); + + Tox_Err_Conference_Peer_Query err; + uint32_t count = tox_conference_peer_count(tox, conference_number, &err); + + if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { + fprintf(stderr, "ERROR: %d\n", err); + exit(EXIT_FAILURE); + } + + fprintf(stderr, "tox%u has %u peers online\n", state->id, count); + state->peers = count; +} + +static void handle_conference_connected(Tox *tox, uint32_t conference_number, void *user_data) +{ + State *state = (State *)user_data; + + // We're tox2, so now we invite tox3. + if (state->id == 2 && !state->invited_next) { + Tox_Err_Conference_Invite err; + tox_conference_invite(tox, 1, state->conference, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox2 failed to invite tox3: err = %d", err); + + state->invited_next = true; + fprintf(stderr, "tox2 invited tox3\n"); + } +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + State state1 = {1}; + State state2 = {2}; + State state3 = {3}; + + // Create toxes. + Tox *tox1 = tox_new_log(nullptr, nullptr, &state1.id); + Tox *tox2 = tox_new_log(nullptr, nullptr, &state2.id); + Tox *tox3 = tox_new_log(nullptr, nullptr, &state3.id); + + // tox1 <-> tox2, tox2 <-> tox3 + uint8_t key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(tox2, key); + tox_friend_add_norequest(tox1, key, nullptr); // tox1 -> tox2 + tox_self_get_public_key(tox1, key); + tox_friend_add_norequest(tox2, key, nullptr); // tox2 -> tox1 + tox_self_get_public_key(tox3, key); + tox_friend_add_norequest(tox2, key, nullptr); // tox2 -> tox3 + tox_self_get_public_key(tox2, key); + tox_friend_add_norequest(tox3, key, nullptr); // tox3 -> tox2 + + printf("bootstrapping tox2 and tox3 off tox1\n"); + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); + + tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); + tox_bootstrap(tox3, "localhost", dht_port, dht_key, nullptr); + + // Connection callbacks. + tox_callback_self_connection_status(tox1, handle_self_connection_status); + tox_callback_self_connection_status(tox2, handle_self_connection_status); + tox_callback_self_connection_status(tox3, handle_self_connection_status); + + tox_callback_friend_connection_status(tox1, handle_friend_connection_status); + tox_callback_friend_connection_status(tox2, handle_friend_connection_status); + tox_callback_friend_connection_status(tox3, handle_friend_connection_status); + + // Conference callbacks. + tox_callback_conference_invite(tox1, handle_conference_invite); + tox_callback_conference_invite(tox2, handle_conference_invite); + tox_callback_conference_invite(tox3, handle_conference_invite); + + tox_callback_conference_connected(tox1, handle_conference_connected); + tox_callback_conference_connected(tox2, handle_conference_connected); + tox_callback_conference_connected(tox3, handle_conference_connected); + + tox_callback_conference_message(tox1, handle_conference_message); + tox_callback_conference_message(tox2, handle_conference_message); + tox_callback_conference_message(tox3, handle_conference_message); + + tox_callback_conference_peer_list_changed(tox1, handle_conference_peer_list_changed); + tox_callback_conference_peer_list_changed(tox2, handle_conference_peer_list_changed); + tox_callback_conference_peer_list_changed(tox3, handle_conference_peer_list_changed); + + // Wait for self connection. + fprintf(stderr, "Waiting for toxes to come online\n"); + + do { + tox_iterate(tox1, &state1); + tox_iterate(tox2, &state2); + tox_iterate(tox3, &state3); + + c_sleep(100); + } while (!state1.self_online || !state2.self_online || !state3.self_online); + + fprintf(stderr, "Toxes are online\n"); + + // Wait for friend connection. + fprintf(stderr, "Waiting for friends to connect\n"); + + do { + tox_iterate(tox1, &state1); + tox_iterate(tox2, &state2); + tox_iterate(tox3, &state3); + + c_sleep(100); + } while (!state1.friend_online || !state2.friend_online || !state3.friend_online); + + fprintf(stderr, "Friends are connected\n"); + + { + // Create new conference, tox1 is the founder. + Tox_Err_Conference_New err; + state1.conference = tox_conference_new(tox1, &err); + state1.joined = true; + ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, "failed to create a conference: err = %d", err); + fprintf(stderr, "Created conference: id = %u\n", state1.conference); + } + + { + // Invite friend. + Tox_Err_Conference_Invite err; + tox_conference_invite(tox1, 0, state1.conference, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "failed to invite a friend: err = %d", err); + state1.invited_next = true; + fprintf(stderr, "tox1 invited tox2\n"); + } + + fprintf(stderr, "Waiting for invitation to arrive\n"); + + do { + tox_iterate(tox1, &state1); + tox_iterate(tox2, &state2); + tox_iterate(tox3, &state3); + + c_sleep(100); + } while (!state1.joined || !state2.joined || !state3.joined); + + fprintf(stderr, "Invitations accepted\n"); + + fprintf(stderr, "Waiting for peers to come online\n"); + + do { + tox_iterate(tox1, &state1); + tox_iterate(tox2, &state2); + tox_iterate(tox3, &state3); + + c_sleep(100); + } while (state1.peers == 0 || state2.peers == 0 || state3.peers == 0); + + fprintf(stderr, "All peers are online\n"); + + { + fprintf(stderr, "tox1 sends a message to the group: \"hello!\"\n"); + Tox_Err_Conference_Send_Message err; + tox_conference_send_message(tox1, state1.conference, TOX_MESSAGE_TYPE_NORMAL, + (const uint8_t *)"hello!", 7, &err); + + if (err != TOX_ERR_CONFERENCE_SEND_MESSAGE_OK) { + fprintf(stderr, "ERROR: %d\n", err); + exit(EXIT_FAILURE); + } + } + + fprintf(stderr, "Waiting for messages to arrive\n"); + + do { + tox_iterate(tox1, &state1); + tox_iterate(tox2, &state2); + tox_iterate(tox3, &state3); + + c_sleep(100); + } while (!state2.received || !state3.received); + + fprintf(stderr, "Messages received. Test complete.\n"); + + tox_kill(tox3); + tox_kill(tox2); + tox_kill(tox1); + + return 0; +} diff --git a/auto_tests/conference_test.c b/auto_tests/conference_test.c new file mode 100644 index 0000000..66edddd --- /dev/null +++ b/auto_tests/conference_test.c @@ -0,0 +1,441 @@ +/* Auto Tests: Conferences. + */ + +#include +#include +#include +#include + +#include "../toxcore/util.h" + +#include "check_compat.h" + +#define NUM_GROUP_TOX 16 +#define NUM_DISCONNECT 8 +#define GROUP_MESSAGE "Install Gentoo" + +#define NAMELEN 9 +#define NAME_FORMAT_STR "Tox #%4u" +#define NEW_NAME_FORMAT_STR "New #%4u" + +typedef struct State { + bool invited_next; +} State; + +#include "auto_test_support.h" + +static void handle_self_connection_status( + Tox *tox, Tox_Connection connection_status, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + + if (connection_status != TOX_CONNECTION_NONE) { + printf("tox #%u: is now connected\n", autotox->index); + } else { + printf("tox #%u: is now disconnected\n", autotox->index); + } +} + +static void handle_friend_connection_status( + Tox *tox, uint32_t friendnumber, Tox_Connection connection_status, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + + if (connection_status != TOX_CONNECTION_NONE) { + printf("tox #%u: is now connected to friend %u\n", autotox->index, friendnumber); + } else { + printf("tox #%u: is now disconnected from friend %u\n", autotox->index, friendnumber); + } +} + +static void handle_conference_invite( + Tox *tox, uint32_t friendnumber, Tox_Conference_Type type, + const uint8_t *data, size_t length, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %d", autotox->index, type); + + Tox_Err_Conference_Join err; + uint32_t g_num = tox_conference_join(tox, friendnumber, data, length, &err); + + ck_assert_msg(err == TOX_ERR_CONFERENCE_JOIN_OK, "tox #%u: error joining group: %d", autotox->index, err); + ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", autotox->index); + + // Try joining again. We should only be allowed to join once. + tox_conference_join(tox, friendnumber, data, length, &err); + ck_assert_msg(err != TOX_ERR_CONFERENCE_JOIN_OK, + "tox #%u: joining groupchat twice should be impossible.", autotox->index); +} + +static void handle_conference_connected( + Tox *tox, uint32_t conference_number, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + if (state->invited_next || tox_self_get_friend_list_size(tox) <= 1) { + return; + } + + Tox_Err_Conference_Invite err; + tox_conference_invite(tox, 1, 0, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "tox #%u failed to invite next friend: err = %d", autotox->index, + err); + printf("tox #%u: invited next friend\n", autotox->index); + state->invited_next = true; +} + +static uint32_t num_recv; + +static void handle_conference_message( + Tox *tox, uint32_t groupnumber, uint32_t peernumber, Tox_Message_Type type, + const uint8_t *message, size_t length, void *user_data) +{ + if (length == (sizeof(GROUP_MESSAGE) - 1) && memcmp(message, GROUP_MESSAGE, sizeof(GROUP_MESSAGE) - 1) == 0) { + ++num_recv; + } +} + +static bool toxes_are_disconnected_from_group(uint32_t tox_count, AutoTox *autotoxes, + bool *disconnected) +{ + uint32_t num_disconnected = 0; + + for (uint32_t i = 0; i < tox_count; ++i) { + num_disconnected += disconnected[i]; + } + + for (uint32_t i = 0; i < tox_count; i++) { + if (disconnected[i]) { + continue; + } + + if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) > tox_count - num_disconnected) { + return false; + } + } + + return true; +} + +static void disconnect_toxes(uint32_t tox_count, AutoTox *autotoxes, + const bool *disconnect, const bool *exclude) +{ + /* Fake a network outage for a set of peers D by iterating only the other + * peers D' until the connections time out according to D', then iterating + * only D until the connections time out according to D. */ + + VLA(bool, disconnect_now, tox_count); + bool invert = false; + + do { + for (uint32_t i = 0; i < tox_count; ++i) { + disconnect_now[i] = exclude[i] || (invert ^ disconnect[i]); + } + + do { + for (uint32_t i = 0; i < tox_count; ++i) { + if (!disconnect_now[i]) { + tox_iterate(autotoxes[i].tox, &autotoxes[i]); + autotoxes[i].clock += 1000; + } + } + + c_sleep(20); + } while (!toxes_are_disconnected_from_group(tox_count, autotoxes, disconnect_now)); + + invert = !invert; + } while (invert); +} + +static bool all_connected_to_group(uint32_t tox_count, AutoTox *autotoxes) +{ + for (uint32_t i = 0; i < tox_count; i++) { + if (tox_conference_peer_count(autotoxes[i].tox, 0, nullptr) < tox_count) { + return false; + } + } + + return true; +} + +static bool names_propagated(uint32_t tox_count, AutoTox *autotoxes) +{ + for (uint32_t i = 0; i < tox_count; ++i) { + for (uint32_t j = 0; j < tox_count; ++j) { + const size_t len = tox_conference_peer_get_name_size(autotoxes[i].tox, 0, j, nullptr); + + if (len != NAMELEN) { + return false; + } + } + } + + return true; +} + + +/** + * returns a random index at which a list of booleans is false + * (some such index is required to exist) + */ +static uint32_t random_false_index(const Random *rng, bool *list, const uint32_t length) +{ + uint32_t index; + + do { + index = random_u32(rng) % length; + } while (list[index]); + + return index; +} + +static void run_conference_tests(AutoTox *autotoxes) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + /* disabling name change propagation check for now, as it occasionally + * fails due to disconnections too short to trigger freezing */ + const bool check_name_change_propagation = false; + + /* each peer should freeze at least its two friends, but freezing more + * should not be necessary */ + const uint32_t max_frozen = max_u32(2, NUM_DISCONNECT / 2); + printf("restricting number of frozen peers to %u\n", max_frozen); + + for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + Tox_Err_Conference_Set_Max_Offline err; + tox_conference_set_max_offline(autotoxes[i].tox, 0, max_frozen, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_OK, + "tox #%u failed to set max offline: err = %d", autotoxes[i].index, err); + } + + printf("letting random toxes timeout\n"); + bool disconnected[NUM_GROUP_TOX] = {0}; + bool restarting[NUM_GROUP_TOX] = {0}; + + ck_assert(NUM_DISCONNECT < NUM_GROUP_TOX); + + for (uint32_t i = 0; i < NUM_DISCONNECT; ++i) { + uint32_t disconnect = random_false_index(rng, disconnected, NUM_GROUP_TOX); + disconnected[disconnect] = true; + + if (i < NUM_DISCONNECT / 2) { + restarting[disconnect] = true; + printf("Restarting #%u\n", autotoxes[disconnect].index); + } else { + printf("Disconnecting #%u\n", autotoxes[disconnect].index); + } + } + + uint8_t *save[NUM_GROUP_TOX]; + size_t save_size[NUM_GROUP_TOX]; + + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + if (restarting[i]) { + save_size[i] = tox_get_savedata_size(autotoxes[i].tox); + ck_assert_msg(save_size[i] != 0, "save is invalid size %u", (unsigned)save_size[i]); + save[i] = (uint8_t *)malloc(save_size[i]); + ck_assert_msg(save[i] != nullptr, "malloc failed"); + tox_get_savedata(autotoxes[i].tox, save[i]); + tox_kill(autotoxes[i].tox); + } + } + + disconnect_toxes(NUM_GROUP_TOX, autotoxes, disconnected, restarting); + + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + if (restarting[i]) { + struct Tox_Options *const options = tox_options_new(nullptr); + ck_assert(options != nullptr); + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + tox_options_set_savedata_data(options, save[i], save_size[i]); + autotoxes[i].tox = tox_new_log(options, nullptr, &autotoxes[i].index); + ck_assert(autotoxes[i].tox != nullptr); + tox_options_free(options); + free(save[i]); + + set_mono_time_callback(&autotoxes[i]); + tox_conference_set_max_offline(autotoxes[i].tox, 0, max_frozen, nullptr); + } + } + + if (check_name_change_propagation) { + printf("changing names\n"); + + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + char name[NAMELEN + 1]; + snprintf(name, NAMELEN + 1, NEW_NAME_FORMAT_STR, autotoxes[i].index); + tox_self_set_name(autotoxes[i].tox, (const uint8_t *)name, NAMELEN, nullptr); + } + } + + for (uint16_t i = 0; i < NUM_GROUP_TOX; ++i) { + const uint32_t num_frozen = tox_conference_offline_peer_count(autotoxes[i].tox, 0, nullptr); + ck_assert_msg(num_frozen <= max_frozen, + "tox #%u has too many offline peers: %u\n", + autotoxes[i].index, num_frozen); + } + + printf("reconnecting toxes\n"); + + do { + iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); + } while (!all_connected_to_group(NUM_GROUP_TOX, autotoxes)); + + printf("running conference tests\n"); + + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + tox_callback_conference_message(autotoxes[i].tox, &handle_conference_message); + + iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); + } + + Tox_Err_Conference_Send_Message err; + ck_assert_msg( + tox_conference_send_message( + autotoxes[random_u32(rng) % NUM_GROUP_TOX].tox, 0, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)GROUP_MESSAGE, + sizeof(GROUP_MESSAGE) - 1, &err) != 0, "failed to send group message"); + ck_assert_msg( + err == TOX_ERR_CONFERENCE_SEND_MESSAGE_OK, "failed to send group message"); + num_recv = 0; + + for (uint8_t j = 0; j < NUM_GROUP_TOX * 2; ++j) { + iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); + } + + ck_assert_msg(num_recv == NUM_GROUP_TOX, "failed to recv group messages"); + + if (check_name_change_propagation) { + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + for (uint32_t j = 0; j < NUM_GROUP_TOX; ++j) { + uint8_t name[NAMELEN]; + tox_conference_peer_get_name(autotoxes[i].tox, 0, j, name, nullptr); + /* Note the toxes will have been reordered */ + ck_assert_msg(memcmp(name, "New", 3) == 0, + "name of #%u according to #%u not updated", autotoxes[j].index, autotoxes[i].index); + } + } + } + + for (uint32_t k = NUM_GROUP_TOX; k != 0 ; --k) { + tox_conference_delete(autotoxes[k - 1].tox, 0, nullptr); + + for (uint8_t j = 0; j < 10 || j < NUM_GROUP_TOX; ++j) { + iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); + } + + for (uint32_t i = 0; i < k - 1; ++i) { + uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, nullptr); + ck_assert_msg(peer_count == (k - 1), "\n\tBad number of group peers (post check)." + "\n\t\t\tExpected: %u but tox_instance(%u) only has: %u\n\n", + k - 1, i, (unsigned)peer_count); + } + } +} + +static void test_many_group(AutoTox *autotoxes) +{ + const time_t test_start_time = time(nullptr); + + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + tox_callback_self_connection_status(autotoxes[i].tox, &handle_self_connection_status); + tox_callback_friend_connection_status(autotoxes[i].tox, &handle_friend_connection_status); + tox_callback_conference_invite(autotoxes[i].tox, &handle_conference_invite); + tox_callback_conference_connected(autotoxes[i].tox, &handle_conference_connected); + + char name[NAMELEN + 1]; + snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, autotoxes[i].index); + tox_self_set_name(autotoxes[i].tox, (const uint8_t *)name, NAMELEN, nullptr); + } + + ck_assert_msg(tox_conference_new(autotoxes[0].tox, nullptr) != UINT32_MAX, "failed to create group"); + printf("tox #%u: inviting its first friend\n", autotoxes[0].index); + ck_assert_msg(tox_conference_invite(autotoxes[0].tox, 0, 0, nullptr) != 0, "failed to invite friend"); + ((State *)autotoxes[0].state)->invited_next = true; + ck_assert_msg(tox_conference_set_title(autotoxes[0].tox, 0, (const uint8_t *)"Gentoo", sizeof("Gentoo") - 1, + nullptr) != 0, + "failed to set group title"); + + + printf("waiting for invitations to be made\n"); + uint32_t invited_count = 0; + + do { + iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); + + invited_count = 0; + + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + invited_count += ((State *)autotoxes[i].state)->invited_next; + } + } while (invited_count != NUM_GROUP_TOX - 1); + + uint64_t pregroup_clock = autotoxes[0].clock; + printf("waiting for all toxes to be in the group\n"); + uint32_t fully_connected_count = 0; + + do { + fully_connected_count = 0; + printf("current peer counts: ["); + + iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); + + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + Tox_Err_Conference_Peer_Query err; + uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, &err); + + if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) { + peer_count = 0; + } + + fully_connected_count += peer_count == NUM_GROUP_TOX; + + if (i != 0) { + printf(", "); + } + + printf("%u", peer_count); + } + + printf("]\n"); + fflush(stdout); + } while (fully_connected_count != NUM_GROUP_TOX); + + for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { + uint32_t peer_count = tox_conference_peer_count(autotoxes[i].tox, 0, nullptr); + + ck_assert_msg(peer_count == NUM_GROUP_TOX, "\n\tBad number of group peers (pre check)." + "\n\t\t\tExpected: %d but tox_instance(%u) only has: %u\n\n", + NUM_GROUP_TOX, i, (unsigned)peer_count); + + uint8_t title[2048]; + size_t ret = tox_conference_get_title_size(autotoxes[i].tox, 0, nullptr); + ck_assert_msg(ret == sizeof("Gentoo") - 1, "Wrong title length"); + tox_conference_get_title(autotoxes[i].tox, 0, title, nullptr); + ck_assert_msg(memcmp("Gentoo", title, ret) == 0, "Wrong title"); + } + + printf("waiting for names to propagate\n"); + + do { + iterate_all_wait(autotoxes, NUM_GROUP_TOX, ITERATION_INTERVAL); + } while (!names_propagated(NUM_GROUP_TOX, autotoxes)); + + printf("group connected, took %d seconds\n", (int)((autotoxes[0].clock - pregroup_clock) / 1000)); + + run_conference_tests(autotoxes); + + printf("test_many_group succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + + run_auto_test(nullptr, NUM_GROUP_TOX, test_many_group, sizeof(State), &options); + return 0; +} diff --git a/auto_tests/conference_two_test.c b/auto_tests/conference_two_test.c new file mode 100644 index 0000000..3a8b102 --- /dev/null +++ b/auto_tests/conference_two_test.c @@ -0,0 +1,27 @@ +// This test checks that we can create two conferences and quit properly. +// +// This test triggers a different code path than if we only allocate a single +// conference. This is the simplest test possible that triggers it. + +#include "../testing/misc_tools.h" +#include "../toxcore/tox.h" +#include "auto_test_support.h" +#include "check_compat.h" + +int main(void) +{ + // Create toxes. + uint32_t id = 1; + Tox *tox1 = tox_new_log(nullptr, nullptr, &id); + + // Create two conferences and then exit. + Tox_Err_Conference_New err; + tox_conference_new(tox1, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, "failed to create conference 1: %d", err); + tox_conference_new(tox1, &err); + ck_assert_msg(err == TOX_ERR_CONFERENCE_NEW_OK, "failed to create conference 2: %d", err); + + tox_kill(tox1); + + return 0; +} diff --git a/auto_tests/crypto_test.c b/auto_tests/crypto_test.c new file mode 100644 index 0000000..f4fdf3e --- /dev/null +++ b/auto_tests/crypto_test.c @@ -0,0 +1,354 @@ +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/crypto_core.h" +#include "../toxcore/net_crypto.h" +#include "check_compat.h" + +static void rand_bytes(const Random *rng, uint8_t *b, size_t blen) +{ + size_t i; + + for (i = 0; i < blen; i++) { + b[i] = random_u08(rng); + } +} + +// These test vectors are from libsodium's test suite + +static const uint8_t alicesk[32] = { + 0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d, + 0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2, 0x66, 0x45, + 0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a, + 0xb1, 0x77, 0xfb, 0xa5, 0x1d, 0xb9, 0x2c, 0x2a +}; + +static const uint8_t bobpk[32] = { + 0xde, 0x9e, 0xdb, 0x7d, 0x7b, 0x7d, 0xc1, 0xb4, + 0xd3, 0x5b, 0x61, 0xc2, 0xec, 0xe4, 0x35, 0x37, + 0x3f, 0x83, 0x43, 0xc8, 0x5b, 0x78, 0x67, 0x4d, + 0xad, 0xfc, 0x7e, 0x14, 0x6f, 0x88, 0x2b, 0x4f +}; + +static const uint8_t test_nonce[24] = { + 0x69, 0x69, 0x6e, 0xe9, 0x55, 0xb6, 0x2b, 0x73, + 0xcd, 0x62, 0xbd, 0xa8, 0x75, 0xfc, 0x73, 0xd6, + 0x82, 0x19, 0xe0, 0x03, 0x6b, 0x7a, 0x0b, 0x37 +}; + +static const uint8_t test_m[131] = { + 0xbe, 0x07, 0x5f, 0xc5, 0x3c, 0x81, 0xf2, 0xd5, + 0xcf, 0x14, 0x13, 0x16, 0xeb, 0xeb, 0x0c, 0x7b, + 0x52, 0x28, 0xc5, 0x2a, 0x4c, 0x62, 0xcb, 0xd4, + 0x4b, 0x66, 0x84, 0x9b, 0x64, 0x24, 0x4f, 0xfc, + 0xe5, 0xec, 0xba, 0xaf, 0x33, 0xbd, 0x75, 0x1a, + 0x1a, 0xc7, 0x28, 0xd4, 0x5e, 0x6c, 0x61, 0x29, + 0x6c, 0xdc, 0x3c, 0x01, 0x23, 0x35, 0x61, 0xf4, + 0x1d, 0xb6, 0x6c, 0xce, 0x31, 0x4a, 0xdb, 0x31, + 0x0e, 0x3b, 0xe8, 0x25, 0x0c, 0x46, 0xf0, 0x6d, + 0xce, 0xea, 0x3a, 0x7f, 0xa1, 0x34, 0x80, 0x57, + 0xe2, 0xf6, 0x55, 0x6a, 0xd6, 0xb1, 0x31, 0x8a, + 0x02, 0x4a, 0x83, 0x8f, 0x21, 0xaf, 0x1f, 0xde, + 0x04, 0x89, 0x77, 0xeb, 0x48, 0xf5, 0x9f, 0xfd, + 0x49, 0x24, 0xca, 0x1c, 0x60, 0x90, 0x2e, 0x52, + 0xf0, 0xa0, 0x89, 0xbc, 0x76, 0x89, 0x70, 0x40, + 0xe0, 0x82, 0xf9, 0x37, 0x76, 0x38, 0x48, 0x64, + 0x5e, 0x07, 0x05 +}; + +static const uint8_t test_c[147] = { + 0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5, + 0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, 0x05, 0xd9, + 0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73, + 0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, 0x76, 0xce, + 0x48, 0x33, 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4, + 0x47, 0x6f, 0xb8, 0xc5, 0x31, 0xa1, 0x18, 0x6a, + 0xc0, 0xdf, 0xc1, 0x7c, 0x98, 0xdc, 0xe8, 0x7b, + 0x4d, 0xa7, 0xf0, 0x11, 0xec, 0x48, 0xc9, 0x72, + 0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, 0x8f, 0xe2, + 0x27, 0x0d, 0x6f, 0xb8, 0x63, 0xd5, 0x17, 0x38, + 0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, 0xcc, 0x8a, + 0xb9, 0x32, 0x16, 0x45, 0x48, 0xe5, 0x26, 0xae, + 0x90, 0x22, 0x43, 0x68, 0x51, 0x7a, 0xcf, 0xea, + 0xbd, 0x6b, 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda, + 0x99, 0x83, 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde, + 0x56, 0x24, 0x4a, 0x9e, 0x88, 0xd5, 0xf9, 0xb3, + 0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6, + 0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, 0x5a, 0x74, + 0xe3, 0x55, 0xa5 +}; + +static void test_known(void) +{ + uint8_t c[147]; + uint8_t m[131]; + uint16_t clen, mlen; + + ck_assert_msg(sizeof(c) == sizeof(m) + CRYPTO_MAC_SIZE * sizeof(uint8_t), + "cyphertext should be CRYPTO_MAC_SIZE bytes longer than plaintext"); + ck_assert_msg(sizeof(test_c) == sizeof(c), "sanity check failed"); + ck_assert_msg(sizeof(test_m) == sizeof(m), "sanity check failed"); + + clen = encrypt_data(bobpk, alicesk, test_nonce, test_m, sizeof(test_m) / sizeof(uint8_t), c); + + ck_assert_msg(memcmp(test_c, c, sizeof(c)) == 0, "cyphertext doesn't match test vector"); + ck_assert_msg(clen == sizeof(c) / sizeof(uint8_t), "wrong ciphertext length"); + + mlen = decrypt_data(bobpk, alicesk, test_nonce, test_c, sizeof(test_c) / sizeof(uint8_t), m); + + ck_assert_msg(memcmp(test_m, m, sizeof(m)) == 0, "decrypted text doesn't match test vector"); + ck_assert_msg(mlen == sizeof(m) / sizeof(uint8_t), "wrong plaintext length"); +} + +static void test_fast_known(void) +{ + uint8_t k[CRYPTO_SHARED_KEY_SIZE]; + uint8_t c[147]; + uint8_t m[131]; + uint16_t clen, mlen; + + encrypt_precompute(bobpk, alicesk, k); + + ck_assert_msg(sizeof(c) == sizeof(m) + CRYPTO_MAC_SIZE * sizeof(uint8_t), + "cyphertext should be CRYPTO_MAC_SIZE bytes longer than plaintext"); + ck_assert_msg(sizeof(test_c) == sizeof(c), "sanity check failed"); + ck_assert_msg(sizeof(test_m) == sizeof(m), "sanity check failed"); + + clen = encrypt_data_symmetric(k, test_nonce, test_m, sizeof(test_m) / sizeof(uint8_t), c); + + ck_assert_msg(memcmp(test_c, c, sizeof(c)) == 0, "cyphertext doesn't match test vector"); + ck_assert_msg(clen == sizeof(c) / sizeof(uint8_t), "wrong ciphertext length"); + + mlen = decrypt_data_symmetric(k, test_nonce, test_c, sizeof(test_c) / sizeof(uint8_t), m); + + ck_assert_msg(memcmp(test_m, m, sizeof(m)) == 0, "decrypted text doesn't match test vector"); + ck_assert_msg(mlen == sizeof(m) / sizeof(uint8_t), "wrong plaintext length"); +} + +static void test_endtoend(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + + // Test 100 random messages and keypairs + for (uint8_t testno = 0; testno < 100; testno++) { + uint8_t pk1[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t sk1[CRYPTO_SECRET_KEY_SIZE]; + uint8_t pk2[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t sk2[CRYPTO_SECRET_KEY_SIZE]; + uint8_t k1[CRYPTO_SHARED_KEY_SIZE]; + uint8_t k2[CRYPTO_SHARED_KEY_SIZE]; + + uint8_t n[CRYPTO_NONCE_SIZE]; + + enum { M_SIZE = 50 }; + uint8_t m[M_SIZE]; + uint8_t c1[sizeof(m) + CRYPTO_MAC_SIZE]; + uint8_t c2[sizeof(m) + CRYPTO_MAC_SIZE]; + uint8_t c3[sizeof(m) + CRYPTO_MAC_SIZE]; + uint8_t c4[sizeof(m) + CRYPTO_MAC_SIZE]; + uint8_t m1[sizeof(m)]; + uint8_t m2[sizeof(m)]; + uint8_t m3[sizeof(m)]; + uint8_t m4[sizeof(m)]; + + //Generate random message (random length from 10 to 50) + const uint16_t mlen = (random_u32(rng) % (M_SIZE - 10)) + 10; + rand_bytes(rng, m, mlen); + rand_bytes(rng, n, CRYPTO_NONCE_SIZE); + + //Generate keypairs + crypto_new_keypair(rng, pk1, sk1); + crypto_new_keypair(rng, pk2, sk2); + + //Precompute shared keys + encrypt_precompute(pk2, sk1, k1); + encrypt_precompute(pk1, sk2, k2); + + ck_assert_msg(memcmp(k1, k2, CRYPTO_SHARED_KEY_SIZE) == 0, "encrypt_precompute: bad"); + + //Encrypt all four ways + const uint16_t c1len = encrypt_data(pk2, sk1, n, m, mlen, c1); + const uint16_t c2len = encrypt_data(pk1, sk2, n, m, mlen, c2); + const uint16_t c3len = encrypt_data_symmetric(k1, n, m, mlen, c3); + const uint16_t c4len = encrypt_data_symmetric(k2, n, m, mlen, c4); + + ck_assert_msg(c1len == c2len && c1len == c3len && c1len == c4len, "cyphertext lengths differ"); + ck_assert_msg(c1len == mlen + (uint16_t)CRYPTO_MAC_SIZE, "wrong cyphertext length"); + ck_assert_msg(memcmp(c1, c2, c1len) == 0 && memcmp(c1, c3, c1len) == 0 + && memcmp(c1, c4, c1len) == 0, "crypertexts differ"); + + //Decrypt all four ways + const uint16_t m1len = decrypt_data(pk2, sk1, n, c1, c1len, m1); + const uint16_t m2len = decrypt_data(pk1, sk2, n, c1, c1len, m2); + const uint16_t m3len = decrypt_data_symmetric(k1, n, c1, c1len, m3); + const uint16_t m4len = decrypt_data_symmetric(k2, n, c1, c1len, m4); + + ck_assert_msg(m1len == m2len && m1len == m3len && m1len == m4len, "decrypted text lengths differ"); + ck_assert_msg(m1len == mlen, "wrong decrypted text length"); + ck_assert_msg(memcmp(m1, m2, mlen) == 0 && memcmp(m1, m3, mlen) == 0 + && memcmp(m1, m4, mlen) == 0, "decrypted texts differ"); + ck_assert_msg(memcmp(m1, m, mlen) == 0, "wrong decrypted text"); + } +} + +static void test_large_data(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + uint8_t k[CRYPTO_SHARED_KEY_SIZE]; + uint8_t n[CRYPTO_NONCE_SIZE]; + + const size_t m1_size = MAX_CRYPTO_PACKET_SIZE - CRYPTO_MAC_SIZE; + uint8_t *m1 = (uint8_t *)malloc(m1_size); + uint8_t *c1 = (uint8_t *)malloc(m1_size + CRYPTO_MAC_SIZE); + uint8_t *m1prime = (uint8_t *)malloc(m1_size); + + const size_t m2_size = MAX_CRYPTO_PACKET_SIZE - CRYPTO_MAC_SIZE; + uint8_t *m2 = (uint8_t *)malloc(m2_size); + uint8_t *c2 = (uint8_t *)malloc(m2_size + CRYPTO_MAC_SIZE); + + ck_assert(m1 != nullptr && c1 != nullptr && m1prime != nullptr && m2 != nullptr && c2 != nullptr); + + //Generate random messages + rand_bytes(rng, m1, m1_size); + rand_bytes(rng, m2, m2_size); + rand_bytes(rng, n, CRYPTO_NONCE_SIZE); + + //Generate key + rand_bytes(rng, k, CRYPTO_SHARED_KEY_SIZE); + + const uint16_t c1len = encrypt_data_symmetric(k, n, m1, m1_size, c1); + const uint16_t c2len = encrypt_data_symmetric(k, n, m2, m2_size, c2); + + ck_assert_msg(c1len == m1_size + CRYPTO_MAC_SIZE, "could not encrypt"); + ck_assert_msg(c2len == m2_size + CRYPTO_MAC_SIZE, "could not encrypt"); + + const uint16_t m1plen = decrypt_data_symmetric(k, n, c1, c1len, m1prime); + + ck_assert_msg(m1plen == m1_size, "decrypted text lengths differ"); + ck_assert_msg(memcmp(m1prime, m1, m1_size) == 0, "decrypted texts differ"); + + free(c2); + free(m2); + free(m1prime); + free(c1); + free(m1); +} + +static void test_large_data_symmetric(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + uint8_t k[CRYPTO_SYMMETRIC_KEY_SIZE]; + + uint8_t n[CRYPTO_NONCE_SIZE]; + + const size_t m1_size = 16 * 16 * 16; + uint8_t *m1 = (uint8_t *)malloc(m1_size); + uint8_t *c1 = (uint8_t *)malloc(m1_size + CRYPTO_MAC_SIZE); + uint8_t *m1prime = (uint8_t *)malloc(m1_size); + + ck_assert(m1 != nullptr && c1 != nullptr && m1prime != nullptr); + + //Generate random messages + rand_bytes(rng, m1, m1_size); + rand_bytes(rng, n, CRYPTO_NONCE_SIZE); + + //Generate key + new_symmetric_key(rng, k); + + const uint16_t c1len = encrypt_data_symmetric(k, n, m1, m1_size, c1); + ck_assert_msg(c1len == m1_size + CRYPTO_MAC_SIZE, "could not encrypt data"); + + const uint16_t m1plen = decrypt_data_symmetric(k, n, c1, c1len, m1prime); + + ck_assert_msg(m1plen == m1_size, "decrypted text lengths differ"); + ck_assert_msg(memcmp(m1prime, m1, m1_size) == 0, "decrypted texts differ"); + + free(m1prime); + free(c1); + free(m1); +} + +static void increment_nonce_number_cmp(uint8_t *nonce, uint32_t num) +{ + uint32_t num1, num2; + memcpy(&num1, nonce + (CRYPTO_NONCE_SIZE - sizeof(num1)), sizeof(num1)); + num1 = net_ntohl(num1); + num2 = num + num1; + + if (num2 < num1) { + for (uint16_t i = CRYPTO_NONCE_SIZE - sizeof(num1); i != 0; --i) { + ++nonce[i - 1]; + + if (nonce[i - 1] != 0) { + break; + } + } + } + + num2 = net_htonl(num2); + memcpy(nonce + (CRYPTO_NONCE_SIZE - sizeof(num2)), &num2, sizeof(num2)); +} + +static void test_increment_nonce(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + + uint32_t i; + + uint8_t n[CRYPTO_NONCE_SIZE]; + + for (i = 0; i < CRYPTO_NONCE_SIZE; ++i) { + n[i] = random_u08(rng); + } + + uint8_t n1[CRYPTO_NONCE_SIZE]; + + memcpy(n1, n, CRYPTO_NONCE_SIZE); + + for (i = 0; i < (1 << 18); ++i) { + increment_nonce_number_cmp(n, 1); + increment_nonce(n1); + ck_assert_msg(memcmp(n, n1, CRYPTO_NONCE_SIZE) == 0, "Bad increment_nonce function"); + } + + for (i = 0; i < (1 << 18); ++i) { + const uint32_t r = random_u32(rng); + increment_nonce_number_cmp(n, r); + increment_nonce_number(n1, r); + ck_assert_msg(memcmp(n, n1, CRYPTO_NONCE_SIZE) == 0, "Bad increment_nonce_number function"); + } +} + +static void test_memzero(void) +{ + uint8_t src[sizeof(test_c)]; + memcpy(src, test_c, sizeof(test_c)); + + crypto_memzero(src, sizeof(src)); + size_t i; + + for (i = 0; i < sizeof(src); i++) { + ck_assert_msg(src[i] == 0, "Memory is not zeroed"); + } +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_known(); + test_fast_known(); + test_endtoend(); /* waiting up to 15 seconds */ + test_large_data(); + test_large_data_symmetric(); + test_increment_nonce(); + test_memzero(); + + return 0; +} diff --git a/auto_tests/data/save.tox b/auto_tests/data/save.tox new file mode 100644 index 0000000..03430c8 Binary files /dev/null and b/auto_tests/data/save.tox differ diff --git a/auto_tests/dht_getnodes_api_test.c b/auto_tests/dht_getnodes_api_test.c new file mode 100644 index 0000000..8071f34 --- /dev/null +++ b/auto_tests/dht_getnodes_api_test.c @@ -0,0 +1,152 @@ +/** + * This autotest creates a small local DHT and makes sure that each peer can crawl + * the entire DHT using the DHT getnodes api functions. + */ + +#include +#include +#include +#include + +#include "../toxcore/tox.h" +#include "../toxcore/tox_private.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#define NUM_TOXES 30 + +typedef struct Dht_Node { + uint8_t public_key[TOX_DHT_NODE_PUBLIC_KEY_SIZE]; + char ip[TOX_DHT_NODE_IP_STRING_SIZE]; + uint16_t port; +} Dht_Node; + +typedef struct State { + Dht_Node **nodes; + size_t num_nodes; + uint8_t **public_key_list; +} State; + +static void free_nodes(Dht_Node **nodes, size_t num_nodes) +{ + for (size_t i = 0; i < num_nodes; ++i) { + free(nodes[i]); + } + + free(nodes); +} + +static bool node_crawled(Dht_Node **nodes, size_t num_nodes, const uint8_t *public_key) +{ + for (size_t i = 0; i < num_nodes; ++i) { + if (memcmp(nodes[i]->public_key, public_key, TOX_DHT_NODE_PUBLIC_KEY_SIZE) == 0) { + return true; + } + } + + return false; +} + +static bool all_nodes_crawled(const AutoTox *autotoxes, uint32_t num_toxes, uint8_t **public_key_list) +{ + for (uint32_t i = 0; i < num_toxes; ++i) { + const State *state = (const State *)autotoxes[i].state; + + // make sure each peer has crawled the correct number of nodes + if (state->num_nodes < num_toxes) { + return false; + } + } + + for (uint32_t i = 0; i < num_toxes; ++i) { + const State *state = (const State *)autotoxes[i].state; + + // make sure each peer has the full list of public keys + for (uint32_t j = 0; j < num_toxes; ++j) { + if (!node_crawled(state->nodes, state->num_nodes, public_key_list[j])) { + return false; + } + } + } + + return true; +} + +static void getnodes_response_cb(Tox *tox, const uint8_t *public_key, const char *ip, uint16_t port, void *user_data) +{ + ck_assert(user_data != nullptr); + + AutoTox *autotoxes = (AutoTox *)user_data; + State *state = (State *)autotoxes->state; + + if (node_crawled(state->nodes, state->num_nodes, public_key)) { + return; + } + + ck_assert(state->num_nodes < NUM_TOXES); + + Dht_Node *node = (Dht_Node *)calloc(1, sizeof(Dht_Node)); + ck_assert(node != nullptr); + + memcpy(node->public_key, public_key, TOX_DHT_NODE_PUBLIC_KEY_SIZE); + snprintf(node->ip, sizeof(node->ip), "%s", ip); + node->port = port; + + state->nodes[state->num_nodes] = node; + ++state->num_nodes; + + // ask new node to give us their close nodes to every public key + for (size_t i = 0; i < NUM_TOXES; ++i) { + tox_dht_get_nodes(tox, public_key, ip, port, state->public_key_list[i], nullptr); + } +} + +static void test_dht_getnodes(AutoTox *autotoxes) +{ + ck_assert(NUM_TOXES >= 2); + + uint8_t **public_key_list = (uint8_t **)calloc(NUM_TOXES, sizeof(uint8_t *)); + ck_assert(public_key_list != nullptr); + + for (size_t i = 0; i < NUM_TOXES; ++i) { + State *state = (State *)autotoxes[i].state; + + state->nodes = (Dht_Node **)calloc(NUM_TOXES, sizeof(Dht_Node *)); + ck_assert(state->nodes != nullptr); + + state->num_nodes = 0; + state->public_key_list = public_key_list; + + public_key_list[i] = (uint8_t *)malloc(sizeof(uint8_t) * TOX_PUBLIC_KEY_SIZE); + ck_assert(public_key_list[i] != nullptr); + + tox_self_get_dht_id(autotoxes[i].tox, public_key_list[i]); + tox_callback_dht_get_nodes_response(autotoxes[i].tox, getnodes_response_cb); + } + + while (!all_nodes_crawled(autotoxes, NUM_TOXES, public_key_list)) { + iterate_all_wait(autotoxes, NUM_TOXES, ITERATION_INTERVAL); + } + + for (size_t i = 0; i < NUM_TOXES; ++i) { + State *state = (State *)autotoxes[i].state; + free_nodes(state->nodes, state->num_nodes); + free(public_key_list[i]); + } + + free(public_key_list); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + + run_auto_test(nullptr, NUM_TOXES, test_dht_getnodes, sizeof(State), &options); + + return 0; +} + +#undef NUM_TOXES diff --git a/auto_tests/encryptsave_test.c b/auto_tests/encryptsave_test.c new file mode 100644 index 0000000..fb49c4e --- /dev/null +++ b/auto_tests/encryptsave_test.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include + +#ifndef VANILLA_NACL +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/crypto_core.h" +#include "../toxcore/tox.h" +#include "../toxencryptsave/toxencryptsave.h" +#include "auto_test_support.h" +#include "check_compat.h" + +static unsigned char test_salt[TOX_PASS_SALT_LENGTH] = {0xB1, 0xC2, 0x09, 0xEE, 0x50, 0x6C, 0xF0, 0x20, 0xC4, 0xD6, 0xEB, 0xC0, 0x44, 0x51, 0x3B, 0x60, 0x4B, 0x39, 0x4A, 0xCF, 0x09, 0x53, 0x4F, 0xEA, 0x08, 0x41, 0xFA, 0xCA, 0x66, 0xD2, 0x68, 0x7F}; +static unsigned char known_key[TOX_PASS_KEY_LENGTH] = {0x29, 0x36, 0x1c, 0x9e, 0x65, 0xbb, 0x46, 0x8b, 0xde, 0xa1, 0xac, 0xf, 0xd5, 0x11, 0x81, 0xc8, 0x29, 0x28, 0x17, 0x23, 0xa6, 0xc3, 0x6b, 0x77, 0x2e, 0xd7, 0xd3, 0x10, 0xeb, 0xd2, 0xf7, 0xc8}; +static const char *pw = "hunter2"; +static unsigned int pwlen = 7; + +static unsigned char known_key2[CRYPTO_SHARED_KEY_SIZE] = {0x7a, 0xfa, 0x95, 0x45, 0x36, 0x8a, 0xa2, 0x5c, 0x40, 0xfd, 0xc0, 0xe2, 0x35, 0x8, 0x7, 0x88, 0xfa, 0xf9, 0x37, 0x86, 0xeb, 0xff, 0x50, 0x4f, 0x3, 0xe2, 0xf6, 0xd9, 0xef, 0x9, 0x17, 0x1}; +// same as above, except standard opslimit instead of extra ops limit for test_known_kdf, and hash pw before kdf for compat + +/* cause I'm shameless */ +static void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) { + return; + } + + if (length == 7 && memcmp("Gentoo", data, 7) == 0) { + tox_friend_add_norequest(m, public_key, nullptr); + } +} + +static void test_known_kdf(void) +{ + unsigned char out[CRYPTO_SHARED_KEY_SIZE]; + int16_t res = crypto_pwhash_scryptsalsa208sha256(out, + CRYPTO_SHARED_KEY_SIZE, + pw, + pwlen, + test_salt, + crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE * 8, + crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE); + ck_assert_msg(res != -1, "crypto function failed"); + ck_assert_msg(memcmp(out, known_key, CRYPTO_SHARED_KEY_SIZE) == 0, "derived key is wrong"); +} + +static void test_save_friend(void) +{ + Tox *tox1 = tox_new_log(nullptr, nullptr, nullptr); + Tox *tox2 = tox_new_log(nullptr, nullptr, nullptr); + ck_assert_msg(tox1 || tox2, "Failed to create 2 tox instances"); + tox_callback_friend_request(tox2, accept_friend_request); + uint8_t address[TOX_ADDRESS_SIZE]; + tox_self_get_address(tox2, address); + uint32_t test = tox_friend_add(tox1, address, (const uint8_t *)"Gentoo", 7, nullptr); + ck_assert_msg(test != UINT32_MAX, "Failed to add friend"); + + size_t size = tox_get_savedata_size(tox1); + uint8_t *data = (uint8_t *)malloc(size); + ck_assert(data != nullptr); + tox_get_savedata(tox1, data); + size_t size2 = size + TOX_PASS_ENCRYPTION_EXTRA_LENGTH; + uint8_t *enc_data = (uint8_t *)malloc(size2); + ck_assert(enc_data != nullptr); + Tox_Err_Encryption error1; + bool ret = tox_pass_encrypt(data, size, (const uint8_t *)"correcthorsebatterystaple", 25, enc_data, &error1); + ck_assert_msg(ret, "failed to encrypted save: %d", error1); + ck_assert_msg(tox_is_data_encrypted(enc_data), "magic number missing"); + + struct Tox_Options *options = tox_options_new(nullptr); + ck_assert(options != nullptr); + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + tox_options_set_savedata_data(options, enc_data, size2); + + Tox_Err_New err2; + Tox *tox3 = tox_new_log(options, &err2, nullptr); + ck_assert_msg(err2 == TOX_ERR_NEW_LOAD_ENCRYPTED, "wrong error! %d. should fail with %d", err2, + TOX_ERR_NEW_LOAD_ENCRYPTED); + ck_assert_msg(tox3 == nullptr, "tox_new with error should return NULL"); + uint8_t *dec_data = (uint8_t *)malloc(size); + ck_assert(dec_data != nullptr); + Tox_Err_Decryption err3; + ret = tox_pass_decrypt(enc_data, size2, (const uint8_t *)"correcthorsebatterystaple", 25, dec_data, &err3); + ck_assert_msg(ret, "failed to decrypt save: %d", err3); + tox_options_set_savedata_data(options, dec_data, size); + tox3 = tox_new_log(options, &err2, nullptr); + ck_assert_msg(err2 == TOX_ERR_NEW_OK, "failed to load from decrypted data: %d", err2); + uint8_t address2[TOX_PUBLIC_KEY_SIZE]; + ret = tox_friend_get_public_key(tox3, 0, address2, nullptr); + ck_assert_msg(ret, "no friends!"); + ck_assert_msg(memcmp(address, address2, TOX_PUBLIC_KEY_SIZE) == 0, "addresses don't match!"); + + size = tox_get_savedata_size(tox3); + uint8_t *data2 = (uint8_t *)malloc(size); + ck_assert(data2 != nullptr); + tox_get_savedata(tox3, data2); + Tox_Err_Key_Derivation keyerr; + Tox_Pass_Key *key = tox_pass_key_derive((const uint8_t *)"123qweasdzxc", 12, &keyerr); + ck_assert_msg(key != nullptr, "pass key allocation failure"); + memcpy((uint8_t *)key, test_salt, TOX_PASS_SALT_LENGTH); + memcpy((uint8_t *)key + TOX_PASS_SALT_LENGTH, known_key2, TOX_PASS_KEY_LENGTH); + size2 = size + TOX_PASS_ENCRYPTION_EXTRA_LENGTH; + uint8_t *encdata2 = (uint8_t *)malloc(size2); + ck_assert(encdata2 != nullptr); + ret = tox_pass_key_encrypt(key, data2, size, encdata2, &error1); + ck_assert_msg(ret, "failed to key encrypt %d", error1); + ck_assert_msg(tox_is_data_encrypted(encdata2), "magic number the second missing"); + + uint8_t *out1 = (uint8_t *)malloc(size); + ck_assert(out1 != nullptr); + uint8_t *out2 = (uint8_t *)malloc(size); + ck_assert(out2 != nullptr); + ret = tox_pass_decrypt(encdata2, size2, (const uint8_t *)pw, pwlen, out1, &err3); + ck_assert_msg(ret, "failed to pw decrypt %d", err3); + ret = tox_pass_key_decrypt(key, encdata2, size2, out2, &err3); + ck_assert_msg(ret, "failed to key decrypt %d", err3); + ck_assert_msg(memcmp(out1, out2, size) == 0, "differing output data"); + + // and now with the code in use (I only bothered with manually to debug this, and it seems a waste + // to remove the manual check now that it's there) + tox_options_set_savedata_data(options, out1, size); + Tox *tox4 = tox_new_log(options, &err2, nullptr); + ck_assert_msg(err2 == TOX_ERR_NEW_OK, "failed to new the third"); + uint8_t address5[TOX_PUBLIC_KEY_SIZE]; + ret = tox_friend_get_public_key(tox4, 0, address5, nullptr); + ck_assert_msg(ret, "no friends! the third"); + ck_assert_msg(memcmp(address, address2, TOX_PUBLIC_KEY_SIZE) == 0, "addresses don't match! the third"); + + tox_pass_key_free(key); + tox_options_free(options); + + tox_kill(tox1); + tox_kill(tox2); + tox_kill(tox3); + tox_kill(tox4); + + free(out2); + free(out1); + free(encdata2); + free(data2); + free(dec_data); + free(enc_data); + free(data); +} + +static void test_keys(void) +{ + Tox_Err_Encryption encerr; + Tox_Err_Decryption decerr; + Tox_Err_Key_Derivation keyerr; + const uint8_t *key_char = (const uint8_t *)"123qweasdzxc"; + Tox_Pass_Key *key = tox_pass_key_derive(key_char, 12, &keyerr); + ck_assert_msg(key != nullptr, "generic failure 1: %d", keyerr); + const uint8_t *string = (const uint8_t *)"No Patrick, mayonnaise is not an instrument."; // 44 + + uint8_t encrypted[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; + bool ret = tox_pass_key_encrypt(key, string, 44, encrypted, &encerr); + ck_assert_msg(ret, "generic failure 2: %d", encerr); + + // Testing how tox handles encryption of large messages. + int size_large = 30 * 1024 * 1024; + int ciphertext_length2a = size_large + TOX_PASS_ENCRYPTION_EXTRA_LENGTH; + int plaintext_length2a = size_large; + uint8_t *encrypted2a = (uint8_t *)malloc(ciphertext_length2a); + ck_assert(encrypted2a != nullptr); + uint8_t *in_plaintext2a = (uint8_t *)malloc(plaintext_length2a); + ck_assert(in_plaintext2a != nullptr); + const Random *rng = system_random(); + ck_assert(rng != nullptr); + random_bytes(rng, in_plaintext2a, plaintext_length2a); + ret = tox_pass_encrypt(in_plaintext2a, plaintext_length2a, key_char, 12, encrypted2a, &encerr); + ck_assert_msg(ret, "tox_pass_encrypt failure 2a: %d", encerr); + + // Decryption of same message. + uint8_t *out_plaintext2a = (uint8_t *)malloc(plaintext_length2a); + ck_assert(out_plaintext2a != nullptr); + ret = tox_pass_decrypt(encrypted2a, ciphertext_length2a, key_char, 12, out_plaintext2a, &decerr); + ck_assert_msg(ret, "tox_pass_decrypt failure 2a: %d", decerr); + ck_assert_msg(memcmp(in_plaintext2a, out_plaintext2a, plaintext_length2a) == 0, "Large message decryption failed"); + free(encrypted2a); + free(in_plaintext2a); + free(out_plaintext2a); + + + uint8_t encrypted2[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; + ret = tox_pass_encrypt(string, 44, key_char, 12, encrypted2, &encerr); + ck_assert_msg(ret, "generic failure 3: %d", encerr); + + uint8_t out1[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; + uint8_t out2[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; + + ret = tox_pass_key_decrypt(key, encrypted, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, out1, &decerr); + ck_assert_msg(ret, "generic failure 4: %d", decerr); + ck_assert_msg(memcmp(out1, string, 44) == 0, "decryption 1 failed"); + + ret = tox_pass_decrypt(encrypted2, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, (const uint8_t *)"123qweasdzxc", 12, out2, + &decerr); + ck_assert_msg(ret, "generic failure 5: %d", decerr); + ck_assert_msg(memcmp(out2, string, 44) == 0, "decryption 2 failed"); + + ret = tox_pass_decrypt(encrypted2, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, nullptr, 0, out2, &decerr); + ck_assert_msg(!ret, "Decrypt succeeded with wrong pass"); + ck_assert_msg(decerr != TOX_ERR_DECRYPTION_FAILED, "Bad error code %d", decerr); + + // test that pass_decrypt can decrypt things from pass_key_encrypt + ret = tox_pass_decrypt(encrypted, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, (const uint8_t *)"123qweasdzxc", 12, out1, + &decerr); + ck_assert_msg(ret, "generic failure 6: %d", decerr); + ck_assert_msg(memcmp(out1, string, 44) == 0, "decryption 3 failed"); + + uint8_t salt[TOX_PASS_SALT_LENGTH]; + Tox_Err_Get_Salt salt_err; + ck_assert_msg(tox_get_salt(encrypted, salt, &salt_err), "couldn't get salt"); + ck_assert_msg(salt_err == TOX_ERR_GET_SALT_OK, "get_salt returned an error"); + Tox_Pass_Key *key2 = tox_pass_key_derive_with_salt((const uint8_t *)"123qweasdzxc", 12, salt, &keyerr); + ck_assert_msg(key2 != nullptr, "generic failure 7: %d", keyerr); + ck_assert_msg(0 == memcmp(key, key2, TOX_PASS_KEY_LENGTH + TOX_PASS_SALT_LENGTH), "salt comparison failed"); + tox_pass_key_free(key2); + tox_pass_key_free(key); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + test_known_kdf(); + test_save_friend(); + test_keys(); + + return 0; +} +#else // VANILLA_NACL +int main(void) +{ + return 0; +} +#endif diff --git a/auto_tests/file_saving_test.c b/auto_tests/file_saving_test.c new file mode 100644 index 0000000..58a1b6d --- /dev/null +++ b/auto_tests/file_saving_test.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Copyright © 2016-2018 The TokTok team. + * Copyright © 2016 Tox project. + */ + +/* + * Small test for checking if obtaining savedata, saving it to disk and using + * works correctly. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#include "../toxencryptsave/toxencryptsave.h" + +static const char *pphrase = "bar"; +static const char *name = "foo"; +static const char *savefile = "./save"; + +static void save_data_encrypted(void) +{ + struct Tox_Options *options = tox_options_new(nullptr); + Tox *t = tox_new_log(options, nullptr, nullptr); + tox_options_free(options); + + tox_self_set_name(t, (const uint8_t *)name, strlen(name), nullptr); + + FILE *f = fopen(savefile, "wb"); + + size_t size = tox_get_savedata_size(t); + uint8_t *clear = (uint8_t *)malloc(size); + + /*this function does not write any data at all*/ + tox_get_savedata(t, clear); + + size += TOX_PASS_ENCRYPTION_EXTRA_LENGTH; + uint8_t *cipher = (uint8_t *)malloc(size); + + Tox_Err_Encryption eerr; + + ck_assert_msg(tox_pass_encrypt(clear, size - TOX_PASS_ENCRYPTION_EXTRA_LENGTH, (const uint8_t *)pphrase, + strlen(pphrase), cipher, + &eerr), "Could not encrypt, error code %d.", eerr); + + size_t written_value = fwrite(cipher, sizeof(*cipher), size, f); + printf("written written_value = %u of %u\n", (unsigned)written_value, (unsigned)size); + + free(cipher); + free(clear); + fclose(f); + tox_kill(t); +} + +static void load_data_decrypted(void) +{ + FILE *f = fopen(savefile, "rb"); + ck_assert(f != nullptr); + fseek(f, 0, SEEK_END); + int64_t size = ftell(f); + fseek(f, 0, SEEK_SET); + + ck_assert_msg(0 <= size && size <= UINT_MAX, "file size out of range"); + + uint8_t *cipher = (uint8_t *)malloc(size); + ck_assert(cipher != nullptr); + uint8_t *clear = (uint8_t *)malloc(size - TOX_PASS_ENCRYPTION_EXTRA_LENGTH); + ck_assert(clear != nullptr); + size_t read_value = fread(cipher, sizeof(*cipher), size, f); + printf("Read read_value = %u of %u\n", (unsigned)read_value, (unsigned)size); + + Tox_Err_Decryption derr; + + ck_assert_msg(tox_pass_decrypt(cipher, size, (const uint8_t *)pphrase, strlen(pphrase), clear, &derr), + "Could not decrypt, error code %d.", derr); + + struct Tox_Options *options = tox_options_new(nullptr); + ck_assert(options != nullptr); + + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + + tox_options_set_savedata_data(options, clear, size); + + Tox_Err_New err; + + Tox *t = tox_new_log(options, &err, nullptr); + + tox_options_free(options); + + ck_assert_msg(t != nullptr, "tox_new returned the error value %d", err); + + uint8_t *readname = (uint8_t *)malloc(tox_self_get_name_size(t)); + ck_assert(readname != nullptr); + tox_self_get_name(t, readname); + + ck_assert_msg(memcmp(readname, name, tox_self_get_name_size(t)) == 0, + "name returned by tox_self_get_name does not match expected result"); + + tox_kill(t); + free(clear); + free(cipher); + free(readname); + fclose(f); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + save_data_encrypted(); + load_data_decrypted(); + + ck_assert_msg(remove(savefile) == 0, "Could not remove the savefile."); + + return 0; +} diff --git a/auto_tests/file_transfer_test.c b/auto_tests/file_transfer_test.c new file mode 100644 index 0000000..cf87b82 --- /dev/null +++ b/auto_tests/file_transfer_test.c @@ -0,0 +1,367 @@ +/* File transfer test. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +/* The Travis-CI container responds poorly to ::1 as a localhost address + * You're encouraged to -D FORCE_TESTS_IPV6 on a local test */ +#ifdef FORCE_TESTS_IPV6 +#define TOX_LOCALHOST "::1" +#else +#define TOX_LOCALHOST "127.0.0.1" +#endif + +static void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +{ + if (length == 7 && memcmp("Gentoo", data, 7) == 0) { + tox_friend_add_norequest(m, public_key, nullptr); + } +} + +static uint64_t size_recv; +static uint64_t sending_pos; + +static uint8_t file_cmp_id[TOX_FILE_ID_LENGTH]; +static uint32_t file_accepted; +static uint64_t file_size; +static void tox_file_receive(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t filesize, + const uint8_t *filename, size_t filename_length, void *userdata) +{ + ck_assert_msg(kind == TOX_FILE_KIND_DATA, "bad kind"); + + ck_assert_msg(filename_length == sizeof("Gentoo.exe") + && memcmp(filename, "Gentoo.exe", sizeof("Gentoo.exe")) == 0, "bad filename"); + + uint8_t file_id[TOX_FILE_ID_LENGTH]; + + ck_assert_msg(tox_file_get_file_id(tox, friend_number, file_number, file_id, nullptr), "tox_file_get_file_id error"); + + ck_assert_msg(memcmp(file_id, file_cmp_id, TOX_FILE_ID_LENGTH) == 0, "bad file_id"); + + uint8_t empty[TOX_FILE_ID_LENGTH] = {0}; + + ck_assert_msg(memcmp(empty, file_cmp_id, TOX_FILE_ID_LENGTH) != 0, "empty file_id"); + + file_size = filesize; + + if (filesize) { + sending_pos = size_recv = 1337; + + Tox_Err_File_Seek err_s; + + ck_assert_msg(tox_file_seek(tox, friend_number, file_number, 1337, &err_s), "tox_file_seek error"); + + ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_OK, "tox_file_seek wrong error"); + + } else { + sending_pos = size_recv = 0; + } + + Tox_Err_File_Control error; + + ck_assert_msg(tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, &error), + "tox_file_control failed. %i", error); + ++file_accepted; + + Tox_Err_File_Seek err_s; + + ck_assert_msg(!tox_file_seek(tox, friend_number, file_number, 1234, &err_s), "tox_file_seek no error"); + + ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_DENIED, "tox_file_seek wrong error"); +} + +static uint32_t sendf_ok; +static void file_print_control(Tox *tox, uint32_t friend_number, uint32_t file_number, Tox_File_Control control, + void *userdata) +{ + /* First send file num is 0.*/ + if (file_number == 0 && control == TOX_FILE_CONTROL_RESUME) { + sendf_ok = 1; + } +} + +static uint64_t max_sending; +static bool m_send_reached; +static uint8_t sending_num; +static bool file_sending_done; +static void tox_file_chunk_request(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + size_t length, void *user_data) +{ + ck_assert_msg(sendf_ok, "didn't get resume control"); + + ck_assert_msg(sending_pos == position, "bad position %lu", (unsigned long)position); + + if (length == 0) { + ck_assert_msg(!file_sending_done, "file sending already done"); + + file_sending_done = 1; + return; + } + + if (position + length > max_sending) { + ck_assert_msg(!m_send_reached, "requested done file transfer"); + + length = max_sending - position; + m_send_reached = 1; + } + + VLA(uint8_t, f_data, length); + memset(f_data, sending_num, length); + + Tox_Err_File_Send_Chunk error; + tox_file_send_chunk(tox, friend_number, file_number, position, f_data, length, &error); + + + ck_assert_msg(error == TOX_ERR_FILE_SEND_CHUNK_OK, + "could not send chunk, error num=%d pos=%d len=%d", (int)error, (int)position, (int)length); + + ++sending_num; + sending_pos += length; +} + + +static uint8_t num; +static bool file_recv; +static void write_file(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data, + size_t length, void *user_data) +{ + ck_assert_msg(size_recv == position, "bad position"); + + if (length == 0) { + file_recv = 1; + return; + } + + VLA(uint8_t, f_data, length); + memset(f_data, num, length); + ++num; + + ck_assert_msg(memcmp(f_data, data, length) == 0, "FILE_CORRUPTED"); + + size_recv += length; +} + +static void file_transfer_test(void) +{ + printf("Starting test: few_clients\n"); + uint32_t index[] = { 1, 2, 3 }; + long long unsigned int cur_time = time(nullptr); + Tox_Err_New t_n_error; + Tox *tox1 = tox_new_log(nullptr, &t_n_error, &index[0]); + ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); + Tox *tox2 = tox_new_log(nullptr, &t_n_error, &index[1]); + ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); + Tox *tox3 = tox_new_log(nullptr, &t_n_error, &index[2]); + ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error"); + + ck_assert_msg(tox1 && tox2 && tox3, "Failed to create 3 tox instances"); + + tox_callback_friend_request(tox2, accept_friend_request); + uint8_t address[TOX_ADDRESS_SIZE]; + tox_self_get_address(tox2, address); + uint32_t test = tox_friend_add(tox3, address, (const uint8_t *)"Gentoo", 7, nullptr); + ck_assert_msg(test == 0, "Failed to add friend error code: %u", test); + + uint8_t dhtKey[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dhtKey); + uint16_t dhtPort = tox_self_get_udp_port(tox1, nullptr); + + tox_bootstrap(tox2, TOX_LOCALHOST, dhtPort, dhtKey, nullptr); + tox_bootstrap(tox3, TOX_LOCALHOST, dhtPort, dhtKey, nullptr); + + printf("Waiting for toxes to come online\n"); + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + tox_iterate(tox3, nullptr); + + printf("Connections: self (%d, %d, %d), friends (%d, %d)\n", + tox_self_get_connection_status(tox1), + tox_self_get_connection_status(tox2), + tox_self_get_connection_status(tox3), + tox_friend_get_connection_status(tox2, 0, nullptr), + tox_friend_get_connection_status(tox3, 0, nullptr)); + c_sleep(ITERATION_INTERVAL); + } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || + tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE || + tox_self_get_connection_status(tox3) == TOX_CONNECTION_NONE || + tox_friend_get_connection_status(tox2, 0, nullptr) == TOX_CONNECTION_NONE || + tox_friend_get_connection_status(tox3, 0, nullptr) == TOX_CONNECTION_NONE); + + printf("Starting file transfer test: 100MiB file.\n"); + + file_accepted = file_size = sendf_ok = size_recv = 0; + file_recv = 0; + max_sending = UINT64_MAX; + uint64_t f_time = time(nullptr); + tox_callback_file_recv_chunk(tox3, write_file); + tox_callback_file_recv_control(tox2, file_print_control); + tox_callback_file_chunk_request(tox2, tox_file_chunk_request); + tox_callback_file_recv_control(tox3, file_print_control); + tox_callback_file_recv(tox3, tox_file_receive); + uint64_t totalf_size = 100 * 1024 * 1024; + uint32_t fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr, (const uint8_t *)"Gentoo.exe", + sizeof("Gentoo.exe"), nullptr); + ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail"); + + Tox_Err_File_Get gfierr; + ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error"); + ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error"); + ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error"); + + const size_t max_iterations = INT16_MAX; + + for (size_t i = 0; i < max_iterations; i++) { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + tox_iterate(tox3, nullptr); + + if (file_sending_done) { + ck_assert_msg(sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size && sending_pos == size_recv + && file_accepted == 1, + "Something went wrong in file transfer %u %u %u %u %u %u %lu %lu %lu", + sendf_ok, file_recv, totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, + file_accepted == 1, (unsigned long)totalf_size, (unsigned long)size_recv, + (unsigned long)sending_pos); + break; + } + + uint32_t tox1_interval = tox_iteration_interval(tox1); + uint32_t tox2_interval = tox_iteration_interval(tox2); + uint32_t tox3_interval = tox_iteration_interval(tox3); + + if ((i + 1) % 500 == 0) { + printf("after %u iterations: %.2fMiB done\n", (unsigned int)i + 1, (double)size_recv / 1024 / 1024); + } + + c_sleep(min_u32(tox1_interval, min_u32(tox2_interval, tox3_interval))); + } + + ck_assert_msg(file_sending_done, "file sending did not complete after %u iterations: sendf_ok:%u file_recv:%u " + "totalf_size==file_size:%u size_recv==file_size:%u sending_pos==size_recv:%u file_accepted:%u " + "totalf_size:%lu size_recv:%lu sending_pos:%lu", + (unsigned int)max_iterations, sendf_ok, file_recv, + totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1, + (unsigned long)totalf_size, (unsigned long)size_recv, + (unsigned long)sending_pos); + + printf("100MiB file sent in %lu seconds\n", (unsigned long)(time(nullptr) - f_time)); + + printf("Starting file streaming transfer test.\n"); + + file_sending_done = 0; + file_accepted = 0; + file_size = 0; + sendf_ok = 0; + size_recv = 0; + file_recv = 0; + tox_callback_file_recv_chunk(tox3, write_file); + tox_callback_file_recv_control(tox2, file_print_control); + tox_callback_file_chunk_request(tox2, tox_file_chunk_request); + tox_callback_file_recv_control(tox3, file_print_control); + tox_callback_file_recv(tox3, tox_file_receive); + totalf_size = UINT64_MAX; + fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr, + (const uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), nullptr); + ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail"); + + ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error"); + ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error"); + ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error"); + + max_sending = 100 * 1024; + m_send_reached = 0; + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + tox_iterate(tox3, nullptr); + + uint32_t tox1_interval = tox_iteration_interval(tox1); + uint32_t tox2_interval = tox_iteration_interval(tox2); + uint32_t tox3_interval = tox_iteration_interval(tox3); + + c_sleep(min_u32(tox1_interval, min_u32(tox2_interval, tox3_interval))); + } while (!file_sending_done); + + ck_assert_msg(sendf_ok && file_recv && m_send_reached && totalf_size == file_size && size_recv == max_sending + && sending_pos == size_recv && file_accepted == 1, + "something went wrong in file transfer %u %u %u %u %u %u %u %lu %lu %lu %lu", sendf_ok, file_recv, + m_send_reached, totalf_size == file_size, size_recv == max_sending, sending_pos == size_recv, file_accepted == 1, + (unsigned long)totalf_size, (unsigned long)file_size, + (unsigned long)size_recv, (unsigned long)sending_pos); + + printf("starting file 0 transfer test.\n"); + + file_sending_done = 0; + file_accepted = 0; + file_size = 0; + sendf_ok = 0; + size_recv = 0; + file_recv = 0; + tox_callback_file_recv_chunk(tox3, write_file); + tox_callback_file_recv_control(tox2, file_print_control); + tox_callback_file_chunk_request(tox2, tox_file_chunk_request); + tox_callback_file_recv_control(tox3, file_print_control); + tox_callback_file_recv(tox3, tox_file_receive); + totalf_size = 0; + fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, nullptr, + (const uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), nullptr); + ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail"); + + ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error"); + ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error"); + ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed"); + ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error"); + + do { + uint32_t tox1_interval = tox_iteration_interval(tox1); + uint32_t tox2_interval = tox_iteration_interval(tox2); + uint32_t tox3_interval = tox_iteration_interval(tox3); + + c_sleep(min_u32(tox1_interval, min_u32(tox2_interval, tox3_interval))); + + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + tox_iterate(tox3, nullptr); + } while (!file_sending_done); + + ck_assert_msg(sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size + && sending_pos == size_recv && file_accepted == 1, + "something went wrong in file transfer %u %u %u %u %u %u %llu %llu %llu", sendf_ok, file_recv, + totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1, + (unsigned long long)totalf_size, (unsigned long long)size_recv, + (unsigned long long)sending_pos); + + printf("file_transfer_test succeeded, took %llu seconds\n", time(nullptr) - cur_time); + + tox_kill(tox1); + tox_kill(tox2); + tox_kill(tox3); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + file_transfer_test(); + return 0; +} diff --git a/auto_tests/forwarding_test.c b/auto_tests/forwarding_test.c new file mode 100644 index 0000000..bc912fb --- /dev/null +++ b/auto_tests/forwarding_test.c @@ -0,0 +1,334 @@ +#include +#include +#include + +#include "../toxcore/tox.h" +#include "../toxcore/announce.h" +#include "../testing/misc_tools.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/forwarding.h" +#include "../toxcore/net_crypto.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#ifndef USE_IPV6 +#define USE_IPV6 1 +#endif + +static inline IP get_loopback(void) +{ + IP ip; +#if USE_IPV6 + ip.family = net_family_ipv6(); + ip.ip.v6 = get_ip6_loopback(); +#else + ip.family = net_family_ipv4(); + ip.ip.v4 = get_ip4_loopback(); +#endif + return ip; +} + +#define NUM_FORWARDER 20 +#define NUM_FORWARDER_TCP 5 +#define NUM_FORWARDER_DHT (NUM_FORWARDER - NUM_FORWARDER_TCP) +#define NUM_FORWARDING_ITERATIONS 1 +#define FORWARD_SEND_INTERVAL 2 +#define FORWARDING_BASE_PORT 36571 + +typedef struct Test_Data { + Networking_Core *net; + uint32_t send_back; + uint64_t sent; + bool returned; +} Test_Data; + +static void test_forwarded_request_cb(void *object, const IP_Port *forwarder, + const uint8_t *sendback, uint16_t sendback_length, + const uint8_t *data, uint16_t length, void *userdata) +{ + Test_Data *test_data = (Test_Data *)object; + uint8_t *index = (uint8_t *)userdata; + + if (length != 12 || memcmp("hello: ", data, 8) != 0) { + printf("[%u] got unexpected data of length %d\n", *index, length); + return; + } + + uint8_t reply[12]; + memcpy(reply, "reply: ", 8); + memcpy(reply + 8, data + 8, 4); + ck_assert_msg(forward_reply(test_data->net, forwarder, sendback, sendback_length, reply, 12), + "[%u] forward_reply failed", *index); +} + +static void test_forwarded_response_cb(void *object, + const uint8_t *data, uint16_t length, void *userdata) +{ + Test_Data *test_data = (Test_Data *)object; + uint8_t *index = (uint8_t *)userdata; + + if (length != 12 || memcmp("reply: ", data, 8) != 0) { + printf("[%u] got unexpected data of length %d\n", *index, length); + return; + } + + uint32_t send_back; + net_unpack_u32(data + 8, &send_back); + + if (test_data->send_back == send_back) { + test_data->returned = true; + } +} + +static bool all_returned(Test_Data *test_data) +{ + for (uint32_t i = 0; i < NUM_FORWARDER; ++i) { + if (!test_data[i].returned) { + return false; + } + } + + return true; +} + +typedef struct Forwarding_Subtox { + Logger *log; + Mono_Time *mono_time; + Networking_Core *net; + DHT *dht; + Net_Crypto *c; + Forwarding *forwarding; + Announcements *announce; +} Forwarding_Subtox; + +static Forwarding_Subtox *new_forwarding_subtox(bool no_udp, uint32_t *index, uint16_t port) +{ + Forwarding_Subtox *subtox = (Forwarding_Subtox *)calloc(1, sizeof(Forwarding_Subtox)); + ck_assert(subtox != nullptr); + + subtox->log = logger_new(); + ck_assert(subtox->log != nullptr); + logger_callback_log(subtox->log, print_debug_logger, nullptr, index); + subtox->mono_time = mono_time_new(nullptr, nullptr); + + const Random *rng= system_random(); + ck_assert(rng != nullptr); + const Network *ns = system_network(); + ck_assert(ns != nullptr); + + if (no_udp) { + subtox->net = new_networking_no_udp(subtox->log, ns); + } else { + const IP ip = get_loopback(); + subtox->net = new_networking_ex(subtox->log, ns, &ip, port, port, nullptr); + } + + subtox->dht = new_dht(subtox->log, rng, ns, subtox->mono_time, subtox->net, true, true); + + const TCP_Proxy_Info inf = {{{{0}}}}; + subtox->c = new_net_crypto(subtox->log, rng, ns, subtox->mono_time, subtox->dht, &inf); + + subtox->forwarding = new_forwarding(subtox->log, rng, subtox->mono_time, subtox->dht); + ck_assert(subtox->forwarding != nullptr); + + subtox->announce = new_announcements(subtox->log, rng, subtox->mono_time, subtox->forwarding); + ck_assert(subtox->announce != nullptr); + + return subtox; +} + +static void kill_forwarding_subtox(Forwarding_Subtox *subtox) +{ + kill_announcements(subtox->announce); + kill_forwarding(subtox->forwarding); + kill_net_crypto(subtox->c); + kill_dht(subtox->dht); + kill_networking(subtox->net); + mono_time_free(subtox->mono_time); + logger_kill(subtox->log); + free(subtox); +} + +static void test_forwarding(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + const Network *ns = system_network(); + ck_assert(ns != nullptr); + + uint32_t index[NUM_FORWARDER]; + Forwarding_Subtox *subtoxes[NUM_FORWARDER]; + Test_Data test_data[NUM_FORWARDER]; + + const IP ip = get_loopback(); + + for (uint32_t i = 0; i < NUM_FORWARDER; ++i) { + index[i] = i + 1; + subtoxes[i] = new_forwarding_subtox(i < NUM_FORWARDER_TCP, &index[i], FORWARDING_BASE_PORT + i); + + test_data[i].net = subtoxes[i]->net; + test_data[i].send_back = 0; + test_data[i].sent = 0; + test_data[i].returned = false; + set_callback_forwarded_request(subtoxes[i]->forwarding, test_forwarded_request_cb, &test_data[i]); + set_callback_forwarded_response(subtoxes[i]->forwarding, test_forwarded_response_cb, &test_data[i]); + set_forwarding_packet_tcp_connection_callback(nc_get_tcp_c(subtoxes[i]->c), test_forwarded_response_cb, &test_data[i]); + } + + printf("testing forwarding via tcp relays and dht\n"); + + struct Tox_Options *opts = tox_options_new(nullptr); + uint16_t forwarder_tcp_relay_port = 36570; + Tox *relay = nullptr; + // Try a few different ports. + for (uint8_t i = 0; i < 100; ++i) { + tox_options_set_tcp_port(opts, forwarder_tcp_relay_port); + relay = tox_new_log(opts, nullptr, nullptr); + if (relay != nullptr) { + break; + } + ++forwarder_tcp_relay_port; + } + tox_options_free(opts); + ck_assert_msg(relay != nullptr, "Failed to create TCP relay"); + + IP_Port relay_ipport_tcp = {ip, net_htons(forwarder_tcp_relay_port)}; + + uint8_t dpk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(relay, dpk); + + printf("1-%d connected only to TCP server; %d-%d connected only to DHT\n", + NUM_FORWARDER_TCP, NUM_FORWARDER_TCP + 1, NUM_FORWARDER); + + for (uint32_t i = 0; i < NUM_FORWARDER_TCP; ++i) { + set_tcp_onion_status(nc_get_tcp_c(subtoxes[i]->c), 1); + ck_assert_msg(add_tcp_relay(subtoxes[i]->c, &relay_ipport_tcp, dpk) == 0, + "Failed to add TCP relay"); + } + + IP_Port relay_ipport_udp = {ip, net_htons(tox_self_get_udp_port(relay, nullptr))}; + + for (uint32_t i = NUM_FORWARDER_TCP; i < NUM_FORWARDER; ++i) { + dht_bootstrap(subtoxes[i]->dht, &relay_ipport_udp, dpk); + } + + printf("allowing DHT to populate\n"); + uint16_t dht_establish_iterations = NUM_FORWARDER * 5; + + for (uint32_t n = 0; n < NUM_FORWARDING_ITERATIONS; ++n) { + for (uint32_t i = 0; i < NUM_FORWARDER; ++i) { + test_data[i].sent = 0; + test_data[i].returned = false; + } + + do { + for (uint32_t i = 0; i < NUM_FORWARDER; ++i) { + Forwarding_Subtox *const subtox = subtoxes[i]; + mono_time_update(subtox->mono_time); + networking_poll(subtox->net, &index[i]); + do_net_crypto(subtox->c, &index[i]); + do_dht(subtox->dht); + + if (dht_establish_iterations || + test_data[i].returned || + !mono_time_is_timeout(subtox->mono_time, test_data[i].sent, FORWARD_SEND_INTERVAL)) { + continue; + } + + printf("%u", i + 1); + + if (i < NUM_FORWARDER_TCP) { + printf(" --> TCPRelay"); + } + + const uint16_t chain_length = i < NUM_FORWARDER_TCP ? i % 5 : i % 4 + 1; + uint8_t chain_keys[4 * CRYPTO_PUBLIC_KEY_SIZE]; + + uint32_t chain_i = NUM_FORWARDER_TCP + (random_u32(rng) % NUM_FORWARDER_DHT); + const IP_Port first_ipp = {ip, net_htons(FORWARDING_BASE_PORT + chain_i)}; + + printf(" --> %u", chain_i + 1); + + for (uint16_t j = 0; j < chain_length; ++j) { + // pick random different dht node: + chain_i += 1 + random_u32(rng) % (NUM_FORWARDER_DHT - 1); + chain_i = NUM_FORWARDER_TCP + (chain_i - NUM_FORWARDER_TCP) % NUM_FORWARDER_DHT; + + const uint8_t *dest_pubkey = dht_get_self_public_key(subtoxes[chain_i]->dht); + + memcpy(chain_keys + j * CRYPTO_PUBLIC_KEY_SIZE, dest_pubkey, CRYPTO_PUBLIC_KEY_SIZE); + printf(" --> %u", chain_i + 1); + } + + printf("\n"); + + const uint16_t length = 12; + uint8_t data[12]; + + memcpy(data, "hello: ", 8); + test_data[i].send_back = random_u32(rng); + net_pack_u32(data + 8, test_data[i].send_back); + + if (i < NUM_FORWARDER_TCP) { + IP_Port tcp_forwarder; + + if (!get_random_tcp_conn_ip_port(subtox->c, &tcp_forwarder)) { + continue; + } + + if (send_tcp_forward_request(subtox->log, subtox->c, &tcp_forwarder, &first_ipp, + chain_keys, chain_length, data, length) == 0) { + test_data[i].sent = mono_time_get(subtox->mono_time); + } + } else { + if (send_forward_request(subtox->net, &first_ipp, + chain_keys, chain_length, data, length)) { + test_data[i].sent = mono_time_get(subtox->mono_time); + } + } + } + + tox_iterate(relay, nullptr); + + if (dht_establish_iterations) { + --dht_establish_iterations; + + if (!dht_establish_iterations) { + printf("making forward requests and expecting replies\n"); + } + } + + c_sleep(50); + } while (!all_returned(test_data)); + + // This doesn't really belong in this test. + // It can be removed once the full announce client test is in place. + printf("checking that nodes are marked as announce nodes\n"); + Node_format nodes[MAX_SENT_NODES]; + ck_assert(NUM_FORWARDER - NUM_FORWARDER_TCP > 1); + + for (uint32_t i = NUM_FORWARDER_TCP; i < NUM_FORWARDER; ++i) { + ck_assert_msg(get_close_nodes(subtoxes[i]->dht, dht_get_self_public_key(subtoxes[i]->dht), nodes, net_family_unspec(), true, + true) > 0, + "node %u has no nodes marked as announce nodes", i); + } + } + + + for (uint32_t i = 0; i < NUM_FORWARDER; ++i) { + kill_forwarding_subtox(subtoxes[i]); + } + + tox_kill(relay); +} + + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_forwarding(); + + return 0; +} diff --git a/auto_tests/friend_connection_test.c b/auto_tests/friend_connection_test.c new file mode 100644 index 0000000..3a34838 --- /dev/null +++ b/auto_tests/friend_connection_test.c @@ -0,0 +1,26 @@ +/* Tests that we can make a friend connection. + * + * This is the simplest test that brings up two toxes that can talk to each + * other. It's useful as a copy/pasteable starting point for testing other + * features. + */ + +#include + +#include "auto_test_support.h" + +static void friend_connection_test(AutoTox *toxes) +{ + // Nothing to do here. When copying this test, add test-specific code here. +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + run_auto_test(nullptr, 2, friend_connection_test, 0, &options); + + return 0; +} diff --git a/auto_tests/friend_request_spam_test.c b/auto_tests/friend_request_spam_test.c new file mode 100644 index 0000000..8916f25 --- /dev/null +++ b/auto_tests/friend_request_spam_test.c @@ -0,0 +1,71 @@ +/* Tests what happens when spamming friend requests from lots of temporary toxes. + */ + +#include +#include +#include +#include + +#include "../toxcore/ccompat.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "../testing/misc_tools.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#define FR_MESSAGE "Gentoo" +// TODO(iphydf): Investigate friend request spam: receiving more than 32 at a time means any further +// friend requests are dropped on the floor and aren't seen again. +#define FR_TOX_COUNT 33 + +typedef struct State { + bool unused; +} State; + +static void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *data, size_t length, + void *userdata) +{ + ck_assert_msg(length == sizeof(FR_MESSAGE) && memcmp(FR_MESSAGE, data, sizeof(FR_MESSAGE)) == 0, + "unexpected friend request message"); + tox_friend_add_norequest(tox, public_key, nullptr); +} + +static void test_friend_request(AutoTox *autotoxes) +{ + const time_t con_time = time(nullptr); + + printf("All toxes add tox1 as friend.\n"); + tox_callback_friend_request(autotoxes[0].tox, accept_friend_request); + + uint8_t address[TOX_ADDRESS_SIZE]; + tox_self_get_address(autotoxes[0].tox, address); + + for (uint32_t i = 2; i < FR_TOX_COUNT; ++i) { + Tox_Err_Friend_Add err; + tox_friend_add(autotoxes[i].tox, address, (const uint8_t *)FR_MESSAGE, sizeof(FR_MESSAGE), &err); + ck_assert_msg(err == TOX_ERR_FRIEND_ADD_OK, "tox %u failed to add friend error code: %d", autotoxes[i].index, err); + } + + for (uint32_t t = 0; t < 100; ++t) { + if (all_friends_connected(autotoxes, FR_TOX_COUNT)) { + break; + } + + iterate_all_wait(autotoxes, FR_TOX_COUNT, ITERATION_INTERVAL); + } + + const size_t size = tox_self_get_friend_list_size(autotoxes[0].tox); + printf("Tox clients connected took %lu seconds; tox1 has %u friends.\n", + (unsigned long)(time(nullptr) - con_time), (unsigned int)size); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + run_auto_test(nullptr, FR_TOX_COUNT, test_friend_request, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/friend_request_test.c b/auto_tests/friend_request_test.c new file mode 100644 index 0000000..8fe8f29 --- /dev/null +++ b/auto_tests/friend_request_test.c @@ -0,0 +1,84 @@ +/* Tests that we can add friends. + */ + +#include +#include +#include +#include + +#include "../toxcore/ccompat.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "../testing/misc_tools.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#define FR_MESSAGE "Gentoo" + +static void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *data, size_t length, + void *userdata) +{ + ck_assert_msg(length == sizeof(FR_MESSAGE) && memcmp(FR_MESSAGE, data, sizeof(FR_MESSAGE)) == 0, + "unexpected friend request message"); + tox_friend_add_norequest(tox, public_key, nullptr); +} + +static void test_friend_request(void) +{ + printf("Initialising 2 toxes.\n"); + uint32_t index[] = { 1, 2 }; + const time_t cur_time = time(nullptr); + Tox *const tox1 = tox_new_log(nullptr, nullptr, &index[0]); + Tox *const tox2 = tox_new_log(nullptr, nullptr, &index[1]); + + ck_assert_msg(tox1 && tox2, "failed to create 2 tox instances"); + + printf("Bootstrapping tox2 off tox1.\n"); + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); + + tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + + c_sleep(ITERATION_INTERVAL); + } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || + tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE); + + printf("Toxes are online, took %lu seconds.\n", (unsigned long)(time(nullptr) - cur_time)); + const time_t con_time = time(nullptr); + + printf("Tox1 adds tox2 as friend, tox2 accepts.\n"); + tox_callback_friend_request(tox2, accept_friend_request); + + uint8_t address[TOX_ADDRESS_SIZE]; + tox_self_get_address(tox2, address); + + const uint32_t test = tox_friend_add(tox1, address, (const uint8_t *)FR_MESSAGE, sizeof(FR_MESSAGE), nullptr); + ck_assert_msg(test == 0, "failed to add friend error code: %u", test); + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + + c_sleep(ITERATION_INTERVAL); + } while (tox_friend_get_connection_status(tox1, 0, nullptr) != TOX_CONNECTION_UDP || + tox_friend_get_connection_status(tox2, 0, nullptr) != TOX_CONNECTION_UDP); + + printf("Tox clients connected took %lu seconds.\n", (unsigned long)(time(nullptr) - con_time)); + printf("friend_request_test succeeded, took %lu seconds.\n", (unsigned long)(time(nullptr) - cur_time)); + + tox_kill(tox1); + tox_kill(tox2); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_friend_request(); + return 0; +} diff --git a/auto_tests/group_general_test.c b/auto_tests/group_general_test.c new file mode 100644 index 0000000..1a506ab --- /dev/null +++ b/auto_tests/group_general_test.c @@ -0,0 +1,437 @@ +/* + * Tests that we can connect to a public group chat through the DHT and make basic queries + * about the group, other peers, and ourselves. We also make sure we can disconnect and + * reconnect to a group while retaining our credentials. + */ + +#include +#include +#include + +#include "auto_test_support.h" + +typedef struct State { + size_t peer_joined_count; + size_t self_joined_count; + size_t peer_exit_count; + bool peer_nick; + bool peer_status; + uint32_t peer_id; + bool is_founder; +} State; + +#define NUM_GROUP_TOXES 2 + +#define GROUP_NAME "NASA Headquarters" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) + +#define TOPIC "Funny topic here" +#define TOPIC_LEN (sizeof(TOPIC) - 1) + +#define PEER0_NICK "Lois" +#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) + +#define PEER0_NICK2 "Terry Davis" +#define PEER0_NICK2_LEN (sizeof(PEER0_NICK2) - 1) + +#define PEER1_NICK "Bran" +#define PEER1_NICK_LEN (sizeof(PEER1_NICK) - 1) + +#define EXIT_MESSAGE "Goodbye world" +#define EXIT_MESSAGE_LEN (sizeof(EXIT_MESSAGE) - 1) + +#define PEER_LIMIT 20 + +static bool all_group_peers_connected(AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, size_t name_length) +{ + for (size_t i = 0; i < tox_count; ++i) { + // make sure we got an invite response + if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != name_length) { + return false; + } + + // make sure we got a sync response + if (tox_group_get_peer_limit(autotoxes[i].tox, groupnumber, nullptr) != PEER_LIMIT) { + return false; + } + + // make sure we're actually connected + if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) { + return false; + } + } + + return true; +} + +static void group_peer_join_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + // we do a connection test here for fun + Tox_Err_Group_Peer_Query pq_err; + TOX_CONNECTION connection_status = tox_group_peer_get_connection_status(tox, groupnumber, peer_id, &pq_err); + ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(connection_status != TOX_CONNECTION_NONE); + + Tox_Group_Role role = tox_group_peer_get_role(tox, groupnumber, peer_id, &pq_err); + ck_assert_msg(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK, "%d", pq_err); + + Tox_User_Status status = tox_group_peer_get_status(tox, groupnumber, peer_id, &pq_err); + ck_assert_msg(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK, "%d", pq_err); + + size_t peer_name_len = tox_group_peer_get_name_size(tox, groupnumber, peer_id, &pq_err); + char peer_name[TOX_MAX_NAME_LENGTH + 1]; + + ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); + + tox_group_peer_get_name(tox, groupnumber, peer_id, (uint8_t *) peer_name, &pq_err); + ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); + + peer_name[peer_name_len] = 0; + + // make sure we see the correct peer state on join + if (!state->is_founder) { + ck_assert_msg(role == TOX_GROUP_ROLE_FOUNDER, "wrong role: %d", role); + + if (state->peer_joined_count == 0) { + ck_assert_msg(status == TOX_USER_STATUS_NONE, "wrong status: %d", status); + ck_assert_msg(peer_name_len == PEER0_NICK_LEN, "wrong nick: %s", peer_name); + ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); + } else { + ck_assert_msg(status == TOX_USER_STATUS_BUSY, "wrong status: %d", status); + ck_assert(peer_name_len == PEER0_NICK2_LEN); + ck_assert(memcmp(peer_name, PEER0_NICK2, peer_name_len) == 0); + } + } else { + ck_assert_msg(role == TOX_GROUP_ROLE_USER, "wrong role: %d", role); + ck_assert(peer_name_len == PEER1_NICK_LEN); + ck_assert(memcmp(peer_name, PEER1_NICK, peer_name_len) == 0); + + if (state->peer_joined_count == 0) { + ck_assert_msg(status == TOX_USER_STATUS_NONE, "wrong status: %d", status); + } else { + ck_assert_msg(status == TOX_USER_STATUS_AWAY, "wrong status: %d", status); + } + } + + state->peer_id = peer_id; + ++state->peer_joined_count; +} + +static void group_peer_self_join_handler(Tox *tox, uint32_t groupnumber, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + // make sure we see our own correct peer state on join callback + + Tox_Err_Group_Self_Query sq_err; + size_t self_length = tox_group_self_get_name_size(tox, groupnumber, &sq_err); + + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + uint8_t self_name[TOX_MAX_NAME_LENGTH]; + tox_group_self_get_name(tox, groupnumber, self_name, &sq_err); + + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + TOX_USER_STATUS self_status = tox_group_self_get_status(tox, groupnumber, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + Tox_Group_Role self_role = tox_group_self_get_role(tox, groupnumber, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + if (state->is_founder) { + // founder doesn't get a self join callback on initial creation of group + ck_assert(self_length == PEER0_NICK2_LEN); + ck_assert(memcmp(self_name, PEER0_NICK2, self_length) == 0); + ck_assert(self_status == TOX_USER_STATUS_BUSY); + ck_assert(self_role == TOX_GROUP_ROLE_FOUNDER); + } else { + ck_assert(self_length == PEER1_NICK_LEN); + ck_assert(memcmp(self_name, PEER1_NICK, self_length) == 0); + ck_assert(self_role == TOX_GROUP_ROLE_USER); + ck_assert(self_status == TOX_USER_STATUS_NONE); + } + + // make sure we see correct group state on join callback + uint8_t group_name[GROUP_NAME_LEN]; + uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + + ck_assert(tox_group_get_peer_limit(tox, groupnumber, nullptr) == PEER_LIMIT); + ck_assert(tox_group_get_name_size(tox, groupnumber, nullptr) == GROUP_NAME_LEN); + ck_assert(tox_group_get_topic_size(tox, groupnumber, nullptr) == TOPIC_LEN); + + Tox_Err_Group_State_Queries query_err; + tox_group_get_name(tox, groupnumber, group_name, &query_err); + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", query_err); + ck_assert(memcmp(group_name, GROUP_NAME, GROUP_NAME_LEN) == 0); + + tox_group_get_topic(tox, groupnumber, topic, &query_err); + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", query_err); + ck_assert(memcmp(topic, TOPIC, TOPIC_LEN) == 0); + + ++state->self_joined_count; +} + +static void group_peer_exit_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, Tox_Group_Exit_Type exit_type, + const uint8_t *name, size_t name_length, const uint8_t *part_message, + size_t length, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ++state->peer_exit_count; + + // first exit is a disconnect. second is a real exit with a part message + if (state->peer_exit_count == 2) { + ck_assert(length == EXIT_MESSAGE_LEN); + ck_assert(memcmp(part_message, EXIT_MESSAGE, length) == 0); + } +} + +static void group_peer_name_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, const uint8_t *name, + size_t length, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + // note: we already test the name_get api call elsewhere + + ck_assert(length == PEER0_NICK2_LEN); + ck_assert(memcmp(name, PEER0_NICK2, length) == 0); + + state->peer_nick = true; +} + +static void group_peer_status_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, TOX_USER_STATUS status, + void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + Tox_Err_Group_Peer_Query err; + TOX_USER_STATUS cur_status = tox_group_peer_get_status(tox, groupnumber, peer_id, &err); + + ck_assert_msg(cur_status == status, "%d, %d", cur_status, status); + ck_assert(status == TOX_USER_STATUS_BUSY); + + state->peer_status = true; +} + +static void group_announce_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert_msg(NUM_GROUP_TOXES == 2, "NUM_GROUP_TOXES needs to be 2"); + + Tox *tox0 = autotoxes[0].tox; + Tox *tox1 = autotoxes[1].tox; + State *state0 = (State *)autotoxes[0].state; + State *state1 = (State *)autotoxes[1].state; + + tox_callback_group_peer_join(tox0, group_peer_join_handler); + tox_callback_group_peer_join(tox1, group_peer_join_handler); + tox_callback_group_self_join(tox0, group_peer_self_join_handler); + tox_callback_group_self_join(tox1, group_peer_self_join_handler); + tox_callback_group_peer_name(tox1, group_peer_name_handler); + tox_callback_group_peer_status(tox1, group_peer_status_handler); + tox_callback_group_peer_exit(tox1, group_peer_exit_handler); + + // tox0 makes new group. + Tox_Err_Group_New err_new; + uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *) GROUP_NAME, + GROUP_NAME_LEN, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, + &err_new); + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + state0->is_founder = true; + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + // changes the state (for sync check purposes) + Tox_Err_Group_Founder_Set_Peer_Limit limit_set_err; + tox_group_founder_set_peer_limit(tox0, groupnumber, PEER_LIMIT, &limit_set_err); + ck_assert_msg(limit_set_err == TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK, "failed to set peer limit: %d", limit_set_err); + + Tox_Err_Group_Topic_Set tp_err; + tox_group_set_topic(tox0, groupnumber, (const uint8_t *)TOPIC, TOPIC_LEN, &tp_err); + ck_assert(tp_err == TOX_ERR_GROUP_TOPIC_SET_OK); + + // get the chat id of the new group. + Tox_Err_Group_State_Queries err_id; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + tox_group_get_chat_id(tox0, groupnumber, chat_id, &err_id); + ck_assert(err_id == TOX_ERR_GROUP_STATE_QUERIES_OK); + + // tox1 joins it. + Tox_Err_Group_Join err_join; + tox_group_join(tox1, chat_id, (const uint8_t *)PEER1_NICK, PEER1_NICK_LEN, nullptr, 0, &err_join); + ck_assert(err_join == TOX_ERR_GROUP_JOIN_OK); + + // peers see each other and themselves join + while (!state1->peer_joined_count || !state1->self_joined_count || !state0->peer_joined_count) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + // wait for group syncing to finish + while (!all_group_peers_connected(autotoxes, NUM_GROUP_TOXES, groupnumber, GROUP_NAME_LEN)) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + fprintf(stderr, "Peers connected to group\n"); + + // tox 0 changes name + Tox_Err_Group_Self_Name_Set n_err; + tox_group_self_set_name(tox0, groupnumber, (const uint8_t *)PEER0_NICK2, PEER0_NICK2_LEN, &n_err); + ck_assert(n_err == TOX_ERR_GROUP_SELF_NAME_SET_OK); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + Tox_Err_Group_Self_Query sq_err; + size_t self_length = tox_group_self_get_name_size(tox0, groupnumber, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_length == PEER0_NICK2_LEN); + + uint8_t self_name[TOX_MAX_NAME_LENGTH]; + tox_group_self_get_name(tox0, groupnumber, self_name, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(memcmp(self_name, PEER0_NICK2, self_length) == 0); + + fprintf(stderr, "Peer 0 successfully changed nick\n"); + + // tox 0 changes status + Tox_Err_Group_Self_Status_Set s_err; + tox_group_self_set_status(tox0, groupnumber, TOX_USER_STATUS_BUSY, &s_err); + ck_assert(s_err == TOX_ERR_GROUP_SELF_STATUS_SET_OK); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + TOX_USER_STATUS self_status = tox_group_self_get_status(tox0, groupnumber, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_status == TOX_USER_STATUS_BUSY); + + fprintf(stderr, "Peer 0 successfully changed status to %d\n", self_status); + + while (!state1->peer_nick && !state1->peer_status) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + // tox 0 and tox 1 should see the same public key for tox 0 + uint8_t tox0_self_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; + tox_group_self_get_public_key(tox0, groupnumber, tox0_self_pk, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + Tox_Err_Group_Peer_Query pq_err; + uint8_t tox0_pk_query[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; + tox_group_peer_get_public_key(tox1, groupnumber, state1->peer_id, tox0_pk_query, &pq_err); + ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(memcmp(tox0_pk_query, tox0_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0); + + fprintf(stderr, "Peer 0 disconnecting...\n"); + // tox 0 disconnects then reconnects + Tox_Err_Group_Disconnect d_err; + tox_group_disconnect(tox0, groupnumber, &d_err); + ck_assert(d_err == TOX_ERR_GROUP_DISCONNECT_OK); + + while (state1->peer_exit_count != 1) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + // tox 1 changes status while alone in the group + tox_group_self_set_status(tox1, groupnumber, TOX_USER_STATUS_AWAY, &s_err); + ck_assert(s_err == TOX_ERR_GROUP_SELF_STATUS_SET_OK); + + fprintf(stderr, "Peer 0 reconnecting...\n"); + Tox_Err_Group_Reconnect r_err; + tox_group_reconnect(tox0, groupnumber, &r_err); + ck_assert(r_err == TOX_ERR_GROUP_RECONNECT_OK); + + while (state1->peer_joined_count != 2 && state0->self_joined_count == 2) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + for (size_t i = 0; i < 100; ++i) { // if we don't do this the exit packet never arrives + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + while (!all_group_peers_connected(autotoxes, NUM_GROUP_TOXES, groupnumber, GROUP_NAME_LEN)) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + // tox 0 should have the same public key and still be founder + uint8_t tox0_self_pk2[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; + tox_group_self_get_public_key(tox0, groupnumber, tox0_self_pk2, &sq_err); + + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(memcmp(tox0_self_pk2, tox0_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) == 0); + + Tox_Group_Role self_role = tox_group_self_get_role(tox0, groupnumber, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + Tox_Group_Role other_role = tox_group_peer_get_role(tox1, groupnumber, state1->peer_id, &pq_err); + ck_assert(pq_err == TOX_ERR_GROUP_PEER_QUERY_OK); + + ck_assert(self_role == other_role && self_role == TOX_GROUP_ROLE_FOUNDER); + + uint32_t num_groups1 = tox_group_get_number_groups(tox0); + uint32_t num_groups2 = tox_group_get_number_groups(tox1); + + ck_assert(num_groups1 == num_groups2 && num_groups2 == 1); + + fprintf(stderr, "Both peers exiting group...\n"); + + Tox_Err_Group_Leave err_exit; + tox_group_leave(tox0, groupnumber, (const uint8_t *)EXIT_MESSAGE, EXIT_MESSAGE_LEN, &err_exit); + ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); + + while (state1->peer_exit_count != 2) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + tox_group_leave(tox1, groupnumber, nullptr, 0, &err_exit); + ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); + + num_groups1 = tox_group_get_number_groups(tox0); + num_groups2 = tox_group_get_number_groups(tox1); + + ck_assert(num_groups1 == num_groups2 && num_groups2 == 0); + + printf("All tests passed!\n"); +#endif // VANILLA_NACL +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + + run_auto_test(nullptr, NUM_GROUP_TOXES, group_announce_test, sizeof(State), &options); + + return 0; +} + +#undef NUM_GROUP_TOXES +#undef PEER1_NICK +#undef PEER0_NICK +#undef PEER0_NICK_LEN +#undef PEER1_NICK_LEN +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef PEER_LIMIT +#undef TOPIC +#undef TOPIC_LEN diff --git a/auto_tests/group_invite_test.c b/auto_tests/group_invite_test.c new file mode 100644 index 0000000..f8dde62 --- /dev/null +++ b/auto_tests/group_invite_test.c @@ -0,0 +1,283 @@ +/* + * Tests group invites as well as join restrictions, including password protection, privacy state, + * and peer limits. Ensures sure that the peer being blocked from joining successfully receives + * the invite fail packet with the correct message. + * + * This test also checks that many peers can successfully join the group simultaneously. + */ + +#include +#include +#include + +#include "auto_test_support.h" +#include "check_compat.h" + +typedef struct State { + uint32_t num_peers; + bool peer_limit_fail; + bool password_fail; + bool connected; + size_t messages_received; +} State; + +#define NUM_GROUP_TOXES 8 // must be > 7 + +#define PASSWORD "dadada" +#define PASS_LEN (sizeof(PASSWORD) - 1) + +#define WRONG_PASS "dadadada" +#define WRONG_PASS_LEN (sizeof(WRONG_PASS) - 1) + +static bool group_has_full_graph(const AutoTox *autotoxes, uint32_t group_number, uint32_t expected_peer_count) +{ + for (size_t i = 7; i < NUM_GROUP_TOXES; ++i) { + const State *state = (const State *)autotoxes[i].state; + + if (state->num_peers < expected_peer_count) { + return false; + } + } + + const State *state0 = (const State *)autotoxes[0].state; + const State *state1 = (const State *)autotoxes[1].state; + const State *state5 = (const State *)autotoxes[5].state; + + if (state0->num_peers < expected_peer_count || state1->num_peers < expected_peer_count + || state5->num_peers < expected_peer_count) { + return false; + } + + return true; +} + +static void group_join_fail_handler(Tox *tox, uint32_t group_number, Tox_Group_Join_Fail fail_type, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + switch (fail_type) { + case TOX_GROUP_JOIN_FAIL_PEER_LIMIT: { + state->peer_limit_fail = true; + break; + } + + case TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD: { + state->password_fail = true; + break; + } + + case TOX_GROUP_JOIN_FAIL_UNKNOWN: + + // intentional fallthrough + default: { + ck_assert_msg(false, "Got unknown join fail"); + return; + } + } +} + +static void group_self_join_handler(Tox *tox, uint32_t group_number, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + state->connected = true; +} + +static void group_peer_join_handler(Tox *tox, uint32_t group_number, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ++state->num_peers; + ck_assert(state->num_peers < NUM_GROUP_TOXES); +} + +static void group_invite_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert_msg(NUM_GROUP_TOXES > 7, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + tox_callback_group_peer_join(autotoxes[i].tox, group_peer_join_handler); + tox_callback_group_join_fail(autotoxes[i].tox, group_join_fail_handler); + tox_callback_group_self_join(autotoxes[i].tox, group_self_join_handler); + } + + Tox *tox0 = autotoxes[0].tox; + Tox *tox1 = autotoxes[1].tox; + Tox *tox2 = autotoxes[2].tox; + Tox *tox3 = autotoxes[3].tox; + Tox *tox4 = autotoxes[4].tox; + Tox *tox5 = autotoxes[5].tox; + Tox *tox6 = autotoxes[6].tox; + + State *state0 = (State *)autotoxes[0].state; + State *state2 = (State *)autotoxes[2].state; + State *state3 = (State *)autotoxes[3].state; + State *state4 = (State *)autotoxes[4].state; + State *state5 = (State *)autotoxes[5].state; + State *state6 = (State *)autotoxes[6].state; + + Tox_Err_Group_New new_err; + uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"test", 4, + (const uint8_t *)"test", 4, &new_err); + ck_assert_msg(new_err == TOX_ERR_GROUP_NEW_OK, "tox_group_new failed: %d", new_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + Tox_Err_Group_State_Queries id_err; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + + tox_group_get_chat_id(tox0, groupnumber, chat_id, &id_err); + ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", id_err); + + // peer 1 joins public group with no password + Tox_Err_Group_Join join_err; + tox_group_join(tox1, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err); + + while (state0->num_peers < 1) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + printf("Peer 1 joined group\n"); + + // founder sets a password + Tox_Err_Group_Founder_Set_Password pass_set_err; + tox_group_founder_set_password(tox0, groupnumber, (const uint8_t *)PASSWORD, PASS_LEN, &pass_set_err); + ck_assert_msg(pass_set_err == TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK, "%d", pass_set_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 5000); + + // peer 2 attempts to join with no password + tox_group_join(tox2, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err); + + while (!state2->password_fail) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + printf("Peer 2 successfully blocked with no password\n"); + + // peer 3 attempts to join with invalid password + tox_group_join(tox3, chat_id, (const uint8_t *)"Test", 4, (const uint8_t *)WRONG_PASS, WRONG_PASS_LEN, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err); + + while (!state3->password_fail) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + printf("Peer 3 successfully blocked with invalid password\n"); + + // founder sets peer limit to 1 + Tox_Err_Group_Founder_Set_Peer_Limit limit_set_err; + tox_group_founder_set_peer_limit(tox0, groupnumber, 1, &limit_set_err); + ck_assert_msg(limit_set_err == TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK, "%d", limit_set_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 5000); + + // peer 4 attempts to join with correct password + tox_group_join(tox4, chat_id, (const uint8_t *)"Test", 4, (const uint8_t *)PASSWORD, PASS_LEN, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err); + + while (!state4->peer_limit_fail) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + printf("Peer 4 successfully blocked from joining full group\n"); + + // founder removes password and increases peer limit to 100 + tox_group_founder_set_password(tox0, groupnumber, nullptr, 0, &pass_set_err); + ck_assert_msg(pass_set_err == TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK, "%d", pass_set_err); + + tox_group_founder_set_peer_limit(tox0, groupnumber, 100, &limit_set_err); + ck_assert_msg(limit_set_err == TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK, "%d", limit_set_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 5000); + + // peer 5 attempts to join group + tox_group_join(tox5, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err); + + while (!state5->connected) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + printf("Peer 5 successfully joined the group\n"); + + // founder makes group private + Tox_Err_Group_Founder_Set_Privacy_State priv_err; + tox_group_founder_set_privacy_state(tox0, groupnumber, TOX_GROUP_PRIVACY_STATE_PRIVATE, &priv_err); + ck_assert_msg(priv_err == TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK, "%d", priv_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 5000); + + // peer 6 attempts to join group via chat ID + tox_group_join(tox6, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err); + + // since we don't receive a fail packet in this case we just wait a while and check if we're in the group + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, 20000); + + ck_assert(!state6->connected); + + printf("Peer 6 failed to join private group via chat ID\n"); + + // founder makes group public again + tox_group_founder_set_privacy_state(tox0, groupnumber, TOX_GROUP_PRIVACY_STATE_PUBLIC, &priv_err); + ck_assert_msg(priv_err == TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK, "%d", priv_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + const uint32_t num_new_peers = NUM_GROUP_TOXES - 7; + printf("Connecting %u peers at the same time\n", num_new_peers); + + for (size_t i = 7; i < NUM_GROUP_TOXES; ++i) { + tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err); + } + + const uint32_t expected_peer_count = num_new_peers + state0->num_peers + 1; + + while (!group_has_full_graph(autotoxes, groupnumber, expected_peer_count)) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + printf("Every peer sees every other peer\n"); + + for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { + Tox_Err_Group_Leave err_exit; + tox_group_leave(autotoxes[i].tox, 0, nullptr, 0, &err_exit); + ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); + } + + printf("All tests passed!\n"); + +#endif // VANILLA_NACL +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options autotest_opts = default_run_auto_options(); + autotest_opts.graph = GRAPH_COMPLETE; + + run_auto_test(nullptr, NUM_GROUP_TOXES, group_invite_test, sizeof(State), &autotest_opts); + + return 0; +} + +#undef NUM_GROUP_TOXES +#undef PASSWORD +#undef PASS_LEN +#undef WRONG_PASS +#undef WRONG_PASS_LEN diff --git a/auto_tests/group_message_test.c b/auto_tests/group_message_test.c new file mode 100644 index 0000000..d7a00a9 --- /dev/null +++ b/auto_tests/group_message_test.c @@ -0,0 +1,546 @@ +/* + * Tests message sending capabilities, including: + * - The ability to send/receive plain, action, and custom messages + * - The lossless UDP implementation + * - The packet splitting implementation + * - The ignore feature + */ + +#include +#include +#include + +#include "auto_test_support.h" +#include "check_compat.h" +#include "../toxcore/util.h" + +typedef struct State { + uint32_t peer_id; + bool peer_joined; + bool message_sent; + bool message_received; + uint32_t pseudo_msg_id; + bool private_message_received; + size_t custom_packets_received; + size_t custom_private_packets_received; + bool lossless_check; + bool wraparound_check; + int32_t last_msg_recv; +} State; + +#define NUM_GROUP_TOXES 2 +#define MAX_NUM_MESSAGES_LOSSLESS_TEST 300 +#define MAX_NUM_MESSAGES_WRAPAROUND_TEST 9001 + +#define TEST_MESSAGE "Where is it I've read that someone condemned to death says or thinks, an hour before his death, that if he had to live on some high rock, on such a narrow ledge that he'd only room to stand, and the ocean, everlasting darkness, everlasting solitude, everlasting tempest around him, if he had to remain standing on a square yard of space all his life, a thousand years, eternity, it were better to live so than to die at once. Only to live, to live and live! Life, whatever it may be!" +#define TEST_MESSAGE_LEN (sizeof(TEST_MESSAGE) - 1) + +#define TEST_GROUP_NAME "Utah Data Center" +#define TEST_GROUP_NAME_LEN (sizeof(TEST_GROUP_NAME) - 1) + +#define TEST_PRIVATE_MESSAGE "Don't spill yer beans" +#define TEST_PRIVATE_MESSAGE_LEN (sizeof(TEST_PRIVATE_MESSAGE) - 1) + +#define TEST_CUSTOM_PACKET "Why'd ya spill yer beans?" +#define TEST_CUSTOM_PACKET_LEN (sizeof(TEST_CUSTOM_PACKET) - 1) + +#define TEST_CUSTOM_PRIVATE_PACKET "This is a custom private packet. Enjoy." +#define TEST_CUSTOM_PRIVATE_PACKET_LEN (sizeof(TEST_CUSTOM_PRIVATE_PACKET) - 1) + +#define IGNORE_MESSAGE "Am I bothering you?" +#define IGNORE_MESSAGE_LEN (sizeof(IGNORE_MESSAGE) - 1) + +#define PEER0_NICK "Thomas" +#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) + +#define PEER1_NICK "Winslow" +#define PEER1_NICK_LEN (sizeof(PEER1_NICK) - 1) + +static uint16_t get_message_checksum(const uint8_t *message, uint16_t length) +{ + uint16_t sum = 0; + + for (size_t i = 0; i < length; ++i) { + sum += message[i]; + } + + return sum; +} + +static void group_invite_handler(Tox *tox, uint32_t friend_number, const uint8_t *invite_data, size_t length, + const uint8_t *group_name, size_t group_name_length, void *user_data) +{ + printf("invite arrived; accepting\n"); + Tox_Err_Group_Invite_Accept err_accept; + tox_group_invite_accept(tox, friend_number, invite_data, length, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, + nullptr, 0, &err_accept); + ck_assert(err_accept == TOX_ERR_GROUP_INVITE_ACCEPT_OK); +} + +static void group_join_fail_handler(Tox *tox, uint32_t groupnumber, Tox_Group_Join_Fail fail_type, void *user_data) +{ + printf("join failed: %d\n", fail_type); +} + +static void group_peer_join_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert_msg(state->peer_joined == false, "Peer timedout"); + + printf("peer %u joined, sending message\n", peer_id); + state->peer_joined = true; + state->peer_id = peer_id; +} + +static void group_custom_private_packet_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, const uint8_t *data, + size_t length, void *user_data) +{ + ck_assert_msg(length == TEST_CUSTOM_PRIVATE_PACKET_LEN, + "Failed to receive custom private packet. Invalid length: %zu\n", length); + + char message_buf[TOX_MAX_CUSTOM_PACKET_SIZE + 1]; + memcpy(message_buf, data, length); + message_buf[length] = 0; + + Tox_Err_Group_Peer_Query q_err; + size_t peer_name_len = tox_group_peer_get_name_size(tox, groupnumber, peer_id, &q_err); + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); + + char peer_name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_peer_get_name(tox, groupnumber, peer_id, (uint8_t *) peer_name, &q_err); + peer_name[peer_name_len] = 0; + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); + + Tox_Err_Group_Self_Query s_err; + size_t self_name_len = tox_group_self_get_name_size(tox, groupnumber, &s_err); + ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_name_len <= TOX_MAX_NAME_LENGTH); + + char self_name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_self_get_name(tox, groupnumber, (uint8_t *) self_name, &s_err); + self_name[self_name_len] = 0; + + ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(memcmp(self_name, PEER1_NICK, self_name_len) == 0); + + printf("%s sent custom private packet to %s: %s\n", peer_name, self_name, message_buf); + ck_assert(memcmp(message_buf, TEST_CUSTOM_PRIVATE_PACKET, length) == 0); + + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ++state->custom_private_packets_received; +} + +static void group_custom_packet_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, const uint8_t *data, + size_t length, void *user_data) +{ + ck_assert_msg(length == TEST_CUSTOM_PACKET_LEN, "Failed to receive custom packet. Invalid length: %zu\n", length); + + char message_buf[TOX_MAX_CUSTOM_PACKET_SIZE + 1]; + memcpy(message_buf, data, length); + message_buf[length] = 0; + + Tox_Err_Group_Peer_Query q_err; + size_t peer_name_len = tox_group_peer_get_name_size(tox, groupnumber, peer_id, &q_err); + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); + + char peer_name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_peer_get_name(tox, groupnumber, peer_id, (uint8_t *) peer_name, &q_err); + peer_name[peer_name_len] = 0; + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); + + Tox_Err_Group_Self_Query s_err; + size_t self_name_len = tox_group_self_get_name_size(tox, groupnumber, &s_err); + ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_name_len <= TOX_MAX_NAME_LENGTH); + + char self_name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_self_get_name(tox, groupnumber, (uint8_t *) self_name, &s_err); + self_name[self_name_len] = 0; + + ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(memcmp(self_name, PEER1_NICK, self_name_len) == 0); + + printf("%s sent custom packet to %s: %s\n", peer_name, self_name, message_buf); + ck_assert(memcmp(message_buf, TEST_CUSTOM_PACKET, length) == 0); + + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ++state->custom_packets_received; +} + +static void group_message_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, TOX_MESSAGE_TYPE type, + const uint8_t *message, size_t length, uint32_t pseudo_msg_id, void *user_data) +{ + ck_assert(!(length == IGNORE_MESSAGE_LEN && memcmp(message, IGNORE_MESSAGE, length) == 0)); + ck_assert_msg(length == TEST_MESSAGE_LEN, "Failed to receive message. Invalid length: %zu\n", length); + + char message_buf[TOX_GROUP_MAX_MESSAGE_LENGTH + 1]; + memcpy(message_buf, message, length); + message_buf[length] = 0; + + Tox_Err_Group_Peer_Query q_err; + size_t peer_name_len = tox_group_peer_get_name_size(tox, groupnumber, peer_id, &q_err); + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); + + char peer_name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_peer_get_name(tox, groupnumber, peer_id, (uint8_t *) peer_name, &q_err); + peer_name[peer_name_len] = 0; + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); + + Tox_Err_Group_Self_Query s_err; + size_t self_name_len = tox_group_self_get_name_size(tox, groupnumber, &s_err); + ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_name_len <= TOX_MAX_NAME_LENGTH); + + char self_name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_self_get_name(tox, groupnumber, (uint8_t *) self_name, &s_err); + self_name[self_name_len] = 0; + + ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(memcmp(self_name, PEER1_NICK, self_name_len) == 0); + + printf("%s sent message to %s:(id:%u) %s\n", peer_name, self_name, pseudo_msg_id, message_buf); + ck_assert(memcmp(message_buf, TEST_MESSAGE, length) == 0); + + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + state->message_received = true; + + state->pseudo_msg_id = pseudo_msg_id; +} + +static void group_private_message_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, TOX_MESSAGE_TYPE type, + const uint8_t *message, size_t length, void *user_data) +{ + ck_assert_msg(length == TEST_PRIVATE_MESSAGE_LEN, "Failed to receive message. Invalid length: %zu\n", length); + + char message_buf[TOX_GROUP_MAX_MESSAGE_LENGTH + 1]; + memcpy(message_buf, message, length); + message_buf[length] = 0; + + Tox_Err_Group_Peer_Query q_err; + size_t peer_name_len = tox_group_peer_get_name_size(tox, groupnumber, peer_id, &q_err); + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); + + char peer_name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_peer_get_name(tox, groupnumber, peer_id, (uint8_t *) peer_name, &q_err); + peer_name[peer_name_len] = 0; + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(memcmp(peer_name, PEER0_NICK, peer_name_len) == 0); + + Tox_Err_Group_Self_Query s_err; + size_t self_name_len = tox_group_self_get_name_size(tox, groupnumber, &s_err); + ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_name_len <= TOX_MAX_NAME_LENGTH); + + char self_name[TOX_MAX_NAME_LENGTH + 1]; + tox_group_self_get_name(tox, groupnumber, (uint8_t *) self_name, &s_err); + self_name[self_name_len] = 0; + + ck_assert(s_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(memcmp(self_name, PEER1_NICK, self_name_len) == 0); + + printf("%s sent private action to %s: %s\n", peer_name, self_name, message_buf); + ck_assert(memcmp(message_buf, TEST_PRIVATE_MESSAGE, length) == 0); + + ck_assert(type == TOX_MESSAGE_TYPE_ACTION); + + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + state->private_message_received = true; +} + +static void group_message_handler_lossless_test(Tox *tox, uint32_t groupnumber, uint32_t peer_id, TOX_MESSAGE_TYPE type, + const uint8_t *message, size_t length, uint32_t pseudo_msg_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(length >= 4 && length <= TOX_GROUP_MAX_MESSAGE_LENGTH); + + uint16_t start; + uint16_t checksum; + memcpy(&start, message, sizeof(uint16_t)); + memcpy(&checksum, message + sizeof(uint16_t), sizeof(uint16_t)); + + ck_assert_msg(start == state->last_msg_recv + 1, "Expected %d, got start %u", state->last_msg_recv + 1, start); + ck_assert_msg(checksum == get_message_checksum(message + 4, length - 4), "Wrong checksum"); + + state->last_msg_recv = start; + + if (state->last_msg_recv == MAX_NUM_MESSAGES_LOSSLESS_TEST) { + state->lossless_check = true; + } +} +static void group_message_handler_wraparound_test(Tox *tox, uint32_t groupnumber, uint32_t peer_id, + TOX_MESSAGE_TYPE type, + const uint8_t *message, size_t length, uint32_t pseudo_msg_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(length == 2); + + uint16_t num; + memcpy(&num, message, sizeof(uint16_t)); + + ck_assert_msg(num == state->last_msg_recv + 1, "Expected %d, got start %u", state->last_msg_recv + 1, num); + + state->last_msg_recv = num; + + if (state->last_msg_recv == MAX_NUM_MESSAGES_WRAPAROUND_TEST) { + state->wraparound_check = true; + } +} + +static void group_message_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert_msg(NUM_GROUP_TOXES >= 2, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); + + const Random *rng = system_random(); + ck_assert(rng != nullptr); + + Tox *tox0 = autotoxes[0].tox; + Tox *tox1 = autotoxes[1].tox; + + State *state0 = (State *)autotoxes[0].state; + State *state1 = (State *)autotoxes[1].state; + + // initialize to different values + state0->pseudo_msg_id = 0; + state1->pseudo_msg_id = 1; + + tox_callback_group_invite(tox1, group_invite_handler); + tox_callback_group_join_fail(tox1, group_join_fail_handler); + tox_callback_group_peer_join(tox1, group_peer_join_handler); + tox_callback_group_join_fail(tox0, group_join_fail_handler); + tox_callback_group_peer_join(tox0, group_peer_join_handler); + tox_callback_group_message(tox0, group_message_handler); + tox_callback_group_custom_packet(tox0, group_custom_packet_handler); + tox_callback_group_custom_private_packet(tox0, group_custom_private_packet_handler); + tox_callback_group_private_message(tox0, group_private_message_handler); + + Tox_Err_Group_Send_Message err_send; + + fprintf(stderr, "Tox 0 creates new group and invites tox1...\n"); + + // tox0 makes new group. + Tox_Err_Group_New err_new; + const uint32_t group_number = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PRIVATE, (const uint8_t *)TEST_GROUP_NAME, + TEST_GROUP_NAME_LEN, (const uint8_t *)PEER1_NICK, PEER1_NICK_LEN, &err_new); + + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + // tox0 invites tox1 + Tox_Err_Group_Invite_Friend err_invite; + tox_group_invite_friend(tox0, group_number, 0, &err_invite); + ck_assert(err_invite == TOX_ERR_GROUP_INVITE_FRIEND_OK); + + while (!state0->message_received) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + if (state1->peer_joined && !state1->message_sent) { + tox_group_send_message(tox1, group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)TEST_MESSAGE, + TEST_MESSAGE_LEN, &state1->pseudo_msg_id, &err_send); + ck_assert(err_send == TOX_ERR_GROUP_SEND_MESSAGE_OK); + state1->message_sent = true; + } + } + + ck_assert_msg(state0->pseudo_msg_id == state1->pseudo_msg_id, "id0:%u id1:%u", state0->pseudo_msg_id, state1->pseudo_msg_id); + + // Make sure we're still connected to each friend + Tox_Connection conn_1 = tox_friend_get_connection_status(tox0, 0, nullptr); + Tox_Connection conn_2 = tox_friend_get_connection_status(tox1, 0, nullptr); + + ck_assert(conn_1 != TOX_CONNECTION_NONE && conn_2 != TOX_CONNECTION_NONE); + + // tox0 ignores tox1 + Tox_Err_Group_Set_Ignore ig_err; + tox_group_set_ignore(tox0, group_number, state0->peer_id, true, &ig_err); + ck_assert_msg(ig_err == TOX_ERR_GROUP_SET_IGNORE_OK, "%d", ig_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + // tox1 sends group a message which should not be seen by tox0's message handler + tox_group_send_message(tox1, group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)IGNORE_MESSAGE, + IGNORE_MESSAGE_LEN, nullptr, &err_send); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + // tox0 unignores tox1 + tox_group_set_ignore(tox0, group_number, state0->peer_id, false, &ig_err); + ck_assert_msg(ig_err == TOX_ERR_GROUP_SET_IGNORE_OK, "%d", ig_err); + + fprintf(stderr, "Sending private message...\n"); + + // tox0 sends a private action to tox1 + Tox_Err_Group_Send_Private_Message m_err; + tox_group_send_private_message(tox1, group_number, state1->peer_id, TOX_MESSAGE_TYPE_ACTION, + (const uint8_t *)TEST_PRIVATE_MESSAGE, TEST_PRIVATE_MESSAGE_LEN, &m_err); + ck_assert_msg(m_err == TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK, "%d", m_err); + + fprintf(stderr, "Sending custom packets...\n"); + + // tox0 sends a lossless and lossy custom packet to tox1 + Tox_Err_Group_Send_Custom_Packet c_err; + tox_group_send_custom_packet(tox1, group_number, true, (const uint8_t *)TEST_CUSTOM_PACKET, TEST_CUSTOM_PACKET_LEN, + &c_err); + ck_assert_msg(c_err == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK, "%d", c_err); + + tox_group_send_custom_packet(tox1, group_number, false, (const uint8_t *)TEST_CUSTOM_PACKET, TEST_CUSTOM_PACKET_LEN, + &c_err); + ck_assert_msg(c_err == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK, "%d", c_err); + + fprintf(stderr, "Sending custom private packets...\n"); + + // tox0 sends a lossless and lossy custom private packet to tox1 + Tox_Err_Group_Send_Custom_Private_Packet cperr; + tox_group_send_custom_private_packet(tox1, group_number, state1->peer_id, true, + (const uint8_t *)TEST_CUSTOM_PRIVATE_PACKET, + TEST_CUSTOM_PRIVATE_PACKET_LEN, &cperr); + + ck_assert_msg(cperr == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK, "%d", cperr); + + tox_group_send_custom_private_packet(tox1, group_number, state1->peer_id, false, + (const uint8_t *)TEST_CUSTOM_PRIVATE_PACKET, + TEST_CUSTOM_PRIVATE_PACKET_LEN, &cperr); + + ck_assert_msg(cperr == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK, "%d", cperr); + + while (!state0->private_message_received || state0->custom_packets_received < 2 + || state0->custom_private_packets_received < 2) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + uint8_t m[TOX_GROUP_MAX_MESSAGE_LENGTH] = {0}; + + fprintf(stderr, "Doing lossless packet test...\n"); + + tox_callback_group_message(tox1, group_message_handler_lossless_test); + state1->last_msg_recv = -1; + + // lossless and packet splitting/reassembly test + for (uint16_t i = 0; i <= MAX_NUM_MESSAGES_LOSSLESS_TEST; ++i) { + if (i % 10 == 0) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + uint16_t message_size = min_u16(4 + (random_u16(rng) % TOX_GROUP_MAX_MESSAGE_LENGTH), TOX_GROUP_MAX_MESSAGE_LENGTH); + + memcpy(m, &i, sizeof(uint16_t)); + + for (size_t j = 4; j < message_size; ++j) { + m[j] = random_u32(rng); + } + + const uint16_t checksum = get_message_checksum(m + 4, message_size - 4); + + memcpy(m + 2, &checksum, sizeof(uint16_t)); + + tox_group_send_message(tox0, group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)m, message_size, nullptr, &err_send); + + ck_assert(err_send == TOX_ERR_GROUP_SEND_MESSAGE_OK); + } + + while (!state1->lossless_check) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + state1->last_msg_recv = -1; + tox_callback_group_message(tox1, group_message_handler_wraparound_test); + + fprintf(stderr, "Doing wraparound test...\n"); + + // packet array wrap-around test + for (uint16_t i = 0; i <= MAX_NUM_MESSAGES_WRAPAROUND_TEST; ++i) { + if (i % 10 == 0) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + memcpy(m, &i, sizeof(uint16_t)); + + tox_group_send_message(tox0, group_number, TOX_MESSAGE_TYPE_NORMAL, (const uint8_t *)m, 2, nullptr, &err_send); + ck_assert(err_send == TOX_ERR_GROUP_SEND_MESSAGE_OK); + } + + while (!state1->wraparound_check) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { + Tox_Err_Group_Leave err_exit; + tox_group_leave(autotoxes[i].tox, group_number, nullptr, 0, &err_exit); + ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); + } + + fprintf(stderr, "All tests passed!\n"); +#endif // VANILLA_NACL +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options autotest_opts = default_run_auto_options(); + autotest_opts.graph = GRAPH_COMPLETE; + + run_auto_test(nullptr, NUM_GROUP_TOXES, group_message_test, sizeof(State), &autotest_opts); + return 0; +} + +#undef NUM_GROUP_TOXES +#undef PEER1_NICK +#undef PEER1_NICK_LEN +#undef PEER0_NICK +#undef PEER0_NICK_LEN +#undef TEST_GROUP_NAME +#undef TEST_GROUP_NAME_LEN +#undef TEST_MESSAGE +#undef TEST_MESSAGE_LEN +#undef TEST_PRIVATE_MESSAGE_LEN +#undef TEST_CUSTOM_PACKET +#undef TEST_CUSTOM_PACKET_LEN +#undef TEST_CUSTOM_PRIVATE_PACKET +#undef TEST_CUSTOM_PRIVATE_PACKET_LEN +#undef IGNORE_MESSAGE +#undef IGNORE_MESSAGE_LEN +#undef MAX_NUM_MESSAGES_LOSSLESS_TEST +#undef MAX_NUM_MESSAGES_WRAPAROUND_TEST diff --git a/auto_tests/group_moderation_test.c b/auto_tests/group_moderation_test.c new file mode 100644 index 0000000..a53609f --- /dev/null +++ b/auto_tests/group_moderation_test.c @@ -0,0 +1,653 @@ +/* + * Tests group moderation functionality. + * + * Note that making the peer count too high will break things. This test should not be relied on + * for general group/syncing functionality. + */ + +#include +#include +#include + +#include "auto_test_support.h" +#include "check_compat.h" + +#include "../toxcore/tox.h" + +#define NUM_GROUP_TOXES 5 +#define GROUP_NAME "NASA Headquarters" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) + +typedef struct Peer { + char name[TOX_MAX_NAME_LENGTH]; + size_t name_length; + uint32_t peer_id; +} Peer; + +typedef struct State { + char self_name[TOX_MAX_NAME_LENGTH]; + size_t self_name_length; + + uint32_t group_number; + + uint32_t num_peers; + Peer peers[NUM_GROUP_TOXES - 1]; + + bool mod_check; + size_t mod_event_count; + char mod_name1[TOX_MAX_NAME_LENGTH]; + char mod_name2[TOX_MAX_NAME_LENGTH]; + + + bool observer_check; + size_t observer_event_count; + char observer_name1[TOX_MAX_NAME_LENGTH]; + char observer_name2[TOX_MAX_NAME_LENGTH]; + + bool user_check; + size_t user_event_count; + + bool kick_check; // mod gets kicked +} State; + +static bool all_peers_connected(AutoTox *autotoxes) +{ + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + State *state = (State *)autotoxes[i].state; + + if (state->num_peers != NUM_GROUP_TOXES - 1) { + return false; + } + + if (!tox_group_is_connected(autotoxes[i].tox, state->group_number, nullptr)) { + return false; + } + } + + return true; +} + +/* + * Waits for all peers to receive the mod event. + */ +static void check_mod_event(AutoTox *autotoxes, size_t num_peers, Tox_Group_Mod_Event event) +{ + uint32_t peers_recv_changes = 0; + + do { + peers_recv_changes = 0; + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + for (size_t i = 0; i < num_peers; ++i) { + State *state = (State *)autotoxes[i].state; + bool check = false; + + switch (event) { + case TOX_GROUP_MOD_EVENT_MODERATOR: { + if (state->mod_check) { + check = true; + state->mod_check = false; + } + + break; + } + + case TOX_GROUP_MOD_EVENT_OBSERVER: { + if (state->observer_check) { + check = true; + state->observer_check = false; + } + + break; + } + + case TOX_GROUP_MOD_EVENT_USER: { + if (state->user_check) { + check = true; + state->user_check = false; + } + + break; + } + + case TOX_GROUP_MOD_EVENT_KICK: { + check = state->kick_check; + break; + } + + default: { + ck_assert(0); + } + } + + if (check) { + ++peers_recv_changes; + } + } + } while (peers_recv_changes < num_peers - 1); +} + +static uint32_t get_peer_id_by_nick(const Peer *peers, uint32_t num_peers, const char *name) +{ + ck_assert(name != nullptr); + + for (uint32_t i = 0; i < num_peers; ++i) { + if (memcmp(peers[i].name, name, peers[i].name_length) == 0) { + return peers[i].peer_id; + } + } + + ck_assert_msg(0, "Failed to find peer id"); +} + +static size_t get_state_index_by_nick(const AutoTox *autotoxes, size_t num_peers, const char *name, size_t name_length) +{ + ck_assert(name != nullptr && name_length <= TOX_MAX_NAME_LENGTH); + + for (size_t i = 0; i < num_peers; ++i) { + State *state = (State *)autotoxes[i].state; + + if (memcmp(state->self_name, name, name_length) == 0) { + return i; + } + } + + ck_assert_msg(0, "Failed to find index"); +} + +static void group_join_fail_handler(Tox *tox, uint32_t group_number, Tox_Group_Join_Fail fail_type, void *user_data) +{ + fprintf(stderr, "Failed to join group: %d", fail_type); +} + +static void group_peer_join_handler(Tox *tox, uint32_t group_number, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(state->group_number == group_number); + + char peer_name[TOX_MAX_NAME_LENGTH + 1]; + + Tox_Err_Group_Peer_Query q_err; + size_t peer_name_len = tox_group_peer_get_name_size(tox, group_number, peer_id, &q_err); + + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); + + tox_group_peer_get_name(tox, group_number, peer_id, (uint8_t *) peer_name, &q_err); + peer_name[peer_name_len] = 0; + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + + Peer *peer = &state->peers[state->num_peers]; + + peer->peer_id = peer_id; + memcpy(peer->name, peer_name, peer_name_len); + peer->name_length = peer_name_len; + + ++state->num_peers; + + ck_assert(state->num_peers < NUM_GROUP_TOXES); +} + +static void handle_mod(State *state, const char *peer_name, size_t peer_name_len, Tox_Group_Role role) +{ + if (state->mod_event_count == 0) { + ck_assert(memcmp(peer_name, state->mod_name1, peer_name_len) == 0); + } else if (state->mod_event_count == 1) { + ck_assert(memcmp(peer_name, state->mod_name2, peer_name_len) == 0); + } else { + ck_assert(false); + } + + ++state->mod_event_count; + state->mod_check = true; + ck_assert(role == TOX_GROUP_ROLE_MODERATOR); +} + +static void handle_observer(State *state, const char *peer_name, size_t peer_name_len, Tox_Group_Role role) +{ + if (state->observer_event_count == 0) { + ck_assert(memcmp(peer_name, state->observer_name1, peer_name_len) == 0); + } else if (state->observer_event_count == 1) { + ck_assert(memcmp(peer_name, state->observer_name2, peer_name_len) == 0); + } else { + ck_assert(false); + } + + ++state->observer_event_count; + state->observer_check = true; + ck_assert(role == TOX_GROUP_ROLE_OBSERVER); +} + +static void handle_user(State *state, const char *peer_name, size_t peer_name_len, Tox_Group_Role role) +{ + // event 1: observer1 gets promoted back to user + // event 2: observer2 gets promoted to moderator + // event 3: moderator 1 gets kicked + // event 4: moderator 2 gets demoted to moderator + if (state->user_event_count == 0) { + ck_assert(memcmp(peer_name, state->observer_name1, peer_name_len) == 0); + } else if (state->user_event_count == 1) { + ck_assert(memcmp(peer_name, state->observer_name2, peer_name_len) == 0); + } else if (state->user_event_count == 2) { + ck_assert(memcmp(peer_name, state->mod_name1, peer_name_len) == 0); + } else if (state->user_event_count == 3) { + ck_assert(memcmp(peer_name, state->mod_name2, peer_name_len) == 0); + } else { + ck_assert(false); + } + + ++state->user_event_count; + state->user_check = true; + ck_assert(role == TOX_GROUP_ROLE_USER); +} + +static void group_mod_event_handler(Tox *tox, uint32_t group_number, uint32_t source_peer_id, uint32_t target_peer_id, + Tox_Group_Mod_Event event, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(state->group_number == group_number); + + char peer_name[TOX_MAX_NAME_LENGTH + 1]; + + Tox_Err_Group_Peer_Query q_err; + size_t peer_name_len = tox_group_peer_get_name_size(tox, group_number, target_peer_id, &q_err); + + if (q_err == TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND) { // may occurr on sync attempts + return; + } + + ck_assert_msg(q_err == TOX_ERR_GROUP_PEER_QUERY_OK, "error %d", q_err); + ck_assert(peer_name_len <= TOX_MAX_NAME_LENGTH); + + tox_group_peer_get_name(tox, group_number, target_peer_id, (uint8_t *) peer_name, &q_err); + peer_name[peer_name_len] = 0; + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + + Tox_Group_Role role = tox_group_peer_get_role(tox, group_number, target_peer_id, &q_err); + ck_assert(q_err == TOX_ERR_GROUP_PEER_QUERY_OK); + + switch (event) { + case TOX_GROUP_MOD_EVENT_MODERATOR: { + handle_mod(state, peer_name, peer_name_len, role); + break; + } + + case TOX_GROUP_MOD_EVENT_OBSERVER: { + handle_observer(state, peer_name, peer_name_len, role); + break; + } + + case TOX_GROUP_MOD_EVENT_USER: { + handle_user(state, peer_name, peer_name_len, role); + break; + } + + case TOX_GROUP_MOD_EVENT_KICK: { + ck_assert(memcmp(peer_name, state->mod_name1, peer_name_len) == 0); + state->kick_check = true; + break; + } + + default: { + ck_assert_msg(0, "Got invalid moderator event %d", event); + return; + } + } +} + +/* Checks that `peer_id` sees itself with the role `role`. */ +static void check_self_role(AutoTox *autotoxes, uint32_t peer_id, Tox_Group_Role role) +{ + Tox_Err_Group_Self_Query sq_err; + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + State *state = (State *)autotoxes[i].state; + + uint32_t self_peer_id = tox_group_self_get_peer_id(autotoxes[i].tox, state->group_number, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + if (self_peer_id == peer_id) { + Tox_Group_Role self_role = tox_group_self_get_role(autotoxes[i].tox, state->group_number, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_role == role); + return; + } + } +} + +/* Makes sure that a peer's role respects the voice state */ +static void voice_state_message_test(AutoTox *autotox, Tox_Group_Voice_State voice_state) +{ + const State *state = (State *)autotox->state; + + Tox_Err_Group_Self_Query sq_err; + Tox_Group_Role self_role = tox_group_self_get_role(autotox->tox, state->group_number, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + Tox_Err_Group_Send_Message msg_err; + bool send_ret = tox_group_send_message(autotox->tox, state->group_number, TOX_MESSAGE_TYPE_NORMAL, + (const uint8_t *)"test", 4, nullptr, &msg_err); + + switch (self_role) { + case TOX_GROUP_ROLE_OBSERVER: { + ck_assert(!send_ret && msg_err == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS); + break; + } + + case TOX_GROUP_ROLE_USER: { + if (voice_state != TOX_GROUP_VOICE_STATE_ALL) { + ck_assert_msg(!send_ret && msg_err == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS, + "%d, %d", send_ret, msg_err); + } else { + ck_assert(send_ret && msg_err == TOX_ERR_GROUP_SEND_MESSAGE_OK); + } + + break; + } + + case TOX_GROUP_ROLE_MODERATOR: { + if (voice_state != TOX_GROUP_VOICE_STATE_FOUNDER) { + ck_assert(send_ret && msg_err == TOX_ERR_GROUP_SEND_MESSAGE_OK); + } else { + ck_assert(!send_ret && msg_err == TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS); + } + + break; + } + + case TOX_GROUP_ROLE_FOUNDER: { + ck_assert(send_ret && msg_err == TOX_ERR_GROUP_SEND_MESSAGE_OK); + break; + } + } +} + +static bool all_peers_got_voice_state_change(AutoTox *autotoxes, uint32_t num_toxes, + Tox_Group_Voice_State expected_voice_state) +{ + Tox_Err_Group_State_Queries query_err; + + for (uint32_t i = 0; i < num_toxes; ++i) { + const State *state = (State *)autotoxes[i].state; + + Tox_Group_Voice_State voice_state = tox_group_get_voice_state(autotoxes[i].tox, state->group_number, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + if (voice_state != expected_voice_state) { + return false; + } + } + + return true; +} + +static void check_voice_state(AutoTox *autotoxes, uint32_t num_toxes) +{ + // founder sets voice state to Moderator + const State *state = (State *)autotoxes[0].state; + Tox_Err_Group_Founder_Set_Voice_State voice_set_err; + tox_group_founder_set_voice_state(autotoxes[0].tox, state->group_number, TOX_GROUP_VOICE_STATE_MODERATOR, + &voice_set_err); + ck_assert(voice_set_err == TOX_ERR_GROUP_FOUNDER_SET_VOICE_STATE_OK); + + for (uint32_t i = 0; i < num_toxes; ++i) { + do { + iterate_all_wait(autotoxes, num_toxes, ITERATION_INTERVAL); + } while (!all_peers_got_voice_state_change(autotoxes, num_toxes, TOX_GROUP_VOICE_STATE_MODERATOR)); + + voice_state_message_test(&autotoxes[i], TOX_GROUP_VOICE_STATE_MODERATOR); + } + + tox_group_founder_set_voice_state(autotoxes[0].tox, state->group_number, TOX_GROUP_VOICE_STATE_FOUNDER, &voice_set_err); + ck_assert(voice_set_err == TOX_ERR_GROUP_FOUNDER_SET_VOICE_STATE_OK); + + for (uint32_t i = 0; i < num_toxes; ++i) { + do { + iterate_all_wait(autotoxes, num_toxes, ITERATION_INTERVAL); + } while (!all_peers_got_voice_state_change(autotoxes, num_toxes, TOX_GROUP_VOICE_STATE_FOUNDER)); + + voice_state_message_test(&autotoxes[i], TOX_GROUP_VOICE_STATE_FOUNDER); + } + + tox_group_founder_set_voice_state(autotoxes[0].tox, state->group_number, TOX_GROUP_VOICE_STATE_ALL, &voice_set_err); + ck_assert(voice_set_err == TOX_ERR_GROUP_FOUNDER_SET_VOICE_STATE_OK); + + for (uint32_t i = 0; i < num_toxes; ++i) { + do { + iterate_all_wait(autotoxes, num_toxes, ITERATION_INTERVAL); + } while (!all_peers_got_voice_state_change(autotoxes, num_toxes, TOX_GROUP_VOICE_STATE_ALL)); + + voice_state_message_test(&autotoxes[i], TOX_GROUP_VOICE_STATE_ALL); + } +} + +static void group_moderation_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert_msg(NUM_GROUP_TOXES >= 4, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); + ck_assert_msg(NUM_GROUP_TOXES < 10, "NUM_GROUP_TOXES is too big: %d", NUM_GROUP_TOXES); + + uint16_t name_length = 6; + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + State *state = (State *)autotoxes[i].state; + state->self_name_length = name_length; + snprintf(state->self_name, sizeof(state->self_name), "peer_%zu", i); + state->self_name[name_length] = 0; + + tox_callback_group_join_fail(autotoxes[i].tox, group_join_fail_handler); + tox_callback_group_peer_join(autotoxes[i].tox, group_peer_join_handler); + tox_callback_group_moderation(autotoxes[i].tox, group_mod_event_handler); + } + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + fprintf(stderr, "Creating new group\n"); + + /* Founder makes new group */ + State *state0 = (State *)autotoxes[0].state; + Tox *tox0 = autotoxes[0].tox; + + Tox_Err_Group_New err_new; + state0->group_number = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, + GROUP_NAME_LEN, (const uint8_t *)state0->self_name, state0->self_name_length, + &err_new); + + ck_assert_msg(err_new == TOX_ERR_GROUP_NEW_OK, "Failed to create group. error: %d\n", err_new); + + /* Founder gets chat ID */ + Tox_Err_Group_State_Queries id_err; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + tox_group_get_chat_id(tox0, state0->group_number, chat_id, &id_err); + + ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "Failed to get chat ID. error: %d", id_err); + + fprintf(stderr, "Peers attemping to join DHT group via the chat ID\n"); + + for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + State *state = (State *)autotoxes[i].state; + Tox_Err_Group_Join join_err; + state->group_number = tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)state->self_name, + state->self_name_length, + nullptr, 0, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "Peer %s (%zu) failed to join group. error %d", + state->self_name, i, join_err); + + c_sleep(100); + } + + // make sure every peer sees every other peer before we continue + do { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } while (!all_peers_connected(autotoxes)); + + /* manually tell the other peers the names of the peers that will be assigned new roles */ + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + State *state = (State *)autotoxes[i].state; + memcpy(state->mod_name1, state0->peers[0].name, sizeof(state->mod_name1)); + memcpy(state->mod_name2, state0->peers[2].name, sizeof(state->mod_name2)); + memcpy(state->observer_name1, state0->peers[1].name, sizeof(state->observer_name1)); + memcpy(state->observer_name2, state0->peers[2].name, sizeof(state->observer_name2)); + } + + /* founder checks his own role */ + Tox_Err_Group_Self_Query sq_err; + Tox_Group_Role self_role = tox_group_self_get_role(tox0, state0->group_number, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_role == TOX_GROUP_ROLE_FOUNDER); + + /* all peers should be user role except founder */ + for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { + State *state = (State *)autotoxes[i].state; + self_role = tox_group_self_get_role(autotoxes[i].tox, state->group_number, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + ck_assert(self_role == TOX_GROUP_ROLE_USER); + } + + /* founder sets first peer to moderator */ + fprintf(stderr, "Founder setting %s to moderator\n", state0->peers[0].name); + + Tox_Err_Group_Mod_Set_Role role_err; + tox_group_mod_set_role(tox0, state0->group_number, state0->peers[0].peer_id, TOX_GROUP_ROLE_MODERATOR, &role_err); + ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set moderator. error: %d", role_err); + + // manually flag the role setter because they don't get a callback + state0->mod_check = true; + ++state0->mod_event_count; + check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_MODERATOR); + + check_self_role(autotoxes, state0->peers[0].peer_id, TOX_GROUP_ROLE_MODERATOR); + + fprintf(stderr, "All peers successfully received mod event\n"); + + /* founder sets second and third peer to observer */ + fprintf(stderr, "Founder setting %s to observer\n", state0->peers[1].name); + + tox_group_mod_set_role(tox0, state0->group_number, state0->peers[1].peer_id, TOX_GROUP_ROLE_OBSERVER, &role_err); + ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set observer. error: %d", role_err); + + state0->observer_check = true; + ++state0->observer_event_count; + check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_OBSERVER); + + fprintf(stderr, "All peers successfully received observer event 1\n"); + + fprintf(stderr, "Founder setting %s to observer\n", state0->peers[2].name); + + tox_group_mod_set_role(tox0, state0->group_number, state0->peers[2].peer_id, TOX_GROUP_ROLE_OBSERVER, &role_err); + ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set observer. error: %d", role_err); + + state0->observer_check = true; + ++state0->observer_event_count; + check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_OBSERVER); + + check_self_role(autotoxes, state0->peers[1].peer_id, TOX_GROUP_ROLE_OBSERVER); + + fprintf(stderr, "All peers successfully received observer event 2\n"); + + /* do voice state test here since we have at least one peer of each role */ + check_voice_state(autotoxes, NUM_GROUP_TOXES); + + fprintf(stderr, "Voice state respected by all peers\n"); + + /* New moderator promotes second peer back to user */ + const uint32_t idx = get_state_index_by_nick(autotoxes, NUM_GROUP_TOXES, state0->peers[0].name, + state0->peers[0].name_length); + State *state1 = (State *)autotoxes[idx].state; + Tox *tox1 = autotoxes[idx].tox; + + const uint32_t obs_peer_id = get_peer_id_by_nick(state1->peers, NUM_GROUP_TOXES - 1, state1->observer_name1); + + fprintf(stderr, "%s is promoting %s back to user\n", state1->self_name, state0->peers[1].name); + + tox_group_mod_set_role(tox1, state1->group_number, obs_peer_id, TOX_GROUP_ROLE_USER, &role_err); + ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to promote observer back to user. error: %d", + role_err); + + state1->user_check = true; + ++state1->user_event_count; + check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_USER); + + fprintf(stderr, "All peers successfully received user event\n"); + + /* founder assigns third peer to moderator (this triggers two events: user and moderator) */ + fprintf(stderr, "Founder setting %s to moderator\n", state0->peers[2].name); + + tox_group_mod_set_role(tox0, state0->group_number, state0->peers[2].peer_id, TOX_GROUP_ROLE_MODERATOR, &role_err); + ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set moderator. error: %d", role_err); + + state0->mod_check = true; + ++state0->mod_event_count; + check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_MODERATOR); + + check_self_role(autotoxes, state0->peers[2].peer_id, TOX_GROUP_ROLE_MODERATOR); + + fprintf(stderr, "All peers successfully received moderator event\n"); + + /* moderator attempts to demote and kick founder */ + uint32_t founder_peer_id = get_peer_id_by_nick(state1->peers, NUM_GROUP_TOXES - 1, state0->self_name); + tox_group_mod_set_role(tox1, state1->group_number, founder_peer_id, TOX_GROUP_ROLE_OBSERVER, &role_err); + ck_assert_msg(role_err != TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Mod set founder to observer"); + + Tox_Err_Group_Mod_Kick_Peer k_err; + tox_group_mod_kick_peer(tox1, state1->group_number, founder_peer_id, &k_err); + ck_assert_msg(k_err != TOX_ERR_GROUP_MOD_KICK_PEER_OK, "Mod kicked founder"); + + /* founder kicks moderator (this triggers two events: user and kick) */ + fprintf(stderr, "Founder is kicking %s\n", state0->peers[0].name); + + tox_group_mod_kick_peer(tox0, state0->group_number, state0->peers[0].peer_id, &k_err); + ck_assert_msg(k_err == TOX_ERR_GROUP_MOD_KICK_PEER_OK, "Failed to kick peer. error: %d", k_err); + + state0->kick_check = true; + check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_KICK); + + fprintf(stderr, "All peers successfully received kick event\n"); + + fprintf(stderr, "Founder is demoting moderator to user\n"); + + tox_group_mod_set_role(tox0, state0->group_number, state0->peers[2].peer_id, TOX_GROUP_ROLE_USER, &role_err); + ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to demote peer 3 to User. error: %d", role_err); + + state0->user_check = true; + ++state0->user_event_count; + + check_mod_event(autotoxes, NUM_GROUP_TOXES, TOX_GROUP_MOD_EVENT_USER); + check_self_role(autotoxes, state0->peers[2].peer_id, TOX_GROUP_ROLE_USER); + + for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { + const State *state = (const State *)autotoxes[i].state; + Tox_Err_Group_Leave err_exit; + tox_group_leave(autotoxes[i].tox, state->group_number, nullptr, 0, &err_exit); + ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); + } + + fprintf(stderr, "All tests passed!\n"); +#endif // VANILLA_NACL +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_COMPLETE; + + run_auto_test(nullptr, NUM_GROUP_TOXES, group_moderation_test, sizeof(State), &options); + return 0; +} + +#undef NUM_GROUP_TOXES +#undef GROUP_NAME +#undef GROUP_NAME_LEN diff --git a/auto_tests/group_save_test.c b/auto_tests/group_save_test.c new file mode 100644 index 0000000..0cca6cd --- /dev/null +++ b/auto_tests/group_save_test.c @@ -0,0 +1,300 @@ +/* + * Tests that we can save a groupchat and load a groupchat with the saved data. + */ + +#include +#include +#include +#include + +#include "auto_test_support.h" + +typedef struct State { + bool peer_joined; +} State; + +#define NUM_GROUP_TOXES 2 +#define GROUP_NAME "The Test Chamber" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) +#define TOPIC "They're waiting for you Gordon..." +#define TOPIC_LEN (sizeof(TOPIC) - 1) +#define NEW_PRIV_STATE TOX_GROUP_PRIVACY_STATE_PRIVATE +#define PASSWORD "password123" +#define PASS_LEN (sizeof(PASSWORD) - 1) +#define PEER_LIMIT 69 +#define PEER0_NICK "Mike" +#define PEER0_NICK_LEN (sizeof(PEER0_NICK) -1) +#define NEW_USER_STATUS TOX_USER_STATUS_BUSY + +static void group_invite_handler(Tox *tox, uint32_t friend_number, const uint8_t *invite_data, size_t length, + const uint8_t *group_name, size_t group_name_length, void *user_data) +{ + Tox_Err_Group_Invite_Accept err_accept; + tox_group_invite_accept(tox, friend_number, invite_data, length, (const uint8_t *)"test2", 5, + nullptr, 0, &err_accept); + ck_assert(err_accept == TOX_ERR_GROUP_INVITE_ACCEPT_OK); + +} + +static void group_peer_join_handler(Tox *tox, uint32_t group_number, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + state->peer_joined = true; +} + +/* Checks that group has the same state according to the above defines + * + * Returns 0 if state is correct. + * Returns a value < 0 if state is incorrect. + */ +static int has_correct_group_state(const Tox *tox, uint32_t group_number, const uint8_t *expected_chat_id) +{ + Tox_Err_Group_State_Queries query_err; + + Tox_Group_Privacy_State priv_state = tox_group_get_privacy_state(tox, group_number, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + if (priv_state != NEW_PRIV_STATE) { + return -1; + } + + size_t pass_len = tox_group_get_password_size(tox, group_number, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + uint8_t password[TOX_GROUP_MAX_PASSWORD_SIZE]; + tox_group_get_password(tox, group_number, password, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + if (pass_len != PASS_LEN || memcmp(password, PASSWORD, pass_len) != 0) { + return -2; + } + + size_t gname_len = tox_group_get_name_size(tox, group_number, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + uint8_t group_name[TOX_GROUP_MAX_GROUP_NAME_LENGTH]; + tox_group_get_name(tox, group_number, group_name, &query_err); + + if (gname_len != GROUP_NAME_LEN || memcmp(group_name, GROUP_NAME, gname_len) != 0) { + return -3; + } + + if (tox_group_get_peer_limit(tox, group_number, nullptr) != PEER_LIMIT) { + return -4; + } + + Tox_Group_Topic_Lock topic_lock = tox_group_get_topic_lock(tox, group_number, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + if (topic_lock != TOX_GROUP_TOPIC_LOCK_DISABLED) { + return -5; + } + + Tox_Err_Group_State_Queries id_err; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + tox_group_get_chat_id(tox, group_number, chat_id, &id_err); + + ck_assert(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + if (memcmp(chat_id, expected_chat_id, TOX_GROUP_CHAT_ID_SIZE) != 0) { + return -6; + } + + return 0; +} + +static int has_correct_self_state(const Tox *tox, uint32_t group_number, const uint8_t *expected_self_pk) +{ + Tox_Err_Group_Self_Query sq_err; + size_t self_length = tox_group_self_get_name_size(tox, group_number, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + uint8_t self_name[TOX_MAX_NAME_LENGTH]; + tox_group_self_get_name(tox, group_number, self_name, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + if (self_length != PEER0_NICK_LEN || memcmp(self_name, PEER0_NICK, self_length) != 0) { + return -1; + } + + TOX_USER_STATUS self_status = tox_group_self_get_status(tox, group_number, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + if (self_status != NEW_USER_STATUS) { + return -2; + } + + Tox_Group_Role self_role = tox_group_self_get_role(tox, group_number, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + if (self_role != TOX_GROUP_ROLE_FOUNDER) { + return -3; + } + + uint8_t self_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; + + tox_group_self_get_public_key(tox, group_number, self_pk, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + if (memcmp(self_pk, expected_self_pk, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) != 0) { + return -4; + } + + return 0; +} + +static void group_save_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert_msg(NUM_GROUP_TOXES > 1, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + tox_callback_group_invite(autotoxes[i].tox, group_invite_handler); + tox_callback_group_peer_join(autotoxes[i].tox, group_peer_join_handler); + } + + Tox *tox0 = autotoxes[0].tox; + + const State *state0 = (State *)autotoxes[0].state; + const State *state1 = (State *)autotoxes[1].state; + + Tox_Err_Group_New err_new; + const uint32_t group_number = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PRIVATE, (const uint8_t *)GROUP_NAME, + GROUP_NAME_LEN, (const uint8_t *)"test", 4, &err_new); + + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + + Tox_Err_Group_State_Queries id_err; + tox_group_get_chat_id(tox0, group_number, chat_id, &id_err); + ck_assert(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + uint8_t founder_pk[TOX_GROUP_PEER_PUBLIC_KEY_SIZE]; + + Tox_Err_Group_Self_Query sq_err; + tox_group_self_get_public_key(tox0, group_number, founder_pk, &sq_err); + ck_assert(sq_err == TOX_ERR_GROUP_SELF_QUERY_OK); + + + Tox_Err_Group_Invite_Friend err_invite; + tox_group_invite_friend(tox0, group_number, 0, &err_invite); + + ck_assert(err_invite == TOX_ERR_GROUP_INVITE_FRIEND_OK); + + while (!state0->peer_joined && !state1->peer_joined) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + printf("tox0 invites tox1 to group\n"); + + // change group state + Tox_Err_Group_Topic_Set top_err; + tox_group_set_topic(tox0, group_number, (const uint8_t *)TOPIC, TOPIC_LEN, &top_err); + ck_assert(top_err == TOX_ERR_GROUP_TOPIC_SET_OK); + + Tox_Err_Group_Founder_Set_Topic_Lock lock_set_err; + tox_group_founder_set_topic_lock(tox0, group_number, TOX_GROUP_TOPIC_LOCK_DISABLED, &lock_set_err); + ck_assert(lock_set_err == TOX_ERR_GROUP_FOUNDER_SET_TOPIC_LOCK_OK); + + Tox_Err_Group_Founder_Set_Privacy_State priv_err; + tox_group_founder_set_privacy_state(tox0, group_number, NEW_PRIV_STATE, &priv_err); + ck_assert(priv_err == TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK); + + Tox_Err_Group_Founder_Set_Password pass_set_err; + tox_group_founder_set_password(tox0, group_number, (const uint8_t *)PASSWORD, PASS_LEN, &pass_set_err); + ck_assert(pass_set_err == TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK); + + Tox_Err_Group_Founder_Set_Peer_Limit limit_set_err; + tox_group_founder_set_peer_limit(tox0, group_number, PEER_LIMIT, &limit_set_err); + ck_assert(limit_set_err == TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK); + + // change self state + Tox_Err_Group_Self_Name_Set n_err; + tox_group_self_set_name(tox0, group_number, (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, &n_err); + ck_assert(n_err == TOX_ERR_GROUP_SELF_NAME_SET_OK); + + Tox_Err_Group_Self_Status_Set s_err; + tox_group_self_set_status(tox0, group_number, NEW_USER_STATUS, &s_err); + ck_assert(s_err == TOX_ERR_GROUP_SELF_STATUS_SET_OK); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + printf("tox0 changes group state\n"); + + const size_t save_length = tox_get_savedata_size(tox0); + + uint8_t *save = (uint8_t *)malloc(save_length); + + ck_assert(save != nullptr); + + tox_get_savedata(tox0, save); + + for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { + tox_group_leave(autotoxes[i].tox, group_number, nullptr, 0, nullptr); + } + + struct Tox_Options *const options = tox_options_new(nullptr); + + ck_assert(options != nullptr); + + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + + tox_options_set_savedata_data(options, save, save_length); + + Tox *new_tox = tox_new_log(options, nullptr, nullptr); + + ck_assert(new_tox != nullptr); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + printf("tox0 saves group and reloads client\n"); + + const int group_ret = has_correct_group_state(new_tox, group_number, chat_id); + + ck_assert_msg(group_ret == 0, "incorrect group state: %d", group_ret); + + const int self_ret = has_correct_self_state(new_tox, group_number, founder_pk); + + ck_assert_msg(self_ret == 0, "incorrect self state: %d", self_ret); + + tox_group_leave(new_tox, group_number, nullptr, 0, nullptr); + + free(save); + + tox_options_free(options); + + tox_kill(new_tox); + + printf("All tests passed!\n"); + +#endif // VANILLA_NACL +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options autotest_opts = default_run_auto_options(); + autotest_opts.graph = GRAPH_COMPLETE; + + run_auto_test(nullptr, NUM_GROUP_TOXES, group_save_test, sizeof(State), &autotest_opts); + + return 0; +} + +#undef NUM_GROUP_TOXES +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef TOPIC +#undef TOPIC_LEN +#undef NEW_PRIV_STATE +#undef PASSWORD +#undef PASS_LEN +#undef PEER_LIMIT +#undef PEER0_NICK +#undef PEER0_NICK_LEN +#undef NEW_USER_STATUS diff --git a/auto_tests/group_state_test.c b/auto_tests/group_state_test.c new file mode 100644 index 0000000..adbe172 --- /dev/null +++ b/auto_tests/group_state_test.c @@ -0,0 +1,345 @@ +/* + * Tests that we can successfully change the group state and that all peers in the group + * receive the correct state changes. + */ + +#include +#include +#include +#include +#include + +#include "auto_test_support.h" +#include "check_compat.h" + +#define NUM_GROUP_TOXES 5 + +#define PEER_LIMIT_1 NUM_GROUP_TOXES +#define PEER_LIMIT_2 50 + +#define PASSWORD "dadada" +#define PASS_LEN (sizeof(PASSWORD) - 1) + +#define GROUP_NAME "The Crystal Palace" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) + +#define PEER0_NICK "David" +#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) + +typedef struct State { + size_t num_peers; +} State; + +static bool all_group_peers_connected(const AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, + size_t name_length, uint32_t peer_limit) +{ + for (uint32_t i = 0; i < tox_count; ++i) { + // make sure we got an invite response + if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != name_length) { + return false; + } + + // make sure we got a sync response + if (tox_group_get_peer_limit(autotoxes[i].tox, groupnumber, nullptr) != peer_limit) { + return false; + } + + // make sure we're actually connected + if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) { + return false; + } + + const State *state = (const State *)autotoxes[i].state; + + // make sure all peers are connected to one another + if (state->num_peers < NUM_GROUP_TOXES - 1) { + return false; + } + } + + return true; +} + +static void group_topic_lock_handler(Tox *tox, uint32_t groupnumber, Tox_Group_Topic_Lock topic_lock, + void *user_data) +{ + Tox_Err_Group_State_Queries err; + Tox_Group_Topic_Lock current_topic_lock = tox_group_get_topic_lock(tox, groupnumber, &err); + + ck_assert(err == TOX_ERR_GROUP_STATE_QUERIES_OK); + ck_assert_msg(current_topic_lock == topic_lock, "topic locks don't match in callback: %d %d", + topic_lock, current_topic_lock); +} + +static void group_voice_state_handler(Tox *tox, uint32_t groupnumber, Tox_Group_Voice_State voice_state, + void *user_data) +{ + Tox_Err_Group_State_Queries err; + Tox_Group_Voice_State current_voice_state = tox_group_get_voice_state(tox, groupnumber, &err); + + ck_assert(err == TOX_ERR_GROUP_STATE_QUERIES_OK); + ck_assert_msg(current_voice_state == voice_state, "voice states don't match in callback: %d %d", + voice_state, current_voice_state); +} + +static void group_privacy_state_handler(Tox *tox, uint32_t groupnumber, Tox_Group_Privacy_State privacy_state, + void *user_data) +{ + Tox_Err_Group_State_Queries err; + Tox_Group_Privacy_State current_pstate = tox_group_get_privacy_state(tox, groupnumber, &err); + + ck_assert(err == TOX_ERR_GROUP_STATE_QUERIES_OK); + ck_assert_msg(current_pstate == privacy_state, "privacy states don't match in callback"); +} + +static void group_peer_limit_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_limit, void *user_data) +{ + Tox_Err_Group_State_Queries err; + uint32_t current_plimit = tox_group_get_peer_limit(tox, groupnumber, &err); + + ck_assert(err == TOX_ERR_GROUP_STATE_QUERIES_OK); + ck_assert_msg(peer_limit == current_plimit, + "Peer limits don't match in callback: %u, %u\n", peer_limit, current_plimit); +} + +static void group_password_handler(Tox *tox, uint32_t groupnumber, const uint8_t *password, size_t length, + void *user_data) +{ + Tox_Err_Group_State_Queries err; + size_t curr_pwlength = tox_group_get_password_size(tox, groupnumber, &err); + + ck_assert(err == TOX_ERR_GROUP_STATE_QUERIES_OK); + ck_assert(length == curr_pwlength); + + uint8_t current_password[TOX_GROUP_MAX_PASSWORD_SIZE]; + tox_group_get_password(tox, groupnumber, current_password, &err); + + ck_assert(err == TOX_ERR_GROUP_STATE_QUERIES_OK); + ck_assert_msg(memcmp(current_password, password, length) == 0, + "Passwords don't match: %s, %s", password, current_password); +} + +static void group_peer_join_handler(Tox *tox, uint32_t group_number, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ++state->num_peers; + ck_assert(state->num_peers < NUM_GROUP_TOXES); +} + +/* Returns 0 if group state is equal to the state passed to this function. + * Returns negative integer if state is invalid. + */ +static int check_group_state(const Tox *tox, uint32_t groupnumber, uint32_t peer_limit, + Tox_Group_Privacy_State priv_state, Tox_Group_Voice_State voice_state, + const uint8_t *password, size_t pass_len, Tox_Group_Topic_Lock topic_lock) +{ + Tox_Err_Group_State_Queries query_err; + + Tox_Group_Privacy_State my_priv_state = tox_group_get_privacy_state(tox, groupnumber, &query_err); + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "Failed to get privacy state: %d", query_err); + + if (my_priv_state != priv_state) { + return -1; + } + + uint32_t my_peer_limit = tox_group_get_peer_limit(tox, groupnumber, &query_err); + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "Failed to get peer limit: %d", query_err); + + if (my_peer_limit != peer_limit) { + return -2; + } + + size_t my_pass_len = tox_group_get_password_size(tox, groupnumber, &query_err); + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "Failed to get password size: %d", query_err); + + if (my_pass_len != pass_len) { + return -5; + } + + if (password != nullptr && my_pass_len > 0) { + ck_assert(my_pass_len <= TOX_GROUP_MAX_PASSWORD_SIZE); + + uint8_t my_pass[TOX_GROUP_MAX_PASSWORD_SIZE]; + tox_group_get_password(tox, groupnumber, my_pass, &query_err); + my_pass[my_pass_len] = 0; + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "Failed to get password: %d", query_err); + + if (memcmp(my_pass, password, my_pass_len) != 0) { + return -6; + } + } + + /* Group name should never change */ + size_t my_gname_len = tox_group_get_name_size(tox, groupnumber, &query_err); + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "Failed to get group name size: %d", query_err); + + if (my_gname_len != GROUP_NAME_LEN) { + return -7; + } + + ck_assert(my_gname_len <= TOX_GROUP_MAX_GROUP_NAME_LENGTH); + + uint8_t my_gname[TOX_GROUP_MAX_GROUP_NAME_LENGTH]; + tox_group_get_name(tox, groupnumber, my_gname, &query_err); + my_gname[my_gname_len] = 0; + + if (memcmp(my_gname, (const uint8_t *)GROUP_NAME, my_gname_len) != 0) { + return -8; + } + + Tox_Group_Topic_Lock current_topic_lock = tox_group_get_topic_lock(tox, groupnumber, &query_err); + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "Failed to get topic lock: %d", query_err); + + if (current_topic_lock != topic_lock) { + return -9; + } + + Tox_Group_Voice_State current_voice_state = tox_group_get_voice_state(tox, groupnumber, &query_err); + ck_assert_msg(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "Failed to get voice state: %d", query_err); + + if (current_voice_state != voice_state) { + return -10; + } + + return 0; +} + +static void set_group_state(Tox *tox, uint32_t groupnumber, uint32_t peer_limit, Tox_Group_Privacy_State priv_state, + Tox_Group_Voice_State voice_state, const uint8_t *password, size_t pass_len, + Tox_Group_Topic_Lock topic_lock) +{ + + Tox_Err_Group_Founder_Set_Peer_Limit limit_set_err; + tox_group_founder_set_peer_limit(tox, groupnumber, peer_limit, &limit_set_err); + ck_assert_msg(limit_set_err == TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK, "failed to set peer limit: %d", limit_set_err); + + Tox_Err_Group_Founder_Set_Privacy_State priv_err; + tox_group_founder_set_privacy_state(tox, groupnumber, priv_state, &priv_err); + ck_assert_msg(priv_err == TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK, "failed to set privacy state: %d", priv_err); + + Tox_Err_Group_Founder_Set_Password pass_set_err; + tox_group_founder_set_password(tox, groupnumber, password, pass_len, &pass_set_err); + ck_assert_msg(pass_set_err == TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK, "failed to set password: %d", pass_set_err); + + Tox_Err_Group_Founder_Set_Topic_Lock lock_set_err; + tox_group_founder_set_topic_lock(tox, groupnumber, topic_lock, &lock_set_err); + ck_assert_msg(lock_set_err == TOX_ERR_GROUP_FOUNDER_SET_TOPIC_LOCK_OK, "failed to set topic lock: %d", + lock_set_err); + + Tox_Err_Group_Founder_Set_Voice_State voice_set_err; + tox_group_founder_set_voice_state(tox, groupnumber, voice_state, &voice_set_err); + ck_assert_msg(voice_set_err == TOX_ERR_GROUP_FOUNDER_SET_VOICE_STATE_OK, "failed to set voice state: %d", + voice_set_err); +} + +static void group_state_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert_msg(NUM_GROUP_TOXES >= 3, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + tox_callback_group_privacy_state(autotoxes[i].tox, group_privacy_state_handler); + tox_callback_group_peer_limit(autotoxes[i].tox, group_peer_limit_handler); + tox_callback_group_password(autotoxes[i].tox, group_password_handler); + tox_callback_group_peer_join(autotoxes[i].tox, group_peer_join_handler); + tox_callback_group_voice_state(autotoxes[i].tox, group_voice_state_handler); + tox_callback_group_topic_lock(autotoxes[i].tox, group_topic_lock_handler); + } + + Tox *tox0 = autotoxes[0].tox; + + /* Tox 0 creates a group and is the founder of a newly created group */ + Tox_Err_Group_New new_err; + uint32_t groupnum = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, GROUP_NAME_LEN, + (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, &new_err); + + ck_assert_msg(new_err == TOX_ERR_GROUP_NEW_OK, "tox_group_new failed: %d", new_err); + + /* Founder sets default group state before anyone else joins */ + set_group_state(tox0, groupnum, PEER_LIMIT_1, TOX_GROUP_PRIVACY_STATE_PUBLIC, TOX_GROUP_VOICE_STATE_ALL, + (const uint8_t *)PASSWORD, PASS_LEN, TOX_GROUP_TOPIC_LOCK_ENABLED); + + /* Founder gets the Chat ID and implicitly shares it publicly */ + Tox_Err_Group_State_Queries id_err; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + tox_group_get_chat_id(tox0, groupnum, chat_id, &id_err); + + ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "tox_group_get_chat_id failed %d", id_err); + + /* All other peers join the group using the Chat ID and password */ + for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + Tox_Err_Group_Join join_err; + tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, (const uint8_t *)PASSWORD, PASS_LEN, + &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "tox_group_join failed: %d", join_err); + } + + fprintf(stderr, "Peers attempting to join group\n"); + + /* Keep checking if all instances have connected to the group until test times out */ + while (!all_group_peers_connected(autotoxes, NUM_GROUP_TOXES, groupnum, GROUP_NAME_LEN, PEER_LIMIT_1)) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + /* Change group state and check that all peers received the changes */ + set_group_state(tox0, groupnum, PEER_LIMIT_2, TOX_GROUP_PRIVACY_STATE_PRIVATE, TOX_GROUP_VOICE_STATE_MODERATOR, + nullptr, 0, TOX_GROUP_TOPIC_LOCK_DISABLED); + + fprintf(stderr, "Changing state\n"); + + while (1) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + uint32_t count = 0; + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + if (check_group_state(autotoxes[i].tox, groupnum, PEER_LIMIT_2, TOX_GROUP_PRIVACY_STATE_PRIVATE, + TOX_GROUP_VOICE_STATE_MODERATOR, nullptr, 0, TOX_GROUP_TOPIC_LOCK_DISABLED) == 0) { + ++count; + } + } + + if (count == NUM_GROUP_TOXES) { + fprintf(stderr, "%u peers successfully received state changes\n", count); + break; + } + } + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + Tox_Err_Group_Leave err_exit; + tox_group_leave(autotoxes[i].tox, groupnum, nullptr, 0, &err_exit); + ck_assert_msg(err_exit == TOX_ERR_GROUP_LEAVE_OK, "%d", err_exit); + } + + fprintf(stderr, "All tests passed!\n"); + +#endif /* VANILLA_NACL */ +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options autotest_opts = default_run_auto_options(); + autotest_opts.graph = GRAPH_COMPLETE; + + run_auto_test(nullptr, NUM_GROUP_TOXES, group_state_test, sizeof(State), &autotest_opts); + + return 0; +} + +#undef PEER0_NICK +#undef PEER0_NICK_LEN +#undef GROUP_NAME_LEN +#undef GROUP_NAME +#undef PASS_LEN +#undef PASSWORD +#undef PEER_LIMIT_2 +#undef PEER_LIMIT_1 +#undef NUM_GROUP_TOXES diff --git a/auto_tests/group_sync_test.c b/auto_tests/group_sync_test.c new file mode 100644 index 0000000..4d2bc18 --- /dev/null +++ b/auto_tests/group_sync_test.c @@ -0,0 +1,464 @@ +/* + * Tests syncing capabilities of groups: we attempt to have multiple peers change the + * group state in a number of ways and make sure that all peers end up with the same + * resulting state after a short period. + */ + +#include +#include +#include + +#include "auto_test_support.h" + +#include "../toxcore/tox.h" +#include "../toxcore/util.h" + +// these should be kept relatively low so integration tests don't always flake out +// but they can be increased for local stress testing +#define NUM_GROUP_TOXES 5 +#define ROLE_SPAM_ITERATIONS 1 +#define TOPIC_SPAM_ITERATIONS 1 + +typedef struct Peers { + uint32_t num_peers; + int64_t *peer_ids; +} Peers; + +typedef struct State { + uint8_t callback_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + size_t topic_length; + Peers *peers; +} State; + +static int add_peer(Peers *peers, uint32_t peer_id) +{ + const uint32_t new_idx = peers->num_peers; + + int64_t *tmp_list = (int64_t *)realloc(peers->peer_ids, sizeof(int64_t) * (peers->num_peers + 1)); + + if (tmp_list == nullptr) { + return -1; + } + + ++peers->num_peers; + + tmp_list[new_idx] = (int64_t)peer_id; + + peers->peer_ids = tmp_list; + + return 0; +} + +static int del_peer(Peers *peers, uint32_t peer_id) +{ + bool found_peer = false; + int64_t i; + + for (i = 0; i < peers->num_peers; ++i) { + if (peers->peer_ids[i] == peer_id) { + found_peer = true; + break; + } + } + + if (!found_peer) { + return -1; + } + + --peers->num_peers; + + if (peers->num_peers == 0) { + free(peers->peer_ids); + peers->peer_ids = nullptr; + return 0; + } + + if (peers->num_peers != i) { + peers->peer_ids[i] = peers->peer_ids[peers->num_peers]; + } + + peers->peer_ids[peers->num_peers] = -1; + + int64_t *tmp_list = (int64_t *)realloc(peers->peer_ids, sizeof(int64_t) * (peers->num_peers)); + + if (tmp_list == nullptr) { + return -1; + } + + peers->peer_ids = tmp_list; + + return 0; +} + +static void peers_cleanup(Peers *peers) +{ + free(peers->peer_ids); + free(peers); +} + +static void group_peer_join_handler(Tox *tox, uint32_t group_number, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(add_peer(state->peers, peer_id) == 0); + +} + +static void group_peer_exit_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, Tox_Group_Exit_Type exit_type, + const uint8_t *name, size_t name_length, const uint8_t *part_message, + size_t length, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(del_peer(state->peers, peer_id) == 0); + +} + +static void group_topic_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, const uint8_t *topic, + size_t length, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(length <= TOX_GROUP_MAX_TOPIC_LENGTH); + + memcpy(state->callback_topic, (const char *)topic, length); + state->topic_length = length; +} + +static bool all_peers_connected(const AutoTox *autotoxes, uint32_t groupnumber) +{ + for (uint32_t i = 0; i < NUM_GROUP_TOXES; ++i) { + // make sure we got an invite response + if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != 4) { + return false; + } + + // make sure we're actually connected + if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) { + return false; + } + + const State *state = (const State *)autotoxes[i].state; + + // make sure all peers are connected to one another + if (state->peers->num_peers == NUM_GROUP_TOXES - 1) { + return false; + } + } + + return true; +} + +static unsigned int get_peer_roles_checksum(const Tox *tox, const State *state, uint32_t groupnumber) +{ + Tox_Group_Role role = tox_group_self_get_role(tox, groupnumber, nullptr); + unsigned int checksum = (unsigned int)role; + + for (size_t i = 0; i < state->peers->num_peers; ++i) { + role = tox_group_peer_get_role(tox, groupnumber, (uint32_t)state->peers->peer_ids[i], nullptr); + checksum += (unsigned int)role; + } + + return checksum; +} + +static bool all_peers_see_same_roles(const AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber) +{ + const State *state0 = (const State *)autotoxes[0].state; + unsigned int expected_checksum = get_peer_roles_checksum(autotoxes[0].tox, state0, groupnumber); + + for (size_t i = 0; i < num_peers; ++i) { + const State *state = (const State *)autotoxes[i].state; + unsigned int checksum = get_peer_roles_checksum(autotoxes[i].tox, state, groupnumber); + + if (checksum != expected_checksum) { + return false; + } + } + + return true; +} + +static void role_spam(const Random *rng, AutoTox *autotoxes, uint32_t num_peers, uint32_t num_demoted, + uint32_t groupnumber) +{ + const State *state0 = (const State *)autotoxes[0].state; + Tox *tox0 = autotoxes[0].tox; + + for (size_t iters = 0; iters < ROLE_SPAM_ITERATIONS; ++iters) { + // founder randomly promotes or demotes one of the non-mods + uint32_t idx = min_u32(random_u32(rng) % num_demoted, state0->peers->num_peers); + Tox_Group_Role f_role = random_u32(rng) % 2 == 0 ? TOX_GROUP_ROLE_MODERATOR : TOX_GROUP_ROLE_USER; + int64_t peer_id = state0->peers->peer_ids[idx]; + + if (peer_id >= 0) { + tox_group_mod_set_role(tox0, groupnumber, (uint32_t)peer_id, f_role, nullptr); + } + + // mods randomly promote or demote one of the non-mods + for (uint32_t i = 1; i < num_peers; ++i) { + const State *state_i = (const State *)autotoxes[i].state; + + for (uint32_t j = num_demoted; j < num_peers; ++j) { + if (i >= state_i->peers->num_peers) { + continue; + } + + const State *state_j = (const State *)autotoxes[j].state; + Tox_Group_Role role = random_u32(rng) % 2 == 0 ? TOX_GROUP_ROLE_USER : TOX_GROUP_ROLE_OBSERVER; + peer_id = state_j->peers->peer_ids[i]; + + if (peer_id >= 0) { + tox_group_mod_set_role(autotoxes[j].tox, groupnumber, (uint32_t)peer_id, role, nullptr); + } + } + } + + iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL); + } + + do { + iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL); + } while (!all_peers_see_same_roles(autotoxes, num_peers, groupnumber)); +} + +/* All peers attempt to set a unique topic. + * + * Return true if all peers successfully changed the topic. + */ +static bool set_topic_all_peers(const Random *rng, AutoTox *autotoxes, size_t num_peers, uint32_t groupnumber) +{ + for (size_t i = 0; i < num_peers; ++i) { + char new_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + snprintf(new_topic, sizeof(new_topic), "peer %zu's topic %u", i, random_u32(rng)); + const size_t length = strlen(new_topic); + + Tox_Err_Group_Topic_Set err; + tox_group_set_topic(autotoxes[i].tox, groupnumber, (const uint8_t *)new_topic, length, &err); + + if (err != TOX_ERR_GROUP_TOPIC_SET_OK) { + return false; + } + } + + return true; +} + +/* Returns true if all peers have the same topic, and the topic from the get_topic API function + * matches the last topic they received in the topic callback. + */ +static bool all_peers_have_same_topic(const AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber) +{ + uint8_t expected_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + + Tox_Err_Group_State_Queries query_err; + size_t expected_topic_length = tox_group_get_topic_size(autotoxes[0].tox, groupnumber, &query_err); + + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + tox_group_get_topic(autotoxes[0].tox, groupnumber, expected_topic, &query_err); + + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + const State *state0 = (const State *)autotoxes[0].state; + + if (expected_topic_length != state0->topic_length) { + return false; + } + + if (memcmp(state0->callback_topic, expected_topic, expected_topic_length) != 0) { + return false; + } + + for (size_t i = 1; i < num_peers; ++i) { + size_t topic_length = tox_group_get_topic_size(autotoxes[i].tox, groupnumber, &query_err); + + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + if (topic_length != expected_topic_length) { + return false; + } + + uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + tox_group_get_topic(autotoxes[i].tox, groupnumber, topic, &query_err); + + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + if (memcmp(expected_topic, (const char *)topic, topic_length) != 0) { + return false; + } + + const State *state = (const State *)autotoxes[i].state; + + if (topic_length != state->topic_length) { + return false; + } + + if (memcmp(state->callback_topic, (const char *)topic, topic_length) != 0) { + return false; + } + } + + return true; +} + +static void topic_spam(const Random *rng, AutoTox *autotoxes, uint32_t num_peers, uint32_t groupnumber) +{ + for (size_t i = 0; i < TOPIC_SPAM_ITERATIONS; ++i) { + do { + iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL); + } while (!set_topic_all_peers(rng, autotoxes, num_peers, groupnumber)); + } + + fprintf(stderr, "all peers set the topic at the same time\n"); + + do { + iterate_all_wait(autotoxes, num_peers, ITERATION_INTERVAL); + } while (!all_peers_have_same_topic(autotoxes, num_peers, groupnumber)); + + fprintf(stderr, "all peers see the same topic\n"); +} + +static void group_sync_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert(NUM_GROUP_TOXES >= 5); + const Random *rng = system_random(); + ck_assert(rng != nullptr); + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + tox_callback_group_peer_join(autotoxes[i].tox, group_peer_join_handler); + tox_callback_group_topic(autotoxes[i].tox, group_topic_handler); + tox_callback_group_peer_exit(autotoxes[i].tox, group_peer_exit_handler); + + State *state = (State *)autotoxes[i].state; + state->peers = (Peers *)calloc(1, sizeof(Peers)); + + ck_assert(state->peers != nullptr); + } + + Tox *tox0 = autotoxes[0].tox; + State *state0 = (State *)autotoxes[0].state; + + Tox_Err_Group_New err_new; + uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *) "test", 4, + (const uint8_t *)"test", 4, &err_new); + + ck_assert(err_new == TOX_ERR_GROUP_NEW_OK); + + fprintf(stderr, "tox0 creats new group and invites all his friends"); + + Tox_Err_Group_State_Queries id_err; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + + tox_group_get_chat_id(tox0, groupnumber, chat_id, &id_err); + ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", id_err); + + for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { + Tox_Err_Group_Join join_err; + tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "%d", join_err); + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } + + do { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } while (!all_peers_connected(autotoxes, groupnumber)); + + fprintf(stderr, "%d peers joined the group\n", NUM_GROUP_TOXES); + + Tox_Err_Group_Founder_Set_Topic_Lock lock_set_err; + tox_group_founder_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_DISABLED, &lock_set_err); + ck_assert_msg(lock_set_err == TOX_ERR_GROUP_FOUNDER_SET_TOPIC_LOCK_OK, "failed to disable topic lock: %d", + lock_set_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + fprintf(stderr, "founder disabled topic lock; all peers try to set the topic\n"); + + topic_spam(rng, autotoxes, NUM_GROUP_TOXES, groupnumber); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + tox_group_founder_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_ENABLED, &lock_set_err); + ck_assert_msg(lock_set_err == TOX_ERR_GROUP_FOUNDER_SET_TOPIC_LOCK_OK, "failed to enable topic lock: %d", + lock_set_err); + + do { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } while (!all_peers_have_same_topic(autotoxes, NUM_GROUP_TOXES, groupnumber) + && !all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber) + && state0->peers->num_peers != NUM_GROUP_TOXES - 1); + + Tox_Err_Group_Mod_Set_Role role_err; + + for (size_t i = 0; i < state0->peers->num_peers; ++i) { + tox_group_mod_set_role(tox0, groupnumber, (uint32_t)state0->peers->peer_ids[i], TOX_GROUP_ROLE_MODERATOR, + &role_err); + ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set moderator. error: %d", role_err); + } + + fprintf(stderr, "founder enabled topic lock and set all peers to moderator role\n"); + + do { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } while (!all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber)); + + topic_spam(rng, autotoxes, NUM_GROUP_TOXES, groupnumber); + + const unsigned int num_demoted = state0->peers->num_peers / 2; + + fprintf(stderr, "founder demoting %u moderators to user\n", num_demoted); + + for (size_t i = 0; i < num_demoted; ++i) { + tox_group_mod_set_role(tox0, groupnumber, (uint32_t)state0->peers->peer_ids[i], TOX_GROUP_ROLE_USER, + &role_err); + ck_assert_msg(role_err == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set user. error: %d", role_err); + } + + do { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + } while (!all_peers_see_same_roles(autotoxes, NUM_GROUP_TOXES, groupnumber)); + + fprintf(stderr, "Remaining moderators spam change non-moderator roles\n"); + + role_spam(rng, autotoxes, NUM_GROUP_TOXES, num_demoted, groupnumber); + + fprintf(stderr, "All peers see the same roles\n"); + + for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { + tox_group_leave(autotoxes[i].tox, groupnumber, nullptr, 0, nullptr); + + State *state = (State *)autotoxes[i].state; + peers_cleanup(state->peers); + } + + fprintf(stderr, "All tests passed!\n"); + +#endif // VANILLA_NACL +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options autotest_opts = default_run_auto_options(); + autotest_opts.graph = GRAPH_COMPLETE; + + run_auto_test(nullptr, NUM_GROUP_TOXES, group_sync_test, sizeof(State), &autotest_opts); + + return 0; +} + +#undef NUM_GROUP_TOXES +#undef ROLE_SPAM_ITERATIONS +#undef TOPIC_SPAM_ITERATIONS diff --git a/auto_tests/group_tcp_test.c b/auto_tests/group_tcp_test.c new file mode 100644 index 0000000..c1f66de --- /dev/null +++ b/auto_tests/group_tcp_test.c @@ -0,0 +1,255 @@ +/* + * Does a basic functionality test for TCP connections. + */ + +#include +#include +#include + +#include "auto_test_support.h" + +#ifdef USE_TEST_NETWORK + +#define NUM_GROUP_TOXES 2 +#define CODEWORD "RONALD MCDONALD" +#define CODEWORD_LEN (sizeof(CODEWORD) - 1) + +typedef struct State { + size_t num_peers; + bool got_code; + bool got_second_code; + uint32_t peer_id[NUM_GROUP_TOXES - 1]; +} State; + +static void group_invite_handler(Tox *tox, uint32_t friend_number, const uint8_t *invite_data, size_t length, + const uint8_t *group_name, size_t group_name_length, void *user_data) +{ + printf("Accepting friend invite\n"); + + Tox_Err_Group_Invite_Accept err_accept; + tox_group_invite_accept(tox, friend_number, invite_data, length, (const uint8_t *)"test", 4, + nullptr, 0, &err_accept); + ck_assert(err_accept == TOX_ERR_GROUP_INVITE_ACCEPT_OK); +} + +static void group_peer_join_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + fprintf(stderr, "joined: %zu, %u\n", state->num_peers, peer_id); + ck_assert_msg(state->num_peers < NUM_GROUP_TOXES - 1, "%zu", state->num_peers); + + state->peer_id[state->num_peers++] = peer_id; +} + +static void group_private_message_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, TOX_MESSAGE_TYPE type, + const uint8_t *message, size_t length, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(length == CODEWORD_LEN); + ck_assert(memcmp(CODEWORD, message, length) == 0); + + printf("Codeword: %s\n", CODEWORD); + + state->got_code = true; +} + +static void group_message_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, TOX_MESSAGE_TYPE type, + const uint8_t *message, size_t length, uint32_t message_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + ck_assert(length == CODEWORD_LEN); + ck_assert(memcmp(CODEWORD, message, length) == 0); + + printf("Codeword: %s\n", CODEWORD); + + state->got_second_code = true; +} + +/* + * We need different constants to make TCP run smoothly. TODO(Jfreegman): is this because of the group + * implementation or just an autotest quirk? + */ +#define GROUP_ITERATION_INTERVAL 100 +static void iterate_group(AutoTox *autotoxes, uint32_t num_toxes, size_t interval) +{ + for (uint32_t i = 0; i < num_toxes; i++) { + if (autotoxes[i].alive) { + tox_iterate(autotoxes[i].tox, &autotoxes[i]); + autotoxes[i].clock += interval; + } + } + + c_sleep(50); +} + +static bool all_peers_connected(AutoTox *autotoxes) +{ + iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); + + size_t count = 0; + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + const State *state = (const State *)autotoxes[i].state; + + if (state->num_peers == NUM_GROUP_TOXES - 1) { + ++count; + } + } + + return count == NUM_GROUP_TOXES; +} + +static bool all_peers_got_code(AutoTox *autotoxes) +{ + iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); + + size_t count = 0; + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + const State *state = (const State *)autotoxes[i].state; + + if (state->got_code) { + ++count; + } + } + + return count == NUM_GROUP_TOXES - 1; +} + +static void group_tcp_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert(NUM_GROUP_TOXES >= 2); + + State *state0 = (State *)autotoxes[0].state; + State *state1 = (State *)autotoxes[1].state; + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + tox_callback_group_peer_join(autotoxes[i].tox, group_peer_join_handler); + tox_callback_group_private_message(autotoxes[i].tox, group_private_message_handler); + } + + tox_callback_group_message(autotoxes[1].tox, group_message_handler); + tox_callback_group_invite(autotoxes[1].tox, group_invite_handler); + + Tox_Err_Group_New new_err; + uint32_t groupnumber = tox_group_new(autotoxes[0].tox, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)"test", 4, + (const uint8_t *)"test", 4, &new_err); + ck_assert_msg(new_err == TOX_ERR_GROUP_NEW_OK, "tox_group_new failed: %d", new_err); + + iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); + + Tox_Err_Group_State_Queries id_err; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + + tox_group_get_chat_id(autotoxes[0].tox, groupnumber, chat_id, &id_err); + ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "%d", id_err); + + printf("Tox 0 created new group...\n"); + + for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { + Tox_Err_Group_Join jerr; + tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"test", 4, nullptr, 0, &jerr); + ck_assert_msg(jerr == TOX_ERR_GROUP_JOIN_OK, "%d", jerr); + iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL * 10); + } + + while (!all_peers_connected(autotoxes)) + ; + + printf("%d peers successfully joined. Waiting for code...\n", NUM_GROUP_TOXES); + printf("Tox 0 sending secret code to all peers\n"); + + + for (size_t i = 0; i < NUM_GROUP_TOXES - 1; ++i) { + + Tox_Err_Group_Send_Private_Message perr; + tox_group_send_private_message(autotoxes[0].tox, groupnumber, state0->peer_id[i], + TOX_MESSAGE_TYPE_NORMAL, + (const uint8_t *)CODEWORD, CODEWORD_LEN, &perr); + ck_assert_msg(perr == TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK, "%d", perr); + } + + while (!all_peers_got_code(autotoxes)) + ; + + Tox_Err_Group_Leave err_exit; + tox_group_leave(autotoxes[1].tox, groupnumber, nullptr, 0, &err_exit); + ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); + + iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); + + state0->num_peers = 0; + state1->num_peers = 0; + + // now do a friend invite to make sure the TCP-specific logic for friend invites is okay + + printf("Tox1 leaves group and Tox0 does a friend group invite for tox1\n"); + + Tox_Err_Group_Invite_Friend err_invite; + tox_group_invite_friend(autotoxes[0].tox, groupnumber, 0, &err_invite); + ck_assert(err_invite == TOX_ERR_GROUP_INVITE_FRIEND_OK); + + while (state0->num_peers == 0 && state1->num_peers == 0) { + iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); + } + + printf("Tox 1 successfully joined. Waiting for code...\n"); + + Tox_Err_Group_Send_Message merr; + tox_group_send_message(autotoxes[0].tox, groupnumber, TOX_MESSAGE_TYPE_NORMAL, + (const uint8_t *)CODEWORD, CODEWORD_LEN, nullptr, &merr); + ck_assert(merr == TOX_ERR_GROUP_SEND_MESSAGE_OK); + + while (!state1->got_second_code) { + iterate_group(autotoxes, NUM_GROUP_TOXES, GROUP_ITERATION_INTERVAL); + } + + for (size_t i = 0; i < NUM_GROUP_TOXES; i++) { + tox_group_leave(autotoxes[i].tox, groupnumber, nullptr, 0, &err_exit); + ck_assert(err_exit == TOX_ERR_GROUP_LEAVE_OK); + } + + printf("Test passed!\n"); + +#endif // VANILLA_NACL +} +#endif // USE_TEST_NETWORK + +int main(void) +{ +#ifdef USE_TEST_NETWORK // TODO(Jfreegman): Enable this test when the mainnet works with DHT groupchats + setvbuf(stdout, nullptr, _IONBF, 0); + + struct Tox_Options *options = (struct Tox_Options *)calloc(1, sizeof(struct Tox_Options)); + ck_assert(options != nullptr); + + tox_options_default(options); + tox_options_set_udp_enabled(options, false); + + Run_Auto_Options autotest_opts = default_run_auto_options(); + autotest_opts.graph = GRAPH_COMPLETE; + + run_auto_test(options, NUM_GROUP_TOXES, group_tcp_test, sizeof(State), &autotest_opts); + + tox_options_free(options); +#endif + return 0; +} + +#ifdef USE_TEST_NETWORK +#undef NUM_GROUP_TOXES +#undef CODEWORD_LEN +#undef CODEWORD +#endif // USE_TEST_NETWORK diff --git a/auto_tests/group_topic_test.c b/auto_tests/group_topic_test.c new file mode 100644 index 0000000..26dafcb --- /dev/null +++ b/auto_tests/group_topic_test.c @@ -0,0 +1,345 @@ +/* + * Tests that we can successfully change the group topic, that all peers receive topic changes + * and that the topic lock works as intended. + */ + +#include +#include +#include +#include + +#include "auto_test_support.h" +#include "check_compat.h" + +#include "../toxcore/tox.h" +#include "../toxcore/group_chats.h" + +#define NUM_GROUP_TOXES 3 + +#define TOPIC "They're waiting for you Gordon...in the test chamber" +#define TOPIC_LEN (sizeof(TOPIC) - 1) + +#define TOPIC2 "They're waiting for you Gordon...in the test chamber 2.0" +#define TOPIC_LEN2 (sizeof(TOPIC2) - 1) + +#define GROUP_NAME "The Test Chamber" +#define GROUP_NAME_LEN (sizeof(GROUP_NAME) - 1) + +#define PEER0_NICK "Koresh" +#define PEER0_NICK_LEN (sizeof(PEER0_NICK) - 1) + +typedef struct State { + uint32_t peer_id; // the id of the peer we set to observer +} State; + +static bool all_group_peers_connected(const AutoTox *autotoxes, uint32_t tox_count, uint32_t groupnumber, + size_t name_length, uint32_t peer_limit) +{ + for (uint32_t i = 0; i < tox_count; ++i) { + // make sure we got an invite + if (tox_group_get_name_size(autotoxes[i].tox, groupnumber, nullptr) != name_length) { + return false; + } + + // make sure we got a sync response + if (peer_limit != 0 && tox_group_get_peer_limit(autotoxes[i].tox, groupnumber, nullptr) != peer_limit) { + return false; + } + + // make sure we're actually connected + if (!tox_group_is_connected(autotoxes[i].tox, groupnumber, nullptr)) { + return false; + } + } + + return true; +} + +static void group_peer_join_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, void *user_data) +{ + AutoTox *autotox = (AutoTox *)user_data; + ck_assert(autotox != nullptr); + + State *state = (State *)autotox->state; + + state->peer_id = peer_id; +} + +static void group_topic_handler(Tox *tox, uint32_t groupnumber, uint32_t peer_id, const uint8_t *topic, + size_t length, void *user_data) +{ + ck_assert(length <= TOX_GROUP_MAX_TOPIC_LENGTH); + + Tox_Err_Group_State_Queries query_err; + uint8_t topic2[TOX_GROUP_MAX_TOPIC_LENGTH]; + tox_group_get_topic(tox, groupnumber, topic2, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + size_t topic_length = tox_group_get_topic_size(tox, groupnumber, &query_err); + ck_assert(query_err == TOX_ERR_GROUP_STATE_QUERIES_OK); + ck_assert_msg(topic_length == length && memcmp(topic, topic2, length) == 0, + "topic differs in callback: %s, %s", topic, topic2); +} + +static void group_topic_lock_handler(Tox *tox, uint32_t groupnumber, Tox_Group_Topic_Lock topic_lock, void *user_data) +{ + Tox_Err_Group_State_Queries err; + Tox_Group_Topic_Lock current_lock = tox_group_get_topic_lock(tox, groupnumber, &err); + + ck_assert(err == TOX_ERR_GROUP_STATE_QUERIES_OK); + ck_assert_msg(topic_lock == current_lock, "topic locks differ in callback"); +} + +/* Sets group topic. + * + * Return true on success. + */ +static bool set_topic(Tox *tox, uint32_t groupnumber, const char *topic, size_t length) +{ + Tox_Err_Group_Topic_Set err; + tox_group_set_topic(tox, groupnumber, (const uint8_t *)topic, length, &err); + + return err == TOX_ERR_GROUP_TOPIC_SET_OK; +} + +/* Returns 0 if group topic matches expected topic. + * Returns a value < 0 on failure. + */ +static int check_topic(const Tox *tox, uint32_t groupnumber, const char *expected_topic, size_t expected_length) +{ + Tox_Err_Group_State_Queries query_err; + size_t topic_length = tox_group_get_topic_size(tox, groupnumber, &query_err); + + if (query_err != TOX_ERR_GROUP_STATE_QUERIES_OK) { + return -1; + } + + if (expected_length != topic_length) { + return -2; + } + + uint8_t topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + tox_group_get_topic(tox, groupnumber, topic, &query_err); + + if (query_err != TOX_ERR_GROUP_STATE_QUERIES_OK) { + return -3; + } + + if (memcmp(expected_topic, (const char *)topic, topic_length) != 0) { + return -4; + } + + return 0; +} + +static void wait_topic_lock(AutoTox *autotoxes, uint32_t groupnumber, Tox_Group_Topic_Lock expected_lock) +{ + while (1) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + uint32_t count = 0; + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + Tox_Err_Group_State_Queries err; + Tox_Group_Topic_Lock topic_lock = tox_group_get_topic_lock(autotoxes[i].tox, groupnumber, &err); + ck_assert(err == TOX_ERR_GROUP_STATE_QUERIES_OK); + + if (topic_lock == expected_lock) { + ++count; + } + } + + if (count == NUM_GROUP_TOXES) { + break; + } + } +} + +/* Waits for all peers in group to see the same topic */ +static void wait_state_topic(AutoTox *autotoxes, uint32_t groupnumber, const char *topic, size_t length) +{ + while (1) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + uint32_t count = 0; + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + const int c_ret = check_topic(autotoxes[i].tox, groupnumber, topic, length); + + if (c_ret == 0) { + ++count; + } + } + + if (count == NUM_GROUP_TOXES) { + break; + } + } +} + +/* All peers attempt to set the topic. + * + * Returns the number of peers who succeeeded. + */ +static uint32_t set_topic_all_peers(const Random *rng, AutoTox *autotoxes, size_t num_peers, uint32_t groupnumber) +{ + uint32_t change_count = 0; + + for (size_t i = 0; i < num_peers; ++i) { + char new_topic[TOX_GROUP_MAX_TOPIC_LENGTH]; + snprintf(new_topic, sizeof(new_topic), "peer %zu changes topic %u", i, random_u32(rng)); + size_t length = strlen(new_topic); + + if (set_topic(autotoxes[i].tox, groupnumber, new_topic, length)) { + wait_state_topic(autotoxes, groupnumber, new_topic, length); + ++change_count; + } else { + fprintf(stderr, "Peer %zu couldn't set the topic\n", i); + } + } + + return change_count; +} + +static void group_topic_test(AutoTox *autotoxes) +{ +#ifndef VANILLA_NACL + ck_assert_msg(NUM_GROUP_TOXES >= 3, "NUM_GROUP_TOXES is too small: %d", NUM_GROUP_TOXES); + + const Random *rng = system_random(); + ck_assert(rng != nullptr); + + Tox *tox0 = autotoxes[0].tox; + const State *state0 = (const State *)autotoxes[0].state; + + tox_callback_group_peer_join(tox0, group_peer_join_handler); + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + tox_callback_group_topic(autotoxes[i].tox, group_topic_handler); + tox_callback_group_topic_lock(autotoxes[i].tox, group_topic_lock_handler); + } + + /* Tox1 creates a group and is the founder of a newly created group */ + Tox_Err_Group_New new_err; + const uint32_t groupnumber = tox_group_new(tox0, TOX_GROUP_PRIVACY_STATE_PUBLIC, (const uint8_t *)GROUP_NAME, + GROUP_NAME_LEN, + (const uint8_t *)PEER0_NICK, PEER0_NICK_LEN, &new_err); + + ck_assert_msg(new_err == TOX_ERR_GROUP_NEW_OK, "tox_group_new failed: %d", new_err); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + /* Founder sets group topic before anyone else joins */ + const bool s_ret = set_topic(tox0, groupnumber, TOPIC, TOPIC_LEN); + ck_assert_msg(s_ret, "Founder failed to set topic"); + + /* Founder gets the Chat ID and implicitly shares it publicly */ + Tox_Err_Group_State_Queries id_err; + uint8_t chat_id[TOX_GROUP_CHAT_ID_SIZE]; + tox_group_get_chat_id(tox0, groupnumber, chat_id, &id_err); + + ck_assert_msg(id_err == TOX_ERR_GROUP_STATE_QUERIES_OK, "tox_group_get_chat_id failed %d", id_err); + + /* All other peers join the group using the Chat ID */ + for (size_t i = 1; i < NUM_GROUP_TOXES; ++i) { + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + Tox_Err_Group_Join join_err; + tox_group_join(autotoxes[i].tox, chat_id, (const uint8_t *)"Test", 4, nullptr, 0, &join_err); + ck_assert_msg(join_err == TOX_ERR_GROUP_JOIN_OK, "tox_group_join failed: %d", join_err); + + c_sleep(100); + } + + fprintf(stderr, "Peers attempting to join group\n"); + + all_group_peers_connected(autotoxes, NUM_GROUP_TOXES, groupnumber, GROUP_NAME_LEN, MAX_GC_PEERS_DEFAULT); + + wait_state_topic(autotoxes, groupnumber, TOPIC, TOPIC_LEN); + + /* Founder disables topic lock */ + Tox_Err_Group_Founder_Set_Topic_Lock lock_set_err; + tox_group_founder_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_DISABLED, &lock_set_err); + ck_assert_msg(lock_set_err == TOX_ERR_GROUP_FOUNDER_SET_TOPIC_LOCK_OK, "failed to disable topic lock: %d", + lock_set_err); + + fprintf(stderr, "Topic lock disabled\n"); + + /* make sure every peer sees the topic lock state change */ + wait_topic_lock(autotoxes, groupnumber, TOX_GROUP_TOPIC_LOCK_DISABLED); + + /* All peers should be able to change the topic now */ + uint32_t change_count = set_topic_all_peers(rng, autotoxes, NUM_GROUP_TOXES, groupnumber); + + ck_assert_msg(change_count == NUM_GROUP_TOXES, "%u peers changed the topic with topic lock disabled", change_count); + + /* founder silences the last peer he saw join */ + Tox_Err_Group_Mod_Set_Role merr; + tox_group_mod_set_role(tox0, groupnumber, state0->peer_id, TOX_GROUP_ROLE_OBSERVER, &merr); + ck_assert_msg(merr == TOX_ERR_GROUP_MOD_SET_ROLE_OK, "Failed to set %u to observer role: %d", state0->peer_id, merr); + + fprintf(stderr, "Random peer is set to observer\n"); + + iterate_all_wait(autotoxes, NUM_GROUP_TOXES, ITERATION_INTERVAL); + + /* All peers except one should now be able to change the topic */ + change_count = set_topic_all_peers(rng, autotoxes, NUM_GROUP_TOXES, groupnumber); + + ck_assert_msg(change_count == NUM_GROUP_TOXES - 1, "%u peers changed the topic with a silenced peer", change_count); + + /* Founder enables topic lock and sets topic back to original */ + tox_group_founder_set_topic_lock(tox0, groupnumber, TOX_GROUP_TOPIC_LOCK_ENABLED, &lock_set_err); + ck_assert_msg(lock_set_err == TOX_ERR_GROUP_FOUNDER_SET_TOPIC_LOCK_OK, "failed to enable topic lock: %d", + lock_set_err); + + fprintf(stderr, "Topic lock enabled\n"); + + /* Wait for all peers to get topic lock state change */ + wait_topic_lock(autotoxes, groupnumber, TOX_GROUP_TOPIC_LOCK_ENABLED); + + const bool s3_ret = set_topic(tox0, groupnumber, TOPIC2, TOPIC_LEN2); + ck_assert_msg(s3_ret, "Founder failed to set topic second time"); + + wait_state_topic(autotoxes, groupnumber, TOPIC2, TOPIC_LEN2); + + /* No peer excluding the founder should be able to set the topic */ + + change_count = set_topic_all_peers(rng, &autotoxes[1], NUM_GROUP_TOXES - 1, groupnumber); + + ck_assert_msg(change_count == 0, "%u peers changed the topic with topic lock enabled", change_count); + + /* A final check that the topic is unchanged */ + wait_state_topic(autotoxes, groupnumber, TOPIC2, TOPIC_LEN2); + + for (size_t i = 0; i < NUM_GROUP_TOXES; ++i) { + Tox_Err_Group_Leave err_exit; + tox_group_leave(autotoxes[i].tox, groupnumber, nullptr, 0, &err_exit); + ck_assert_msg(err_exit == TOX_ERR_GROUP_LEAVE_OK, "%d", err_exit); + } + + fprintf(stderr, "All tests passed!\n"); + +#endif /* VANILLA_NACL */ +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options autotest_opts = default_run_auto_options(); + autotest_opts.graph = GRAPH_COMPLETE; + + run_auto_test(nullptr, NUM_GROUP_TOXES, group_topic_test, sizeof(State), &autotest_opts); + + return 0; +} + +#undef TOPIC +#undef TOPIC_LEN +#undef TOPIC2 +#undef TOPIC_LEN2 +#undef NUM_GROUP_TOXES +#undef GROUP_NAME +#undef GROUP_NAME_LEN +#undef PEER0_NICK +#undef PEER0_NICK_LEN diff --git a/auto_tests/invalid_tcp_proxy_test.c b/auto_tests/invalid_tcp_proxy_test.c new file mode 100644 index 0000000..08869c5 --- /dev/null +++ b/auto_tests/invalid_tcp_proxy_test.c @@ -0,0 +1,48 @@ +// Test to make sure that when UDP is disabled, and we set an invalid proxy, +// i.e. one that doesn't run a proxy server, then we don't get any connection. + +#include + +#include "../testing/misc_tools.h" +#include "auto_test_support.h" +#include "check_compat.h" + +static uint8_t const key[] = { + 0x15, 0xE9, 0xC3, 0x09, 0xCF, 0xCB, 0x79, 0xFD, + 0xDF, 0x0E, 0xBA, 0x05, 0x7D, 0xAB, 0xB4, 0x9F, + 0xE1, 0x5F, 0x38, 0x03, 0xB1, 0xBF, 0xF0, 0x65, + 0x36, 0xAE, 0x2E, 0x5B, 0xA5, 0xE4, 0x69, 0x0E, +}; + +// Try to bootstrap for 30 seconds. +#define NUM_ITERATIONS (unsigned)(30.0 / (ITERATION_INTERVAL / 1000.0)) + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + struct Tox_Options *opts = tox_options_new(nullptr); + tox_options_set_udp_enabled(opts, false); + tox_options_set_proxy_type(opts, TOX_PROXY_TYPE_SOCKS5); + tox_options_set_proxy_host(opts, "localhost"); + tox_options_set_proxy_port(opts, 51724); + Tox *tox = tox_new_log(opts, nullptr, nullptr); + tox_options_free(opts); + + tox_add_tcp_relay(tox, "tox.ngc.zone", 33445, key, nullptr); + tox_bootstrap(tox, "tox.ngc.zone", 33445, key, nullptr); + + printf("Waiting for connection...\n"); + + for (uint16_t i = 0; i < NUM_ITERATIONS; i++) { + tox_iterate(tox, nullptr); + c_sleep(ITERATION_INTERVAL); + // None of the iterations should have a connection. + const Tox_Connection status = tox_self_get_connection_status(tox); + ck_assert_msg(status == TOX_CONNECTION_NONE, + "unexpectedly got a connection (%d)", status); + } + + tox_kill(tox); + return 0; +} diff --git a/auto_tests/invalid_udp_proxy_test.c b/auto_tests/invalid_udp_proxy_test.c new file mode 100644 index 0000000..cf24be4 --- /dev/null +++ b/auto_tests/invalid_udp_proxy_test.c @@ -0,0 +1,48 @@ +// Test that if UDP is enabled, and a proxy is provided that does not support +// UDP proxying, we disable UDP. + +#include + +#include "../testing/misc_tools.h" +#include "auto_test_support.h" +#include "check_compat.h" + +static uint8_t const key[] = { + 0x15, 0xE9, 0xC3, 0x09, 0xCF, 0xCB, 0x79, 0xFD, + 0xDF, 0x0E, 0xBA, 0x05, 0x7D, 0xAB, 0xB4, 0x9F, + 0xE1, 0x5F, 0x38, 0x03, 0xB1, 0xBF, 0xF0, 0x65, + 0x36, 0xAE, 0x2E, 0x5B, 0xA5, 0xE4, 0x69, 0x0E, +}; + +// Try to bootstrap for 30 seconds. +#define NUM_ITERATIONS (unsigned)(30.0 / (ITERATION_INTERVAL / 1000.0)) + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + struct Tox_Options *opts = tox_options_new(nullptr); + tox_options_set_udp_enabled(opts, true); + tox_options_set_proxy_type(opts, TOX_PROXY_TYPE_SOCKS5); + tox_options_set_proxy_host(opts, "localhost"); + tox_options_set_proxy_port(opts, 51724); + Tox *tox = tox_new_log(opts, nullptr, nullptr); + tox_options_free(opts); + + tox_add_tcp_relay(tox, "tox.ngc.zone", 33445, key, nullptr); + tox_bootstrap(tox, "tox.ngc.zone", 33445, key, nullptr); + + printf("Waiting for connection..."); + + for (uint16_t i = 0; i < NUM_ITERATIONS; i++) { + tox_iterate(tox, nullptr); + c_sleep(ITERATION_INTERVAL); + // None of the iterations should have a connection. + const Tox_Connection status = tox_self_get_connection_status(tox); + ck_assert_msg(status == TOX_CONNECTION_NONE, + "unexpectedly got a connection (%d)", status); + } + + tox_kill(tox); + return 0; +} diff --git a/auto_tests/lan_discovery_test.c b/auto_tests/lan_discovery_test.c new file mode 100644 index 0000000..d7bf594 --- /dev/null +++ b/auto_tests/lan_discovery_test.c @@ -0,0 +1,53 @@ +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/tox_struct.h" +#include "auto_test_support.h" + +static uint64_t get_state_clock_callback(void *user_data) +{ + const uint64_t *clock = (const uint64_t *)user_data; + return *clock; +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Tox *tox1 = tox_new_log_lan(nullptr, nullptr, nullptr, /* lan_discovery */true); + Tox *tox2 = tox_new_log_lan(nullptr, nullptr, nullptr, /* lan_discovery */true); + ck_assert(tox1 != nullptr); + ck_assert(tox2 != nullptr); + + uint64_t clock = current_time_monotonic(tox1->mono_time); + Mono_Time *mono_time; + + mono_time = tox1->mono_time; + mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &clock); + + mono_time = tox2->mono_time; + mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &clock); + + printf("Waiting for LAN discovery. This loop will attempt to run until successful."); + + do { + printf("."); + fflush(stdout); + + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + c_sleep(5); + clock += 100; + } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || + tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE); + + printf(" %d <-> %d\n", + tox_self_get_connection_status(tox1), + tox_self_get_connection_status(tox2)); + + tox_kill(tox2); + tox_kill(tox1); + return 0; +} diff --git a/auto_tests/lossless_packet_test.c b/auto_tests/lossless_packet_test.c new file mode 100644 index 0000000..9d3f824 --- /dev/null +++ b/auto_tests/lossless_packet_test.c @@ -0,0 +1,67 @@ +/* Tests that we can send lossless packets. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/util.h" +#include "check_compat.h" + +typedef struct State { + bool custom_packet_received; +} State; + +#include "auto_test_support.h" + +#define LOSSLESS_PACKET_FILLER 160 + +static void handle_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data) +{ + uint8_t *cmp_packet = (uint8_t *)malloc(tox_max_custom_packet_size()); + ck_assert(cmp_packet != nullptr); + memset(cmp_packet, LOSSLESS_PACKET_FILLER, tox_max_custom_packet_size()); + + if (length == tox_max_custom_packet_size() && memcmp(data, cmp_packet, tox_max_custom_packet_size()) == 0) { + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + state->custom_packet_received = true; + } + + free(cmp_packet); +} + +static void test_lossless_packet(AutoTox *autotoxes) +{ + tox_callback_friend_lossless_packet(autotoxes[1].tox, &handle_lossless_packet); + const size_t packet_size = tox_max_custom_packet_size() + 1; + uint8_t *packet = (uint8_t *)malloc(packet_size); + ck_assert(packet != nullptr); + memset(packet, LOSSLESS_PACKET_FILLER, packet_size); + + bool ret = tox_friend_send_lossless_packet(autotoxes[0].tox, 0, packet, packet_size, nullptr); + ck_assert_msg(ret == false, "should not be able to send custom packets this big %i", ret); + + ret = tox_friend_send_lossless_packet(autotoxes[0].tox, 0, packet, tox_max_custom_packet_size(), nullptr); + ck_assert_msg(ret == true, "tox_friend_send_lossless_packet fail %i", ret); + + free(packet); + + do { + iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); + } while (!((State *)autotoxes[1].state)->custom_packet_received); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + + run_auto_test(nullptr, 2, test_lossless_packet, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/lossy_packet_test.c b/auto_tests/lossy_packet_test.c new file mode 100644 index 0000000..3074541 --- /dev/null +++ b/auto_tests/lossy_packet_test.c @@ -0,0 +1,67 @@ +/* Tests that we can send lossy packets. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/util.h" +#include "check_compat.h" + +typedef struct State { + bool custom_packet_received; +} State; + +#include "auto_test_support.h" + +#define LOSSY_PACKET_FILLER 200 + +static void handle_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data) +{ + uint8_t *cmp_packet = (uint8_t *)malloc(tox_max_custom_packet_size()); + ck_assert(cmp_packet != nullptr); + memset(cmp_packet, LOSSY_PACKET_FILLER, tox_max_custom_packet_size()); + + if (length == tox_max_custom_packet_size() && memcmp(data, cmp_packet, tox_max_custom_packet_size()) == 0) { + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + state->custom_packet_received = true; + } + + free(cmp_packet); +} + +static void test_lossy_packet(AutoTox *autotoxes) +{ + tox_callback_friend_lossy_packet(autotoxes[1].tox, &handle_lossy_packet); + const size_t packet_size = tox_max_custom_packet_size() + 1; + uint8_t *packet = (uint8_t *)malloc(packet_size); + ck_assert(packet != nullptr); + memset(packet, LOSSY_PACKET_FILLER, packet_size); + + bool ret = tox_friend_send_lossy_packet(autotoxes[0].tox, 0, packet, packet_size, nullptr); + ck_assert_msg(ret == false, "should not be able to send custom packets this big %i", ret); + + ret = tox_friend_send_lossy_packet(autotoxes[0].tox, 0, packet, tox_max_custom_packet_size(), nullptr); + ck_assert_msg(ret == true, "tox_friend_send_lossy_packet fail %i", ret); + + free(packet); + + do { + iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); + } while (!((State *)autotoxes[1].state)->custom_packet_received); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + + run_auto_test(nullptr, 2, test_lossy_packet, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/network_test.c b/auto_tests/network_test.c new file mode 100644 index 0000000..df3b625 --- /dev/null +++ b/auto_tests/network_test.c @@ -0,0 +1,173 @@ +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/network.h" +#include "check_compat.h" + +#ifndef USE_IPV6 +#define USE_IPV6 1 +#endif + +static void test_addr_resolv_localhost(void) +{ +#ifdef __CYGWIN__ + /* force initialization of network stack + * normally this should happen automatically + * cygwin doesn't do it for every network related function though + * e.g. not for getaddrinfo... */ + net_socket(0, 0, 0); + errno = 0; +#endif + + const Network *ns = system_network(); + ck_assert(ns != nullptr); + + const char localhost[] = "localhost"; + + IP ip; + ip_init(&ip, 0); // ipv6enabled = 0 + + bool res = addr_resolve_or_parse_ip(ns, localhost, &ip, nullptr); + + int error = net_error(); + char *strerror = net_new_strerror(error); + ck_assert_msg(res, "Resolver failed: %d, %s", error, strerror); + net_kill_strerror(strerror); + + Ip_Ntoa ip_str; + ck_assert_msg(net_family_is_ipv4(ip.family), "Expected family TOX_AF_INET, got %u.", ip.family.value); + const uint32_t loopback = get_ip4_loopback().uint32; + ck_assert_msg(ip.ip.v4.uint32 == loopback, "Expected 127.0.0.1, got %s.", + net_ip_ntoa(&ip, &ip_str)); + + ip_init(&ip, 1); // ipv6enabled = 1 + res = addr_resolve_or_parse_ip(ns, localhost, &ip, nullptr); + +#if USE_IPV6 + + int localhost_split = 0; + + if (!net_family_is_ipv6(ip.family)) { + res = addr_resolve_or_parse_ip(ns, "ip6-localhost", &ip, nullptr); + localhost_split = 1; + } + + error = net_error(); + strerror = net_new_strerror(error); + ck_assert_msg(res, "Resolver failed: %d, %s", error, strerror); + net_kill_strerror(strerror); + + ck_assert_msg(net_family_is_ipv6(ip.family), "Expected family TOX_AF_INET6 (%d), got %u.", TOX_AF_INET6, + ip.family.value); + IP6 ip6_loopback = get_ip6_loopback(); + ck_assert_msg(!memcmp(&ip.ip.v6, &ip6_loopback, sizeof(IP6)), "Expected ::1, got %s.", + net_ip_ntoa(&ip, &ip_str)); + + if (localhost_split) { + printf("Localhost seems to be split in two.\n"); + return; + } + +#endif + + ip_init(&ip, 1); // ipv6enabled = 1 + ip.family = net_family_unspec(); + IP extra; + ip_reset(&extra); + res = addr_resolve_or_parse_ip(ns, localhost, &ip, &extra); + error = net_error(); + strerror = net_new_strerror(error); + ck_assert_msg(res, "Resolver failed: %d, %s", error, strerror); + net_kill_strerror(strerror); + +#if USE_IPV6 + ck_assert_msg(net_family_is_ipv6(ip.family), "Expected family TOX_AF_INET6 (%d), got %u.", TOX_AF_INET6, + ip.family.value); + ck_assert_msg(!memcmp(&ip.ip.v6, &ip6_loopback, sizeof(IP6)), "Expected ::1, got %s.", + net_ip_ntoa(&ip, &ip_str)); + + ck_assert_msg(net_family_is_ipv4(extra.family), "Expected family TOX_AF_INET (%d), got %u.", TOX_AF_INET, + extra.family.value); + ck_assert_msg(extra.ip.v4.uint32 == loopback, "Expected 127.0.0.1, got %s.", + net_ip_ntoa(&ip, &ip_str)); +#elif 0 + // TODO(iphydf): Fix this to work on IPv6-supporting systems. + ck_assert_msg(net_family_is_ipv4(ip.family), "Expected family TOX_AF_INET (%d), got %u.", TOX_AF_INET, ip.family.value); + ck_assert_msg(ip.ip.v4.uint32 == loopback, "Expected 127.0.0.1, got %s.", + net_ip_ntoa(&ip, &ip_str)); +#endif +} + +static void test_ip_equal(void) +{ + int res; + IP ip1, ip2; + ip_reset(&ip1); + ip_reset(&ip2); + + res = ip_equal(nullptr, nullptr); + ck_assert_msg(res == 0, "ip_equal(NULL, NULL): expected result 0, got %d.", res); + + res = ip_equal(&ip1, nullptr); + ck_assert_msg(res == 0, "ip_equal(PTR, NULL): expected result 0, got %d.", res); + + res = ip_equal(nullptr, &ip1); + ck_assert_msg(res == 0, "ip_equal(NULL, PTR): expected result 0, got %d.", res); + + ip1.family = net_family_ipv4(); + ip1.ip.v4.uint32 = net_htonl(0x7F000001); + + res = ip_equal(&ip1, &ip2); + ck_assert_msg(res == 0, "ip_equal( {TOX_AF_INET, 127.0.0.1}, {TOX_AF_UNSPEC, 0} ): " + "expected result 0, got %d.", res); + + ip2.family = net_family_ipv4(); + ip2.ip.v4.uint32 = net_htonl(0x7F000001); + + res = ip_equal(&ip1, &ip2); + ck_assert_msg(res != 0, "ip_equal( {TOX_AF_INET, 127.0.0.1}, {TOX_AF_INET, 127.0.0.1} ): " + "expected result != 0, got 0."); + + ip2.ip.v4.uint32 = net_htonl(0x7F000002); + + res = ip_equal(&ip1, &ip2); + ck_assert_msg(res == 0, "ip_equal( {TOX_AF_INET, 127.0.0.1}, {TOX_AF_INET, 127.0.0.2} ): " + "expected result 0, got %d.", res); + + ip2.family = net_family_ipv6(); + ip2.ip.v6.uint32[0] = 0; + ip2.ip.v6.uint32[1] = 0; + ip2.ip.v6.uint32[2] = net_htonl(0xFFFF); + ip2.ip.v6.uint32[3] = net_htonl(0x7F000001); + + ck_assert_msg(ipv6_ipv4_in_v6(&ip2.ip.v6) != 0, + "ipv6_ipv4_in_v6(::ffff:127.0.0.1): expected != 0, got 0."); + + res = ip_equal(&ip1, &ip2); + ck_assert_msg(res != 0, "ip_equal( {TOX_AF_INET, 127.0.0.1}, {TOX_AF_INET6, ::ffff:127.0.0.1} ): " + "expected result != 0, got 0."); + + IP6 ip6_loopback = get_ip6_loopback(); + memcpy(&ip2.ip.v6, &ip6_loopback, sizeof(IP6)); + res = ip_equal(&ip1, &ip2); + ck_assert_msg(res == 0, "ip_equal( {TOX_AF_INET, 127.0.0.1}, {TOX_AF_INET6, ::1} ): expected result 0, got %d.", res); + + memcpy(&ip1, &ip2, sizeof(IP)); + res = ip_equal(&ip1, &ip2); + ck_assert_msg(res != 0, "ip_equal( {TOX_AF_INET6, ::1}, {TOX_AF_INET6, ::1} ): expected result != 0, got 0."); + + ip2.ip.v6.uint8[15]++; + res = ip_equal(&ip1, &ip2); + ck_assert_msg(res == 0, "ip_equal( {TOX_AF_INET6, ::1}, {TOX_AF_INET6, ::2} ): expected result 0, got %d.", res); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_addr_resolv_localhost(); + test_ip_equal(); + + return 0; +} diff --git a/auto_tests/onion_test.c b/auto_tests/onion_test.c new file mode 100644 index 0000000..02a15b2 --- /dev/null +++ b/auto_tests/onion_test.c @@ -0,0 +1,668 @@ +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/mono_time.h" +#include "../toxcore/onion.h" +#include "../toxcore/onion_announce.h" +#include "../toxcore/onion_client.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#ifndef USE_IPV6 +#define USE_IPV6 1 +#endif + +static inline IP get_loopback(void) +{ + IP ip; +#if USE_IPV6 + ip.family = net_family_ipv6(); + ip.ip.v6 = get_ip6_loopback(); +#else + ip.family = net_family_ipv4(); + ip.ip.v4 = get_ip4_loopback(); +#endif + return ip; +} +static void do_onion(Mono_Time *mono_time, Onion *onion) +{ + mono_time_update(mono_time); + + networking_poll(onion->net, nullptr); + do_dht(onion->dht); +} + +static int handled_test_1; +static int handle_test_1(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + const char req_message[] = "Install Gentoo"; + uint8_t req_packet[1 + sizeof(req_message)]; + req_packet[0] = NET_PACKET_ANNOUNCE_REQUEST; + memcpy(req_packet + 1, req_message, sizeof(req_message)); + + if (memcmp(packet, req_packet, sizeof(req_packet)) != 0) { + return 1; + } + + const char res_message[] = "install gentoo"; + uint8_t res_packet[1 + sizeof(res_message)]; + res_packet[0] = NET_PACKET_ANNOUNCE_RESPONSE; + memcpy(res_packet + 1, res_message, sizeof(res_message)); + + if (send_onion_response(onion->net, source, res_packet, sizeof(res_packet), + packet + sizeof(res_packet)) == -1) { + return 1; + } + + handled_test_1 = 1; + return 0; +} + +static int handled_test_2; +static int handle_test_2(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + const char res_message[] = "install gentoo"; + uint8_t res_packet[1 + sizeof(res_message)]; + res_packet[0] = NET_PACKET_ANNOUNCE_RESPONSE; + memcpy(res_packet + 1, res_message, sizeof(res_message)); + + if (length != sizeof(res_packet)) { + return 1; + } + + if (memcmp(packet, res_packet, sizeof(res_packet)) != 0) { + return 1; + } + + handled_test_2 = 1; + return 0; +} +#if 0 +void print_client_id(uint8_t *client_id, uint32_t length) +{ + uint32_t j; + + for (j = 0; j < length; j++) { + printf("%02X", client_id[j]); + } + + printf("\n"); +} +#endif +static uint8_t sb_data[ONION_ANNOUNCE_SENDBACK_DATA_LENGTH]; +static int handled_test_3; +static uint8_t test_3_pub_key[CRYPTO_PUBLIC_KEY_SIZE]; +static uint8_t test_3_ping_id[CRYPTO_SHA256_SIZE]; +static int handle_test_3(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length < ONION_ANNOUNCE_RESPONSE_MIN_SIZE || length > ONION_ANNOUNCE_RESPONSE_MAX_SIZE) { + return 1; + } + + uint8_t plain[2 + CRYPTO_SHA256_SIZE]; +#if 0 + print_client_id(packet, length); +#endif + int len = decrypt_data(test_3_pub_key, dht_get_self_secret_key(onion->dht), + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE, + 2 + CRYPTO_SHA256_SIZE + CRYPTO_MAC_SIZE, plain); + + if (len == -1) { + return 1; + } + + + if (memcmp(packet + 1, sb_data, ONION_ANNOUNCE_SENDBACK_DATA_LENGTH) != 0) { + return 1; + } + + memcpy(test_3_ping_id, plain + 1, CRYPTO_SHA256_SIZE); +#if 0 + print_client_id(test_3_ping_id, sizeof(test_3_ping_id)); +#endif + handled_test_3 = 1; + return 0; +} + +/* TODO: DEPRECATE */ +static int handle_test_3_old(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, + void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length < ONION_ANNOUNCE_RESPONSE_MIN_SIZE || length > ONION_ANNOUNCE_RESPONSE_MAX_SIZE) { + return 1; + } + + uint8_t plain[2 + CRYPTO_SHA256_SIZE]; +#if 0 + print_client_id(packet, length); +#endif + int len = decrypt_data(test_3_pub_key, dht_get_self_secret_key(onion->dht), + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, + packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE, + 1 + CRYPTO_SHA256_SIZE + CRYPTO_MAC_SIZE, plain); + + if (len == -1) { + return 1; + } + + + if (memcmp(packet + 1, sb_data, ONION_ANNOUNCE_SENDBACK_DATA_LENGTH) != 0) { + return 1; + } + + memcpy(test_3_ping_id, plain + 1, CRYPTO_SHA256_SIZE); +#if 0 + print_client_id(test_3_ping_id, sizeof(test_3_ping_id)); +#endif + handled_test_3 = 1; + return 0; +} + +static uint8_t nonce[CRYPTO_NONCE_SIZE]; +static int handled_test_4; +static int handle_test_4(void *object, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) +{ + Onion *onion = (Onion *)object; + + if (length != (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + sizeof("Install gentoo") + + CRYPTO_MAC_SIZE)) { + return 1; + } + + uint8_t plain[sizeof("Install gentoo")] = {0}; + + if (memcmp(nonce, packet + 1, CRYPTO_NONCE_SIZE) != 0) { + return 1; + } + + int len = decrypt_data(packet + 1 + CRYPTO_NONCE_SIZE, dht_get_self_secret_key(onion->dht), packet + 1, + packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, sizeof("Install gentoo") + CRYPTO_MAC_SIZE, plain); + + if (len == -1) { + return 1; + } + + if (memcmp(plain, "Install gentoo", sizeof("Install gentoo")) != 0) { + return 1; + } + + handled_test_4 = 1; + return 0; +} + +/** Create and send a onion packet. + * + * Use Onion_Path path to send data of length to dest. + * Maximum length of data is ONION_MAX_DATA_SIZE. + */ +static void send_onion_packet(const Networking_Core *net, const Random *rng, const Onion_Path *path, const IP_Port *dest, const uint8_t *data, uint16_t length) +{ + uint8_t packet[ONION_MAX_PACKET_SIZE]; + const int len = create_onion_packet(rng, packet, sizeof(packet), path, dest, data, length); + ck_assert_msg(len != -1, "failed to create onion packet"); + ck_assert_msg(sendpacket(net, &path->ip_port1, packet, len) == len, "failed to send onion packet"); +} + +/** Initialize networking. + * Added for reverse compatibility with old new_networking calls. + */ +static Networking_Core *new_networking(const Logger *log, const Network *ns, const IP *ip, uint16_t port) +{ + return new_networking_ex(log, ns, ip, port, port + (TOX_PORTRANGE_TO - TOX_PORTRANGE_FROM), nullptr); +} + +static void test_basic(void) +{ + uint32_t index[] = { 1, 2, 3 }; + const Network *ns = system_network(); + + Logger *log1 = logger_new(); + logger_callback_log(log1, print_debug_logger, nullptr, &index[0]); + Logger *log2 = logger_new(); + logger_callback_log(log2, print_debug_logger, nullptr, &index[1]); + + const Random *rng = system_random(); + ck_assert(rng != nullptr); + Mono_Time *mono_time1 = mono_time_new(nullptr, nullptr); + Mono_Time *mono_time2 = mono_time_new(nullptr, nullptr); + + IP ip = get_loopback(); + Onion *onion1 = new_onion(log1, mono_time1, rng, new_dht(log1, rng, ns, mono_time1, new_networking(log1, ns, &ip, 36567), true, false)); + Onion *onion2 = new_onion(log2, mono_time2, rng, new_dht(log2, rng, ns, mono_time2, new_networking(log2, ns, &ip, 36568), true, false)); + ck_assert_msg((onion1 != nullptr) && (onion2 != nullptr), "Onion failed initializing."); + networking_registerhandler(onion2->net, NET_PACKET_ANNOUNCE_REQUEST, &handle_test_1, onion2); + + IP_Port on1 = {ip, net_port(onion1->net)}; + Node_format n1; + memcpy(n1.public_key, dht_get_self_public_key(onion1->dht), CRYPTO_PUBLIC_KEY_SIZE); + n1.ip_port = on1; + + IP_Port on2 = {ip, net_port(onion2->net)}; + Node_format n2; + memcpy(n2.public_key, dht_get_self_public_key(onion2->dht), CRYPTO_PUBLIC_KEY_SIZE); + n2.ip_port = on2; + + const char req_message[] = "Install Gentoo"; + uint8_t req_packet[1 + sizeof(req_message)]; + req_packet[0] = NET_PACKET_ANNOUNCE_REQUEST; + memcpy(req_packet + 1, req_message, sizeof(req_message)); + + Node_format nodes[4]; + nodes[0] = n1; + nodes[1] = n2; + nodes[2] = n1; + nodes[3] = n2; + Onion_Path path; + create_onion_path(rng, onion1->dht, &path, nodes); + send_onion_packet(onion1->net, rng, &path, &nodes[3].ip_port, req_packet, sizeof(req_packet)); + + handled_test_1 = 0; + + do { + do_onion(mono_time1, onion1); + do_onion(mono_time2, onion2); + } while (handled_test_1 == 0); + + networking_registerhandler(onion1->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_test_2, onion1); + handled_test_2 = 0; + + do { + do_onion(mono_time1, onion1); + do_onion(mono_time2, onion2); + } while (handled_test_2 == 0); + + Onion_Announce *onion1_a = new_onion_announce(log1, rng, mono_time1, onion1->dht); + Onion_Announce *onion2_a = new_onion_announce(log2, rng, mono_time2, onion2->dht); + networking_registerhandler(onion1->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_test_3, onion1); + networking_registerhandler(onion1->net, NET_PACKET_ANNOUNCE_RESPONSE_OLD, &handle_test_3_old, onion1); + ck_assert_msg((onion1_a != nullptr) && (onion2_a != nullptr), "Onion_Announce failed initializing."); + uint8_t zeroes[64] = {0}; + random_bytes(rng, sb_data, sizeof(sb_data)); + uint64_t s; + memcpy(&s, sb_data, sizeof(uint64_t)); + memcpy(test_3_pub_key, nodes[3].public_key, CRYPTO_PUBLIC_KEY_SIZE); + int ret = send_announce_request(onion1->net, rng, &path, &nodes[3], + dht_get_self_public_key(onion1->dht), + dht_get_self_secret_key(onion1->dht), + zeroes, + dht_get_self_public_key(onion1->dht), + dht_get_self_public_key(onion1->dht), s); + ck_assert_msg(ret == 0, "Failed to create/send onion announce_request packet."); + handled_test_3 = 0; + + do { + do_onion(mono_time1, onion1); + do_onion(mono_time2, onion2); + c_sleep(50); + } while (handled_test_3 == 0); + + printf("test 3 complete\n"); + + random_bytes(rng, sb_data, sizeof(sb_data)); + memcpy(&s, sb_data, sizeof(uint64_t)); + memcpy(onion_announce_entry_public_key(onion2_a, 1), dht_get_self_public_key(onion2->dht), CRYPTO_PUBLIC_KEY_SIZE); + onion_announce_entry_set_time(onion2_a, 1, mono_time_get(mono_time2)); + networking_registerhandler(onion1->net, NET_PACKET_ONION_DATA_RESPONSE, &handle_test_4, onion1); + send_announce_request(onion1->net, rng, &path, &nodes[3], + dht_get_self_public_key(onion1->dht), + dht_get_self_secret_key(onion1->dht), + test_3_ping_id, + dht_get_self_public_key(onion1->dht), + dht_get_self_public_key(onion1->dht), s); + + do { + do_onion(mono_time1, onion1); + do_onion(mono_time2, onion2); + c_sleep(50); + } while (memcmp(onion_announce_entry_public_key(onion2_a, ONION_ANNOUNCE_MAX_ENTRIES - 2), + dht_get_self_public_key(onion1->dht), + CRYPTO_PUBLIC_KEY_SIZE) != 0); + + c_sleep(1000); + Logger *log3 = logger_new(); + logger_callback_log(log3, print_debug_logger, nullptr, &index[2]); + + Mono_Time *mono_time3 = mono_time_new(nullptr, nullptr); + + Onion *onion3 = new_onion(log3, mono_time3, rng, new_dht(log3, rng, ns, mono_time3, new_networking(log3, ns, &ip, 36569), true, false)); + ck_assert_msg((onion3 != nullptr), "Onion failed initializing."); + + random_nonce(rng, nonce); + ret = send_data_request(onion3->net, rng, &path, &nodes[3].ip_port, + dht_get_self_public_key(onion1->dht), + dht_get_self_public_key(onion1->dht), + nonce, (const uint8_t *)"Install gentoo", sizeof("Install gentoo")); + ck_assert_msg(ret == 0, "Failed to create/send onion data_request packet."); + handled_test_4 = 0; + + do { + do_onion(mono_time1, onion1); + do_onion(mono_time2, onion2); + c_sleep(50); + } while (handled_test_4 == 0); + + printf("test 4 complete\n"); + + kill_onion_announce(onion2_a); + kill_onion_announce(onion1_a); + + { + Onion *onion = onion3; + + Networking_Core *net = dht_get_net(onion->dht); + DHT *dht = onion->dht; + kill_onion(onion); + kill_dht(dht); + kill_networking(net); + mono_time_free(mono_time3); + logger_kill(log3); + } + + { + Onion *onion = onion2; + + Networking_Core *net = dht_get_net(onion->dht); + DHT *dht = onion->dht; + kill_onion(onion); + kill_dht(dht); + kill_networking(net); + mono_time_free(mono_time2); + logger_kill(log2); + } + + { + Onion *onion = onion1; + + Networking_Core *net = dht_get_net(onion->dht); + DHT *dht = onion->dht; + kill_onion(onion); + kill_dht(dht); + kill_networking(net); + mono_time_free(mono_time1); + logger_kill(log1); + } +} + +typedef struct { + Logger *log; + Mono_Time *mono_time; + Onion *onion; + Onion_Announce *onion_a; + Onion_Client *onion_c; +} Onions; + +static Onions *new_onions(const Random *rng, uint16_t port, uint32_t *index) +{ + IP ip = get_loopback(); + ip.ip.v6.uint8[15] = 1; + const Network *ns = system_network(); + Onions *on = (Onions *)malloc(sizeof(Onions)); + + if (!on) { + return nullptr; + } + + on->log = logger_new(); + + if (!on->log) { + free(on); + return nullptr; + } + + logger_callback_log(on->log, print_debug_logger, nullptr, index); + + on->mono_time = mono_time_new(nullptr, nullptr); + + if (!on->mono_time) { + logger_kill(on->log); + free(on); + return nullptr; + } + + Networking_Core *net = new_networking(on->log, ns, &ip, port); + + if (!net) { + mono_time_free(on->mono_time); + logger_kill(on->log); + free(on); + return nullptr; + } + + DHT *dht = new_dht(on->log, rng, ns, on->mono_time, net, true, false); + + if (!dht) { + kill_networking(net); + mono_time_free(on->mono_time); + logger_kill(on->log); + free(on); + return nullptr; + } + + on->onion = new_onion(on->log, on->mono_time, rng, dht); + + if (!on->onion) { + kill_dht(dht); + kill_networking(net); + mono_time_free(on->mono_time); + logger_kill(on->log); + free(on); + return nullptr; + } + + on->onion_a = new_onion_announce(on->log, rng, on->mono_time, dht); + + if (!on->onion_a) { + kill_onion(on->onion); + kill_dht(dht); + kill_networking(net); + mono_time_free(on->mono_time); + logger_kill(on->log); + free(on); + return nullptr; + } + + TCP_Proxy_Info inf = {{{{0}}}}; + on->onion_c = new_onion_client(on->log, rng, on->mono_time, new_net_crypto(on->log, rng, ns, on->mono_time, dht, &inf)); + + if (!on->onion_c) { + kill_onion_announce(on->onion_a); + kill_onion(on->onion); + kill_dht(dht); + kill_networking(net); + mono_time_free(on->mono_time); + logger_kill(on->log); + free(on); + return nullptr; + } + + return on; +} + +static void do_onions(Onions *on) +{ + mono_time_update(on->mono_time); + + networking_poll(on->onion->net, nullptr); + do_dht(on->onion->dht); + do_onion_client(on->onion_c); +} + +static void kill_onions(Onions *on) +{ + Networking_Core *net = dht_get_net(on->onion->dht); + DHT *dht = on->onion->dht; + Net_Crypto *c = onion_get_net_crypto(on->onion_c); + kill_onion_client(on->onion_c); + kill_onion_announce(on->onion_a); + kill_onion(on->onion); + kill_net_crypto(c); + kill_dht(dht); + kill_networking(net); + mono_time_free(on->mono_time); + logger_kill(on->log); + free(on); +} + +#define NUM_ONIONS 50 +#define NUM_FIRST 7 +#define NUM_LAST 37 + +static bool first_ip, last_ip; +static void dht_ip_callback(void *object, int32_t number, const IP_Port *ip_port) +{ + if (NUM_FIRST == number) { + first_ip = 1; + return; + } + + if (NUM_LAST == number) { + last_ip = 1; + return; + } + + ck_abort_msg("Error."); +} + +static bool first, last; +static uint8_t first_dht_pk[CRYPTO_PUBLIC_KEY_SIZE]; +static uint8_t last_dht_pk[CRYPTO_PUBLIC_KEY_SIZE]; + +static void dht_pk_callback(void *object, int32_t number, const uint8_t *dht_public_key, void *userdata) +{ + if ((NUM_FIRST == number && !first) || (NUM_LAST == number && !last)) { + Onions *on = (Onions *)object; + uint32_t token = 0; + int ret = dht_addfriend(on->onion->dht, dht_public_key, &dht_ip_callback, object, number, &token); + ck_assert_msg(ret == 0, "dht_addfriend() did not return 0"); + ck_assert_msg(token == 1, "Count not 1, count is %u", token); + + if (NUM_FIRST == number && !first) { + first = 1; + + if (memcmp(dht_public_key, last_dht_pk, CRYPTO_PUBLIC_KEY_SIZE) != 0) { + ck_abort_msg("Error wrong dht key."); + } + + return; + } + + if (NUM_LAST == number && !last) { + last = 1; + + if (memcmp(dht_public_key, first_dht_pk, CRYPTO_PUBLIC_KEY_SIZE) != 0) { + ck_abort_msg("Error wrong dht key."); + } + + return; + } + + ck_abort_msg("Error."); + } +} + +static void test_announce(void) +{ + uint32_t i, j; + uint32_t index[NUM_ONIONS]; + Onions *onions[NUM_ONIONS]; + const Random *rng = system_random(); + ck_assert(rng != nullptr); + + for (i = 0; i < NUM_ONIONS; ++i) { + index[i] = i + 1; + onions[i] = new_onions(rng, i + 36655, &index[i]); + ck_assert_msg(onions[i] != nullptr, "Failed to create onions. %u", i); + } + + IP ip = get_loopback(); + + for (i = 3; i < NUM_ONIONS; ++i) { + IP_Port ip_port = {ip, net_port(onions[i - 1]->onion->net)}; + dht_bootstrap(onions[i]->onion->dht, &ip_port, dht_get_self_public_key(onions[i - 1]->onion->dht)); + IP_Port ip_port1 = {ip, net_port(onions[i - 2]->onion->net)}; + dht_bootstrap(onions[i]->onion->dht, &ip_port1, dht_get_self_public_key(onions[i - 2]->onion->dht)); + IP_Port ip_port2 = {ip, net_port(onions[i - 3]->onion->net)}; + dht_bootstrap(onions[i]->onion->dht, &ip_port2, dht_get_self_public_key(onions[i - 3]->onion->dht)); + } + + uint32_t connected = 0; + + do { + connected = 0; + + for (i = 0; i < NUM_ONIONS; ++i) { + do_onions(onions[i]); + connected += dht_isconnected(onions[i]->onion->dht); + } + + c_sleep(50); + } while (connected != NUM_ONIONS); + + printf("connected\n"); + + for (i = 0; i < 25 * 2; ++i) { + for (j = 0; j < NUM_ONIONS; ++j) { + do_onions(onions[j]); + } + + c_sleep(50); + } + + memcpy(first_dht_pk, dht_get_self_public_key(onions[NUM_FIRST]->onion->dht), CRYPTO_PUBLIC_KEY_SIZE); + memcpy(last_dht_pk, dht_get_self_public_key(onions[NUM_LAST]->onion->dht), CRYPTO_PUBLIC_KEY_SIZE); + + printf("adding friend\n"); + int frnum_f = onion_addfriend(onions[NUM_FIRST]->onion_c, + nc_get_self_public_key(onion_get_net_crypto(onions[NUM_LAST]->onion_c))); + int frnum = onion_addfriend(onions[NUM_LAST]->onion_c, + nc_get_self_public_key(onion_get_net_crypto(onions[NUM_FIRST]->onion_c))); + + onion_dht_pk_callback(onions[NUM_FIRST]->onion_c, frnum_f, &dht_pk_callback, onions[NUM_FIRST], NUM_FIRST); + onion_dht_pk_callback(onions[NUM_LAST]->onion_c, frnum, &dht_pk_callback, onions[NUM_LAST], NUM_LAST); + + IP_Port ip_port; + + do { + for (i = 0; i < NUM_ONIONS; ++i) { + do_onions(onions[i]); + } + + c_sleep(50); + } while (!first || !last); + + printf("Waiting for ips\n"); + + do { + for (i = 0; i < NUM_ONIONS; ++i) { + do_onions(onions[i]); + } + + c_sleep(50); + } while (!first_ip || !last_ip); + + onion_getfriendip(onions[NUM_LAST]->onion_c, frnum, &ip_port); + ck_assert_msg(ip_port.port == net_port(onions[NUM_FIRST]->onion->net), "Port in returned ip not correct."); + + for (i = 0; i < NUM_ONIONS; ++i) { + kill_onions(onions[i]); + } +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_basic(); + test_announce(); + + return 0; +} diff --git a/auto_tests/overflow_recvq_test.c b/auto_tests/overflow_recvq_test.c new file mode 100644 index 0000000..8855f70 --- /dev/null +++ b/auto_tests/overflow_recvq_test.c @@ -0,0 +1,64 @@ +/* Try to overflow the net_crypto packet buffer. + */ + +#include + +typedef struct State { + uint32_t recv_count; +} State; + +#include "auto_test_support.h" + +#define NUM_MSGS 40000 + +static void handle_friend_message(Tox *tox, uint32_t friend_number, Tox_Message_Type type, + const uint8_t *message, size_t length, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + state->recv_count++; +} + +static void net_crypto_overflow_test(AutoTox *autotoxes) +{ + tox_callback_friend_message(autotoxes[0].tox, handle_friend_message); + + printf("sending many messages to tox0\n"); + + for (uint32_t tox_index = 1; tox_index < 3; tox_index++) { + for (uint32_t i = 0; i < NUM_MSGS; i++) { + uint8_t message[128] = {0}; + snprintf((char *)message, sizeof(message), "%u-%u", tox_index, i); + + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(autotoxes[tox_index].tox, 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof message, &err); + + if (err == TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ) { + printf("tox%u sent %u messages to friend 0\n", tox_index, i); + break; + } + + ck_assert_msg(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK, + "tox%u failed to send message number %u: %d", tox_index, i, err); + } + } + + // TODO(iphydf): Wait until all messages have arrived. Currently, not all + // messages arrive, so this test would always fail. + for (uint32_t i = 0; i < 200; i++) { + iterate_all_wait(autotoxes, 3, ITERATION_INTERVAL); + } + + printf("tox%u received %u messages\n", autotoxes[0].index, ((State *)autotoxes[0].state)->recv_count); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + run_auto_test(nullptr, 3, net_crypto_overflow_test, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/overflow_sendq_test.c b/auto_tests/overflow_sendq_test.c new file mode 100644 index 0000000..fab0f00 --- /dev/null +++ b/auto_tests/overflow_sendq_test.c @@ -0,0 +1,46 @@ +/* Try to overflow the net_crypto packet buffer. + */ + +#include + +#include "auto_test_support.h" + +#define NUM_MSGS 40000 + +static void net_crypto_overflow_test(AutoTox *autotoxes) +{ + const uint8_t message[] = {0}; + bool errored = false; + + for (uint32_t i = 0; i < NUM_MSGS; i++) { + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(autotoxes[0].tox, 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof message, &err); + + if (err != TOX_ERR_FRIEND_SEND_MESSAGE_OK) { + errored = true; + } + + if (errored) { + // As soon as we get the first error, we expect the same error (SENDQ) + // every time we try to send. + ck_assert_msg(err == TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ, + "expected SENDQ error on message %u, but got %d", i, err); + } else { + ck_assert_msg(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK, + "failed to send message number %u: %d", i, err); + } + } + + ck_assert_msg(errored, "expected SENDQ error at some point (increase NUM_MSGS?)"); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + run_auto_test(nullptr, 2, net_crypto_overflow_test, 0, &options); + + return 0; +} diff --git a/auto_tests/proxy_test.c b/auto_tests/proxy_test.c new file mode 100644 index 0000000..d6f2d8c --- /dev/null +++ b/auto_tests/proxy_test.c @@ -0,0 +1,129 @@ +/* Tests that we can send messages to friends. + */ + +#include +#include +#include + +#include "auto_test_support.h" + +static void *proxy_routine(void *arg) +{ + const char *proxy_bin = (const char *)arg; + ck_assert(proxy_bin != nullptr); + printf("starting http/sock5 proxy: %s\n", proxy_bin); + ck_assert(system(proxy_bin) == 0); + return nullptr; +} + +static bool try_bootstrap(Tox *tox1, Tox *tox2, Tox *tox3, Tox *tox4) +{ + for (uint32_t i = 0; i < 400; ++i) { + if (tox_self_get_connection_status(tox1) != TOX_CONNECTION_NONE && + tox_self_get_connection_status(tox2) != TOX_CONNECTION_NONE && + tox_self_get_connection_status(tox3) != TOX_CONNECTION_NONE && + tox_self_get_connection_status(tox4) != TOX_CONNECTION_NONE) { + printf("%d %d %d %d\n", + tox_self_get_connection_status(tox1), + tox_self_get_connection_status(tox2), + tox_self_get_connection_status(tox3), + tox_self_get_connection_status(tox4)); + return true; + } + + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + tox_iterate(tox3, nullptr); + tox_iterate(tox4, nullptr); + + if (i % 10 == 0) { + printf("%d %d %d %d\n", + tox_self_get_connection_status(tox1), + tox_self_get_connection_status(tox2), + tox_self_get_connection_status(tox3), + tox_self_get_connection_status(tox4)); + } + + c_sleep(tox_iteration_interval(tox1)); + } + + return false; +} + +int main(int argc, char **argv) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + if (argc >= 3) { + char *proxy_bin = argv[2]; + pthread_t proxy_thread; + pthread_create(&proxy_thread, nullptr, proxy_routine, proxy_bin); + c_sleep(100); + } + + const uint16_t tcp_port = 8082; + uint32_t index[] = { 1, 2, 3, 4 }; + + struct Tox_Options *tox_options = tox_options_new(nullptr); + ck_assert(tox_options != nullptr); + + // tox1 is a TCP server and has UDP enabled. + tox_options_set_udp_enabled(tox_options, true); + tox_options_set_tcp_port(tox_options, tcp_port); + + Tox *tox1 = tox_new_log(tox_options, nullptr, &index[0]); + ck_assert(tox1 != nullptr); + + // Get tox1's DHT key and port. + uint8_t dht_pk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dht_pk); + uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); + ck_assert(dht_port != 0); + + // tox2 is a regular DHT node bootstrapping against tox1. + tox_options_set_udp_enabled(tox_options, true); + tox_options_set_tcp_port(tox_options, 0); + + Tox *tox2 = tox_new_log(tox_options, nullptr, &index[1]); + ck_assert(tox2 != nullptr); + + // tox2 bootstraps against tox1 with UDP. + ck_assert(tox_bootstrap(tox2, "127.0.0.1", dht_port, dht_pk, nullptr)); + + // tox3 has UDP disabled and connects to tox1 via an HTTP proxy + tox_options_set_udp_enabled(tox_options, false); + tox_options_set_proxy_host(tox_options, "127.0.0.1"); + tox_options_set_proxy_port(tox_options, 8080); + tox_options_set_proxy_type(tox_options, TOX_PROXY_TYPE_HTTP); + + Tox *tox3 = tox_new_log(tox_options, nullptr, &index[2]); + ck_assert(tox3 != nullptr); + + // tox4 has UDP disabled and connects to tox1 via a SOCKS5 proxy + tox_options_set_udp_enabled(tox_options, false); + tox_options_set_proxy_host(tox_options, "127.0.0.1"); + tox_options_set_proxy_port(tox_options, 8081); + tox_options_set_proxy_type(tox_options, TOX_PROXY_TYPE_SOCKS5); + + Tox *tox4 = tox_new_log(tox_options, nullptr, &index[3]); + ck_assert(tox4 != nullptr); + + // tox3 and tox4 bootstrap against tox1 and add it as a TCP relay + ck_assert(tox_bootstrap(tox3, "127.0.0.1", dht_port, dht_pk, nullptr)); + ck_assert(tox_add_tcp_relay(tox3, "127.0.0.1", tcp_port, dht_pk, nullptr)); + + ck_assert(tox_bootstrap(tox4, "127.0.0.1", dht_port, dht_pk, nullptr)); + ck_assert(tox_add_tcp_relay(tox4, "127.0.0.1", tcp_port, dht_pk, nullptr)); + + int ret = 1; + if (try_bootstrap(tox1, tox2, tox3, tox4)) { + ret = 0; + } + + tox_options_free(tox_options); + tox_kill(tox4); + tox_kill(tox3); + tox_kill(tox2); + tox_kill(tox1); + + return ret; +} diff --git a/auto_tests/reconnect_test.c b/auto_tests/reconnect_test.c new file mode 100644 index 0000000..253f66b --- /dev/null +++ b/auto_tests/reconnect_test.c @@ -0,0 +1,102 @@ +/* Auto Tests: Reconnection. + * + * This test checks that when a tox instance is suspended for long enough that + * its friend connections time out, those connections are promptly + * re-established when the instance is resumed. + */ + +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/friend_connection.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "check_compat.h" + +#define TOX_COUNT 2 +#define RECONNECT_TIME_MAX (FRIEND_CONNECTION_TIMEOUT + 3) + +#include "auto_test_support.h" + +static uint32_t tox_connected_count(uint32_t tox_count, AutoTox *autotoxes, uint32_t index) +{ + const size_t friend_count = tox_self_get_friend_list_size(autotoxes[index].tox); + uint32_t connected_count = 0; + + for (size_t j = 0; j < friend_count; j++) { + if (tox_friend_get_connection_status(autotoxes[index].tox, j, nullptr) != TOX_CONNECTION_NONE) { + ++connected_count; + } + } + + return connected_count; +} + +static bool all_disconnected_from(uint32_t tox_count, AutoTox *autotoxes, uint32_t index) +{ + for (uint32_t i = 0; i < tox_count; i++) { + if (i == index) { + continue; + } + + if (tox_connected_count(tox_count, autotoxes, i) >= tox_count - 1) { + return false; + } + } + + return true; +} + +static void test_reconnect(AutoTox *autotoxes) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + const time_t test_start_time = time(nullptr); + + printf("letting connections settle\n"); + + do { + iterate_all_wait(autotoxes, TOX_COUNT, ITERATION_INTERVAL); + } while (time(nullptr) - test_start_time < 2); + + const uint16_t disconnect = random_u16(rng) % TOX_COUNT; + printf("disconnecting #%u\n", autotoxes[disconnect].index); + + do { + for (uint16_t i = 0; i < TOX_COUNT; ++i) { + if (i != disconnect) { + tox_iterate(autotoxes[i].tox, &autotoxes[i]); + autotoxes[i].clock += 1000; + } + } + + c_sleep(20); + } while (!all_disconnected_from(TOX_COUNT, autotoxes, disconnect)); + + const uint64_t reconnect_start_time = autotoxes[0].clock; + + printf("reconnecting\n"); + + do { + iterate_all_wait(autotoxes, TOX_COUNT, ITERATION_INTERVAL); + } while (!all_friends_connected(autotoxes, TOX_COUNT)); + + const uint64_t reconnect_time = autotoxes[0].clock - reconnect_start_time; + ck_assert_msg(reconnect_time <= RECONNECT_TIME_MAX * 1000, "reconnection took %d seconds; expected at most %d seconds", + (int)(reconnect_time / 1000), RECONNECT_TIME_MAX); + + printf("test_reconnect succeeded, took %d seconds\n", (int)(time(nullptr) - test_start_time)); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + run_auto_test(nullptr, TOX_COUNT, test_reconnect, 0, &options); + + return 0; +} diff --git a/auto_tests/save_compatibility_test.c b/auto_tests/save_compatibility_test.c new file mode 100644 index 0000000..74d7b51 --- /dev/null +++ b/auto_tests/save_compatibility_test.c @@ -0,0 +1,161 @@ +// Tests to make sure new save code is compatible with old save files + +#include "../testing/misc_tools.h" +#include "../toxcore/tox.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#include +#include +#include + +#define LOADED_SAVE_FILE "../auto_tests/data/save.tox" + +// Information from the save file +#define EXPECTED_NAME "name" +#define EXPECTED_NAME_SIZE strlen(EXPECTED_NAME) +#define EXPECTED_STATUS_MESSAGE "Hello World" +#define EXPECTED_STATUS_MESSAGE_SIZE strlen(EXPECTED_STATUS_MESSAGE) +#define EXPECTED_NUM_FRIENDS 1 +#define EXPECTED_NOSPAM "4C762C7D" +#define EXPECTED_TOX_ID "B70E97D41F69B7F4C42A5BC7BD7A76B95B8030BE1B7C0E9E6FC19FC4ABEB195B4C762C7D800B" + +static size_t get_file_size(const char *save_path) +{ + FILE *const fp = fopen(save_path, "rb"); + + if (fp == nullptr) { + return 0; + } + + fseek(fp, 0, SEEK_END); + const size_t size = ftell(fp); + fclose(fp); + + return size; +} + +static uint8_t *read_save(const char *save_path, size_t *length) +{ + const size_t size = get_file_size(save_path); + + if (size == 0) { + return nullptr; + } + + FILE *const fp = fopen(save_path, "rb"); + + if (!fp) { + return nullptr; + } + + uint8_t *const data = (uint8_t *)malloc(size); + + if (!data) { + fclose(fp); + return nullptr; + } + + if (fread(data, size, 1, fp) != 1) { + free(data); + fclose(fp); + return nullptr; + } + + *length = size; + fclose(fp); + + return data; +} + +static void test_save_compatibility(const char *save_path) +{ + struct Tox_Options options = {0}; + tox_options_default(&options); + + size_t size = 0; + uint8_t *save_data = read_save(save_path, &size); + ck_assert_msg(save_data != nullptr, "error while reading save file '%s'", save_path); + + options.savedata_data = save_data; + options.savedata_length = size; + options.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE; + + size_t index = 0; + Tox_Err_New err; + Tox *tox = tox_new_log(&options, &err, &index); + ck_assert_msg(tox, "failed to create tox, error number: %d", err); + + free(save_data); + + const size_t name_size = tox_self_get_name_size(tox); + ck_assert_msg(name_size == EXPECTED_NAME_SIZE, "name sizes do not match expected %zu got %zu", EXPECTED_NAME_SIZE, + name_size); + + uint8_t *name = (uint8_t *)malloc(tox_self_get_name_size(tox)); + ck_assert(name != nullptr); + tox_self_get_name(tox, name); + ck_assert_msg(strncmp((const char *)name, EXPECTED_NAME, name_size) == 0, + "names do not match, expected %s got %s", EXPECTED_NAME, name); + + const size_t status_message_size = tox_self_get_status_message_size(tox); + ck_assert_msg(status_message_size == EXPECTED_STATUS_MESSAGE_SIZE, + "status message sizes do not match, expected %zu got %zu", EXPECTED_STATUS_MESSAGE_SIZE, status_message_size); + + uint8_t *status_message = (uint8_t *)malloc(tox_self_get_status_message_size(tox)); + ck_assert(status_message != nullptr); + tox_self_get_status_message(tox, status_message); + ck_assert_msg(strncmp((const char *)status_message, EXPECTED_STATUS_MESSAGE, status_message_size) == 0, + "status messages do not match, expected %s got %s", + EXPECTED_STATUS_MESSAGE, status_message); + + const size_t num_friends = tox_self_get_friend_list_size(tox); + ck_assert_msg(num_friends == EXPECTED_NUM_FRIENDS, + "number of friends do not match, expected %d got %zu", EXPECTED_NUM_FRIENDS, num_friends); + + const uint32_t nospam = tox_self_get_nospam(tox); + char nospam_str[TOX_NOSPAM_SIZE * 2 + 1]; + const size_t length = snprintf(nospam_str, sizeof(nospam_str), "%08X", nospam); + nospam_str[length] = '\0'; + ck_assert_msg(strcmp(nospam_str, EXPECTED_NOSPAM) == 0, + "nospam does not match, expected %s got %s", EXPECTED_NOSPAM, nospam_str); + + uint8_t tox_id[TOX_ADDRESS_SIZE]; + char tox_id_str[TOX_ADDRESS_SIZE * 2 + 1] = {0}; + tox_self_get_address(tox, tox_id); + to_hex(tox_id_str, tox_id, TOX_ADDRESS_SIZE); + ck_assert_msg(strncmp(tox_id_str, EXPECTED_TOX_ID, TOX_ADDRESS_SIZE * 2) == 0, + "tox ids do not match, expected %s got %s", EXPECTED_TOX_ID, tox_id_str); + + /* Giving the tox a chance to error on iterate due to corrupted loaded structures */ + tox_iterate(tox, nullptr); + + free(status_message); + free(name); + tox_kill(tox); +} + +int main(int argc, char *argv[]) +{ + char base_path[4096] = {0}; + + if (argc <= 1) { + const char *srcdir = getenv("srcdir"); + + if (srcdir == nullptr) { + srcdir = "."; + } + + snprintf(base_path, sizeof(base_path), "%s", srcdir); + } else { + snprintf(base_path, sizeof(base_path), "%s", argv[1]); + base_path[strrchr(base_path, '/') - base_path] = '\0'; + } + + char save_path[4096 + sizeof(LOADED_SAVE_FILE)]; + snprintf(save_path, sizeof(save_path), "%s/%s", base_path, LOADED_SAVE_FILE); + + test_save_compatibility(save_path); + + return 0; +} diff --git a/auto_tests/save_friend_test.c b/auto_tests/save_friend_test.c new file mode 100644 index 0000000..80a6ed0 --- /dev/null +++ b/auto_tests/save_friend_test.c @@ -0,0 +1,159 @@ +/* Auto Tests: Save and load friends. + */ + +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/crypto_core.h" +#include "../toxcore/tox.h" +#include "auto_test_support.h" +#include "check_compat.h" + +struct test_data { + uint8_t *name; + uint8_t *status_message; + bool received_name; + bool received_status_message; +}; + +static void set_random(Tox *m, const Random *rng, bool (*setter)(Tox *, const uint8_t *, size_t, Tox_Err_Set_Info *), size_t length) +{ + VLA(uint8_t, text, length); + uint32_t i; + + for (i = 0; i < length; ++i) { + text[i] = random_u08(rng); + } + + setter(m, text, SIZEOF_VLA(text), nullptr); +} + +static void alloc_string(uint8_t **to, size_t length) +{ + free(*to); + *to = (uint8_t *)malloc(length); + ck_assert(*to != nullptr); +} + +static void set_string(uint8_t **to, const uint8_t *from, size_t length) +{ + alloc_string(to, length); + memcpy(*to, from, length); +} + +static void namechange_callback(Tox *tox, uint32_t friend_number, const uint8_t *name, size_t length, void *user_data) +{ + struct test_data *to_compare = (struct test_data *)user_data; + set_string(&to_compare->name, name, length); + to_compare->received_name = true; +} + +static void statuschange_callback(Tox *tox, uint32_t friend_number, const uint8_t *message, size_t length, + void *user_data) +{ + struct test_data *to_compare = (struct test_data *)user_data; + set_string(&to_compare->status_message, message, length); + to_compare->received_status_message = true; +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Tox *const tox1 = tox_new_log(nullptr, nullptr, nullptr); + Tox *const tox2 = tox_new_log(nullptr, nullptr, nullptr); + + printf("bootstrapping tox2 off tox1\n"); + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); + + tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); + + struct test_data to_compare = {nullptr}; + + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(tox1, public_key); + tox_friend_add_norequest(tox2, public_key, nullptr); + tox_self_get_public_key(tox2, public_key); + tox_friend_add_norequest(tox1, public_key, nullptr); + + uint8_t *reference_name = (uint8_t *)malloc(tox_max_name_length()); + uint8_t *reference_status = (uint8_t *)malloc(tox_max_status_message_length()); + ck_assert(reference_name != nullptr); + ck_assert(reference_status != nullptr); + + const Random *rng = system_random(); + ck_assert(rng != nullptr); + set_random(tox1, rng, tox_self_set_name, tox_max_name_length()); + set_random(tox2, rng, tox_self_set_name, tox_max_name_length()); + set_random(tox1, rng, tox_self_set_status_message, tox_max_status_message_length()); + set_random(tox2, rng, tox_self_set_status_message, tox_max_status_message_length()); + + tox_self_get_name(tox2, reference_name); + tox_self_get_status_message(tox2, reference_status); + + tox_callback_friend_name(tox1, namechange_callback); + tox_callback_friend_status_message(tox1, statuschange_callback); + + while (true) { + if (tox_self_get_connection_status(tox1) && + tox_self_get_connection_status(tox2) && + tox_friend_get_connection_status(tox1, 0, nullptr) == TOX_CONNECTION_UDP) { + printf("Connected.\n"); + break; + } + + tox_iterate(tox1, &to_compare); + tox_iterate(tox2, nullptr); + + c_sleep(tox_iteration_interval(tox1)); + } + + while (true) { + if (to_compare.received_name && to_compare.received_status_message) { + printf("Exchanged names and status messages.\n"); + break; + } + + tox_iterate(tox1, &to_compare); + tox_iterate(tox2, nullptr); + + c_sleep(tox_iteration_interval(tox1)); + } + + size_t save_size = tox_get_savedata_size(tox1); + uint8_t *savedata = (uint8_t *)malloc(save_size); + tox_get_savedata(tox1, savedata); + + struct Tox_Options *const options = tox_options_new(nullptr); + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + tox_options_set_savedata_data(options, savedata, save_size); + + Tox *const tox_to_compare = tox_new_log(options, nullptr, nullptr); + + alloc_string(&to_compare.name, tox_friend_get_name_size(tox_to_compare, 0, nullptr)); + tox_friend_get_name(tox_to_compare, 0, to_compare.name, nullptr); + alloc_string(&to_compare.status_message, tox_friend_get_status_message_size(tox_to_compare, 0, nullptr)); + tox_friend_get_status_message(tox_to_compare, 0, to_compare.status_message, nullptr); + + ck_assert_msg(memcmp(reference_name, to_compare.name, tox_max_name_length()) == 0, + "incorrect name: should be all zeroes"); + ck_assert_msg(memcmp(reference_status, to_compare.status_message, tox_max_status_message_length()) == 0, + "incorrect status message: should be all zeroes"); + + tox_options_free(options); + tox_kill(tox1); + tox_kill(tox2); + tox_kill(tox_to_compare); + free(savedata); + free(to_compare.name); + free(to_compare.status_message); + free(reference_status); + free(reference_name); + + return 0; +} diff --git a/auto_tests/save_load_test.c b/auto_tests/save_load_test.c new file mode 100644 index 0000000..9707773 --- /dev/null +++ b/auto_tests/save_load_test.c @@ -0,0 +1,268 @@ +/* Tests that we can save and load Tox data. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/tox.h" +#include "../toxcore/tox_struct.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +/* The Travis-CI container responds poorly to ::1 as a localhost address + * You're encouraged to -D FORCE_TESTS_IPV6 on a local test */ +#ifdef TOX_LOCALHOST +#undef TOX_LOCALHOST +#endif +#ifdef FORCE_TESTS_IPV6 +#define TOX_LOCALHOST "::1" +#else +#define TOX_LOCALHOST "127.0.0.1" +#endif + +#ifdef TCP_RELAY_PORT +#undef TCP_RELAY_PORT +#endif +#define TCP_RELAY_PORT 33431 + +static void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +{ + if (length == 7 && memcmp("Gentoo", data, 7) == 0) { + tox_friend_add_norequest(m, public_key, nullptr); + } +} + +static unsigned int connected_t1; +static void tox_connection_status(Tox *tox, Tox_Connection connection_status, void *user_data) +{ + if (connected_t1 && !connection_status) { + ck_abort_msg("Tox went offline"); + } + + ck_assert_msg(connection_status != TOX_CONNECTION_NONE, "wrong status %d", connection_status); + + connected_t1 = connection_status; +} + +/* validate that: + * a) saving stays within the confined space + * b) a saved state can be loaded back successfully + * c) a second save is of equal size + * d) the second save is of equal content */ +static void reload_tox(Tox **tox, struct Tox_Options *const in_opts, void *user_data) +{ + const size_t extra = 64; + const size_t save_size1 = tox_get_savedata_size(*tox); + ck_assert_msg(save_size1 != 0, "save is invalid size %u", (unsigned)save_size1); + printf("%u\n", (unsigned)save_size1); + + uint8_t *buffer = (uint8_t *)malloc(save_size1 + 2 * extra); + ck_assert_msg(buffer != nullptr, "malloc failed"); + memset(buffer, 0xCD, extra); + memset(buffer + extra + save_size1, 0xCD, extra); + tox_get_savedata(*tox, buffer + extra); + tox_kill(*tox); + + for (size_t i = 0; i < extra; ++i) { + ck_assert_msg(buffer[i] == 0xCD, "Buffer underwritten from tox_get_savedata() @%u", (unsigned)i); + ck_assert_msg(buffer[extra + save_size1 + i] == 0xCD, "Buffer overwritten from tox_get_savedata() @%u", (unsigned)i); + } + + struct Tox_Options *const options = (in_opts == nullptr) ? tox_options_new(nullptr) : in_opts; + + tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_TOX_SAVE); + + tox_options_set_savedata_data(options, buffer + extra, save_size1); + + *tox = tox_new_log(options, nullptr, user_data); + + if (in_opts == nullptr) { + tox_options_free(options); + } + + ck_assert_msg(*tox != nullptr, "Failed to load back stored buffer"); + + const size_t save_size2 = tox_get_savedata_size(*tox); + + ck_assert_msg(save_size1 == save_size2, "Tox save data changed in size from a store/load cycle: %u -> %u", + (unsigned)save_size1, (unsigned)save_size2); + + uint8_t *buffer2 = (uint8_t *)malloc(save_size2); + + ck_assert_msg(buffer2 != nullptr, "malloc failed"); + + tox_get_savedata(*tox, buffer2); + + ck_assert_msg(!memcmp(buffer + extra, buffer2, save_size2), "Tox state changed by store/load/store cycle"); + + free(buffer2); + + free(buffer); +} + +typedef struct Time_Data { + pthread_mutex_t lock; + uint64_t clock; +} Time_Data; + +static uint64_t get_state_clock_callback(void *user_data) +{ + Time_Data *time_data = (Time_Data *)user_data; + pthread_mutex_lock(&time_data->lock); + uint64_t clock = time_data->clock; + pthread_mutex_unlock(&time_data->lock); + return clock; +} + +static void increment_clock(Time_Data *time_data, uint64_t count) +{ + pthread_mutex_lock(&time_data->lock); + time_data->clock += count; + pthread_mutex_unlock(&time_data->lock); +} + +static void set_current_time_callback(Tox *tox, Time_Data *time_data) +{ + Mono_Time *mono_time = tox->mono_time; + mono_time_set_current_time_callback(mono_time, get_state_clock_callback, time_data); +} + +static void test_few_clients(void) +{ + uint32_t index[] = { 1, 2, 3 }; + time_t con_time = 0, cur_time = time(nullptr); + + struct Tox_Options *opts1 = tox_options_new(nullptr); + tox_options_set_tcp_port(opts1, TCP_RELAY_PORT); + Tox_Err_New t_n_error; + Tox *tox1 = tox_new_log(opts1, &t_n_error, &index[0]); + ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "Failed to create tox instance: %d", t_n_error); + tox_options_free(opts1); + + struct Tox_Options *opts2 = tox_options_new(nullptr); + tox_options_set_udp_enabled(opts2, false); + tox_options_set_local_discovery_enabled(opts2, false); + Tox *tox2 = tox_new_log(opts2, &t_n_error, &index[1]); + ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "Failed to create tox instance: %d", t_n_error); + + struct Tox_Options *opts3 = tox_options_new(nullptr); + tox_options_set_local_discovery_enabled(opts3, false); + Tox *tox3 = tox_new_log(opts3, &t_n_error, &index[2]); + ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "Failed to create tox instance: %d", t_n_error); + + ck_assert_msg(tox1 && tox2 && tox3, "Failed to create 3 tox instances"); + + + Time_Data time_data; + ck_assert_msg(pthread_mutex_init(&time_data.lock, nullptr) == 0, "Failed to init time_data mutex"); + time_data.clock = current_time_monotonic(tox1->mono_time); + set_current_time_callback(tox1, &time_data); + set_current_time_callback(tox2, &time_data); + set_current_time_callback(tox3, &time_data); + + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); + + printf("using tox1 as tcp relay for tox2\n"); + tox_add_tcp_relay(tox2, TOX_LOCALHOST, TCP_RELAY_PORT, dht_key, nullptr); + + printf("bootstrapping toxes off tox1\n"); + tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); + tox_bootstrap(tox3, "localhost", dht_port, dht_key, nullptr); + + connected_t1 = 0; + tox_callback_self_connection_status(tox1, tox_connection_status); + tox_callback_friend_request(tox2, accept_friend_request); + uint8_t address[TOX_ADDRESS_SIZE]; + tox_self_get_address(tox2, address); + uint32_t test = tox_friend_add(tox3, address, (const uint8_t *)"Gentoo", 7, nullptr); + ck_assert_msg(test == 0, "Failed to add friend error code: %u", test); + + uint8_t off = 1; + + while (true) { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + tox_iterate(tox3, nullptr); + + if (tox_self_get_connection_status(tox1) && tox_self_get_connection_status(tox2) + && tox_self_get_connection_status(tox3)) { + if (off) { + printf("Toxes are online, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); + con_time = time(nullptr); + off = 0; + } + + if (tox_friend_get_connection_status(tox2, 0, nullptr) == TOX_CONNECTION_TCP + && tox_friend_get_connection_status(tox3, 0, nullptr) == TOX_CONNECTION_TCP) { + break; + } + } + + increment_clock(&time_data, 200); + c_sleep(5); + } + + ck_assert_msg(connected_t1, "Tox1 isn't connected. %u", connected_t1); + printf("tox clients connected took %lu seconds\n", (unsigned long)(time(nullptr) - con_time)); + + // We're done with this callback, so unset it to ensure we don't fail the + // test if tox1 goes offline while tox2 and 3 are reloaded. + tox_callback_self_connection_status(tox1, nullptr); + + reload_tox(&tox2, opts2, &index[1]); + + reload_tox(&tox3, opts3, &index[2]); + + cur_time = time(nullptr); + + off = 1; + + while (true) { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + tox_iterate(tox3, nullptr); + + if (tox_self_get_connection_status(tox1) && tox_self_get_connection_status(tox2) + && tox_self_get_connection_status(tox3)) { + if (off) { + printf("Toxes are online again after reloading, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); + con_time = time(nullptr); + off = 0; + } + + if (tox_friend_get_connection_status(tox2, 0, nullptr) == TOX_CONNECTION_TCP + && tox_friend_get_connection_status(tox3, 0, nullptr) == TOX_CONNECTION_TCP) { + break; + } + } + + increment_clock(&time_data, 100); + c_sleep(5); + } + + printf("tox clients connected took %lu seconds\n", (unsigned long)(time(nullptr) - con_time)); + + printf("test_few_clients succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); + + tox_kill(tox1); + tox_kill(tox2); + tox_kill(tox3); + + tox_options_free(opts2); + tox_options_free(opts3); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_few_clients(); + return 0; +} diff --git a/auto_tests/send_message_test.c b/auto_tests/send_message_test.c new file mode 100644 index 0000000..52470f3 --- /dev/null +++ b/auto_tests/send_message_test.c @@ -0,0 +1,79 @@ +/* Tests that we can send messages to friends. + */ + +#include +#include +#include + +typedef struct State { + bool message_received; +} State; + +#include "auto_test_support.h" + +#define MESSAGE_FILLER 'G' + +static void message_callback( + Tox *m, uint32_t friendnumber, Tox_Message_Type type, + const uint8_t *string, size_t length, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + + if (type != TOX_MESSAGE_TYPE_NORMAL) { + ck_abort_msg("Bad type"); + } + + const size_t cmp_msg_len = tox_max_message_length(); + uint8_t *cmp_msg = (uint8_t *)malloc(cmp_msg_len); + ck_assert(cmp_msg != nullptr); + memset(cmp_msg, MESSAGE_FILLER, cmp_msg_len); + + if (length == tox_max_message_length() && memcmp(string, cmp_msg, cmp_msg_len) == 0) { + state->message_received = true; + } + + free(cmp_msg); +} + +static void send_message_test(AutoTox *autotoxes) +{ + tox_callback_friend_message(autotoxes[1].tox, &message_callback); + + const size_t msgs_len = tox_max_message_length() + 1; + uint8_t *msgs = (uint8_t *)malloc(msgs_len); + memset(msgs, MESSAGE_FILLER, msgs_len); + + Tox_Err_Friend_Send_Message errm; + tox_friend_send_message(autotoxes[0].tox, 0, TOX_MESSAGE_TYPE_NORMAL, msgs, msgs_len, &errm); + ck_assert_msg(errm == TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG, "tox_max_message_length() is too small? error=%d", errm); + + tox_friend_send_message(autotoxes[0].tox, 0, TOX_MESSAGE_TYPE_NORMAL, msgs, tox_max_message_length(), &errm); + ck_assert_msg(errm == TOX_ERR_FRIEND_SEND_MESSAGE_OK, "tox_max_message_length() is too big? error=%d", errm); + + free(msgs); + + do { + iterate_all_wait(autotoxes, 2, ITERATION_INTERVAL); + } while (!((State *)autotoxes[1].state)->message_received); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + struct Tox_Options *tox_options = tox_options_new(nullptr); + ck_assert(tox_options != nullptr); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + tox_options_set_ipv6_enabled(tox_options, true); + run_auto_test(tox_options, 2, send_message_test, sizeof(State), &options); + + tox_options_set_ipv6_enabled(tox_options, false); + run_auto_test(tox_options, 2, send_message_test, sizeof(State), &options); + + tox_options_free(tox_options); + + return 0; +} diff --git a/auto_tests/set_name_test.c b/auto_tests/set_name_test.c new file mode 100644 index 0000000..9ad7946 --- /dev/null +++ b/auto_tests/set_name_test.c @@ -0,0 +1,100 @@ +/* Tests that we can set our name. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#define NICKNAME "Gentoo" + +static void nickchange_callback(Tox *tox, uint32_t friendnumber, const uint8_t *string, size_t length, void *userdata) +{ + ck_assert_msg(length == sizeof(NICKNAME), "Name length not correct: %d != %d", (uint16_t)length, + (uint16_t)sizeof(NICKNAME)); + ck_assert_msg(memcmp(string, NICKNAME, sizeof(NICKNAME)) == 0, "Name not correct: %s", (const char *)string); + bool *nickname_updated = (bool *)userdata; + *nickname_updated = true; +} + +static void test_set_name(void) +{ + printf("initialising 2 toxes\n"); + uint32_t index[] = { 1, 2 }; + const time_t cur_time = time(nullptr); + Tox *const tox1 = tox_new_log(nullptr, nullptr, &index[0]); + Tox *const tox2 = tox_new_log(nullptr, nullptr, &index[1]); + + ck_assert_msg(tox1 && tox2, "failed to create 2 tox instances"); + + printf("tox1 adds tox2 as friend, tox2 adds tox1\n"); + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(tox2, public_key); + tox_friend_add_norequest(tox1, public_key, nullptr); + tox_self_get_public_key(tox1, public_key); + tox_friend_add_norequest(tox2, public_key, nullptr); + + printf("bootstrapping tox2 off tox1\n"); + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); + + tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + c_sleep(ITERATION_INTERVAL); + } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || + tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE); + + printf("toxes are online, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); + const time_t con_time = time(nullptr); + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + c_sleep(ITERATION_INTERVAL); + } while (tox_friend_get_connection_status(tox1, 0, nullptr) != TOX_CONNECTION_UDP || + tox_friend_get_connection_status(tox2, 0, nullptr) != TOX_CONNECTION_UDP); + + printf("tox clients connected took %lu seconds\n", (unsigned long)(time(nullptr) - con_time)); + + tox_callback_friend_name(tox2, nickchange_callback); + Tox_Err_Set_Info err_n; + bool ret = tox_self_set_name(tox1, (const uint8_t *)NICKNAME, sizeof(NICKNAME), &err_n); + ck_assert_msg(ret && err_n == TOX_ERR_SET_INFO_OK, "tox_self_set_name failed because %d\n", err_n); + + bool nickname_updated = false; + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, &nickname_updated); + c_sleep(ITERATION_INTERVAL); + } while (!nickname_updated); + + ck_assert_msg(tox_friend_get_name_size(tox2, 0, nullptr) == sizeof(NICKNAME), "Name length not correct"); + uint8_t temp_name[sizeof(NICKNAME)]; + tox_friend_get_name(tox2, 0, temp_name, nullptr); + ck_assert_msg(memcmp(temp_name, NICKNAME, sizeof(NICKNAME)) == 0, "Name not correct"); + + printf("test_set_name succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); + + tox_kill(tox1); + tox_kill(tox2); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_set_name(); + return 0; +} diff --git a/auto_tests/set_status_message_test.c b/auto_tests/set_status_message_test.c new file mode 100644 index 0000000..435d223 --- /dev/null +++ b/auto_tests/set_status_message_test.c @@ -0,0 +1,105 @@ +/* Tests that we can set our status message + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#define STATUS_MESSAGE "Installing Gentoo" + +static void status_callback(Tox *tox, uint32_t friend_number, const uint8_t *message, size_t length, void *user_data) +{ + ck_assert_msg(length == sizeof(STATUS_MESSAGE) && + memcmp(message, STATUS_MESSAGE, sizeof(STATUS_MESSAGE)) == 0, + "incorrect data in status callback"); + bool *status_updated = (bool *)user_data; + *status_updated = true; +} + +static void test_set_status_message(void) +{ + printf("initialising 2 toxes\n"); + uint32_t index[] = { 1, 2 }; + const time_t cur_time = time(nullptr); + Tox *const tox1 = tox_new_log(nullptr, nullptr, &index[0]); + Tox *const tox2 = tox_new_log(nullptr, nullptr, &index[1]); + + ck_assert_msg(tox1 && tox2, "failed to create 2 tox instances"); + + printf("tox1 adds tox2 as friend, tox2 adds tox1\n"); + uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_public_key(tox2, public_key); + tox_friend_add_norequest(tox1, public_key, nullptr); + tox_self_get_public_key(tox1, public_key); + tox_friend_add_norequest(tox2, public_key, nullptr); + + printf("bootstrapping tox2 off tox1\n"); + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(tox1, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(tox1, nullptr); + + tox_bootstrap(tox2, "localhost", dht_port, dht_key, nullptr); + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + + c_sleep(ITERATION_INTERVAL); + } while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE || + tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE); + + printf("toxes are online, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); + const time_t con_time = time(nullptr); + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, nullptr); + + c_sleep(ITERATION_INTERVAL); + } while (tox_friend_get_connection_status(tox1, 0, nullptr) != TOX_CONNECTION_UDP || + tox_friend_get_connection_status(tox2, 0, nullptr) != TOX_CONNECTION_UDP); + + printf("tox clients connected took %lu seconds\n", (unsigned long)(time(nullptr) - con_time)); + + Tox_Err_Set_Info err_n; + tox_callback_friend_status_message(tox2, status_callback); + bool ret = tox_self_set_status_message(tox1, (const uint8_t *)STATUS_MESSAGE, sizeof(STATUS_MESSAGE), + &err_n); + ck_assert_msg(ret && err_n == TOX_ERR_SET_INFO_OK, "tox_self_set_status_message failed because %d\n", err_n); + + bool status_updated = false; + + do { + tox_iterate(tox1, nullptr); + tox_iterate(tox2, &status_updated); + c_sleep(ITERATION_INTERVAL); + } while (!status_updated); + + ck_assert_msg(tox_friend_get_status_message_size(tox2, 0, nullptr) == sizeof(STATUS_MESSAGE), + "status message length not correct"); + uint8_t cmp_status[sizeof(STATUS_MESSAGE)]; + tox_friend_get_status_message(tox2, 0, cmp_status, nullptr); + ck_assert_msg(memcmp(cmp_status, STATUS_MESSAGE, sizeof(STATUS_MESSAGE)) == 0, + "status message not correct"); + + printf("test_set_status_message succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); + + tox_kill(tox1); + tox_kill(tox2); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_set_status_message(); + return 0; +} diff --git a/auto_tests/tcp_relay_test.c b/auto_tests/tcp_relay_test.c new file mode 100644 index 0000000..b098f5c --- /dev/null +++ b/auto_tests/tcp_relay_test.c @@ -0,0 +1,37 @@ +#include + +#include "../testing/misc_tools.h" +#include "check_compat.h" + +#include "auto_test_support.h" + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + struct Tox_Options *opts = tox_options_new(nullptr); + tox_options_set_udp_enabled(opts, false); + Tox *tox_tcp = tox_new_log(opts, nullptr, nullptr); + tox_options_free(opts); + + bootstrap_tox_live_network(tox_tcp, true); + + printf("Waiting for connection"); + + do { + printf("."); + fflush(stdout); + + tox_iterate(tox_tcp, nullptr); + c_sleep(ITERATION_INTERVAL); + } while (tox_self_get_connection_status(tox_tcp) == TOX_CONNECTION_NONE); + + const Tox_Connection status = tox_self_get_connection_status(tox_tcp); + ck_assert_msg(status == TOX_CONNECTION_TCP, + "expected TCP connection, but got %d", status); + printf("Connection (TCP): %d\n", status); + + tox_kill(tox_tcp); + + return 0; +} diff --git a/auto_tests/tox_dispatch_test.c b/auto_tests/tox_dispatch_test.c new file mode 100644 index 0000000..3b2e991 --- /dev/null +++ b/auto_tests/tox_dispatch_test.c @@ -0,0 +1,164 @@ +/* Auto Tests: Many clients. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/tox.h" +#include "../toxcore/tox_dispatch.h" +#include "../toxcore/tox_events.h" +#include "auto_test_support.h" +#include "check_compat.h" + +// Set to true to produce an msgpack file at /tmp/test.mp. +static const bool want_dump_events = false; + +static void handle_events_friend_message(Tox *tox, const Tox_Event_Friend_Message *event, void *user_data) +{ + bool *success = (bool *)user_data; + + ck_assert(tox_event_friend_message_get_message_length(event) == sizeof("hello")); + const uint8_t *msg = tox_event_friend_message_get_message(event); + ck_assert_msg(memcmp(msg, "hello", sizeof("hello")) == 0, + "message was not expected 'hello' but '%s'", (const char *)msg); + + *success = true; +} + +static void dump_events(const char *path, const Tox_Events *events) +{ + if (want_dump_events) { + FILE *fh = fopen(path, "w"); + ck_assert(fh != nullptr); + const uint32_t len = tox_events_bytes_size(events); + uint8_t *buf = (uint8_t *)malloc(len); + ck_assert(buf != nullptr); + tox_events_get_bytes(events, buf); + fwrite(buf, 1, len, fh); + free(buf); + fclose(fh); + } +} + +static void print_events(Tox_Events *events) +{ + const uint32_t size = tox_events_bytes_size(events); + + uint8_t *bytes = (uint8_t *)malloc(size); + ck_assert(bytes != nullptr); + + tox_events_get_bytes(events, bytes); + + Tox_Events *events_copy = tox_events_load(bytes, size); + ck_assert(events_copy != nullptr); + free(bytes); + + ck_assert(tox_events_equal(events, events_copy)); + + tox_events_free(events_copy); + tox_events_free(events); +} + +static bool await_message(Tox **toxes, const Tox_Dispatch *dispatch) +{ + for (uint32_t i = 0; i < 100; ++i) { + // Ignore events on tox 1. + print_events(tox_events_iterate(toxes[0], false, nullptr)); + // Check if tox 2 got the message from tox 1. + Tox_Events *events = tox_events_iterate(toxes[1], false, nullptr); + + dump_events("/tmp/test.mp", events); + + bool success = false; + tox_dispatch_invoke(dispatch, events, toxes[1], &success); + print_events(events); + + if (success) { + return true; + } + + c_sleep(tox_iteration_interval(toxes[0])); + } + + return false; +} + +static void test_tox_events(void) +{ + uint8_t message[sizeof("hello")]; + memcpy(message, "hello", sizeof(message)); + + Tox *toxes[2]; + uint32_t index[2]; + + for (uint32_t i = 0; i < 2; ++i) { + index[i] = i + 1; + toxes[i] = tox_new_log(nullptr, nullptr, &index[i]); + tox_events_init(toxes[i]); + ck_assert_msg(toxes[i] != nullptr, "failed to create tox instances %u", i); + } + + Tox_Err_Dispatch_New err_new; + Tox_Dispatch *dispatch = tox_dispatch_new(&err_new); + ck_assert_msg(dispatch != nullptr, "failed to create event dispatcher"); + ck_assert(err_new == TOX_ERR_DISPATCH_NEW_OK); + + tox_events_callback_friend_message(dispatch, handle_events_friend_message); + + uint8_t pk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(toxes[0], pk); + tox_bootstrap(toxes[1], "localhost", tox_self_get_udp_port(toxes[0], nullptr), pk, nullptr); + + tox_self_get_public_key(toxes[0], pk); + tox_friend_add_norequest(toxes[1], pk, nullptr); + + tox_self_get_public_key(toxes[1], pk); + tox_friend_add_norequest(toxes[0], pk, nullptr); + + printf("bootstrapping and connecting 2 toxes\n"); + + while (tox_self_get_connection_status(toxes[0]) == TOX_CONNECTION_NONE || + tox_self_get_connection_status(toxes[1]) == TOX_CONNECTION_NONE) { + // Ignore connection events for now. + print_events(tox_events_iterate(toxes[0], false, nullptr)); + print_events(tox_events_iterate(toxes[1], false, nullptr)); + + c_sleep(tox_iteration_interval(toxes[0])); + } + + printf("toxes online, waiting for friend connection\n"); + + while (tox_friend_get_connection_status(toxes[0], 0, nullptr) == TOX_CONNECTION_NONE || + tox_friend_get_connection_status(toxes[1], 0, nullptr) == TOX_CONNECTION_NONE) { + // Ignore connection events for now. + print_events(tox_events_iterate(toxes[0], false, nullptr)); + print_events(tox_events_iterate(toxes[1], false, nullptr)); + + c_sleep(tox_iteration_interval(toxes[0])); + } + + printf("friends are connected via %s, now sending message\n", + tox_friend_get_connection_status(toxes[0], 0, nullptr) == TOX_CONNECTION_TCP ? "TCP" : "UDP"); + + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(toxes[0], 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof(message), &err); + ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); + + ck_assert(await_message(toxes, dispatch)); + + tox_dispatch_free(dispatch); + + for (uint32_t i = 0; i < 2; ++i) { + tox_kill(toxes[i]); + } +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + test_tox_events(); + return 0; +} diff --git a/auto_tests/tox_events_test.c b/auto_tests/tox_events_test.c new file mode 100644 index 0000000..04549b2 --- /dev/null +++ b/auto_tests/tox_events_test.c @@ -0,0 +1,123 @@ +/* Auto Tests: Many clients. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/tox.h" +#include "../toxcore/tox_events.h" +#include "../toxcore/tox_struct.h" +#include "auto_test_support.h" +#include "check_compat.h" + +static bool await_message(Tox **toxes) +{ + for (uint32_t i = 0; i < 100; ++i) { + // Ignore events on tox 1. + tox_events_free(tox_events_iterate(toxes[0], false, nullptr)); + // Check if tox 2 got the message from tox 1. + Tox_Events *events = tox_events_iterate(toxes[1], false, nullptr); + + if (events != nullptr) { + ck_assert(tox_events_get_friend_message_size(events) == 1); + const Tox_Event_Friend_Message *msg_event = tox_events_get_friend_message(events, 0); + ck_assert(tox_event_friend_message_get_message_length(msg_event) == sizeof("hello")); + const uint8_t *msg = tox_event_friend_message_get_message(msg_event); + ck_assert_msg(memcmp(msg, "hello", sizeof("hello")) == 0, + "message was not expected 'hello' but '%s'", (const char *)msg); + tox_events_free(events); + return true; + } + + c_sleep(tox_iteration_interval(toxes[0])); + } + + return false; +} + +static uint64_t get_state_clock_callback(void *user_data) +{ + const uint64_t *clock = (const uint64_t *)user_data; + return *clock; +} + +static void test_tox_events(void) +{ + uint8_t message[sizeof("hello")]; + memcpy(message, "hello", sizeof(message)); + + Tox *toxes[2]; + uint32_t index[2]; + + for (uint32_t i = 0; i < 2; ++i) { + index[i] = i + 1; + toxes[i] = tox_new_log(nullptr, nullptr, &index[i]); + tox_events_init(toxes[i]); + ck_assert_msg(toxes[i] != nullptr, "failed to create tox instances %u", i); + } + + uint64_t clock = current_time_monotonic(toxes[0]->mono_time); + Mono_Time *mono_time; + + mono_time = toxes[0]->mono_time; + mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &clock); + mono_time = toxes[1]->mono_time; + mono_time_set_current_time_callback(mono_time, get_state_clock_callback, &clock); + + uint8_t pk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(toxes[0], pk); + tox_bootstrap(toxes[1], "localhost", tox_self_get_udp_port(toxes[0], nullptr), pk, nullptr); + + tox_self_get_public_key(toxes[0], pk); + tox_friend_add_norequest(toxes[1], pk, nullptr); + + tox_self_get_public_key(toxes[1], pk); + tox_friend_add_norequest(toxes[0], pk, nullptr); + + printf("bootstrapping and connecting 2 toxes\n"); + + while (tox_self_get_connection_status(toxes[0]) == TOX_CONNECTION_NONE || + tox_self_get_connection_status(toxes[1]) == TOX_CONNECTION_NONE) { + // Ignore connection events for now. + tox_events_free(tox_events_iterate(toxes[0], false, nullptr)); + tox_events_free(tox_events_iterate(toxes[1], false, nullptr)); + + clock += 100; + c_sleep(5); + } + + printf("toxes online, waiting for friend connection\n"); + + while (tox_friend_get_connection_status(toxes[0], 0, nullptr) == TOX_CONNECTION_NONE || + tox_friend_get_connection_status(toxes[1], 0, nullptr) == TOX_CONNECTION_NONE) { + // Ignore connection events for now. + tox_events_free(tox_events_iterate(toxes[0], false, nullptr)); + tox_events_free(tox_events_iterate(toxes[1], false, nullptr)); + + clock += 100; + c_sleep(5); + } + + printf("friends are connected via %s, now sending message\n", + tox_friend_get_connection_status(toxes[0], 0, nullptr) == TOX_CONNECTION_TCP ? "TCP" : "UDP"); + + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(toxes[0], 0, TOX_MESSAGE_TYPE_NORMAL, message, sizeof(message), &err); + ck_assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); + + ck_assert(await_message(toxes)); + + for (uint32_t i = 0; i < 2; ++i) { + tox_kill(toxes[i]); + } +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + test_tox_events(); + return 0; +} diff --git a/auto_tests/tox_many_tcp_test.c b/auto_tests/tox_many_tcp_test.c new file mode 100644 index 0000000..f466e3e --- /dev/null +++ b/auto_tests/tox_many_tcp_test.c @@ -0,0 +1,263 @@ +/* Auto Tests: Many TCP. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/crypto_core.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +/* The Travis-CI container responds poorly to ::1 as a localhost address + * You're encouraged to -D FORCE_TESTS_IPV6 on a local test */ +#ifdef FORCE_TESTS_IPV6 +#define TOX_LOCALHOST "::1" +#else +#define TOX_LOCALHOST "127.0.0.1" +#endif + +static bool enable_broken_tests = false; + +static void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) { + return; + } + + if (length == 7 && memcmp("Gentoo", data, 7) == 0) { + tox_friend_add_norequest(m, public_key, nullptr); + } +} + +#define NUM_FRIENDS 50 +#define NUM_TOXES_TCP 40 + +static uint16_t tcp_relay_port = 33448; + +static void test_many_clients_tcp(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + long long unsigned int cur_time = time(nullptr); + Tox *toxes[NUM_TOXES_TCP]; + uint32_t index[NUM_TOXES_TCP]; + uint32_t i, j; + uint32_t to_comp = 974536; + + for (i = 0; i < NUM_TOXES_TCP; ++i) { + struct Tox_Options *opts = tox_options_new(nullptr); + + if (i == 0) { + tox_options_set_tcp_port(opts, tcp_relay_port); + } else { + tox_options_set_udp_enabled(opts, false); + } + + index[i] = i + 1; + Tox_Err_New err; + toxes[i] = tox_new_log(opts, &err, &index[i]); + if (i == 0 && err == TOX_ERR_NEW_PORT_ALLOC) { + ck_assert(toxes[i] == nullptr); + --i; + ++tcp_relay_port; + tox_options_free(opts); + continue; + } + ck_assert_msg(toxes[i] != nullptr, "Failed to create tox instances %u", i); + tox_callback_friend_request(toxes[i], accept_friend_request); + uint8_t dpk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(toxes[0], dpk); + Tox_Err_Bootstrap error = TOX_ERR_BOOTSTRAP_OK; + ck_assert_msg(tox_add_tcp_relay(toxes[i], TOX_LOCALHOST, tcp_relay_port, dpk, &error), "add relay error, %u, %d", i, + error); + uint16_t first_port = tox_self_get_udp_port(toxes[0], nullptr); + ck_assert_msg(tox_bootstrap(toxes[i], TOX_LOCALHOST, first_port, dpk, nullptr), "Bootstrap error"); + + tox_options_free(opts); + } + + struct { + uint16_t tox1; + uint16_t tox2; + } pairs[NUM_FRIENDS]; + + uint8_t address[TOX_ADDRESS_SIZE]; + + for (i = 0; i < NUM_FRIENDS; ++i) { +loop_top: + pairs[i].tox1 = random_u32(rng) % NUM_TOXES_TCP; + pairs[i].tox2 = (pairs[i].tox1 + random_u32(rng) % (NUM_TOXES_TCP - 1) + 1) % NUM_TOXES_TCP; + + for (j = 0; j < i; ++j) { + if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2) { + goto loop_top; + } + } + + tox_self_get_address(toxes[pairs[i].tox1], address); + + Tox_Err_Friend_Add test; + uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (const uint8_t *)"Gentoo", 7, &test); + + if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) { + goto loop_top; + } + + ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend error code: %i", test); + } + + while (true) { + uint16_t counter = 0; + + for (i = 0; i < NUM_TOXES_TCP; ++i) { + for (j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j) { + if (tox_friend_get_connection_status(toxes[i], j, nullptr) == TOX_CONNECTION_TCP) { + ++counter; + } + } + } + + if (counter == NUM_FRIENDS * 2) { + break; + } + + for (i = 0; i < NUM_TOXES_TCP; ++i) { + tox_iterate(toxes[i], &to_comp); + } + + c_sleep(50); + } + + for (i = 0; i < NUM_TOXES_TCP; ++i) { + tox_kill(toxes[i]); + } + + printf("test_many_clients_tcp succeeded, took %llu seconds\n", time(nullptr) - cur_time); +} + +#define NUM_TCP_RELAYS 3 + +static void test_many_clients_tcp_b(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + long long unsigned int cur_time = time(nullptr); + Tox *toxes[NUM_TOXES_TCP]; + uint32_t index[NUM_TOXES_TCP]; + uint32_t i, j; + uint32_t to_comp = 974536; + + for (i = 0; i < NUM_TOXES_TCP; ++i) { + struct Tox_Options *opts = tox_options_new(nullptr); + + if (i < NUM_TCP_RELAYS) { + tox_options_set_tcp_port(opts, tcp_relay_port + i); + } else { + tox_options_set_udp_enabled(opts, 0); + } + + index[i] = i + 1; + toxes[i] = tox_new_log(opts, nullptr, &index[i]); + ck_assert_msg(toxes[i] != nullptr, "Failed to create tox instances %u", i); + tox_callback_friend_request(toxes[i], accept_friend_request); + uint8_t dpk[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(toxes[(i % NUM_TCP_RELAYS)], dpk); + ck_assert_msg(tox_add_tcp_relay(toxes[i], TOX_LOCALHOST, tcp_relay_port + (i % NUM_TCP_RELAYS), dpk, nullptr), + "add relay error"); + tox_self_get_dht_id(toxes[0], dpk); + uint16_t first_port = tox_self_get_udp_port(toxes[0], nullptr); + ck_assert_msg(tox_bootstrap(toxes[i], TOX_LOCALHOST, first_port, dpk, nullptr), "Bootstrap error"); + + tox_options_free(opts); + } + + struct { + uint16_t tox1; + uint16_t tox2; + } pairs[NUM_FRIENDS]; + + uint8_t address[TOX_ADDRESS_SIZE]; + + for (i = 0; i < NUM_FRIENDS; ++i) { +loop_top: + pairs[i].tox1 = random_u32(rng) % NUM_TOXES_TCP; + pairs[i].tox2 = (pairs[i].tox1 + random_u32(rng) % (NUM_TOXES_TCP - 1) + 1) % NUM_TOXES_TCP; + + for (j = 0; j < i; ++j) { + if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2) { + goto loop_top; + } + } + + tox_self_get_address(toxes[pairs[i].tox1], address); + + Tox_Err_Friend_Add test; + uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (const uint8_t *)"Gentoo", 7, &test); + + if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) { + goto loop_top; + } + + ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend error code: %i", test); + } + + uint16_t last_count = 0; + + while (true) { + uint16_t counter = 0; + + for (i = 0; i < NUM_TOXES_TCP; ++i) { + for (j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j) { + if (tox_friend_get_connection_status(toxes[i], j, nullptr) == TOX_CONNECTION_TCP) { + ++counter; + } + } + } + + if (counter != last_count) { + printf("many_clients_tcp_b got to %u\n", counter); + last_count = counter; + } + + if (counter == NUM_FRIENDS * 2) { + break; + } + + for (i = 0; i < NUM_TOXES_TCP; ++i) { + tox_iterate(toxes[i], &to_comp); + } + + c_sleep(30); + } + + for (i = 0; i < NUM_TOXES_TCP; ++i) { + tox_kill(toxes[i]); + } + + printf("test_many_clients_tcp_b succeeded, took %llu seconds\n", time(nullptr) - cur_time); +} + + +static void tox_suite(void) +{ + /* Each tox connects to a single tox TCP */ + test_many_clients_tcp(); + + if (enable_broken_tests) { + /* Try to make a connection to each "older sibling" tox instance via TCP */ + /* Currently this test intermittently fails for unknown reasons. */ + test_many_clients_tcp_b(); + } +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + tox_suite(); + return 0; +} diff --git a/auto_tests/tox_many_test.c b/auto_tests/tox_many_test.c new file mode 100644 index 0000000..8501b5c --- /dev/null +++ b/auto_tests/tox_many_test.c @@ -0,0 +1,134 @@ +/* Auto Tests: Many clients. + */ + +#include +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/crypto_core.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +static void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) +{ + if (length == 7 && memcmp("Gentoo", data, 7) == 0) { + tox_friend_add_norequest(m, public_key, nullptr); + } +} + + +#define TCP_TEST_NUM_TOXES 90 +#define TCP_TEST_NUM_FRIENDS 50 + +static void test_many_clients(void) +{ + const Random *rng = system_random(); + ck_assert(rng != nullptr); + time_t cur_time = time(nullptr); + Tox *toxes[TCP_TEST_NUM_TOXES]; + uint32_t index[TCP_TEST_NUM_TOXES]; + + for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { + index[i] = i + 1; + toxes[i] = tox_new_log(nullptr, nullptr, &index[i]); + ck_assert_msg(toxes[i] != nullptr, "failed to create tox instances %u", i); + tox_callback_friend_request(toxes[i], accept_friend_request); + } + + struct { + uint16_t tox1; + uint16_t tox2; + } pairs[TCP_TEST_NUM_FRIENDS]; + + uint8_t address[TOX_ADDRESS_SIZE]; + + uint32_t num_f = 0; + + for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { + num_f += tox_self_get_friend_list_size(toxes[i]); + } + + ck_assert_msg(num_f == 0, "bad num friends: %u", num_f); + + for (uint32_t i = 0; i < TCP_TEST_NUM_FRIENDS; ++i) { +loop_top: + pairs[i].tox1 = random_u32(rng) % TCP_TEST_NUM_TOXES; + pairs[i].tox2 = (pairs[i].tox1 + random_u32(rng) % (TCP_TEST_NUM_TOXES - 1) + 1) % TCP_TEST_NUM_TOXES; + + for (uint32_t j = 0; j < i; ++j) { + if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2) { + goto loop_top; + } + } + + tox_self_get_address(toxes[pairs[i].tox1], address); + + Tox_Err_Friend_Add test; + uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (const uint8_t *)"Gentoo", 7, &test); + + if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) { + goto loop_top; + } + + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(toxes[pairs[i].tox1], dht_key); + const uint16_t dht_port = tox_self_get_udp_port(toxes[pairs[i].tox1], nullptr); + + tox_bootstrap(toxes[pairs[i].tox2], "localhost", dht_port, dht_key, nullptr); + + ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "failed to add friend error code: %i", test); + } + + for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { + num_f += tox_self_get_friend_list_size(toxes[i]); + } + + ck_assert_msg(num_f == TCP_TEST_NUM_FRIENDS, "bad num friends: %u", num_f); + + uint16_t last_count = 0; + + while (true) { + uint16_t counter = 0; + + for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { + for (uint32_t j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j) { + if (tox_friend_get_connection_status(toxes[i], j, nullptr) == TOX_CONNECTION_UDP) { + ++counter; + } + } + } + + if (counter != last_count) { + printf("many_clients got to %u\n", counter); + last_count = counter; + } + + if (counter == TCP_TEST_NUM_FRIENDS * 2) { + break; + } + + for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { + tox_iterate(toxes[i], nullptr); + } + + c_sleep(50); + } + + for (uint32_t i = 0; i < TCP_TEST_NUM_TOXES; ++i) { + tox_kill(toxes[i]); + } + + printf("test_many_clients succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_many_clients(); + return 0; +} diff --git a/auto_tests/tox_strncasecmp_test.c b/auto_tests/tox_strncasecmp_test.c new file mode 100644 index 0000000..6060c75 --- /dev/null +++ b/auto_tests/tox_strncasecmp_test.c @@ -0,0 +1,165 @@ +#include +#include + +#include "../testing/misc_tools.h" +#include "check_compat.h" + +typedef enum { + NEGATIVE, + ZERO, + POSITIVE +} Comparison; + +static const char *Comparison_Str[] = { "NEGATIVE", "ZERO", "POSITIVE" }; + +static void verify(const char *s1, const char *s2, size_t n, Comparison expected) +{ + int r = tox_strncasecmp(s1, s2, n); + Comparison actual = r < 0 ? NEGATIVE : r == 0 ? ZERO : POSITIVE; + + ck_assert_msg(actual == expected, + "tox_strncasecmp(\"%s\", \"%s\", %u) == %s, but expected %s.", + s1, s2, (unsigned)n, Comparison_Str[actual], Comparison_Str[expected]); +} + +static void test_general(void) +{ + // empty strings are equal + verify("", "", 100, ZERO); + verify("", "", -1, ZERO); + + // ====== Same Case Test Cases ====== + + // equal strings with n=0 are equal + verify("", "", 0, ZERO); + verify("AAA", "AAA", 0, ZERO); + + // unequal strings with n=0 are equal + verify("A", "B", 0, ZERO); + verify("AAA", "BBB", 0, ZERO); + verify("AAA", "BBBBBB", 0, ZERO); + verify("AAAAAA", "BBB", 0, ZERO); + + // equal strings are equal + verify("AAA", "AAA", 0, ZERO); + verify("AAA", "AAA", 1, ZERO); + verify("AAA", "AAA", 2, ZERO); + verify("AAA", "AAA", 3, ZERO); + verify("AAA", "AAA", 4, ZERO); + verify("AAA", "AAA", 5, ZERO); + verify("AAA", "AAA", -1, ZERO); + + verify("AAA", "AAAAAA", 0, ZERO); + verify("AAA", "AAAAAA", 1, ZERO); + verify("AAA", "AAAAAA", 2, ZERO); + verify("AAA", "AAAAAA", 3, ZERO); + verify("AAA", "AAAAAA", 4, NEGATIVE); + verify("AAA", "AAAAAA", 5, NEGATIVE); + verify("AAA", "AAAAAA", -1, NEGATIVE); + + verify("AAAAAA", "AAA", 0, ZERO); + verify("AAAAAA", "AAA", 1, ZERO); + verify("AAAAAA", "AAA", 2, ZERO); + verify("AAAAAA", "AAA", 3, ZERO); + verify("AAAAAA", "AAA", 4, POSITIVE); + verify("AAAAAA", "AAA", 5, POSITIVE); + verify("AAAAAA", "AAA", -1, POSITIVE); + + verify("I'm eating wafers and drinking tea.", "I'm eating wafers and drinking tea.", -1, ZERO); + + // unequal strings are equal only up to n + verify("AAAB", "AAAA", 0, ZERO); + verify("AAAB", "AAAA", 1, ZERO); + verify("AAAB", "AAAA", 2, ZERO); + verify("AAAB", "AAAA", 3, ZERO); + verify("AAAB", "AAAA", 4, POSITIVE); + verify("AAAB", "AAAA", 5, POSITIVE); + verify("AAAB", "AAAA", -1, POSITIVE); + + verify("AAAA", "AAAB", 0, ZERO); + verify("AAAA", "AAAB", 1, ZERO); + verify("AAAA", "AAAB", 2, ZERO); + verify("AAAA", "AAAB", 3, ZERO); + verify("AAAA", "AAAB", 4, NEGATIVE); + verify("AAAA", "AAAB", 5, NEGATIVE); + verify("AAAA", "AAAB", -1, NEGATIVE); + + verify("The wafers are salty.", "The wafers are sweet.", 16, ZERO); + verify("The wafers are salty.", "The wafers are sweet.", 17, NEGATIVE); + verify("The wafers are salty.", "The wafers are sweet.", -1, NEGATIVE); + + // the comparison should stop at first mismatch + verify("AAABA", "AAAAB", -1, POSITIVE); + verify("AAAAB", "AAABA", -1, NEGATIVE); + + // ====== Different Case Test Cases ====== + + // equal strings with n=0 are equal + verify("", "", 0, ZERO); + verify("aaa", "AAA", 0, ZERO); + + // unequal strings with n=0 are equal + verify("a", "B", 0, ZERO); + verify("aaa", "BBB", 0, ZERO); + verify("aaa", "BBBBBB", 0, ZERO); + verify("aaaaaa", "BBB", 0, ZERO); + + // equal strings are equal + verify("aaa", "AAA", 0, ZERO); + verify("AAA", "aaa", 1, ZERO); + verify("aaa", "AAA", 2, ZERO); + verify("aaa", "AAA", 3, ZERO); + verify("AAA", "aaa", 4, ZERO); + verify("AAA", "aaa", 5, ZERO); + verify("AAA", "aaa", -1, ZERO); + + verify("aaa", "AAAAAA", 0, ZERO); + verify("AAA", "AAAaaa", 1, ZERO); + verify("aaA", "aaaAAA", 2, ZERO); + verify("AaA", "aAAAAA", 3, ZERO); + verify("AAA", "AAAAAA", 4, NEGATIVE); + verify("Aaa", "AAaaAA", 5, NEGATIVE); + verify("AAA", "AAAAAa", -1, NEGATIVE); + + verify("AAAAAA", "aaa", 0, ZERO); + verify("AAAaaa", "AAA", 1, ZERO); + verify("aaaAAA", "aaA", 2, ZERO); + verify("aAAAAA", "AaA", 3, ZERO); + verify("AAAAAA", "AAA", 4, POSITIVE); + verify("AAaaAA", "Aaa", 5, POSITIVE); + verify("AAAAAa", "AAA", -1, POSITIVE); + + verify("I'm Eating Wafers And Drinking Tea.", "I'm eating wafers and drinking tea.", -1, ZERO); + + // unequal strings are equal only up to n + verify("aaaB", "AAAA", 0, ZERO); + verify("AaAB", "aAAA", 1, ZERO); + verify("aAAB", "AaAA", 2, ZERO); + verify("AAAB", "AAaA", 3, ZERO); + verify("AAAB", "AAAA", 4, POSITIVE); + verify("AAAb", "AAAA", 5, POSITIVE); + verify("AAAB", "AAAa", -1, POSITIVE); + + verify("AAAA", "aaaB", 0, ZERO); + verify("aAAA", "AaAB", 1, ZERO); + verify("AaAA", "aAAB", 2, ZERO); + verify("AAaA", "AAAB", 3, ZERO); + verify("AAAA", "AAAB", 4, NEGATIVE); + verify("AAAA", "AAAb", 5, NEGATIVE); + verify("AAAa", "AAAB", -1, NEGATIVE); + + verify("The Wafers Are Salty.", "The wafers are sweet.", 16, ZERO); + verify("The Wafers Are Salty.", "The wafers are sweet.", 17, NEGATIVE); + verify("The Wafers Are Salty.", "The wafers are sweet.", -1, NEGATIVE); + + // the comparison should stop at first mismatch + verify("aAaBA", "AAAAb", -1, POSITIVE); + verify("AAAAb", "aAaBA", -1, NEGATIVE); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + test_general(); + return 0; +} diff --git a/auto_tests/toxav_basic_test.c b/auto_tests/toxav_basic_test.c new file mode 100644 index 0000000..241bf5c --- /dev/null +++ b/auto_tests/toxav_basic_test.c @@ -0,0 +1,520 @@ +#include +#include +#include +#include + +#include + +#include "../testing/misc_tools.h" +#include "../toxav/toxav.h" +#include "../toxcore/crypto_core.h" +#include "../toxcore/logger.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +#define TEST_REGULAR_AV 1 +#define TEST_REGULAR_A 1 +#define TEST_REGULAR_V 1 +#define TEST_REJECT 1 +#define TEST_CANCEL 1 +#define TEST_MUTE_UNMUTE 1 +#define TEST_STOP_RESUME_PAYLOAD 1 +#define TEST_PAUSE_RESUME_SEND 1 + + +#define ck_assert_call_control(a, b, c) do { \ + Toxav_Err_Call_Control cc_err; \ + bool ok = toxav_call_control(a, b, c, &cc_err); \ + if (!ok) { \ + printf("toxav_call_control returned error %d\n", cc_err); \ + } \ + ck_assert(ok); \ + ck_assert(cc_err == TOXAV_ERR_CALL_CONTROL_OK); \ +} while (0) + + +typedef struct { + bool incoming; + uint32_t state; +} CallControl; + +static void clear_call_control(CallControl *cc) +{ + const CallControl empty = {0}; + *cc = empty; +} + + +/** + * Callbacks + */ +static void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) +{ + printf("Handling CALL callback\n"); + ((CallControl *)user_data)->incoming = true; +} + +static void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) +{ + printf("Handling CALL STATE callback: %u\n", state); + ((CallControl *)user_data)->state = state; +} + +static void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number, + uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, + void *user_data) +{ + printf("Received video payload\n"); +} + +static void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, + int16_t const *pcm, + size_t sample_count, + uint8_t channels, + uint32_t sampling_rate, + void *user_data) +{ + printf("Received audio payload\n"); +} + +static void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, + void *userdata) +{ + if (length == 7 && memcmp("gentoo", data, 7) == 0) { + ck_assert(tox_friend_add_norequest(m, public_key, nullptr) != (uint32_t) -1); + } +} + + +/** + * Iterate helper + */ +static void iterate_tox(Tox *bootstrap, Tox *Alice, Tox *Bob) +{ + c_sleep(100); + tox_iterate(bootstrap, nullptr); + tox_iterate(Alice, nullptr); + tox_iterate(Bob, nullptr); +} + +static bool toxav_audio_send_frame_helper(ToxAV *av, uint32_t friend_number, Toxav_Err_Send_Frame *error) +{ + static const int16_t PCM[960] = {0}; + return toxav_audio_send_frame(av, 0, PCM, 960, 1, 48000, nullptr); +} + +static void regular_call_flow( + Tox *Alice, Tox *Bob, Tox *bootstrap, + ToxAV *AliceAV, ToxAV *BobAV, + CallControl *AliceCC, CallControl *BobCC, + int a_br, int v_br) +{ + clear_call_control(AliceCC); + clear_call_control(BobCC); + + Toxav_Err_Call call_err; + toxav_call(AliceAV, 0, a_br, v_br, &call_err); + + ck_assert_msg(call_err == TOXAV_ERR_CALL_OK, "toxav_call failed: %d\n", call_err); + + const time_t start_time = time(nullptr); + + do { + if (BobCC->incoming) { + Toxav_Err_Answer answer_err; + toxav_answer(BobAV, 0, a_br, v_br, &answer_err); + + ck_assert_msg(answer_err == TOXAV_ERR_ANSWER_OK, "toxav_answer failed: %d\n", answer_err); + + BobCC->incoming = false; + } else { /* TODO(mannol): rtp */ + if (time(nullptr) - start_time >= 1) { + + Toxav_Err_Call_Control cc_err; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &cc_err); + + ck_assert_msg(cc_err == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %d\n", cc_err); + } + } + + iterate_tox(bootstrap, Alice, Bob); + } while (BobCC->state != TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); +} + +static void test_av_flows(void) +{ + Tox *Alice, *Bob, *bootstrap; + ToxAV *AliceAV, *BobAV; + uint32_t index[] = { 1, 2, 3 }; + + CallControl AliceCC, BobCC; + + { + Tox_Err_New error; + + bootstrap = tox_new_log(nullptr, &error, &index[0]); + ck_assert(error == TOX_ERR_NEW_OK); + + Alice = tox_new_log(nullptr, &error, &index[1]); + ck_assert(error == TOX_ERR_NEW_OK); + + Bob = tox_new_log(nullptr, &error, &index[2]); + ck_assert(error == TOX_ERR_NEW_OK); + } + + printf("Created 3 instances of Tox\n"); + printf("Preparing network...\n"); + long long unsigned int cur_time = time(nullptr); + + uint8_t address[TOX_ADDRESS_SIZE]; + + tox_callback_friend_request(Alice, t_accept_friend_request_cb); + tox_self_get_address(Alice, address); + + printf("bootstrapping Alice and Bob off a third bootstrap node\n"); + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(bootstrap, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(bootstrap, nullptr); + + tox_bootstrap(Alice, "localhost", dht_port, dht_key, nullptr); + tox_bootstrap(Bob, "localhost", dht_port, dht_key, nullptr); + + ck_assert(tox_friend_add(Bob, address, (const uint8_t *)"gentoo", 7, nullptr) != (uint32_t) -1); + + uint8_t off = 1; + + while (true) { + iterate_tox(bootstrap, Alice, Bob); + + if (tox_self_get_connection_status(bootstrap) && + tox_self_get_connection_status(Alice) && + tox_self_get_connection_status(Bob) && off) { + printf("Toxes are online, took %llu seconds\n", time(nullptr) - cur_time); + off = 0; + } + + if (tox_friend_get_connection_status(Alice, 0, nullptr) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bob, 0, nullptr) == TOX_CONNECTION_UDP) { + break; + } + + c_sleep(20); + } + + + { + Toxav_Err_New error; + AliceAV = toxav_new(Alice, &error); + ck_assert(error == TOXAV_ERR_NEW_OK); + + BobAV = toxav_new(Bob, &error); + ck_assert(error == TOXAV_ERR_NEW_OK); + } + + toxav_callback_call(AliceAV, t_toxav_call_cb, &AliceCC); + toxav_callback_call_state(AliceAV, t_toxav_call_state_cb, &AliceCC); + toxav_callback_video_receive_frame(AliceAV, t_toxav_receive_video_frame_cb, &AliceCC); + toxav_callback_audio_receive_frame(AliceAV, t_toxav_receive_audio_frame_cb, &AliceCC); + + toxav_callback_call(BobAV, t_toxav_call_cb, &BobCC); + toxav_callback_call_state(BobAV, t_toxav_call_state_cb, &BobCC); + toxav_callback_video_receive_frame(BobAV, t_toxav_receive_video_frame_cb, &BobCC); + toxav_callback_audio_receive_frame(BobAV, t_toxav_receive_audio_frame_cb, &BobCC); + + printf("Created 2 instances of ToxAV\n"); + printf("All set after %llu seconds!\n", time(nullptr) - cur_time); + + if (TEST_REGULAR_AV) { + printf("\nTrying regular call (Audio and Video)...\n"); + regular_call_flow(Alice, Bob, bootstrap, AliceAV, BobAV, &AliceCC, &BobCC, + 48, 4000); + } + + if (TEST_REGULAR_A) { + printf("\nTrying regular call (Audio only)...\n"); + regular_call_flow(Alice, Bob, bootstrap, AliceAV, BobAV, &AliceCC, &BobCC, + 48, 0); + } + + if (TEST_REGULAR_V) { + printf("\nTrying regular call (Video only)...\n"); + regular_call_flow(Alice, Bob, bootstrap, AliceAV, BobAV, &AliceCC, &BobCC, + 0, 4000); + } + + if (TEST_REJECT) { /* Alice calls; Bob rejects */ + printf("\nTrying reject flow...\n"); + + clear_call_control(&AliceCC); + clear_call_control(&BobCC); + + { + Toxav_Err_Call rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %d\n", rc); + } + + do { + iterate_tox(bootstrap, Alice, Bob); + } while (!BobCC.incoming); + + /* Reject */ + { + Toxav_Err_Call_Control rc; + toxav_call_control(BobAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %d\n", rc); + } + + do { + iterate_tox(bootstrap, Alice, Bob); + } while (AliceCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); + } + + if (TEST_CANCEL) { /* Alice calls; Alice cancels while ringing */ + printf("\nTrying cancel (while ringing) flow...\n"); + + clear_call_control(&AliceCC); + clear_call_control(&BobCC); + + { + Toxav_Err_Call rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %d\n", rc); + } + + do { + iterate_tox(bootstrap, Alice, Bob); + } while (!BobCC.incoming); + + /* Cancel */ + { + Toxav_Err_Call_Control rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %d\n", rc); + } + + /* Alice will not receive end state */ + do { + iterate_tox(bootstrap, Alice, Bob); + } while (BobCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); + } + + if (TEST_MUTE_UNMUTE) { /* Check Mute-Unmute etc */ + printf("\nTrying mute functionality...\n"); + + clear_call_control(&AliceCC); + clear_call_control(&BobCC); + + /* Assume sending audio and video */ + { + Toxav_Err_Call rc; + toxav_call(AliceAV, 0, 48, 1000, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %d\n", rc); + } + + do { + iterate_tox(bootstrap, Alice, Bob); + } while (!BobCC.incoming); + + /* At first try all stuff while in invalid state */ + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, nullptr)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, nullptr)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, nullptr)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, nullptr)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO, nullptr)); + ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO, nullptr)); + + { + Toxav_Err_Answer rc; + toxav_answer(BobAV, 0, 48, 4000, &rc); + + ck_assert_msg(rc == TOXAV_ERR_ANSWER_OK, "toxav_answer failed: %d\n", rc); + } + + iterate_tox(bootstrap, Alice, Bob); + + /* Pause and Resume */ + printf("Pause and Resume\n"); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state == 0); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & (TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_SENDING_V)); + + /* Mute/Unmute single */ + printf("Mute/Unmute single\n"); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); + + /* Mute/Unmute both */ + printf("Mute/Unmute both\n"); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_V); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V); + + { + Toxav_Err_Call_Control rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %d\n", rc); + } + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); + } + + if (TEST_STOP_RESUME_PAYLOAD) { /* Stop and resume audio/video payload */ + printf("\nTrying stop/resume functionality...\n"); + + clear_call_control(&AliceCC); + clear_call_control(&BobCC); + + /* Assume sending audio and video */ + { + Toxav_Err_Call rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %d\n", rc); + } + + do { + iterate_tox(bootstrap, Alice, Bob); + } while (!BobCC.incoming); + + { + Toxav_Err_Answer rc; + toxav_answer(BobAV, 0, 48, 0, &rc); + + ck_assert_msg(rc == TOXAV_ERR_ANSWER_OK, "toxav_answer failed: %d\n", rc); + } + + iterate_tox(bootstrap, Alice, Bob); + + printf("Call started as audio only\n"); + printf("Turning on video for Alice...\n"); + ck_assert(toxav_video_set_bit_rate(AliceAV, 0, 1000, nullptr)); + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_V); + + printf("Turning off video for Alice...\n"); + ck_assert(toxav_video_set_bit_rate(AliceAV, 0, 0, nullptr)); + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(!(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)); + + printf("Turning off audio for Alice...\n"); + ck_assert(toxav_audio_set_bit_rate(AliceAV, 0, 0, nullptr)); + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(!(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_A)); + + { + Toxav_Err_Call_Control rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %d\n", rc); + } + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); + } + + if (TEST_PAUSE_RESUME_SEND) { /* Stop and resume audio/video payload and test send options */ + printf("\nTrying stop/resume functionality...\n"); + + clear_call_control(&AliceCC); + clear_call_control(&BobCC); + + /* Assume sending audio and video */ + { + Toxav_Err_Call rc; + toxav_call(AliceAV, 0, 48, 0, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_OK, "toxav_call failed: %d\n", rc); + } + + do { + iterate_tox(bootstrap, Alice, Bob); + } while (!BobCC.incoming); + + { + Toxav_Err_Answer rc; + toxav_answer(BobAV, 0, 48, 0, &rc); + + ck_assert_msg(rc == TOXAV_ERR_ANSWER_OK, "toxav_answer failed: %d\n", rc); + } + + iterate_tox(bootstrap, Alice, Bob); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(!toxav_audio_send_frame_helper(AliceAV, 0, nullptr)); + ck_assert(!toxav_audio_send_frame_helper(BobAV, 0, nullptr)); + ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME); + iterate_tox(bootstrap, Alice, Bob); + ck_assert(toxav_audio_send_frame_helper(AliceAV, 0, nullptr)); + ck_assert(toxav_audio_send_frame_helper(BobAV, 0, nullptr)); + iterate_tox(bootstrap, Alice, Bob); + + { + Toxav_Err_Call_Control rc; + toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); + + ck_assert_msg(rc == TOXAV_ERR_CALL_CONTROL_OK, "toxav_call_control failed: %d\n", rc); + } + + iterate_tox(bootstrap, Alice, Bob); + ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED); + + printf("Success!\n"); + } + + toxav_kill(BobAV); + toxav_kill(AliceAV); + tox_kill(Bob); + tox_kill(Alice); + tox_kill(bootstrap); + + printf("\nTest successful!\n"); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_av_flows(); + return 0; +} diff --git a/auto_tests/toxav_many_test.c b/auto_tests/toxav_many_test.c new file mode 100644 index 0000000..725fe61 --- /dev/null +++ b/auto_tests/toxav_many_test.c @@ -0,0 +1,381 @@ +#include +#include +#include +#include +#include +#include + +#if !defined(_WIN32) && !defined(__WIN32__) && !defined(WIN32) +#include +#endif + +#include + +#include "../testing/misc_tools.h" +#include "../toxav/toxav.h" +#include "../toxcore/crypto_core.h" +#include "../toxcore/logger.h" +#include "../toxcore/tox.h" +#include "../toxcore/tox_struct.h" +#include "../toxcore/util.h" +#include "auto_test_support.h" +#include "check_compat.h" + +typedef struct { + bool incoming; + uint32_t state; +} CallControl; + +typedef struct { + ToxAV *AliceAV; + ToxAV *BobAV; + CallControl *AliceCC; + CallControl *BobCC; + uint32_t friend_number; +} thread_data; + +/** + * Callbacks + */ +static void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data) +{ + printf("Handling CALL callback\n"); + ((CallControl *)user_data)[friend_number].incoming = true; +} + +static void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data) +{ + printf("Handling CALL STATE callback: %u %p\n", state, (void *)av); + ((CallControl *)user_data)[friend_number].state = state; +} + +static void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number, + uint16_t width, uint16_t height, + uint8_t const *y, uint8_t const *u, uint8_t const *v, + int32_t ystride, int32_t ustride, int32_t vstride, + void *user_data) +{ +} + +static void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number, + int16_t const *pcm, + size_t sample_count, + uint8_t channels, + uint32_t sampling_rate, + void *user_data) +{ +} + +static void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, + void *userdata) +{ + if (length == 7 && memcmp("gentoo", data, 7) == 0) { + ck_assert(tox_friend_add_norequest(m, public_key, nullptr) != (uint32_t) -1); + } +} + + +/** + * Iterate helper + */ +static ToxAV *setup_av_instance(Tox *tox, CallControl *CC) +{ + Toxav_Err_New error; + + ToxAV *av = toxav_new(tox, &error); + ck_assert(error == TOXAV_ERR_NEW_OK); + + toxav_callback_call(av, t_toxav_call_cb, CC); + toxav_callback_call_state(av, t_toxav_call_state_cb, CC); + toxav_callback_video_receive_frame(av, t_toxav_receive_video_frame_cb, CC); + toxav_callback_audio_receive_frame(av, t_toxav_receive_audio_frame_cb, CC); + + return av; +} + +static void *call_thread(void *pd) +{ + ToxAV *AliceAV = ((thread_data *) pd)->AliceAV; + ToxAV *BobAV = ((thread_data *) pd)->BobAV; + uint32_t friend_number = ((thread_data *) pd)->friend_number; + + int16_t *PCM = (int16_t *)calloc(960, sizeof(int16_t)); + uint8_t *video_y = (uint8_t *)calloc(800 * 600, sizeof(uint8_t)); + uint8_t *video_u = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t)); + uint8_t *video_v = (uint8_t *)calloc(800 * 600 / 4, sizeof(uint8_t)); + + time_t start_time = time(nullptr); + + do { + toxav_iterate(AliceAV); + toxav_iterate(BobAV); + + toxav_audio_send_frame(AliceAV, friend_number, PCM, 960, 1, 48000, nullptr); + toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, nullptr); + + toxav_video_send_frame(AliceAV, friend_number, 800, 600, video_y, video_u, video_v, nullptr); + toxav_video_send_frame(BobAV, 0, 800, 600, video_y, video_u, video_v, nullptr); + + c_sleep(10); + } while (time(nullptr) - start_time < 4); + + free(PCM); + free(video_y); + free(video_u); + free(video_v); + + printf("Closing thread\n"); + pthread_exit(nullptr); + + return nullptr; +} + +typedef struct Time_Data { + pthread_mutex_t lock; + uint64_t clock; +} Time_Data; + +static uint64_t get_state_clock_callback(void *user_data) +{ + Time_Data *time_data = (Time_Data *)user_data; + pthread_mutex_lock(&time_data->lock); + uint64_t clock = time_data->clock; + pthread_mutex_unlock(&time_data->lock); + return clock; +} + +static void increment_clock(Time_Data *time_data, uint64_t count) +{ + pthread_mutex_lock(&time_data->lock); + time_data->clock += count; + pthread_mutex_unlock(&time_data->lock); +} + +static void set_current_time_callback(Tox *tox, Time_Data *time_data) +{ + Mono_Time *mono_time = tox->mono_time; + mono_time_set_current_time_callback(mono_time, get_state_clock_callback, time_data); +} + +static void test_av_three_calls(void) +{ + uint32_t index[] = { 1, 2, 3, 4, 5 }; + Tox *Alice, *bootstrap, *Bobs[3]; + ToxAV *AliceAV, *BobsAV[3]; + void *retval; + + CallControl AliceCC[3], BobsCC[3]; + + Time_Data time_data; + pthread_mutex_init(&time_data.lock, nullptr); + { + Tox_Err_New error; + + bootstrap = tox_new_log(nullptr, &error, &index[0]); + ck_assert(error == TOX_ERR_NEW_OK); + + time_data.clock = current_time_monotonic(bootstrap->mono_time); + set_current_time_callback(bootstrap, &time_data); + + Alice = tox_new_log(nullptr, &error, &index[1]); + ck_assert(error == TOX_ERR_NEW_OK); + set_current_time_callback(Alice, &time_data); + + Bobs[0] = tox_new_log(nullptr, &error, &index[2]); + ck_assert(error == TOX_ERR_NEW_OK); + set_current_time_callback(Bobs[0], &time_data); + + Bobs[1] = tox_new_log(nullptr, &error, &index[3]); + ck_assert(error == TOX_ERR_NEW_OK); + set_current_time_callback(Bobs[1], &time_data); + + Bobs[2] = tox_new_log(nullptr, &error, &index[4]); + ck_assert(error == TOX_ERR_NEW_OK); + set_current_time_callback(Bobs[2], &time_data); + } + + printf("Created 5 instances of Tox\n"); + printf("Preparing network...\n"); + time_t cur_time = time(nullptr); + + uint8_t address[TOX_ADDRESS_SIZE]; + + tox_callback_friend_request(Alice, t_accept_friend_request_cb); + tox_self_get_address(Alice, address); + + printf("bootstrapping Alice and the %u Bobs off a third bootstrap node\n", + (unsigned)(sizeof(Bobs) / sizeof(Bobs[0]))); + uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; + tox_self_get_dht_id(bootstrap, dht_key); + const uint16_t dht_port = tox_self_get_udp_port(bootstrap, nullptr); + + tox_bootstrap(Alice, "localhost", dht_port, dht_key, nullptr); + tox_bootstrap(Bobs[0], "localhost", dht_port, dht_key, nullptr); + tox_bootstrap(Bobs[1], "localhost", dht_port, dht_key, nullptr); + tox_bootstrap(Bobs[2], "localhost", dht_port, dht_key, nullptr); + + ck_assert(tox_friend_add(Bobs[0], address, (const uint8_t *)"gentoo", 7, nullptr) != (uint32_t) -1); + ck_assert(tox_friend_add(Bobs[1], address, (const uint8_t *)"gentoo", 7, nullptr) != (uint32_t) -1); + ck_assert(tox_friend_add(Bobs[2], address, (const uint8_t *)"gentoo", 7, nullptr) != (uint32_t) -1); + + uint8_t off = 1; + + while (true) { + tox_iterate(bootstrap, nullptr); + tox_iterate(Alice, nullptr); + tox_iterate(Bobs[0], nullptr); + tox_iterate(Bobs[1], nullptr); + tox_iterate(Bobs[2], nullptr); + + if (tox_self_get_connection_status(bootstrap) && + tox_self_get_connection_status(Alice) && + tox_self_get_connection_status(Bobs[0]) && + tox_self_get_connection_status(Bobs[1]) && + tox_self_get_connection_status(Bobs[2]) && off) { + printf("Toxes are online, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); + off = 0; + } + + if (tox_friend_get_connection_status(Alice, 0, nullptr) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Alice, 1, nullptr) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Alice, 2, nullptr) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bobs[0], 0, nullptr) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bobs[1], 0, nullptr) == TOX_CONNECTION_UDP && + tox_friend_get_connection_status(Bobs[2], 0, nullptr) == TOX_CONNECTION_UDP) { + break; + } + + increment_clock(&time_data, 200); + c_sleep(5); + } + + AliceAV = setup_av_instance(Alice, AliceCC); + BobsAV[0] = setup_av_instance(Bobs[0], &BobsCC[0]); + BobsAV[1] = setup_av_instance(Bobs[1], &BobsCC[1]); + BobsAV[2] = setup_av_instance(Bobs[2], &BobsCC[2]); + + printf("Created 4 instances of ToxAV\n"); + printf("All set after %lu seconds!\n", (unsigned long)(time(nullptr) - cur_time)); + + thread_data tds[3]; + + for (size_t i = 0; i < 3; i++) { + tds[i].AliceAV = AliceAV; + tds[i].BobAV = BobsAV[i]; + tds[i].AliceCC = &AliceCC[i]; + tds[i].BobCC = &BobsCC[i]; + tds[i].friend_number = i; + memset(tds[i].AliceCC, 0, sizeof(CallControl)); + memset(tds[i].BobCC, 0, sizeof(CallControl)); + } + + pthread_t tids[3]; + + for (size_t i = 0; i < 3; i++) { + (void) pthread_create(&tids[i], nullptr, call_thread, &tds[i]); + } + + time_t start_time = time(nullptr); + + do { + tox_iterate(bootstrap, nullptr); + tox_iterate(Alice, nullptr); + tox_iterate(Bobs[0], nullptr); + tox_iterate(Bobs[1], nullptr); + tox_iterate(Bobs[2], nullptr); + + increment_clock(&time_data, 100); + c_sleep(5); + } while (time(nullptr) - start_time < 1); + + /* Call */ + for (size_t i = 0; i < 3; i++) { + Toxav_Err_Call rc; + toxav_call(AliceAV, tds[i].friend_number, 48, 3000, &rc); + + if (rc != TOXAV_ERR_CALL_OK) { + printf("toxav_call failed: %d\n", rc); + ck_assert(0); + } + } + + + do { + tox_iterate(bootstrap, nullptr); + tox_iterate(Alice, nullptr); + tox_iterate(Bobs[0], nullptr); + tox_iterate(Bobs[1], nullptr); + tox_iterate(Bobs[2], nullptr); + + for (size_t i = 0; i < 3; i++) { + if (BobsCC[i].incoming) { + /* Answer */ + Toxav_Err_Answer rc; + toxav_answer(BobsAV[i], 0, 8, 500, &rc); + + if (rc != TOXAV_ERR_ANSWER_OK) { + printf("toxav_answer failed: %d\n", rc); + ck_assert(0); + } + + BobsCC[i].incoming = false; + } + } + + increment_clock(&time_data, 100); + c_sleep(5); + } while (time(nullptr) - start_time < 3); + + /* Hangup */ + for (size_t i = 0; i < 3; i++) { + Toxav_Err_Call_Control rc; + toxav_call_control(AliceAV, i, TOXAV_CALL_CONTROL_CANCEL, &rc); + + if (rc != TOXAV_ERR_CALL_CONTROL_OK) { + printf("toxav_call_control failed: %d %p %p\n", rc, (void *)AliceAV, (void *)&BobsAV[i]); + } + } + + do { + tox_iterate(bootstrap, nullptr); + tox_iterate(Alice, nullptr); + tox_iterate(Bobs[0], nullptr); + tox_iterate(Bobs[1], nullptr); + tox_iterate(Bobs[2], nullptr); + + increment_clock(&time_data, 100); + c_sleep(5); + } while (time(nullptr) - start_time < 5); + + ck_assert(pthread_join(tids[0], &retval) == 0); + ck_assert(retval == nullptr); + + ck_assert(pthread_join(tids[1], &retval) == 0); + ck_assert(retval == nullptr); + + ck_assert(pthread_join(tids[2], &retval) == 0); + ck_assert(retval == nullptr); + + printf("Killing all instances\n"); + toxav_kill(BobsAV[2]); + toxav_kill(BobsAV[1]); + toxav_kill(BobsAV[0]); + toxav_kill(AliceAV); + tox_kill(Bobs[2]); + tox_kill(Bobs[1]); + tox_kill(Bobs[0]); + tox_kill(Alice); + tox_kill(bootstrap); + + pthread_mutex_destroy(&time_data.lock); + + printf("\nTest successful!\n"); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + test_av_three_calls(); + return 0; +} diff --git a/auto_tests/typing_test.c b/auto_tests/typing_test.c new file mode 100644 index 0000000..5c01598 --- /dev/null +++ b/auto_tests/typing_test.c @@ -0,0 +1,63 @@ +/* Tests that our typing notifications work. + */ + +#include +#include +#include + +#include "../testing/misc_tools.h" +#include "../toxcore/ccompat.h" +#include "../toxcore/tox.h" +#include "../toxcore/util.h" +#include "check_compat.h" + +typedef struct State { + bool friend_is_typing; +} State; + +#include "auto_test_support.h" + +static void typing_callback(Tox *m, uint32_t friendnumber, bool typing, void *user_data) +{ + const AutoTox *autotox = (AutoTox *)user_data; + State *state = (State *)autotox->state; + state->friend_is_typing = typing; +} + +static void test_typing(AutoTox *autotoxes) +{ + time_t cur_time = time(nullptr); + + tox_callback_friend_typing(autotoxes[1].tox, &typing_callback); + tox_self_set_typing(autotoxes[0].tox, 0, true, nullptr); + + do { + iterate_all_wait(autotoxes, 2, 200); + } while (!((State *)autotoxes[1].state)->friend_is_typing); + + ck_assert_msg(tox_friend_get_typing(autotoxes[1].tox, 0, nullptr) == 1, + "tox_friend_get_typing should have returned true, but it didn't"); + tox_self_set_typing(autotoxes[0].tox, 0, false, nullptr); + + do { + iterate_all_wait(autotoxes, 2, 200); + } while (((State *)autotoxes[1].state)->friend_is_typing); + + Tox_Err_Friend_Query err_t; + ck_assert_msg(tox_friend_get_typing(autotoxes[1].tox, 0, &err_t) == 0, + "tox_friend_get_typing should have returned false, but it didn't"); + ck_assert_msg(err_t == TOX_ERR_FRIEND_QUERY_OK, "tox_friend_get_typing call did not return correct error"); + + printf("test_typing succeeded, took %lu seconds\n", (unsigned long)(time(nullptr) - cur_time)); +} + +int main(void) +{ + setvbuf(stdout, nullptr, _IONBF, 0); + + Run_Auto_Options options = default_run_auto_options(); + options.graph = GRAPH_LINEAR; + run_auto_test(nullptr, 2, test_typing, sizeof(State), &options); + + return 0; +} diff --git a/auto_tests/version_test.c b/auto_tests/version_test.c new file mode 100644 index 0000000..3f572fe --- /dev/null +++ b/auto_tests/version_test.c @@ -0,0 +1,96 @@ +#include "../toxcore/tox.h" +#include "check_compat.h" + +#define check(major, minor, patch, expected) \ + do_check(TOX_VERSION_MAJOR, TOX_VERSION_MINOR, TOX_VERSION_PATCH, \ + major, minor, patch, \ + TOX_VERSION_IS_API_COMPATIBLE(major, minor, patch), expected) + +static void do_check(int lib_major, int lib_minor, int lib_patch, + int cli_major, int cli_minor, int cli_patch, + bool actual, bool expected) +{ + ck_assert_msg(actual == expected, + "Client version %d.%d.%d is%s compatible with library version %d.%d.%d, but it should%s be\n", + cli_major, cli_minor, cli_patch, actual ? "" : " not", + lib_major, lib_minor, lib_patch, expected ? "" : " not"); +} + +#undef TOX_VERSION_MAJOR +#undef TOX_VERSION_MINOR +#undef TOX_VERSION_PATCH + +int main(void) +{ +#define TOX_VERSION_MAJOR 0 +#define TOX_VERSION_MINOR 0 +#define TOX_VERSION_PATCH 4 + // Tox versions from 0.0.* are only compatible with themselves. + check(0, 0, 0, false); + check(0, 0, 3, false); + check(0, 0, 4, true); + check(0, 0, 5, false); + check(1, 0, 4, false); +#undef TOX_VERSION_MAJOR +#undef TOX_VERSION_MINOR +#undef TOX_VERSION_PATCH + +#define TOX_VERSION_MAJOR 0 +#define TOX_VERSION_MINOR 1 +#define TOX_VERSION_PATCH 4 + // Tox versions from 0.1.* are only compatible with themselves or 0.1.<* + check(0, 0, 0, false); + check(0, 0, 4, false); + check(0, 0, 5, false); + check(0, 1, 0, true); + check(0, 1, 4, true); + check(0, 1, 5, false); + check(0, 2, 0, false); + check(0, 2, 4, false); + check(0, 2, 5, false); + check(1, 0, 0, false); + check(1, 0, 4, false); + check(1, 0, 5, false); + check(1, 1, 4, false); +#undef TOX_VERSION_MAJOR +#undef TOX_VERSION_MINOR +#undef TOX_VERSION_PATCH + +#define TOX_VERSION_MAJOR 1 +#define TOX_VERSION_MINOR 0 +#define TOX_VERSION_PATCH 4 + // Beyond 0.*.* Tox is comfortable with any lower version within their major + check(0, 0, 4, false); + check(1, 0, 0, true); + check(1, 0, 1, true); + check(1, 0, 4, true); + check(1, 0, 5, false); + check(1, 1, 0, false); + check(2, 0, 0, false); + check(2, 0, 4, false); +#undef TOX_VERSION_MAJOR +#undef TOX_VERSION_MINOR +#undef TOX_VERSION_PATCH + +#define TOX_VERSION_MAJOR 1 +#define TOX_VERSION_MINOR 1 +#define TOX_VERSION_PATCH 4 + check(0, 0, 4, false); + check(1, 0, 0, true); + check(1, 0, 4, true); + check(1, 0, 5, true); + check(1, 1, 0, true); + check(1, 1, 1, true); + check(1, 1, 4, true); + check(1, 1, 5, false); + check(1, 2, 0, false); + check(1, 2, 4, false); + check(1, 2, 5, false); + check(2, 0, 0, false); + check(2, 1, 4, false); +#undef TOX_VERSION_MAJOR +#undef TOX_VERSION_MINOR +#undef TOX_VERSION_PATCH + + return 0; +} diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..9de319e --- /dev/null +++ b/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/sh -e + +echo 'Running autoreconf -if...' +( + autoreconf -if +) diff --git a/build/Makefile.am b/build/Makefile.am new file mode 100644 index 0000000..cbaca76 --- /dev/null +++ b/build/Makefile.am @@ -0,0 +1,28 @@ +bin_PROGRAMS = +noinst_PROGRAMS = +noinst_LTLIBRARIES = +lib_LTLIBRARIES = +noinst_bindir = $(top_builddir)/build +EXTRA_DIST= + +if SET_SO_VERSION + +include ../so.version + +LT_LDFLAGS=-version-info $(CURRENT):$(REVISION):$(AGE) + +else + +LT_LDFLAGS=-avoid-version + +endif + +include ../toxcore/Makefile.inc +include ../toxencryptsave/Makefile.inc +include ../toxav/Makefile.inc +include ../other/Makefile.inc +include ../testing/Makefile.inc +include ../other/bootstrap_daemon/src/Makefile.inc +include ../auto_tests/Makefile.inc + +build-tests: $(check_PROGRAMS) diff --git a/cmake/CompileGTest.cmake b/cmake/CompileGTest.cmake new file mode 100644 index 0000000..9aa4712 --- /dev/null +++ b/cmake/CompileGTest.cmake @@ -0,0 +1,62 @@ +# Find and compile the GTest library. + +include(CheckCXXCompilerFlag) +include(CheckIncludeFileCXX) + +message(STATUS "Checking for gtest") + +# Look for the sources. +find_file(GTEST_ALL_CC gtest-all.cc PATHS + ${CMAKE_SOURCE_DIR}/third_party/googletest/googletest/src + /usr/src/gtest/src + NO_DEFAULT_PATH +) + +if(GTEST_ALL_CC) + # ../.. from the source file is the source root. + get_filename_component(GTEST_SRC_DIR ${GTEST_ALL_CC} DIRECTORY) + get_filename_component(GTEST_SRC_ROOT ${GTEST_SRC_DIR} DIRECTORY) + + # Look for the header file. + include(CheckIncludeFileCXX) + include_directories(SYSTEM ${GTEST_SRC_ROOT}/include) + check_include_file_cxx("gtest/gtest.h" HAVE_GTEST_GTEST_H) + + if(HAVE_GTEST_GTEST_H) + message(STATUS "Found gtest: ${GTEST_SRC_ROOT}") + + add_library(gtest + ${GTEST_SRC_DIR}/gtest-all.cc + ${GTEST_SRC_DIR}/gtest_main.cc) + target_include_directories(gtest PRIVATE ${GTEST_SRC_ROOT}) + + # Ignore all warnings for gtest. We don't care about their implementation. + check_cxx_compiler_flag("-w" HAVE_CXX_W QUIET) + if(HAVE_CXX_W) + set_target_properties(gtest PROPERTIES COMPILE_FLAGS "-w") + endif() + + set(HAVE_GTEST TRUE) + set(TEST_CXX_FLAGS "") + + check_cxx_compiler_flag("-Wno-global-constructors" HAVE_CXX_W_NO_GLOBAL_CONSTRUCTORS QUIET) + if(HAVE_CXX_W_NO_GLOBAL_CONSTRUCTORS) + set(TEST_CXX_FLAGS "${TEST_CXX_FLAGS} -Wno-global-constructors") + endif() + + check_cxx_compiler_flag("-Wno-zero-as-null-pointer-constant" HAVE_CXX_W_NO_ZERO_AS_NULL_POINTER_CONSTANT QUIET) + if(HAVE_CXX_W_NO_ZERO_AS_NULL_POINTER_CONSTANT) + set(TEST_CXX_FLAGS "${TEST_CXX_FLAGS} -Wno-zero-as-null-pointer-constant") + endif() + endif() +endif() + +function(unit_test subdir target) + if(HAVE_GTEST) + add_executable(unit_${target}_test ${subdir}/${target}_test.cc) + target_link_modules(unit_${target}_test toxcore gtest) + set_target_properties(unit_${target}_test PROPERTIES COMPILE_FLAGS "${TEST_CXX_FLAGS}") + add_test(NAME ${target} COMMAND ${CROSSCOMPILING_EMULATOR} unit_${target}_test) + set_property(TEST ${target} PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw") + endif() +endfunction() diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake new file mode 100644 index 0000000..fb49944 --- /dev/null +++ b/cmake/Dependencies.cmake @@ -0,0 +1,76 @@ +############################################################################### +# +# :: For UNIX-like systems that have pkg-config. +# +############################################################################### + +include(ModulePackage) + +find_package(Threads REQUIRED) + +find_library(NSL_LIBRARIES nsl ) +find_library(RT_LIBRARIES rt ) +find_library(SOCKET_LIBRARIES socket ) + +# For toxcore. +pkg_use_module(LIBSODIUM libsodium ) + +# For toxav. +pkg_use_module(OPUS "opus;Opus" ) +pkg_use_module(VPX "vpx;libvpx" ) + +# For tox-bootstrapd. +pkg_use_module(LIBCONFIG libconfig ) + +############################################################################### +# +# :: For MSVC Windows builds. +# +# These require specific installation paths of dependencies: +# - libsodium in third-party/libsodium/Win32/Release/v140/dynamic +# - pthreads in third-party/pthreads-win32/Pre-built.2 +# +############################################################################### + +if(MSVC) + # libsodium + # --------- + if(NOT LIBSODIUM_FOUND) + find_library(LIBSODIUM_LIBRARIES + NAMES sodium libsodium + PATHS + "third_party/libsodium/Win32/Release/v140/dynamic" + "third_party/libsodium/x64/Release/v140/dynamic" + ) + if(LIBSODIUM_LIBRARIES) + include_directories("third_party/libsodium/include") + set(LIBSODIUM_FOUND TRUE) + message("libsodium: ${LIBSODIUM_LIBRARIES}") + else() + message(FATAL_ERROR "libsodium libraries not found") + endif() + endif() + + # pthreads + # -------- + if(CMAKE_USE_WIN32_THREADS_INIT) + find_library(CMAKE_THREAD_LIBS_INIT + NAMES pthreadVC2 + PATHS + "third_party/pthreads-win32/Pre-built.2/lib/x86" + "third_party/pthreads-win32/Pre-built.2/lib/x64" + ) + if(CMAKE_THREAD_LIBS_INIT) + include_directories("third_party/pthreads-win32/Pre-built.2/include") + add_definitions(-DHAVE_STRUCT_TIMESPEC) + message("libpthreads: ${CMAKE_THREAD_LIBS_INIT}") + else() + find_package(pthreads4w) + if(NOT pthreads4w_FOUND) + message(FATAL_ERROR "libpthreads libraries not found") + endif() + include_directories(${pthreads4w_INCLUDE_DIR}) + link_libraries(${pthreads4w_LIBRARIES}) + endif() + endif() +endif() diff --git a/cmake/MacRpath.cmake b/cmake/MacRpath.cmake new file mode 100644 index 0000000..ae5429d --- /dev/null +++ b/cmake/MacRpath.cmake @@ -0,0 +1,24 @@ +# Taken from https://cmake.org/Wiki/CMake_RPATH_handling#Always_full_RPATH. +# +# In many cases you will want to make sure that the required libraries are +# always found independent from LD_LIBRARY_PATH and the install location. Then +# you can use these settings: + +# Use, i.e. don't skip the full RPATH for the build tree. +set(CMAKE_SKIP_BUILD_RPATH FALSE) + +# When building, don't use the install RPATH already +# (but later on when installing). +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + +# Add the automatically determined parts of the RPATH +# which point to directories outside the build tree to the install RPATH. +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +# The RPATH to be used when installing, but only if it's not a system directory. +list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) +if("${isSystemDir}" STREQUAL "-1") + set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") +endif() diff --git a/cmake/ModulePackage.cmake b/cmake/ModulePackage.cmake new file mode 100644 index 0000000..04dbf16 --- /dev/null +++ b/cmake/ModulePackage.cmake @@ -0,0 +1,171 @@ +option(ENABLE_SHARED "Build shared (dynamic) libraries for all modules" ON) +option(ENABLE_STATIC "Build static libraries for all modules" ON) + +if(NOT ENABLE_SHARED AND NOT ENABLE_STATIC) + message(WARNING + "Both static and shared libraries are disabled; " + "enabling only shared libraries. Use -DENABLE_SHARED or -DENABLE_STATIC to " + "select one manually.") + set(ENABLE_SHARED ON) +endif() + +option(FULLY_STATIC "Build fully static executables" OFF) +if(FULLY_STATIC) + set(CMAKE_EXE_LINKER_FLAGS "-static -no-pie") + # remove -Wl,-Bdynamic + set(CMAKE_EXE_LINK_DYNAMIC_C_FLAGS) + set(CMAKE_EXE_LINK_DYNAMIC_CXX_FLAGS) + set(ENABLE_SHARED OFF) + set(ENABLE_STATIC ON) +endif() + +find_package(PkgConfig) + +function(pkg_use_module mod pkgs) + foreach(pkg IN ITEMS ${pkgs}) + if(PKG_CONFIG_FOUND) + pkg_search_module(${mod} ${pkg}) + endif() + if(NOT ${mod}_FOUND) + find_package(${pkg} QUIET) + # This is very very ugly, but the variables are sometimes used in this scope + # and sometimes in the parent scope, so we have to set them to both places. + set(${mod}_FOUND ${${pkg}_FOUND}) + set(${mod}_FOUND ${${pkg}_FOUND} PARENT_SCOPE) + set(${mod}_LIBRARIES ${${pkg}_LIBS}) + set(${mod}_LIBRARIES ${${pkg}_LIBS} PARENT_SCOPE) + set(${mod}_LIBRARY_DIRS ${${pkg}_LIBRARY_DIRS}) + set(${mod}_LIBRARY_DIRS ${${pkg}_LIBRARY_DIRS} PARENT_SCOPE) + set(${mod}_INCLUDE_DIRS ${${pkg}_INCLUDE_DIRS}) + set(${mod}_INCLUDE_DIRS ${${pkg}_INCLUDE_DIRS} PARENT_SCOPE) + endif() + if(${mod}_FOUND) + link_directories(${${mod}_LIBRARY_DIRS}) + include_directories(${${mod}_INCLUDE_DIRS}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${${mod}_CFLAGS_OTHER}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${${mod}_CFLAGS_OTHER}" PARENT_SCOPE) + + if(NOT MSVC) + foreach(dir ${${mod}_INCLUDE_DIRS}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -isystem ${dir}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${dir}" PARENT_SCOPE) + endforeach() + endif() + break() + endif() + endforeach() +endfunction() + +function(add_module lib) + set(${lib}_SOURCES ${ARGN} PARENT_SCOPE) + + if(ENABLE_SHARED) + add_library(${lib}_shared SHARED ${ARGN}) + set_target_properties(${lib}_shared PROPERTIES OUTPUT_NAME ${lib}) + endif() + if(ENABLE_STATIC) + add_library(${lib}_static STATIC ${ARGN}) + set_target_properties(${lib}_static PROPERTIES OUTPUT_NAME ${lib}) + endif() +endfunction() + +function(install_module lib) + if(ENABLE_SHARED) + set_target_properties(${lib}_shared PROPERTIES + VERSION ${SOVERSION} + SOVERSION ${SOVERSION_MAJOR} + ) + install(TARGETS ${lib}_shared + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif() + if(ENABLE_STATIC) + install(TARGETS ${lib}_static + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif() + + string(REPLACE ";" " " ${lib}_PKGCONFIG_LIBS "${${lib}_PKGCONFIG_LIBS}") + string(REPLACE ";" " " ${lib}_PKGCONFIG_REQUIRES "${${lib}_PKGCONFIG_REQUIRES}") + + configure_file( + "${${lib}_SOURCE_DIR}/other/pkgconfig/${lib}.pc.in" + "${CMAKE_BINARY_DIR}/${lib}.pc" + @ONLY + ) + + configure_file( + "${toxcore_SOURCE_DIR}/other/rpm/${lib}.spec.in" + "${CMAKE_BINARY_DIR}/${lib}.spec" + @ONLY + ) + + install(FILES + ${CMAKE_BINARY_DIR}/${lib}.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + + foreach(sublib ${${lib}_API_HEADERS}) + string(REPLACE "^" ";" sublib ${sublib}) + list(GET sublib 0 header) + + install(FILES ${header} ${ARGN}) + endforeach() +endfunction() + +function(target_link_modules target) + # If the target we're adding dependencies to is a shared library, add it to + # the set of targets. + if(TARGET ${target}_shared) + set(_targets ${_targets} ${target}_shared) + # Shared libraries should first try to link against other shared libraries. + set(${target}_shared_primary shared) + # If that fails (because the shared target doesn't exist), try linking + # against the static library. This requires the static library's objects to + # be PIC. + set(${target}_shared_secondary static) + endif() + # It can also be a static library at the same time. + if(TARGET ${target}_static) + set(_targets ${_targets} ${target}_static) + # Static libraries aren't actually linked, but their dependencies are + # recorded by "linking" them. If we link an executable to a static library, + # we want to also link statically against its transitive dependencies. + set(${target}_static_primary static) + # If a dependency doesn't exist as static library, we link against the + # shared one. + set(${target}_static_secondary shared) + endif() + # If it's neither, then it's an executable. + if(NOT _targets) + set(_targets ${_targets} ${target}) + # Executables preferably link against static libraries, so they are + # standalone and can be shipped without any external dependencies. As a + # frame of reference: tests become roughly 600-800K binaries instead of + # 50-100K on x86_64 Linux. + set(${target}_primary static) + set(${target}_secondary shared) + endif() + + foreach(dep ${ARGN}) + foreach(_target ${_targets}) + if(TARGET ${dep}_${${_target}_primary}) + target_link_libraries(${_target} ${dep}_${${_target}_primary}) + elseif(TARGET ${dep}_${${_target}_secondary}) + target_link_libraries(${_target} ${dep}_${${_target}_secondary}) + else() + # We record the modules linked to this target, so that we can collect + # them later when linking a composed module. + list(FIND LINK_MODULES ${dep} _index) + if(_index EQUAL -1) + set(LINK_MODULES ${LINK_MODULES} ${dep}) + endif() + + target_link_libraries(${_target} ${dep}) + endif() + endforeach() + endforeach() + + set(${target}_LINK_MODULES ${${target}_LINK_MODULES} ${LINK_MODULES} PARENT_SCOPE) +endfunction() diff --git a/cmake/StrictAbi.cmake b/cmake/StrictAbi.cmake new file mode 100644 index 0000000..22b1ca4 --- /dev/null +++ b/cmake/StrictAbi.cmake @@ -0,0 +1,76 @@ +################################################################################ +# +# :: Strict ABI +# +# Enabling the STRICT_ABI flag will generate and use an LD version script. +# It ensures that the dynamic libraries (libtoxcore.so, libtoxav.so) only +# export the symbols that are defined in their public API (tox.h and toxav.h, +# respectively). +# +################################################################################ + +find_program(SHELL NAMES sh dash bash zsh fish) + +macro(make_version_script) + if(STRICT_ABI AND SHELL AND ENABLE_SHARED) + _make_version_script(${ARGN}) + endif() +endmacro() + +function(_make_version_script target) + set(${target}_VERSION_SCRIPT "${CMAKE_BINARY_DIR}/${target}.ld") + + file(WRITE ${${target}_VERSION_SCRIPT} + "{ global:\n") + + foreach(sublib ${ARGN}) + string(REPLACE "^" ";" sublib ${sublib}) + list(GET sublib 0 header) + list(GET sublib 1 ns) + + execute_process( + COMMAND ${SHELL} -c "egrep '^\\w' ${header} | grep '${ns}_[a-z0-9_]*(' | grep -v '^typedef' | grep -o '${ns}_[a-z0-9_]*(' | egrep -o '\\w+' | sort -u" + OUTPUT_VARIABLE sublib_SYMS + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REPLACE "\n" ";" sublib_SYMS ${sublib_SYMS}) + + foreach(sym ${sublib_SYMS}) + file(APPEND ${${target}_VERSION_SCRIPT} + "${sym};\n") + endforeach(sym) + endforeach(sublib) + + file(APPEND ${${target}_VERSION_SCRIPT} + "local: *; };\n") + + set_target_properties(${target}_shared PROPERTIES + LINK_FLAGS -Wl,--version-script,${${target}_VERSION_SCRIPT}) +endfunction() + +option(STRICT_ABI "Enforce strict ABI export in dynamic libraries" OFF) +if(WIN32 OR APPLE) + # Windows and OSX don't have this linker functionality. + set(STRICT_ABI OFF) +endif() + +if(STRICT_ABI AND NOT ENABLE_STATIC) + if(AUTOTEST) + message("AUTOTEST option is incompatible with STRICT_ABI. Disabling AUTOTEST.") + endif() + set(AUTOTEST OFF) + + if(BUILD_MISC_TESTS) + message("BUILD_MISC_TESTS option is incompatible with STRICT_ABI. Disabling BUILD_MISC_TESTS.") + endif() + set(BUILD_MISC_TESTS OFF) + + if(BOOTSTRAP_DAEMON) + message("BOOTSTRAP_DAEMON option is incompatible with STRICT_ABI. Disabling BOOTSTRAP_DAEMON.") + endif() + set(BOOTSTRAP_DAEMON OFF) + + if(DHT_BOOTSTRAP) + message("DHT_BOOTSTRAP option is incompatible with STRICT_ABI. Disabling DHT_BOOTSTRAP.") + endif() + set(DHT_BOOTSTRAP OFF) +endif() diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..45b48ca --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +--- +coverage: + precision: 2 + round: down + range: "80...100" + + status: + project: + default: + # Allow coverage to fluctuate 2% up and down. We can never go below 80% + # because of the above range, but toxcore coverage fluctuates a lot due + # to low coverage of error paths that sometimes happen. + threshold: 2% diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..b278434 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,86 @@ +# pylint: disable=not-callable +import os +import re + +from conans import CMake +from conans import ConanFile +from conans.tools import collect_libs +from conans.tools import load + + +class ToxConan(ConanFile): + name = "c-toxcore" + url = "https://tox.chat" + description = "The future of online communications." + license = "GPL-3.0-only" + settings = "os", "compiler", "build_type", "arch" + requires = "libsodium/1.0.18", "opus/1.3.1", "libvpx/1.9.0" + generators = "cmake_find_package" + scm = {"type": "git", "url": "auto", "revision": "auto"} + + options = { + "shared": [True, False], + "with_tests": [True, False], + } + default_options = { + "shared": False, + "with_tests": False, + } + + _cmake = None + + def _create_cmake(self): + if self._cmake is not None: + return self._cmake + + self._cmake = CMake(self) + self._cmake.definitions["AUTOTEST"] = self.options.with_tests + self._cmake.definitions["BUILD_MISC_TESTS"] = self.options.with_tests + + self._cmake.definitions[ + "CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS"] = self.options.shared + self._cmake.definitions["ENABLE_SHARED"] = self.options.shared + self._cmake.definitions["ENABLE_STATIC"] = not self.options.shared + self._cmake.definitions["MUST_BUILD_TOXAV"] = True + if self.settings.compiler == "Visual Studio": + self._cmake.definitions["MSVC_STATIC_SODIUM"] = True + self._cmake.definitions[ + "FLAT_OUTPUT_STRUCTURE"] = self.options.shared + + self._cmake.configure() + return self._cmake + + def set_version(self): + content = load(os.path.join(self.recipe_folder, "CMakeLists.txt")) + version_major = re.search(r"set\(PROJECT_VERSION_MAJOR \"(.*)\"\)", + content).group(1) + version_minor = re.search(r"set\(PROJECT_VERSION_MINOR \"(.*)\"\)", + content).group(1) + version_patch = re.search(r"set\(PROJECT_VERSION_PATCH \"(.*)\"\)", + content).group(1) + self.version = "%s.%s.%s" % ( + version_major.strip(), + version_minor.strip(), + version_patch.strip(), + ) + + def requirements(self): + if self.settings.os == "Windows": + self.requires("pthreads4w/3.0.0") + + def build(self): + cmake = self._create_cmake() + cmake.build() + + if self.options.with_tests: + cmake.test(output_on_failure=True) + + def package(self): + cmake = self._create_cmake() + cmake.install() + + def package_info(self): + self.cpp_info.libs = collect_libs(self) + + if self.settings.os == "Windows": + self.cpp_info.system_libs = ["Ws2_32", "Iphlpapi"] diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..1afb0a7 --- /dev/null +++ b/configure.ac @@ -0,0 +1,572 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.65]) +AC_INIT([tox], [0.2.18]) +AC_CONFIG_AUX_DIR(configure_aux) +AC_CONFIG_SRCDIR([toxcore/net_crypto.c]) +AM_INIT_AUTOMAKE([foreign 1.10 -Wall -Werror subdir-objects tar-ustar]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AC_CONFIG_MACRO_DIR([m4]) + +EXTRA_LT_LDFLAGS= + +if test "x${prefix}" = "xNONE"; then + prefix="${ac_default_prefix}" +fi + +BUILD_DHT_BOOTSTRAP_DAEMON="no" +BUILD_DHT_BOOTSTRAP="no" +BUILD_TESTS="yes" +BUILD_AV="yes" +BUILD_TESTING="yes" + +LIBCONFIG_FOUND="no" +WANT_NACL="no" +ADD_NACL_OBJECTS_TO_PKGCONFIG="yes" +SET_SO_VERSION="yes" + +AC_ARG_ENABLE([soname-versions], + [AC_HELP_STRING([--enable-soname-versions], [enable soname versions (must be disabled for android) (default: enabled)]) ], + [ + if test "x$enableval" = "xno"; then + SET_SO_VERSION="no" + elif test "x$enableval" = "xyes"; then + SET_SO_VERSION="yes" + fi + ] +) +AM_CONDITIONAL(SET_SO_VERSION, test "x$SET_SO_VERSION" = "xyes") + +AC_ARG_ENABLE([nacl], + [AC_HELP_STRING([--enable-nacl], [use nacl instead of libsodium (default: disabled)]) ], + [ + if test "x$enableval" = "xno"; then + WANT_NACL="no" + elif test "x$enableval" = "xyes"; then + WANT_NACL="yes" + fi + ] +) + +AC_ARG_ENABLE([randombytes-stir], + [AC_HELP_STRING([--enable-randombytes-stir], [use randombytes_stir() instead of sodium_init() for faster startup on android (default: disabled)]) ], + [ + if test "x$enableval" = "xyes"; then + if test "x$WANT_NACL" = "xyes"; then + AC_MSG_WARN([randombytes_stir() is not available with NaCl library]) + else + AC_DEFINE([USE_RANDOMBYTES_STIR], [1], [randombytes_stir() instead of sodium_init()]) + fi + fi + ] +) + +AC_ARG_WITH(log-level, + AC_HELP_STRING([--with-log-level=LEVEL], + [Logger levels: TRACE; DEBUG; INFO; WARNING; ERROR ]), + [ + if test "x$withval" = "xTRACE"; then + AC_DEFINE([MIN_LOGGER_LEVEL], [LOGGER_LEVEL_TRACE], [Logger_Level value]) + + elif test "x$withval" = "xDEBUG"; then + AC_DEFINE([MIN_LOGGER_LEVEL], [LOGGER_LEVEL_DEBUG], [Logger_Level value]) + + elif test "x$withval" = "xINFO"; then + AC_DEFINE([MIN_LOGGER_LEVEL], [LOGGER_LEVEL_INFO], [Logger_Level value]) + + elif test "x$withval" = "xWARNING"; then + AC_DEFINE([MIN_LOGGER_LEVEL], [LOGGER_LEVEL_WARNING], [Logger_Level value]) + + elif test "x$withval" = "xERROR"; then + AC_DEFINE([MIN_LOGGER_LEVEL], [LOGGER_LEVEL_ERROR], [Logger_Level value]) + else + AC_MSG_WARN([Invalid logger level: $withval. Using default.]) + fi + ] +) + +PKG_PROG_PKG_CONFIG + +AC_ARG_ENABLE([av], + [AC_HELP_STRING([--disable-av], [build AV support libraries (default: auto)]) ], + [ + if test "x$enableval" = "xno"; then + BUILD_AV="no" + elif test "x$enableval" = "xyes"; then + BUILD_AV="yes" + fi + ] +) + +AC_ARG_ENABLE([tests], + [AC_HELP_STRING([--disable-tests], [build unit tests (default: auto)]) ], + [ + if test "x$enableval" = "xno"; then + BUILD_TESTS="no" + elif test "x$enableval" = "xyes"; then + BUILD_TESTS="yes" + fi + ] +) + +AC_ARG_ENABLE([daemon], + [AC_HELP_STRING([--enable-daemon], [build DHT bootstrap daemon (default: auto)]) ], + [ + if test "x$enableval" = "xno"; then + BUILD_DHT_BOOTSTRAP_DAEMON="no" + elif test "x$enableval" = "xyes"; then + BUILD_DHT_BOOTSTRAP_DAEMON="yes" + fi + ] +) + +AC_ARG_ENABLE([dht-bootstrap], + [AC_HELP_STRING([--enable-dht-bootstrap], [build DHT bootstrap utility (default: disabled)]) ], + [ + if test "x$enableval" = "xno"; then + BUILD_DHT_BOOTSTRAP="no" + elif test "x$enableval" = "xyes"; then + BUILD_DHT_BOOTSTRAP="yes" + fi + ] +) + + +AC_ARG_ENABLE([rt], + [AC_HELP_STRING([--disable-rt], [Disables the librt check (default: auto)]) ], + [ + if test "x$enableval" = "xno"; then + DISABLE_RT="yes" + elif test "x$enableval" = "xyes"; then + DISABLE_RT="no" + fi + ] +) + +AC_ARG_ENABLE([testing], + [AC_HELP_STRING([--disable-testing], [build various testing tools (default: auto)]) ], + [ + if test "x$enableval" = "xno"; then + BUILD_TESTING="no" + elif test "x$enableval" = "xyes"; then + BUILD_TESTING="yes" + fi + ] +) + +AC_ARG_ENABLE([[epoll]], + [AS_HELP_STRING([[--enable-epoll[=ARG]]], [enable epoll support (yes, no, auto) [auto]])], + [enable_epoll=${enableval}], + [enable_epoll='auto'] + ) + +AC_ARG_ENABLE([[ipv6]], + [AS_HELP_STRING([[--disable-ipv6[=ARG]]], [use ipv4 in tests (yes, no, auto) [auto]])], + [use_ipv6=${enableval}], + [use_ipv6='auto'] + ) + +if test "$use_ipv6" != "yes"; then + AC_DEFINE([USE_IPV6],[0],[define to 0 to force ipv4]) +fi + +AC_ARG_ENABLE([[test_network]], + [AS_HELP_STRING([[--enable-test-network[=ARG]]], [build tox for a test network incompatible with the main DHT [no]])], + [use_test_network=${enableval}], + [use_test_network='no'] + ) + +if test "$use_test_network" == "yes"; then + AC_DEFINE([USE_TEST_NETWORK],[1],[define to 1 to enable the test network]) +fi + +AX_HAVE_EPOLL +if test "$enable_epoll" != "no"; then + if test "${ax_cv_have_epoll}" = "yes"; then + AC_DEFINE([TCP_SERVER_USE_EPOLL],[1],[define to 1 to enable epoll support]) + enable_epoll='yes' + else + if test "$enable_epoll" = "yes"; then + AC_MSG_ERROR([[Support for epoll was explicitly requested but cannot be enabled on this platform.]]) + fi + enable_epoll='no' + fi +fi + +DEPSEARCH= +LIBSODIUM_SEARCH_HEADERS= +LIBSODIUM_SEARCH_LIBS= +NACL_SEARCH_HEADERS= +NACL_SEARCH_LIBS= + +AC_ARG_WITH(dependency-search, + AC_HELP_STRING([--with-dependency-search=DIR], + [search for dependencies in DIR, i.e., look for libraries in + DIR/lib and for headers in DIR/include]), + [ + DEPSEARCH="$withval" + ] +) + +if test -n "$DEPSEARCH"; then + CFLAGS="$CFLAGS -I$DEPSEARCH/include" + CPPFLAGS="$CPPFLAGS -I$DEPSEARCH/include" + LDFLAGS="$LDFLAGS -L$DEPSEARCH/lib" + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$DEPSEARCH/lib/pkgconfig +fi + +AC_ARG_WITH(nacl-headers, + AC_HELP_STRING([--with-nacl-headers=DIR], + [search for nacl header files in DIR]), + [ + NACL_SEARCH_HEADERS="$withval" + AC_MSG_NOTICE([will search for nacl header files in $withval]) + ] +) + +AC_ARG_WITH(nacl-libs, + AC_HELP_STRING([--with-nacl-libs=DIR], + [search for nacl libraries in DIR]), + [ + NACL_SEARCH_LIBS="$withval" + AC_MSG_NOTICE([will search for nacl libraries in $withval]) + ] +) + +AC_ARG_WITH(libsodium-headers, + AC_HELP_STRING([--with-libsodium-headers=DIR], + [search for libsodium header files in DIR]), + [ + LIBSODIUM_SEARCH_HEADERS="$withval" + AC_MSG_NOTICE([will search for libsodium header files in $withval]) + ] +) + +AC_ARG_WITH(libsodium-libs, + AC_HELP_STRING([--with-libsodium-libs=DIR], + [search for libsodium libraries in DIR]), + [ + LIBSODIUM_SEARCH_LIBS="$withval" + AC_MSG_NOTICE([will search for libsodium libraries in $withval]) + ] +) + +if test "x$WANT_NACL" = "xyes"; then + enable_shared=no + enable_static=yes +fi + +# Checks for programs. +AC_PROG_CC_C99 + +if test "x$ac_cv_prog_cc_c99" = "xno" ; then + AC_MSG_ERROR([c-toxcore requires a C99 compatible compiler]) +fi + +AM_PROG_CC_C_O +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) +AC_LIBTOOL_WIN32_DLL +AC_PROG_LIBTOOL + +WIN32=no +MACH=no +AC_CANONICAL_HOST +case $host_os in + *mingw*) + WIN32="yes" + EXTRA_LT_LDFLAGS="$EXTRA_LT_LDFLAGS -no-undefined" + ;; + *solaris*) + LIBS="$LIBS -lssp -lsocket -lnsl" + ;; + *qnx*) + LIBS="$LIBS -lsocket" + ;; + *freebsd*|*openbsd*) + LDFLAGS="$LDFLAGS -L/usr/local/lib" + CFLAGS="$CFLAGS -I/usr/local/include" + CPPFLAGS="$CPPFLAGS -I/usr/local/include" + ADD_NACL_OBJECTS_TO_PKGCONFIG="no" + ;; + darwin*) + MACH=yes + ;; +esac +AM_CONDITIONAL(WIN32, test "x$WIN32" = "xyes") + +AC_SUBST(EXTRA_LT_LDFLAGS) + +# Needed math flags for some compilers + +MATH_LDFLAGS="-lm" +AC_SUBST(MATH_LDFLAGS) + +# Checks for libraries. +AC_CHECK_FUNCS([explicit_bzero memset_s]) +PKG_CHECK_MODULES([LIBSODIUM], [libsodium], + [ + LIBSODIUM_FOUND="yes" + ], + [ + LIBSODIUM_FOUND="no" + ]) + +if test "x$WANT_NACL" = "xyes"; then + NACL_LIBS= + NACL_LDFLAGS= + NACL_OBJECTS= + NACL_OBJECTS_PKGCONFIG= + LDFLAGS_SAVE="$LDFLAGS" + if test -n "$NACL_SEARCH_LIBS"; then + LDFLAGS="-L$NACL_SEARCH_LIBS $LDFLAGS" + AC_CHECK_LIB(nacl, random, + [ + NACL_LDFLAGS="-L$NACL_SEARCH_LIBS" + NACL_LIBS="-lnacl" + ], + [ + AC_MSG_ERROR([library nacl was not found in requested location $NACL_SEARCH_LIBS]) + ] + ) + else + AC_CHECK_LIB(nacl, random, + [], + [ + AC_MSG_ERROR([you enabled nacl support, but library nacl was not found on your system]) + ] + ) + fi + + if (test -f "$NACL_SEARCH_LIBS/cpucycles.o") && + (test -f "$NACL_SEARCH_LIBS/randombytes.o"); then + NACL_OBJECTS="$NACL_SEARCH_LIBS/cpucycles.o $NACL_SEARCH_LIBS/randombytes.o" + AC_MSG_NOTICE([found extra NaCl objects: $NACL_OBJECTS]) + if test "x$ADD_NACL_OBJECTS_TO_PKGCONFIG" = "xyes"; then + AC_MSG_NOTICE([adding extra NaCl objects to pkg-config file]) + NACL_OBJECTS_PKGCONFIG="$NACL_OBJECTS" + fi + else + AC_MSG_ERROR([required NaCl object files cpucycles.o randombytes.o not found, please specify their location using the --with-nacl-libs parameter]) + fi + + LDFLAGS="$LDFLAGS_SAVE" + AC_SUBST(NACL_LIBS) + AC_SUBST(NACL_LDFLAGS) + AC_SUBST(NACL_OBJECTS) + AC_SUBST(NACL_OBJECTS_PKGCONFIG) +elif test "x$LIBSODIUM_FOUND" = "xno"; then + LIBSODIUM_LIBS= + LIBSODIUM_LDFLAGS= + LDFLAGS_SAVE="$LDFLAGS" + if test -n "$LIBSODIUM_SEARCH_LIBS"; then + LDFLAGS="-L$LIBSODIUM_SEARCH_LIBS $LDFLAGS" + AC_CHECK_LIB(sodium, crypto_pwhash_scryptsalsa208sha256, + [ + LIBSODIUM_LDFLAGS="-L$LIBSODIUM_SEARCH_LIBS" + LIBSODIUM_LIBS="-lsodium" + ], + [ + AC_MSG_ERROR([required library libsodium was not found in requested location $LIBSODIUM_SEARCH_LIBS or library version is too old]) + ] + ) + else + AC_CHECK_LIB(sodium, crypto_pwhash_scryptsalsa208sha256, + [], + [ + AC_MSG_ERROR([required library libsodium was not found on your system, please check http://download.libsodium.org/libsodium/releases/ or library version is too old]) + ] + ) + fi + + LDFLAGS="$LDFLAGS_SAVE" + AC_SUBST(LIBSODIUM_LIBS) + AC_SUBST(LIBSODIUM_LDFLAGS) +fi + +# Checks for header files. +AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/socket.h sys/time.h unistd.h]) + + +if test "x$WANT_NACL" = "xyes"; then + NACL_CFLAGS= + CFLAGS_SAVE="$CFLAGS" + CPPFLAGS_SAVE="$CPPFLAGS" + if test -n "$NACL_SEARCH_HEADERS"; then + CFLAGS="-I$NACL_SEARCH_HEADERS $CFLAGS" + CPPFLAGS="-I$NACL_SEARCH_HEADERS $CPPFLAGS" + AC_CHECK_HEADER(crypto_box.h, + [ + NACL_CFLAGS="-I$NACL_SEARCH_HEADERS" + ], + [ + AC_MSG_ERROR([header files for library nacl were not found in requested location $NACL_SEARCH_HEADERS]) + ] + ) + else + AC_CHECK_HEADER(crypto_box.h, + [], + [ + AC_MSG_ERROR([you enabled nacl support, but nacl header files were not found on your system]) + ] + ) + fi + CFLAGS="$CFLAGS_SAVE" + CPPFLAGS="$CPPFLAGS_SAVE" + AC_SUBST(NACL_CFLAGS) + AC_DEFINE([VANILLA_NACL], [1], [use nacl instead of libsodium]) +elif test "x$LIBSODIUM_FOUND" = "xno"; then + LIBSODIUM_CFLAGS= + CFLAGS_SAVE="$CFLAGS" + CPPFLAGS_SAVE="$CPPFLAGS" + if test -n "$LIBSODIUM_SEARCH_HEADERS"; then + CFLAGS="-I$LIBSODIUM_SEARCH_HEADERS $CFLAGS" + CPPFLAGS="-I$LIBSODIUM_SEARCH_HEADERS $CPPFLAGS" + AC_CHECK_HEADER(sodium.h, + [ + LIBSODIUM_CFLAGS="-I$LIBSODIUM_SEARCH_HEADERS" + ], + [ + AC_MSG_ERROR([header files for required library libsodium were not found in requested location $LIBSODIUM_SEARCH_HEADERS]) + ] + ) + else + AC_CHECK_HEADER(sodium.h, + [], + [ + AC_MSG_ERROR([header files for required library libsodium was not found on your system, please check http://download.libsodium.org/libsodium/releases/]) + ] + ) + fi + CFLAGS="$CFLAGS_SAVE" + CPPFLAGS="$CPPFLAGS_SAVE" + AC_SUBST(LIBSODIUM_CFLAGS) +fi + +# Checks for library functions. +if (test "x$WIN32" != "xyes") && (test "x$MACH" != "xyes") && (test "x${host_os#*openbsd}" = "x$host_os") && (test "x$DISABLE_RT" != "xyes"); then + AC_CHECK_LIB(rt, clock_gettime, + [ + RT_LIBS="-lrt" + AC_SUBST(RT_LIBS) + ], + [ + AC_MSG_ERROR([required library rt was not found on your system]) + ] + ) +fi + + +AX_PTHREAD( + [], + [ + AC_MSG_ERROR([required library pthread was not found on your system]) + ] +) + +AC_CHECK_LIB([pthread], [pthread_self], + [ + PTHREAD_LDFLAGS="-lpthread" + AC_SUBST(PTHREAD_LDFLAGS) + ] +) + +if test "x$BUILD_AV" = "xyes"; then + PKG_CHECK_MODULES([OPUS], [opus], + [], + [ + AC_MSG_WARN([disabling AV support $OPUS_PKG_ERRORS]) + BUILD_AV="no" + ] + ) +fi + +if test "x$BUILD_AV" = "xyes"; then + PKG_CHECK_MODULES([VPX], [vpx], + [], + [ + AC_MSG_WARN([disabling AV support $VPX_PKG_ERRORS]) + BUILD_AV="no" + ] + ) +fi + +if test "x$BUILD_AV" = "xyes"; then + # toxcore lib needs an global? + # So far this works okay + AV_LIBS="$OPUS_LIBS $VPX_LIBS" + AC_SUBST(AV_LIBS) + + AV_CFLAGS="$OPUS_CFLAGS $VPX_CFLAGS" + AC_SUBST(AV_CFLAGS) +fi + +if test -n "$PKG_CONFIG"; then + if test "x$BUILD_DHT_BOOTSTRAP_DAEMON" = "xyes"; then + PKG_CHECK_MODULES([LIBCONFIG], [libconfig >= 1.4.6], + [ + LIBCONFIG_FOUND="yes" + ], + [ + AC_MSG_WARN([$LIBCONFIG_PKG_ERRORS]) + AC_MSG_WARN([libconfig not available, will not build DHT bootstrap daemon]) + BUILD_DHT_BOOTSTRAP_DAEMON="no" + ]) + fi +else + AC_MSG_WARN([pkg-config was not found on your system, will search for libraries manually]) +fi + +if (test "x$BUILD_DHT_BOOTSTRAP_DAEMON" = "xyes") && \ + (test "x$LIBCONFIG_FOUND" = "xno"); then + AC_CHECK_HEADER(libconfig.h, + [], + [ + AC_MSG_WARN([header files for library libconfig was not found on your system, not building DHT bootstrap daemon]) + BUILD_DHT_BOOTSTRAP_DAEMON="no" + ] + ) + + if test "x$BUILD_DHT_BOOTSTRAP_DAEMON" = "xyes"; then + AC_CHECK_LIB(config, config_read, + [], + [ + AC_MSG_WARN([library libconfig was not found on the system]) + BUILD_DHT_BOOTSTRAP_DAEMON="no" + ] + ) + fi +fi + +if test "x$WIN32" = "xyes"; then + AC_CHECK_LIB(ws2_32, main, + [ + WINSOCK2_LIBS="-liphlpapi -lws2_32" + AC_SUBST(WINSOCK2_LIBS) + ], + [ + AC_MSG_ERROR([required library was not found on the system, please check your MinGW installation]) + ] + ) +fi + +AM_CONDITIONAL(BUILD_DHT_BOOTSTRAP_DAEMON, test "x$BUILD_DHT_BOOTSTRAP_DAEMON" = "xyes") +AM_CONDITIONAL(BUILD_DHT_BOOTSTRAP, test "x$BUILD_DHT_BOOTSTRAP" = "xyes") +AM_CONDITIONAL(BUILD_TESTS, test "x$BUILD_TESTS" = "xyes") +AM_CONDITIONAL(BUILD_AV, test "x$BUILD_AV" = "xyes") +AM_CONDITIONAL(BUILD_TESTING, test "x$BUILD_TESTING" = "xyes") +AM_CONDITIONAL(WITH_NACL, test "x$WANT_NACL" = "xyes") +AM_CONDITIONAL(WIN32, test "x$WIN32" = "xyes") + +AC_CONFIG_FILES([Makefile + build/Makefile + libtoxcore.pc + tox.spec + ]) + +AM_COND_IF(BUILD_AV, + [ + AC_CONFIG_FILES([libtoxav.pc]) + ],) + +AC_OUTPUT diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 0000000..e31d670 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,2638 @@ +# Doxyfile 1.9.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Tox Protocol Reference Implementation" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "The future of online communications" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = _docs + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = NO + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = toxav toxcore toxencryptsave + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATOR_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /