pax_global_header00006660000000000000000000000064141535072440014517gustar00rootroot0000000000000052 comment=4348b96a5b482134a9cd55cb0ef9616798b4eb3c c-toxcore-0.2.13/000077500000000000000000000000001415350724400135055ustar00rootroot00000000000000c-toxcore-0.2.13/.circleci/000077500000000000000000000000001415350724400153405ustar00rootroot00000000000000c-toxcore-0.2.13/.circleci/cmake-asan000077500000000000000000000017461415350724400172760ustar00rootroot00000000000000#!/bin/bash set -eu CACHEDIR="$HOME/cache" . ".travis/flags-$CC.sh" add_flag -Werror add_flag -fdiagnostics-color=always add_flag -fno-omit-frame-pointer add_flag -fsanitize=address,undefined 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=ON \ -DSTRICT_ABI=ON \ -DTEST_TIMEOUT_SECONDS=120 \ -DUSE_IPV6=OFF \ -DAUTOTEST=ON cd _build ninja install -j"$(nproc)" export 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 || ctest -j50 --output-on-failure --rerun-failed c-toxcore-0.2.13/.circleci/cmake-tsan000077500000000000000000000015211415350724400173100ustar00rootroot00000000000000#!/bin/bash set -eu CACHEDIR="$HOME/cache" . ".travis/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" \ -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="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 || ctest -j50 --output-on-failure --rerun-failed c-toxcore-0.2.13/.circleci/config.yml000066400000000000000000000037331415350724400173360ustar00rootroot00000000000000--- version: 2 workflows: version: 2 program-analysis: jobs: # Dynamic analysis - asan - tsan # Static analysis - static-analysis - infer jobs: asan: working_directory: ~/work docker: - image: ubuntu steps: - checkout - run: &apt_install apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends clang cmake libconfig-dev libgtest-dev libopus-dev libsodium-dev libvpx-dev llvm-dev ninja-build pkg-config - run: CC=clang .circleci/cmake-asan tsan: working_directory: ~/work docker: - image: ubuntu steps: - checkout - run: *apt_install - run: CC=clang .circleci/cmake-tsan infer: working_directory: ~/work docker: - image: toxchat/infer steps: - run: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends git libopus-dev libsodium-dev libvpx-dev pkg-config - checkout - run: infer --no-progress-bar -- cc toxav/*.c toxcore/*.c $(pkg-config --cflags opus vpx) static-analysis: working_directory: ~/work docker: - image: ubuntu steps: - checkout - run: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends clang cppcheck g++ libconfig-dev libopus-dev libsodium-dev libvpx-dev llvm - run: other/analysis/check_logger_levels - run: other/analysis/run-check-recursion - run: other/analysis/run-clang - run: other/analysis/run-clang-analyze - run: other/analysis/run-cppcheck - run: other/analysis/run-gcc c-toxcore-0.2.13/.cirrus.yml000066400000000000000000000017521415350724400156220ustar00rootroot00000000000000--- cirrus-ci_task: container: image: toxchat/toktok-stack:0.0.23-third_party cpu: 2 memory: 2G configure_script: - /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 --config=release -- //c-toxcore/... -//c-toxcore/auto_tests:tcp_relay_test # TODO(robinlinden): Why does this pass locally but not in Cirrus? cimple_task: container: image: toxchat/toktok-stack:0.0.23-third_party cpu: 2 memory: 4G configure_script: - /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 --config=release -- //c-toxcore/... c-toxcore-0.2.13/.editorconfig000066400000000000000000000004551415350724400161660ustar00rootroot00000000000000root = 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 c-toxcore-0.2.13/.github/000077500000000000000000000000001415350724400150455ustar00rootroot00000000000000c-toxcore-0.2.13/.github/CODEOWNERS000066400000000000000000000000311415350724400164320ustar00rootroot00000000000000/.github/ @TokTok/admins c-toxcore-0.2.13/.github/settings.yml000066400000000000000000000021131415350724400174250ustar00rootroot00000000000000--- _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: - Codacy Static Code Analysis - CodeFactor - "ci/circleci: asan" - "ci/circleci: infer" - "ci/circleci: static-analysis" - "ci/circleci: tsan" - cimple - cirrus-ci - code-review/reviewable - continuous-integration/appveyor/pr # 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" c-toxcore-0.2.13/.gitignore000066400000000000000000000014471415350724400155030ustar00rootroot00000000000000# OS files .DS_Store .DS_Store? ._* .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/ #kdevelop .kdev/ *.kdev* # VScode .vscode/ # Netbeans nbproject # astyle *.orig # Android buildscript android-toolchain-* toxcore-android-* # cscope files list cscope.files # rpm tox.spec c-toxcore-0.2.13/.hadolint.yaml000066400000000000000000000000711415350724400162470ustar00rootroot00000000000000--- ignored: - DL3007 - DL3008 - DL3013 - DL3018 c-toxcore-0.2.13/.restyled.yaml000066400000000000000000000006111415350724400163000ustar00rootroot00000000000000--- exclude: - "**/*.api.h" - "toxencryptsave/crypto_pwhash_scryptsalsa208sha256/**/*" restylers: - astyle: arguments: ["--options=other/astyle/astylerc"] - autopep8 - black - clang-format: arguments: ["-style={BasedOnStyle: Google, ColumnLimit: 100}"] include: - "**/*.cc" - prettier-yaml - reorder-python-imports - shellharden - shfmt - yapf c-toxcore-0.2.13/.travis.yml000066400000000000000000000053121415350724400156170ustar00rootroot00000000000000--- # For Pull Requests, we build only the first three jobs: autotools on Linux, # cmake on Linux, and cmake on Windows 32 bit. # # For branch builds and cron builds (once a day), we build all the jobs. This is # achieved using the "if: type IN (push, api, cron)" fields in the jobs list. language: c dist: xenial os: linux jobs: include: - stage: "Stage 1" env: JOB=cmake-linux compiler: gcc addons: apt: packages: - libconfig-dev # For tox-bootstrapd. - libopus-dev # For toxav. - libsodium-dev # For toxcore. - libgtest-dev # For unit tests. - libvpx-dev # For toxav. - ninja-build - pylint3 install: .travis/$JOB install script: .travis/$JOB script after_script: .travis/upload-coverage - stage: "Stage 1" env: JOB=autotools-linux compiler: clang addons: apt: packages: - libconfig-dev # For tox-bootstrapd. - libopus-dev # For toxav. - libvpx-dev # For toxav. install: .travis/$JOB install script: .travis/$JOB script - stage: "Stage 1" env: JOB=tox-bootstrapd-docker services: [docker] script: .travis/$JOB - stage: "Stage 1" if: type IN (push, api, cron) env: JOB=cmake-win32 services: [docker] install: .travis/$JOB install script: .travis/$JOB script - stage: "Stage 1" if: type IN (push, api, cron) env: JOB=cmake-win64 services: [docker] install: .travis/$JOB install script: .travis/$JOB script - stage: "Stage 1" if: type IN (push, api, cron) env: JOB=cmake-freebsd install: .travis/$JOB-stage1 install script: .travis/$JOB-stage1 script - stage: "Stage 1" if: type IN (push, api, cron) env: JOB=cmake-osx os: osx install: .travis/$JOB install script: .travis/$JOB script - stage: "Stage 2" if: type IN (push, api, cron) env: JOB=cmake-freebsd install: .travis/$JOB-stage2 install script: .travis/$JOB-stage2 script fast_finish: true cache: directories: - $HOME/cache - /opt/freebsd/cache notifications: irc: channels: - "chat.freenode.net#toktok-status" template: - "%{result} %{repository_name} %{build_url}" - "#%{build_number} changes: %{compare_url}" # Only build pull requests and releases, don't build master on pushes, # except through api or cron. if: type IN (pull_request, api, cron) OR tag IS present c-toxcore-0.2.13/.travis/000077500000000000000000000000001415350724400150735ustar00rootroot00000000000000c-toxcore-0.2.13/.travis/autotools-linux000077500000000000000000000024141415350724400202100ustar00rootroot00000000000000#!/bin/bash ACTION="$1" set -eu CACHEDIR="$HOME/cache" NPROC=$(nproc) travis_install() { # Install vanilla NaCl only. [ -f "$CACHEDIR/lib/amd64/libnacl.a" ] || { curl https://hyperelliptic.org/nacl/nacl-20110221.tar.bz2 | tar jx cd nacl-20110221 # pushd "./do" # "make install" mkdir -p "$CACHEDIR/include" mv build/*/include/* "$CACHEDIR/include" mkdir -p "$CACHEDIR/lib" mv build/*/lib/* "$CACHEDIR/lib" cd - # popd } } travis_script() { . ".travis/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 --enable-logging 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 } if [ "-z" "$ACTION" ]; then "travis_script" else "travis_$ACTION" fi c-toxcore-0.2.13/.travis/cmake-freebsd-env.sh000066400000000000000000000023171415350724400207100ustar00rootroot00000000000000#!/bin/bash # Common variables and functions NPROC=$(nproc) SCREEN_SESSION=freebsd SSH_PORT=10022 FREEBSD_VERSION="12.1" IMAGE_NAME=FreeBSD-$FREEBSD_VERSION-RELEASE-amd64.raw # https://download.freebsd.org/ftp/releases/VM-IMAGES/12.1-RELEASE/amd64/Latest/ IMAGE_SHA512="a65da6260f5f894fc86fbe1f27dad7800906da7cffaa5077f82682ab74b6dd46c4ce87158c14b726d74ca3c6d611bea3bb336164da3f1cb990550310b110da22" RUN() { ssh -t -o ConnectionAttempts=120 -o ConnectTimeout=2 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@localhost -p "$SSH_PORT" "$@" } start_vm() { screen -d -m qemu-system-x86_64 -curses -m 2048 -smp "$NPROC" -net user,hostfwd=tcp::"$SSH_PORT"-:22 -net nic "$IMAGE_NAME" # Wait for ssh to start listening on the port while ! echo "exit" | nc localhost "$SSH_PORT" | grep 'OpenSSH'; do sleep 5 done # Test that ssh works RUN uname -a RUN last } stop_vm() { # Turn it off # We use this contraption because for some reason `shutdown -h now` and # `poweroff` result in FreeBSD not shutting down on Travis (they work on my # machine though) RUN "shutdown -p +5sec && sleep 30" || true # Wait for the qemu process to terminate while pgrep qemu; do sleep 5 done } c-toxcore-0.2.13/.travis/cmake-freebsd-stage1000077500000000000000000000071421415350724400206770ustar00rootroot00000000000000#!/bin/bash # Download and initial setup of the FreeBSD VM ACTION="$1" set -eux . .travis/cmake-freebsd-env.sh travis_install() { git tag -l --sort=version:refname >GIT_TAGS OLD_PWD="$PWD" mkdir -p /opt/freebsd/cache cd /opt/freebsd/cache # === Get the VM image, set it up and cache === # Create image if it's not cached, or if this build script has changed, or a new toxcore tag was pushed sha256sum "$OLD_PWD/.travis/cmake-freebsd-env.sh" >/tmp/sha sha256sum "$OLD_PWD/.travis/cmake-freebsd-stage1" >>/tmp/sha sha256sum "$OLD_PWD/.travis/cmake-freebsd-stage1.expect" >>/tmp/sha if [ ! -f "./$IMAGE_NAME.tgz" ] || [ ! -f ./cmake-freebsd-stage1-all.sha256 ] || [ "$(cat cmake-freebsd-stage1-all.sha256)" != "$(cat /tmp/sha)" ] || ! diff -u ./GIT_TAGS "$OLD_PWD/GIT_TAGS"; then rm -rf ./* while true; do # Selecting random mirror from https://www.freebsd.org/doc/handbook/mirrors-ftp.html # Note that not all mirrors listed on that page are working, so we have removed them # There are no arrays in sh so we get a bit clever DL_MIRROR_1=1 DL_MIRROR_2=2 DL_MIRROR_3=3 DL_MIRROR_4=4 DL_MIRROR_5=5 DL_MIRROR_6=6 DL_MIRROR_7=7 DL_MIRROR_8=10 DL_MIRROR_9=11 DL_MIRROR_10=13 DL_MIRROR_11=14 # There are 11 mirrors DL_MIRROR_RANDOM=$(expr "$(date +%s)" % 11 + 1) DL_URL="ftp://ftp$(eval echo \$DL_MIRROR_"$DL_MIRROR_RANDOM").us.freebsd.org/pub/FreeBSD/releases/VM-IMAGES/$FREEBSD_VERSION-RELEASE/amd64/Latest/$IMAGE_NAME.xz" # Make sure there are no partial downloads from the previous loop iterations rm -rf ./* wget --tries 1 "$DL_URL" && break done if ! (echo "$IMAGE_SHA512 $IMAGE_NAME.xz" | sha512sum -c --status -); then echo "Error: sha512 of $IMAGE_NAME.xz doesn't match the known one" exit 1 fi unxz "$IMAGE_NAME.xz" sudo apt-get update sudo apt-get install -y qemu screen expect # The downloaded image has little free disk space qemu-img resize -f raw "$IMAGE_NAME" +5G NPROC=$NPROC SSH_PORT=$SSH_PORT IMAGE_NAME="$IMAGE_NAME" screen "$OLD_PWD/.travis/cmake-freebsd-stage1.expect" start_vm # Update system RUN env PAGER=cat env ASSUME_ALWAYS_YES=YES freebsd-update --not-running-from-cron fetch # It fails if there is nothing to install, so we make it always succeed with true RUN env PAGER=cat env ASSUME_ALWAYS_YES=YES freebsd-update --not-running-from-cron install || true # Update packages RUN env PAGER=cat env ASSUME_ALWAYS_YES=YES pkg upgrade # Install and set bash as the default shell for the root user RUN env PAGER=cat env ASSUME_ALWAYS_YES=YES pkg install bash RUN chsh -s /usr/local/bin/bash root # Install required toxcore dependencies RUN PAGER=cat ASSUME_ALWAYS_YES=YES pkg install \ git \ opus \ libconfig \ libvpx \ libsodium \ gmake \ cmake \ pkgconf \ portaudio \ libsndfile \ texinfo \ autotools # === Cache the VM image === stop_vm # Create cache tar -Sczvf "$IMAGE_NAME.tgz" "$IMAGE_NAME" rm "$IMAGE_NAME" cp "$OLD_PWD/GIT_TAGS" . sha256sum "$OLD_PWD/.travis/cmake-freebsd-env.sh" >cmake-freebsd-stage1-all.sha256 sha256sum "$OLD_PWD/.travis/cmake-freebsd-stage1" >>cmake-freebsd-stage1-all.sha256 sha256sum "$OLD_PWD/.travis/cmake-freebsd-stage1.expect" >>cmake-freebsd-stage1-all.sha256 ls -lh fi cd "$OLD_PWD" } travis_script() { echo "Nothing to do here. Building happens in stage 2." } if [ "-z" "$ACTION" ]; then "travis_script" else "travis_$ACTION" fi c-toxcore-0.2.13/.travis/cmake-freebsd-stage1.expect000077500000000000000000000022361415350724400221650ustar00rootroot00000000000000#!/usr/bin/expect -f set timeout -1 # Note: doesn't work if -nographic is used instead of -curses spawn qemu-system-x86_64 -curses -m 2048 -smp $env(NPROC) -net user,hostfwd=tcp::$env(SSH_PORT)-:22 -net nic "$env(IMAGE_NAME)" # Skip the boot menu expect "to boot or any other key to stop" send -- "\r" expect "login: " send -- "root\r" # Setup DHCP networking and paswordless ssh expect "root@freebsd:~ # " send -- "echo \"ifconfig_em0=DHCP\" >> /etc/rc.conf\r" expect "root@freebsd:~ # " send -- "echo \"Port 22\" >> /etc/ssh/sshd_config\r" expect "root@freebsd:~ # " send -- "echo \"PermitRootLogin yes\" >> /etc/ssh/sshd_config\r" expect "root@freebsd:~ # " send -- "echo \"PasswordAuthentication yes\" >> /etc/ssh/sshd_config\r" expect "root@freebsd:~ # " send -- "echo \"PermitEmptyPasswords yes\" >> /etc/ssh/sshd_config\r" expect "root@freebsd:~ # " send -- "echo \"sshd_enable=YES\" >> /etc/rc.conf\r" expect "root@freebsd:~ # " # Set the empty password send -- "passwd\r" expect "New Password:" send -- "\r" expect "Retype New Password:" send -- "\r" expect "root@freebsd:~ # " # Done send -- "poweroff\r" wait exit 0 c-toxcore-0.2.13/.travis/cmake-freebsd-stage2000077500000000000000000000031761415350724400207030ustar00rootroot00000000000000#!/bin/bash # Toxcore building ACTION="$1" set -eux . .travis/cmake-freebsd-env.sh travis_install() { sudo apt-get update sudo apt-get install -y qemu screen OLD_PWD="$PWD" mkdir -p /opt/freebsd/cache cd /opt/freebsd/cache # Extract the cached image tar -Sxzvf "$IMAGE_NAME.tgz" # Get the image we will be using out of the cached directory mv "$IMAGE_NAME" .. ls -lh cd .. ls -lh # === Get VM ready to build the code === start_vm cd "$OLD_PWD" # Copy over toxcore code from Travis to qemu scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -P "$SSH_PORT" -r ./* root@localhost:~ RUN ls -lh } travis_script() { CACHEDIR="none" . ".travis/flags-$CC.sh" add_ld_flag -Wl,-z,defs # Make compilation error on a warning add_flag -Werror RUN '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="_install" \ -DMIN_LOGGER_LEVEL=TRACE \ -DMUST_BUILD_TOXAV=ON \ -DNON_HERMETIC_TESTS=ON \ -DSTRICT_ABI=ON \ -DTEST_TIMEOUT_SECONDS=300 \ -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 || \ gmake "-j1" -C_build test ARGS="-j1 --rerun-failed" || \ gmake "-j1" -C_build test ARGS="-j1 --rerun-failed" CTEST_OUTPUT_ON_FAILURE=1 || \ true' } if [ "-z" "$ACTION" ]; then "travis_script" else "travis_$ACTION" fi c-toxcore-0.2.13/.travis/cmake-linux000077500000000000000000000052041415350724400172370ustar00rootroot00000000000000#!/bin/bash ACTION="$1" set -eu CACHEDIR="$HOME/cache" NPROC=$(nproc) ASTYLE="$CACHEDIR/astyle/build/gcc/bin/astyle" ASTYLE_VERSION=3.1 TRAVIS_TOOL="https://raw.githubusercontent.com/TokTok/ci-tools/master/bin/travis-haskell" travis_install() { bash <(curl -s "$TRAVIS_TOOL") download travis-haskell download TokTok/hs-tokstyle tokstyle "$HOME/.local" which coveralls || { # Install cpp-coveralls to upload test coverage results. pip install --user ndg-httpsclient urllib3[secure] cpp-coveralls # Work around https://github.com/eddyxu/cpp-coveralls/issues/108 by manually # installing the pyOpenSSL module and injecting it into urllib3 as per # https://urllib3.readthedocs.io/en/latest/user-guide.html#ssl-py2 sed -i -e '/^import sys$/a import urllib3.contrib.pyopenssl\nurllib3.contrib.pyopenssl.inject_into_urllib3()' "$(which coveralls)" } # Install astyle (version in ubuntu-precise too old). ([ -f "$ASTYLE" ] && "$ASTYLE" --version | grep "$ASTYLE_VERSION" >/dev/null) || { wget -O ../astyle.tar.gz "https://deb.debian.org/debian/pool/main/a/astyle/astyle_$ASTYLE_VERSION.orig.tar.gz" tar -xf ../astyle.tar.gz -C "$CACHEDIR" make -C "$CACHEDIR/astyle/build/gcc" clean make -C "$CACHEDIR/astyle/build/gcc" "-j$NPROC" } } run_static_analysis() { pylint3 -E other/analysis/check_recursion export CPPFLAGS="-isystem $CACHEDIR/include" export LDFLAGS="-L$CACHEDIR/lib" other/analysis/run-check_recursion other/analysis/run-clang other/analysis/run-clang-analyze } travis_script() { . ".travis/flags-$CC.sh" add_ld_flag -Wl,-z,defs # Make compilation error on a warning add_flag -Werror # Coverage flags. add_flag -fprofile-arcs -ftest-coverage "$ASTYLE" --version other/astyle/format-source . "$ASTYLE" echo "Running TokTok style checker" "$HOME/.local/bin/check-cimple" # Use () to run in a separate process so the exports are local. (run_static_analysis) other/analysis/check_logger_levels 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=ON \ -DSTRICT_ABI=ON \ -DTEST_TIMEOUT_SECONDS=120 \ -DUSE_IPV6=OFF \ -DAUTOTEST=ON cmake --build _build --parallel "$NPROC" --target install -- -k 0 cd _build # pushd ctest -j50 --output-on-failure || ctest -j50 --output-on-failure --rerun-failed cd - # popd } if [ "-z" "$ACTION" ]; then "travis_script" else "travis_$ACTION" fi c-toxcore-0.2.13/.travis/cmake-osx000077500000000000000000000020201415350724400167020ustar00rootroot00000000000000#!/bin/bash ACTION="$1" set -eu CACHEDIR="$HOME/cache" NPROC=$(sysctl -n hw.physicalcpu) travis_install() { # Workaround for bug in Homebrew where it only finds an old Ruby version. brew update brew install libsodium libvpx opus libconfig } travis_script() { . ".travis/flags-$CC.sh" add_ld_flag -undefined error # Make compilation error on a warning add_flag -Werror 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 make "-j$NPROC" test ARGS="-j50" || make "-j$NPROC" test ARGS="-j50 --rerun-failed" CTEST_OUTPUT_ON_FAILURE=1 cd - # popd } if [ "-z" "$ACTION" ]; then "travis_script" else "travis_$ACTION" fi c-toxcore-0.2.13/.travis/cmake-win32000077500000000000000000000001231415350724400170350ustar00rootroot00000000000000#!/bin/bash i686=true x86_64=false WINDOWS_ARCH=win32 . .travis/cmake-windows.sh c-toxcore-0.2.13/.travis/cmake-win64000077500000000000000000000001231415350724400170420ustar00rootroot00000000000000#!/bin/bash i686=false x86_64=true WINDOWS_ARCH=win64 . .travis/cmake-windows.sh c-toxcore-0.2.13/.travis/cmake-windows.sh000066400000000000000000000020561415350724400202020ustar00rootroot00000000000000#!/bin/bash ACTION="$1" set -eu CACHEDIR="$HOME/cache" NPROC=$(nproc) travis_install() { cd other/docker/windows docker build \ --build-arg SUPPORT_ARCH_i686="$i686" \ --build-arg SUPPORT_ARCH_x86_64="$x86_64" \ --build-arg SUPPORT_TEST=true \ -t toxcore-"$WINDOWS_ARCH" \ . } travis_script() { . ".travis/flags-$CC.sh" # Allows wine to display source code file names and line numbers on crash in # its backtrace. add_flag -gdwarf-2 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 \ toxcore-"$WINDOWS_ARCH" } if [ "-z" "$ACTION" ]; then "travis_script" else "travis_$ACTION" fi c-toxcore-0.2.13/.travis/flags-clang.sh000066400000000000000000000044271415350724400176140ustar00rootroot00000000000000#!/bin/bash . .travis/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++. # TODO(iphydf): Clean these up. Probably all of these are actual bugs. add_flag -Wno-cast-align # 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 # 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. add_flag -Wno-missing-field-initializers # 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 # 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 # 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 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=documentation-unknown-command add_flag -Wno-error=unreachable-code add_flag -Wno-error=unused-variable c-toxcore-0.2.13/.travis/flags-gcc.sh000066400000000000000000000010001415350724400172440ustar00rootroot00000000000000#!/bin/bash . .travis/flags.sh # Add all warning flags we can. add_flag -Wall add_flag -Wextra # 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 c-toxcore-0.2.13/.travis/flags.sh000066400000000000000000000015551415350724400165310ustar00rootroot00000000000000#!/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 "$@" } export LD_LIBRARY_PATH="$CACHEDIR/lib" export PKG_CONFIG_PATH="$CACHEDIR/lib/pkgconfig" # 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 c-toxcore-0.2.13/.travis/tox-bootstrapd-docker000077500000000000000000000032731415350724400212640ustar00rootroot00000000000000#!/bin/bash set -exu readarray -t FILES <<<"$(git ls-files)" tar c "${FILES[@]}" | docker build -f other/bootstrap_daemon/docker/Dockerfile -t toxchat/bootstrap-node - 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 \ -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 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 # Wait a bit befrore 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 c-toxcore-0.2.13/.travis/upload-coverage000077500000000000000000000003151415350724400200750ustar00rootroot00000000000000#!/bin/bash # We only submit coverage from the Linux build. coveralls \ --exclude auto_tests \ --exclude other \ --exclude testing \ --gcov-options '\-lp' bash <(curl -s https://codecov.io/bash) c-toxcore-0.2.13/BUILD.bazel000066400000000000000000000020021415350724400153550ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_library") load("//tools/project:build_defs.bzl", "project") project() genrule( name = "copy_headers", srcs = [ "//c-toxcore/toxav:public_headers", "//c-toxcore/toxcore:public_headers", "//c-toxcore/toxencryptsave:public_headers", ], outs = [ "tox/toxav.h", "tox/tox.h", "tox/toxencryptsave.h", ], cmd = """ cp $(location //c-toxcore/toxav:public_headers) $(GENDIR)/c-toxcore/tox/toxav.h cp $(location //c-toxcore/toxcore:public_headers) $(GENDIR)/c-toxcore/tox/tox.h cp $(location //c-toxcore/toxencryptsave:public_headers) $(GENDIR)/c-toxcore/tox/toxencryptsave.h """, ) cc_library( name = "c-toxcore", hdrs = [ "tox/tox.h", "tox/toxav.h", "tox/toxencryptsave.h", ], includes = ["."], visibility = ["//visibility:public"], deps = [ "//c-toxcore/toxav", "//c-toxcore/toxcore", "//c-toxcore/toxencryptsave", ], ) c-toxcore-0.2.13/CHANGELOG.md000066400000000000000000003025761415350724400153330ustar00rootroot00000000000000 ## 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. - [#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 - [#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 - [#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: - [#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) - [#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 ## 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. ## 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? - [#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 - [#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 ## 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: - [#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: - [#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 ## 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 c-toxcore-0.2.13/CMakeLists.txt000066400000000000000000000425061415350724400162540ustar00rootroot00000000000000################################################################################ # # 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) ################################################################################ # # :: 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 "13") 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(ApiDsl) 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++11") endif() else() # Set standard version for compiler. set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 11) 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() option(EXECUTION_TRACE "Print a function trace during execution (for debugging)" OFF) if(EXECUTION_TRACE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -finstrument-functions") 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_STDERR_LOGGER "Enable logging to stderr when the logger is NULL" OFF) if(USE_STDERR_LOGGER) add_definitions(-DUSE_STDERR_LOGGER=1) endif() option(NON_HERMETIC_TESTS "Whether to build and run tests that depend on an internet connection" OFF) option(BUILD_TOXAV "Whether to build the tox AV library" ON) option(MUST_BUILD_TOXAV "Fail the build if toxav cannot be built" OFF) 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() ################################################################################ # # :: 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) # LAYER 1: Crypto core # -------------------- apidsl(toxcore/crypto_core.api.h) set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/ccompat.h toxcore/crypto_core.c toxcore/crypto_core.h toxcore/crypto_core_mem.c) include(CheckFunctionExists) check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO) check_function_exists(memset_s HAVE_MEMSET_S) set(toxcore_LINK_MODULES ${toxcore_LINK_MODULES} ${LIBSODIUM_LIBRARIES}) set(toxcore_PKGCONFIG_REQUIRES ${toxcore_PKGCONFIG_REQUIRES} libsodium) # LAYER 2: Basic networking # ------------------------- set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/logger.c toxcore/logger.h toxcore/mono_time.c toxcore/mono_time.h toxcore/network.c toxcore/network.h toxcore/state.c toxcore/state.h toxcore/util.c toxcore/util.h) # LAYER 3: Distributed Hash Table # ------------------------------- apidsl(toxcore/LAN_discovery.api.h) apidsl(toxcore/ping.api.h) apidsl(toxcore/ping_array.api.h) set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/DHT.c toxcore/DHT.h toxcore/LAN_discovery.c toxcore/LAN_discovery.h toxcore/ping.c toxcore/ping.h toxcore/ping_array.c toxcore/ping_array.h) # LAYER 4: Onion routing, TCP connections, crypto connections # ----------------------------------------------------------- set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/TCP_client.c toxcore/TCP_client.h toxcore/TCP_connection.c toxcore/TCP_connection.h toxcore/TCP_server.c toxcore/TCP_server.h toxcore/list.c toxcore/list.h toxcore/net_crypto.c toxcore/net_crypto.h toxcore/onion.c toxcore/onion.h toxcore/onion_announce.c toxcore/onion_announce.h toxcore/onion_client.c toxcore/onion_client.h) # LAYER 5: Friend requests and connections # ---------------------------------------- set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/friend_connection.c toxcore/friend_connection.h toxcore/friend_requests.c toxcore/friend_requests.h) # LAYER 6: Tox messenger # ---------------------- set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/Messenger.c toxcore/Messenger.h) # LAYER 7: Group chats # -------------------- set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/group.c toxcore/group.h) # LAYER 8: Public API # ------------------- apidsl(toxcore/tox.api.h) set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/tox_api.c toxcore/tox.c toxcore/tox_private.h toxcore/tox.h) set(toxcore_API_HEADERS ${toxcore_API_HEADERS} ${toxcore_SOURCE_DIR}/toxcore/tox.h^tox) ################################################################################ # # :: Audio/Video Library # ################################################################################ if(BUILD_TOXAV) apidsl(toxav/toxav.api.h) 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_LINK_MODULES ${toxcore_LINK_MODULES} ${OPUS_LIBRARIES} ${VPX_LIBRARIES}) set(toxcore_PKGCONFIG_REQUIRES ${toxcore_PKGCONFIG_REQUIRES} opus vpx) set(toxcore_API_HEADERS ${toxcore_API_HEADERS} ${toxcore_SOURCE_DIR}/toxav/toxav.h^toxav) endif() ################################################################################ # # :: Block encryption libraries # ################################################################################ apidsl(toxencryptsave/toxencryptsave.api.h) 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 crypto_core) unit_test(toxcore mono_time) unit_test(toxcore ping_array) unit_test(toxcore util) ################################################################################ # # :: Automated regression tests: create a tox network and run integration tests # ################################################################################ set(misc_tools_SOURCES testing/misc_tools.c testing/misc_tools.h) if(EXECUTION_TRACE) set(misc_tools_SOURCES ${misc_tools_SOURCES} testing/trace.cc) endif() add_library(misc_tools ${misc_tools_SOURCES}) target_link_modules(misc_tools toxcore) set(TEST_TIMEOUT_SECONDS "" CACHE STRING "Limit runtime of each test to the number of seconds specified") if(ANDROID_CPU_FEATURES) # We need to compile cpufeatures.c as many times as there are executables, # because libvpx doesn't include it although it depends on it. We can't get # the link ordering right in cmake, so we need to compile the cpufeatures # library into every binary explicitly. # # The alternative is to #include the library in every main file, but I # (@iphydf) felt that this solution was cleaner. add_definitions(-DANDROID_CPU_FEATURES="${ANDROID_CPU_FEATURES}") set(CPUFEATURES other/cpufeatures.c) endif() option(AUTOTEST "Enable autotests (mainly for CI)" OFF) function(auto_test target) if(AUTOTEST AND NOT (MSVC AND ARGV1 STREQUAL "MSVC_DONT_BUILD")) add_executable(auto_${target}_test ${CPUFEATURES} auto_tests/${target}_test.c) target_link_modules(auto_${target}_test toxcore misc_tools) 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}") set_property(TEST ${target} PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw") endif() endif() endfunction() auto_test(TCP) 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 MSVC_DONT_BUILD) auto_test(dht MSVC_DONT_BUILD) auto_test(encryptsave) auto_test(file_transfer) auto_test(file_saving) auto_test(friend_connection) auto_test(friend_request) auto_test(invalid_tcp_proxy) auto_test(invalid_udp_proxy) auto_test(lan_discovery) auto_test(lossless_packet) auto_test(lossy_packet) auto_test(messenger MSVC_DONT_BUILD) 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(skeleton) auto_test(tox_many) auto_test(tox_many_tcp) auto_test(tox_one) 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) auto_test(toxav_basic) auto_test(toxav_many) endif() ################################################################################ # # :: Bootstrap daemon # ################################################################################ option(DHT_BOOTSTRAP "Enable building of DHT_bootstrap" ON) if(DHT_BOOTSTRAP) add_executable(DHT_bootstrap ${CPUFEATURES} 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() option(BOOTSTRAP_DAEMON "Enable building of tox-bootstrapd" ON) if(BOOTSTRAP_DAEMON AND WIN32) message(WARNING "Building tox-bootstrapd for Windows is not supported") set(BOOTSTRAP_DAEMON OFF) endif() if(BOOTSTRAP_DAEMON) if(NOT LIBCONFIG_FOUND) message(WARNING "Option BOOTSTRAP_DAEMON is enabled but required library LIBCONFIG was not found.") set(BOOTSTRAP_DAEMON OFF) else() add_executable(tox-bootstrapd ${CPUFEATURES} other/bootstrap_daemon/src/command_line_arguments.c other/bootstrap_daemon/src/command_line_arguments.h other/bootstrap_daemon/src/config.c other/bootstrap_daemon/src/config.h other/bootstrap_daemon/src/config_defaults.h other/bootstrap_daemon/src/global.h other/bootstrap_daemon/src/log.c other/bootstrap_daemon/src/log.h other/bootstrap_daemon/src/log_backend_stdout.c other/bootstrap_daemon/src/log_backend_stdout.h other/bootstrap_daemon/src/log_backend_syslog.c other/bootstrap_daemon/src/log_backend_syslog.h other/bootstrap_daemon/src/tox-bootstrapd.c other/bootstrap_node_packets.c other/bootstrap_node_packets.h) target_link_modules(tox-bootstrapd toxcore ${LIBCONFIG_LIBRARIES}) install(TARGETS tox-bootstrapd RUNTIME DESTINATION bin) endif() endif() ################################################################################ # # :: Test programs # ################################################################################ option(BUILD_MISC_TESTS "Build additional tests" OFF) if (BUILD_MISC_TESTS) add_executable(DHT_test ${CPUFEATURES} testing/DHT_test.c) target_link_modules(DHT_test toxcore misc_tools) add_executable(Messenger_test ${CPUFEATURES} testing/Messenger_test.c) target_link_modules(Messenger_test toxcore misc_tools) add_executable(random_testing ${CPUFEATURES} testing/random_testing.cc) target_link_modules(random_testing toxcore misc_tools) add_executable(save-generator other/fun/save-generator.c) target_link_modules(save-generator toxcore misc_tools) add_executable(afl_toxsave testing/afl_toxsave.c) target_link_modules(afl_toxsave toxcore) endif() c-toxcore-0.2.13/DONATORS000066400000000000000000000003611415350724400146610ustar00rootroot00000000000000Minnesota > 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 c-toxcore-0.2.13/INSTALL.md000066400000000000000000000424641415350724400151470ustar00rootroot00000000000000# 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). | #### Secondary There are some programs that are not plugged into the CMake build system which you might find interesting. You would need to build those programs yourself. These programs reside in [`other/fun`](other/fun) directory. | Name | Type | Dependencies | Platform | Description | |--------------------------|------------|----------------------|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `bootstrap_node_info` | Script | python3 | Cross-platform | Prints version and Message Of The Day (MOTD) information of a specified DHT bootstrap node. | | `cracker` | Executable | libnacl or libsodium | Cross-platform | Tries to find a curve25519 key pair, hex representation of the public key of which starts with a specified byte sequence. | | `make-funny-savefile` | Script | python | Cross-platform | Generates a Tox profile file (savedata file) with the provided key pair. Useful for generating Tox profiles from the output of cracker or strkey programs. | | `minimal-save-generator` | Executable | libsodium | Cross-platform | Generates a minimal Tox profile file (savedata file) with a random key pair. | | `save-generator` | Executable | libtoxcore | Cross-platform | Generates a Tox profile file (savedata file) with a random key pair using libtoxcore. Allows setting a name and adding friends. | | `sign` | Executable | libsodium | Cross-platform | Signs a file with a ed25519 key. | | `strkey` | Executable | libsodium | Cross-platform | Tries to find a curve25519 key pair, hex representation of the public key of which contains a specified byte pattern at a specified position or at any position. | ## 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. #### 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_MISC_TESTS` | Build additional tests. | ON or OFF | OFF | | `BUILD_TOXAV` | Whether to build the tox AV library. | ON or OFF | ON | | `CMAKE_INSTALL_PREFIX` | Path to where everything should be installed. | Directory path. | Platform-dependent. Refer to CMake documentation. | | `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 | | `MIN_LOGGER_LEVEL` | Logging level to use. | TRACE, DEBUG, INFO, WARNING, ERROR or nothing (empty string) for default. | Empty string. | | `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 MIN_LOGGER_LEVEL=DEBUG \ -D CMAKE_INSTALL_PREFIX=/opt \ -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.9.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. c-toxcore-0.2.13/LICENSE000066400000000000000000001045141415350724400145170ustar00rootroot00000000000000 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 . c-toxcore-0.2.13/Makefile.am000066400000000000000000000013451415350724400155440ustar00rootroot00000000000000SUBDIRS = 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 c-toxcore-0.2.13/README.md000066400000000000000000000156721415350724400147770ustar00rootroot00000000000000# ![Project Tox](https://raw.github.com/TokTok/c-toxcore/master/other/tox.png "Project Tox") **Current build status:** [![Build Status](https://travis-ci.org/TokTok/c-toxcore.svg?branch=master)](https://travis-ci.org/TokTok/c-toxcore) **Current Coverage:** [![Coverage Status](https://coveralls.io/repos/github/TokTok/c-toxcore/badge.svg?branch=master)](https://coveralls.io/github/TokTok/c-toxcore?branch=master) [**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://wiki.tox.chat/Binaries) **|** [**Clients**](https://wiki.tox.chat/doku.php?id=clients) **|** [**Compiling**](/INSTALL.md) **|** [**Toxcore's Projects**](https://github.com/TokTok/c-toxcore/projects) **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). 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) c-toxcore-0.2.13/appveyor.yml000066400000000000000000000006701415350724400161000ustar00rootroot00000000000000--- cache: - '%USERPROFILE%\.conan' install: - set PATH=C:\Python38-x64\Scripts;%PATH% - py -3 -m pip install conan before_build: - conan install -if _build . build_script: - conan build -bf _build -if _build . test_script: # TODO(iphydf): Tests are unstable and slow on windows at the moment. - set CONAN_CPU_COUNT=50 - conan install -if _build -o with_tests=True . - conan build -bf _build -if _build . & exit 0 c-toxcore-0.2.13/auto_tests/000077500000000000000000000000001415350724400156775ustar00rootroot00000000000000c-toxcore-0.2.13/auto_tests/BUILD.bazel000066400000000000000000000014601415350724400175560ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") cc_library( name = "check_compat", testonly = True, hdrs = ["check_compat.h"], ) cc_library( name = "run_auto_test", testonly = True, hdrs = ["run_auto_test.h"], ) flaky_tests = { "crypto_core_test": True, "lan_discovery_test": True, } [cc_test( name = src[:-2], size = "small", srcs = [src], args = ["$(location %s)" % src], data = glob(["data/*"]), flaky = flaky_tests.get( src[:-2], False, ), deps = [ ":check_compat", ":run_auto_test", "//c-toxcore/testing:misc_tools", "//c-toxcore/toxav", "//c-toxcore/toxcore", "//c-toxcore/toxcore:DHT_srcs", "//c-toxcore/toxencryptsave", ], ) for src in glob(["*_test.c"])] c-toxcore-0.2.13/auto_tests/Makefile.inc000066400000000000000000000174421415350724400201170ustar00rootroot00000000000000if BUILD_TESTS TESTS = \ bootstrap_test \ conference_double_invite_test \ conference_invite_merge_test \ conference_peer_nick_test \ conference_simple_test \ conference_test \ conference_two_test \ crypto_test \ dht_test \ encryptsave_test \ file_saving_test \ file_transfer_test \ friend_connection_test \ friend_request_test \ invalid_tcp_proxy_test \ invalid_udp_proxy_test \ lan_discovery_test \ lossless_packet_test \ lossy_packet_test \ messenger_test \ network_test \ onion_test \ overflow_recvq_test \ overflow_sendq_test \ reconnect_test \ save_compatibility_test \ save_friend_test \ save_load_test \ send_message_test \ set_name_test \ set_status_message_test \ skeleton_test \ TCP_test \ tcp_relay_test \ tox_many_tcp_test \ tox_many_test \ tox_one_test \ tox_strncasecmp_test \ typing_test \ version_test AUTOTEST_CFLAGS = \ $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) AUTOTEST_LDADD = \ $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ libmisc_tools.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) bootstrap_test_SOURCES = ../auto_tests/bootstrap_test.c bootstrap_test_CFLAGS = $(AUTOTEST_CFLAGS) bootstrap_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) dht_test_SOURCES = ../auto_tests/dht_test.c dht_test_CFLAGS = $(AUTOTEST_CFLAGS) dht_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) 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) 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) messenger_test_SOURCES = ../auto_tests/messenger_test.c messenger_test_CFLAGS = $(AUTOTEST_CFLAGS) messenger_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) save_load_test_SOURCES = ../auto_tests/save_load_test.c save_load_test_CFLAGS = $(AUTOTEST_CFLAGS) save_load_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) skeleton_test_SOURCES = ../auto_tests/skeleton_test.c skeleton_test_CFLAGS = $(AUTOTEST_CFLAGS) skeleton_test_LDADD = $(AUTOTEST_LDADD) tcp_relay_test_SOURCES = ../auto_tests/tcp_relay_test.c tcp_relay_test_CFLAGS = $(AUTOTEST_CFLAGS) tcp_relay_test_LDADD = $(AUTOTEST_LDADD) TCP_test_SOURCES = ../auto_tests/TCP_test.c TCP_test_CFLAGS = $(AUTOTEST_CFLAGS) TCP_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_one_test_SOURCES = ../auto_tests/tox_one_test.c tox_one_test_CFLAGS = $(AUTOTEST_CFLAGS) tox_one_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/run_auto_test.h c-toxcore-0.2.13/auto_tests/TCP_test.c000066400000000000000000001062741415350724400175420ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "../testing/misc_tools.h" #include "../toxcore/TCP_client.h" #include "../toxcore/TCP_server.h" #include "../toxcore/crypto_core.h" #include "../toxcore/mono_time.h" #include "../toxcore/util.h" #include "check_compat.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 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_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}; START_TEST(test_basic) { Mono_Time *mono_time = mono_time_new(); Logger *logger = logger_new(); // 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(self_public_key, self_secret_key); TCP_Server *tcp_s = new_TCP_server(logger, USE_IPV6, NUM_PORTS, ports, self_secret_key, 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}; // Check all opened ports for connectivity. for (uint8_t i = 0; i < NUM_PORTS; i++) { sock = net_socket(net_family_ipv6, TOX_SOCK_STREAM, TOX_PROTO_TCP); IP_Port ip_port_loopback; ip_port_loopback.ip = get_loopback(); ip_port_loopback.port = net_htons(ports[i]); int ret = net_connect(sock, ip_port_loopback); ck_assert_msg(ret == 0, "Failed to connect to created TCP relay server on port %d.", ports[i]); // Leave open one connection for the next test. if (i + 1 < NUM_PORTS) { kill_sock(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(f_public_key, f_secret_key); random_nonce(f_nonce); // Generation of the initial handshake. uint8_t t_secret_key[CRYPTO_SECRET_KEY_SIZE]; uint8_t handshake_plain[TCP_HANDSHAKE_PLAIN_SIZE]; crypto_new_keypair(handshake_plain, t_secret_key); memcpy(handshake_plain + CRYPTO_PUBLIC_KEY_SIZE, f_nonce, CRYPTO_NONCE_SIZE); uint8_t handshake[TCP_CLIENT_HANDSHAKE_SIZE]; memcpy(handshake, f_public_key, CRYPTO_PUBLIC_KEY_SIZE); random_nonce(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."); // Sending the handshake ck_assert_msg(net_send(sock, handshake, TCP_CLIENT_HANDSHAKE_SIZE - 1) == 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(sock, handshake + (TCP_CLIENT_HANDSHAKE_SIZE - 1), 1) == 1, "The attempt to send the last byte of handshake failed."); 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(sock, response, TCP_SERVER_HANDSHAKE_SIZE) == 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(sock, r_req + i, msg_length) == 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(sock, packet_resp, 2 + 2 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE); ck_assert_msg(recv_data_len == 2 + 2 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE, "Failed to receive server response to request. %u", 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(public_key_cmp(packet_resp_plain + 2, f_public_key) == 0, "Server sent the wrong public key."); // Closing connections. kill_sock(sock); kill_TCP_server(tcp_s); logger_kill(logger); mono_time_free(mono_time); } END_TEST struct sec_TCP_con { Socket sock; 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(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); Socket sock = net_socket(net_family_ipv6, TOX_SOCK_STREAM, TOX_PROTO_TCP); IP_Port ip_port_loopback; ip_port_loopback.ip = get_loopback(); ip_port_loopback.port = net_htons(ports[random_u32() % NUM_PORTS]); int ret = net_connect(sock, ip_port_loopback); ck_assert_msg(ret == 0, "Failed to connect to the test TCP relay server."); uint8_t f_secret_key[CRYPTO_SECRET_KEY_SIZE]; crypto_new_keypair(sec_c->public_key, f_secret_key); random_nonce(sec_c->sent_nonce); uint8_t t_secret_key[CRYPTO_SECRET_KEY_SIZE]; uint8_t handshake_plain[TCP_HANDSHAKE_PLAIN_SIZE]; crypto_new_keypair(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(handshake + CRYPTO_PUBLIC_KEY_SIZE); 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(sock, handshake, TCP_CLIENT_HANDSHAKE_SIZE - 1) == 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(sock, handshake + (TCP_CLIENT_HANDSHAKE_SIZE - 1), 1) == 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(sock, response, TCP_SERVER_HANDSHAKE_SIZE) == 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->sock); free(con); } static int write_packet_TCP_secure_connection(struct sec_TCP_con *con, 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); ck_assert_msg(net_send(con->sock, packet, SIZEOF_VLA(packet)) == SIZEOF_VLA(packet), "Failed to send a packet."); return 0; } static int read_packet_sec_TCP(struct sec_TCP_con *con, uint8_t *data, uint16_t length) { int rlen = net_recv(con->sock, data, length); 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; } START_TEST(test_some) { Mono_Time *mono_time = mono_time_new(); 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(self_public_key, self_secret_key); TCP_Server *tcp_s = new_TCP_server(logger, USE_IPV6, NUM_PORTS, ports, self_secret_key, 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(tcp_s, mono_time); struct sec_TCP_con *con2 = new_TCP_con(tcp_s, mono_time); struct sec_TCP_con *con3 = new_TCP_con(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_secure_connection(con1, requ_p, sizeof(requ_p)); memcpy(requ_p + 1, con1->public_key, CRYPTO_PUBLIC_KEY_SIZE); write_packet_TCP_secure_connection(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(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(public_key_cmp(data + 2, con3->public_key) == 0, "Key in response packet wrong."); // Connection 3 len = read_packet_sec_TCP(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(public_key_cmp(data + 2, con1->public_key) == 0, "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_secure_connection(con3, test_packet, sizeof(test_packet)); write_packet_TCP_secure_connection(con3, test_packet, sizeof(test_packet)); write_packet_TCP_secure_connection(con3, test_packet, sizeof(test_packet)); do_TCP_server_delay(tcp_s, mono_time, 50); len = read_packet_sec_TCP(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(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(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(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(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_secure_connection(con1, test_packet, sizeof(test_packet)); write_packet_TCP_secure_connection(con1, test_packet, sizeof(test_packet)); write_packet_TCP_secure_connection(con1, test_packet, sizeof(test_packet)); do_TCP_server_delay(tcp_s, mono_time, 50); len = read_packet_sec_TCP(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(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(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_secure_connection(con1, ping_packet, sizeof(ping_packet)); do_TCP_server_delay(tcp_s, mono_time, 50); len = read_packet_sec_TCP(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); } END_TEST 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 *)((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 (public_key_cmp(public_key, oob_pubkey) != 0) { 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; } START_TEST(test_client) { Mono_Time *mono_time = mono_time_new(); 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(self_public_key, self_secret_key); TCP_Server *tcp_s = new_TCP_server(logger, USE_IPV6, NUM_PORTS, ports, self_secret_key, 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(f_public_key, f_secret_key); IP_Port ip_port_tcp_s; ip_port_tcp_s.port = net_htons(ports[random_u32() % NUM_PORTS]); ip_port_tcp_s.ip = get_loopback(); TCP_Client_Connection *conn = new_TCP_connection(mono_time, 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(f2_public_key, f2_secret_key); ip_port_tcp_s.port = net_htons(ports[random_u32() % NUM_PORTS]); TCP_Client_Connection *conn2 = new_TCP_connection(mono_time, 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(conn2, f_public_key, data, 5); send_routing_request(conn, f2_public_key); send_routing_request(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(public_key_cmp(response_callback_public_key, f2_public_key) == 0, "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(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(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); } END_TEST // Test how the client handles servers that don't respond. START_TEST(test_client_invalid) { Mono_Time *mono_time = mono_time_new(); 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(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(f_public_key, f_secret_key); IP_Port ip_port_tcp_s; ip_port_tcp_s.port = net_htons(ports[random_u32() % NUM_PORTS]); ip_port_tcp_s.ip = get_loopback(); TCP_Client_Connection *conn = new_TCP_connection(mono_time, 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); } END_TEST #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; } START_TEST(test_tcp_connection) { Mono_Time *mono_time = mono_time_new(); Logger *logger = logger_new(); 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(self_public_key, self_secret_key); TCP_Server *tcp_s = new_TCP_server(logger, USE_IPV6, NUM_PORTS, ports, self_secret_key, nullptr); ck_assert_msg(public_key_cmp(tcp_server_public_key(tcp_s), self_public_key) == 0, "Wrong public key"); TCP_Proxy_Info proxy_info; proxy_info.proxy_type = TCP_PROXY_NONE; crypto_new_keypair(self_public_key, self_secret_key); TCP_Connections *tc_1 = new_tcp_connections(mono_time, self_secret_key, &proxy_info); ck_assert_msg(public_key_cmp(tcp_connections_public_key(tc_1), self_public_key) == 0, "Wrong public key"); crypto_new_keypair(self_public_key, self_secret_key); TCP_Connections *tc_2 = new_tcp_connections(mono_time, self_secret_key, &proxy_info); ck_assert_msg(public_key_cmp(tcp_connections_public_key(tc_2), self_public_key) == 0, "Wrong public key"); IP_Port ip_port_tcp_s; ip_port_tcp_s.port = net_htons(ports[random_u32() % 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() % 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); } END_TEST 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) { TCP_Connections *tcp_c = (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; } START_TEST(test_tcp_connection2) { Mono_Time *mono_time = mono_time_new(); Logger *logger = logger_new(); 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(self_public_key, self_secret_key); TCP_Server *tcp_s = new_TCP_server(logger, USE_IPV6, NUM_PORTS, ports, self_secret_key, nullptr); ck_assert_msg(public_key_cmp(tcp_server_public_key(tcp_s), self_public_key) == 0, "Wrong public key"); TCP_Proxy_Info proxy_info; proxy_info.proxy_type = TCP_PROXY_NONE; crypto_new_keypair(self_public_key, self_secret_key); TCP_Connections *tc_1 = new_tcp_connections(mono_time, self_secret_key, &proxy_info); ck_assert_msg(public_key_cmp(tcp_connections_public_key(tc_1), self_public_key) == 0, "Wrong public key"); crypto_new_keypair(self_public_key, self_secret_key); TCP_Connections *tc_2 = new_tcp_connections(mono_time, self_secret_key, &proxy_info); ck_assert_msg(public_key_cmp(tcp_connections_public_key(tc_2), self_public_key) == 0, "Wrong public key"); IP_Port ip_port_tcp_s; ip_port_tcp_s.port = net_htons(ports[random_u32() % 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); } END_TEST static Suite *TCP_suite(void) { Suite *s = suite_create("TCP"); DEFTESTCASE_SLOW(basic, 5); DEFTESTCASE_SLOW(some, 10); DEFTESTCASE_SLOW(client, 10); DEFTESTCASE_SLOW(client_invalid, 15); DEFTESTCASE_SLOW(tcp_connection, 20); DEFTESTCASE_SLOW(tcp_connection2, 20); return s; } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); Suite *TCP = TCP_suite(); SRunner *test_runner = srunner_create(TCP); int number_failed = 0; srunner_run_all(test_runner, CK_NORMAL); number_failed = srunner_ntests_failed(test_runner); srunner_free(test_runner); return number_failed; } c-toxcore-0.2.13/auto_tests/bootstrap_test.c000066400000000000000000000026451415350724400211260ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "../testing/misc_tools.h" #include "check_compat.h" static uint8_t const key1[] = { 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, }; static uint8_t const key2[] = { 0x3F, 0x0A, 0x45, 0xA2, 0x68, 0x36, 0x7C, 0x1B, 0xEA, 0x65, 0x2F, 0x25, 0x8C, 0x85, 0xF4, 0xA6, 0x6D, 0xA7, 0x6B, 0xCA, 0xA6, 0x67, 0xA4, 0x9E, 0x77, 0x0B, 0xCC, 0x49, 0x17, 0xAB, 0x6A, 0x25, }; int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); Tox *tox_udp = tox_new_log(nullptr, nullptr, nullptr); tox_bootstrap(tox_udp, "78.46.73.141", 33445, key1, nullptr); tox_bootstrap(tox_udp, "tox.initramfs.io", 33445, key2, nullptr); 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; } c-toxcore-0.2.13/auto_tests/check_compat.h000066400000000000000000000041551415350724400204750ustar00rootroot00000000000000#ifndef C_TOXCORE_AUTO_TESTS_CHECK_COMPAT_H #define C_TOXCORE_AUTO_TESTS_CHECK_COMPAT_H #include "../toxcore/ccompat.h" #include #include #include #define START_TEST(name) static void name(void) #define END_TEST #define DEFTESTCASE(NAME) test_##NAME() #define DEFTESTCASE_SLOW(NAME, TIMEOUT) test_##NAME() typedef struct Suite Suite; typedef struct SRunner SRunner; enum SRunMode { CK_NORMAL }; static inline Suite *suite_create(const char *title) { printf("Running test suite: %s\n", title); return nullptr; } static inline SRunner *srunner_create(Suite *s) { return nullptr; } static inline void srunner_free(SRunner *s) { } static inline void srunner_run_all(SRunner *r, int mode) { } static inline int srunner_ntests_failed(SRunner *r) { return 0; } #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 c-toxcore-0.2.13/auto_tests/conference_av_test.c000066400000000000000000000360341415350724400217050ustar00rootroot00000000000000/* Auto Tests: Conferences AV. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #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) typedef struct State { uint32_t index; uint64_t clock; bool invited_next; uint32_t received_audio_peers[NUM_AV_GROUP_TOX]; uint32_t received_audio_num; } State; #include "run_auto_test.h" static void handle_self_connection_status( Tox *tox, Tox_Connection connection_status, void *user_data) { const State *state = (State *)user_data; if (connection_status != TOX_CONNECTION_NONE) { printf("tox #%u: is now connected\n", state->index); } else { printf("tox #%u: is now disconnected\n", state->index); } } static void handle_friend_connection_status( Tox *tox, uint32_t friendnumber, Tox_Connection connection_status, void *user_data) { const State *state = (State *)user_data; if (connection_status != TOX_CONNECTION_NONE) { printf("tox #%u: is now connected to friend %u\n", state->index, friendnumber); } else { printf("tox #%u: is now disconnected from friend %u\n", state->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 *userdata) { if (samples == 0) { return; } State *state = (State *)userdata; 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 State *state = (State *)user_data; ck_assert_msg(type == TOX_CONFERENCE_TYPE_AV, "tox #%u: wrong conference type: %d", state->index, type); ck_assert_msg(toxav_join_av_groupchat(tox, friendnumber, data, length, audio_callback, user_data) == 0, "tox #%u: failed to join group", state->index); } static void handle_conference_connected( Tox *tox, uint32_t conference_number, void *user_data) { State *state = (State *)user_data; 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", state->index, err); printf("tox #%u: invited next friend\n", state->index); state->invited_next = true; } static bool toxes_are_disconnected_from_group(uint32_t tox_count, Tox **toxes, 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(toxes[i], 0, nullptr) > tox_count - num_disconnected) { return false; } } return true; } static void disconnect_toxes(uint32_t tox_count, Tox **toxes, State *state, 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(toxes[i], &state[i]); state[i].clock += 1000; } } c_sleep(20); } while (!toxes_are_disconnected_from_group(tox_count, toxes, disconnect_now)); invert = !invert; } while (invert); } static bool all_connected_to_group(uint32_t tox_count, Tox **toxes) { for (uint32_t i = 0; i < tox_count; i++) { if (tox_conference_peer_count(toxes[i], 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(bool *list, const uint32_t length) { uint32_t index; do { index = random_u32() % length; } while (list[index]); return index; } static bool all_got_audio(State *state, 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) { if (disabled[i] ^ (state[i].received_audio_num != NUM_AV_GROUP_TOX - num_disabled - 1)) { return false; } } return true; } static void reset_received_audio(Tox **toxes, State *state) { for (uint32_t j = 0; j < NUM_AV_GROUP_TOX; ++j) { state[j].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(Tox **toxes, State *state, const bool *disabled, bool quiet) { if (!quiet) { printf("testing sending and receiving audio\n"); } int16_t PCM[GROUP_AV_TEST_SAMPLES]; reset_received_audio(toxes, state); 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(toxes[i], 0, PCM, GROUP_AV_TEST_SAMPLES, 1, 48000) != 0) { if (!quiet) { ck_abort_msg("#%u failed to send audio", state[i].index); } return false; } } iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL); if (all_got_audio(state, disabled)) { return true; } } if (!quiet) { ck_abort_msg("group failed to receive audio"); } return false; } static void test_eventual_audio(Tox **toxes, State *state, const bool *disabled, uint64_t timeout) { uint64_t start = state[0].clock; while (state[0].clock < start + timeout) { if (test_audio(toxes, state, disabled, true) && test_audio(toxes, state, disabled, true)) { printf("audio test successful after %d seconds\n", (int)((state[0].clock - start) / 1000)); return; } } printf("audio seems not to be getting through: testing again with errors.\n"); test_audio(toxes, state, disabled, false); } static void do_audio(Tox **toxes, State *state, uint32_t iterations) { int16_t PCM[GROUP_AV_TEST_SAMPLES]; 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(toxes[i], 0, PCM, GROUP_AV_TEST_SAMPLES, 1, 48000) == 0, "#%u failed to send audio", state[i].index); iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, 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(Tox **toxes, State *state) { bool disabled[NUM_AV_GROUP_TOX] = {0}; test_audio(toxes, state, 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(toxes, state, 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(disconnected, NUM_AV_GROUP_TOX); disconnected[disconnect] = true; if (i < NUM_AV_DISCONNECT / 2) { restarting[disconnect] = true; printf("Restarting #%u\n", state[disconnect].index); } else { printf("Disconnecting #%u\n", state[disconnect].index); } } uint8_t *save[NUM_AV_GROUP_TOX]; size_t save_size[NUM_AV_GROUP_TOX]; for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { if (restarting[i]) { save_size[i] = tox_get_savedata_size(toxes[i]); 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(toxes[i], save[i]); tox_kill(toxes[i]); } } disconnect_toxes(NUM_AV_GROUP_TOX, toxes, state, disconnected, restarting); for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { if (restarting[i]) { 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, save[i], save_size[i]); toxes[i] = tox_new_log(options, nullptr, &state[i].index); tox_options_free(options); free(save[i]); set_mono_time_callback(toxes[i], &state[i]); } } printf("reconnecting toxes\n"); do { iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL); } while (!all_connected_to_group(NUM_AV_GROUP_TOX, toxes)); for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { if (restarting[i]) { ck_assert_msg(!toxav_groupchat_av_enabled(toxes[i], 0), "#%u restarted but av enabled", state[i].index); ck_assert_msg(toxav_groupchat_enable_av(toxes[i], 0, audio_callback, &state[i]) == 0, "#%u failed to re-enable av", state[i].index); ck_assert_msg(toxav_groupchat_av_enabled(toxes[i], 0), "#%u av not enabled even after enabling", state[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(toxes, state, 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(disabled, NUM_AV_GROUP_TOX); disabled[disable] = true; printf("Disabling #%u\n", state[disable].index); ck_assert_msg(toxav_groupchat_enable_av(toxes[disable], 0, audio_callback, &state[disable]) != 0, "#%u could enable already enabled av!", state[i].index); ck_assert_msg(toxav_groupchat_disable_av(toxes[disable], 0) == 0, "#%u failed to disable av", state[i].index); } // Run test without error to clear out messages from now-disabled peers. test_audio(toxes, state, disabled, true); printf("testing audio with some peers having disabled their av\n"); test_audio(toxes, state, 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(toxes[i], 0) != 0, "#%u could disable already disabled av!", state[i].index); ck_assert_msg(!toxav_groupchat_av_enabled(toxes[i], 0), "#%u av enabled after disabling", state[i].index); ck_assert_msg(toxav_groupchat_enable_av(toxes[i], 0, audio_callback, &state[i]) == 0, "#%u failed to re-enable av", state[i].index); } printf("testing audio after re-enabling all av\n"); test_eventual_audio(toxes, state, disabled, JITTER_SETTLE_TIME); } static void test_groupav(Tox **toxes, State *state) { const time_t test_start_time = time(nullptr); for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { tox_callback_self_connection_status(toxes[i], &handle_self_connection_status); tox_callback_friend_connection_status(toxes[i], &handle_friend_connection_status); tox_callback_conference_invite(toxes[i], &handle_conference_invite); tox_callback_conference_connected(toxes[i], &handle_conference_connected); } ck_assert_msg(toxav_add_av_groupchat(toxes[0], audio_callback, &state[0]) != UINT32_MAX, "failed to create group"); printf("tox #%u: inviting its first friend\n", state[0].index); ck_assert_msg(tox_conference_invite(toxes[0], 0, 0, nullptr) != 0, "failed to invite friend"); state[0].invited_next = true; printf("waiting for invitations to be made\n"); uint32_t invited_count = 0; do { iterate_all_wait(NUM_AV_GROUP_TOX, toxes, state, ITERATION_INTERVAL); invited_count = 0; for (uint32_t i = 0; i < NUM_AV_GROUP_TOX; ++i) { invited_count += state[i].invited_next; } } while (invited_count != NUM_AV_GROUP_TOX - 1); uint64_t pregroup_clock = state[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(NUM_AV_GROUP_TOX, toxes, state, 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(toxes[i], 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)((state[0].clock - pregroup_clock) / 1000)); run_conference_tests(toxes, state); 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_test(NUM_AV_GROUP_TOX, test_groupav, true); return 0; } c-toxcore-0.2.13/auto_tests/conference_double_invite_test.c000066400000000000000000000054131415350724400241240ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include typedef struct State { uint32_t index; uint64_t clock; bool self_online; bool friend_online; bool joined; uint32_t conference; } State; #include "run_auto_test.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) { State *state = (State *)user_data; fprintf(stderr, "handle_conference_invite(#%u, %u, %d, uint8_t[%u], _)\n", state->index, friend_number, type, (unsigned)length); fprintf(stderr, "tox%u joining conference\n", state->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", state->index, state->conference); state->joined = true; } } static void conference_double_invite_test(Tox **toxes, State *state) { // Conference callbacks. tox_callback_conference_invite(toxes[0], handle_conference_invite); tox_callback_conference_invite(toxes[1], handle_conference_invite); { // Create new conference, tox0 is the founder. Tox_Err_Conference_New err; state[0].conference = tox_conference_new(toxes[0], &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(toxes[0], 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(2, toxes, state, 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(toxes[0], 0, state[0].conference, nullptr); iterate_all_wait(2, toxes, state, ITERATION_INTERVAL); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); run_auto_test(2, conference_double_invite_test, false); return 0; } c-toxcore-0.2.13/auto_tests/conference_invite_merge_test.c000066400000000000000000000155311415350724400237530ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include typedef struct State { uint32_t index; uint64_t clock; size_t save_size; uint8_t *save_state; bool alive; bool connected; uint32_t conference; } State; #define NUM_INVITE_MERGE_TOX 5 #include "run_auto_test.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) { State *state = (State *)user_data; 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", state->index, state->conference); } } static void handle_conference_connected( Tox *tox, uint32_t conference_number, void *user_data) { State *state = (State *)user_data; fprintf(stderr, "#%u connected to conference %u\n", state->index, state->conference); state->connected = true; } static void iterate_alive(Tox **toxes, State *state) { for (uint32_t i = 0; i < NUM_INVITE_MERGE_TOX; i++) { if (!state[i].alive) { continue; } tox_iterate(toxes[i], &state[i]); state[i].clock += ITERATION_INTERVAL; } c_sleep(20); } static void save(Tox **toxes, State *state, uint32_t n) { fprintf(stderr, "Saving #%u\n", state[n].index); if (state[n].save_state != nullptr) { free(state[n].save_state); } state[n].save_size = tox_get_savedata_size(toxes[n]); state[n].save_state = (uint8_t *)malloc(state[n].save_size); ck_assert_msg(state[n].save_state != nullptr, "malloc failed"); tox_get_savedata(toxes[n], state[n].save_state); } static void kill(Tox **toxes, State *state, uint32_t n) { fprintf(stderr, "Killing #%u\n", state[n].index); state[n].alive = false; tox_kill(toxes[n]); } static void reload(Tox **toxes, State *state, uint32_t n) { if (state[n].alive) { state[n].alive = false; tox_kill(toxes[n]); } fprintf(stderr, "Reloading #%u\n", state[n].index); ck_assert(state[n].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, state[n].save_state, state[n].save_size); toxes[n] = tox_new_log(options, nullptr, &state[n].index); ck_assert(toxes[n] != nullptr); tox_options_free(options); set_mono_time_callback(toxes[n], &state[n]); state[n].alive = true; } static void wait_connected(Tox **toxes, State *state, uint32_t n, uint32_t friendnumber) { do { iterate_alive(toxes, state); } while (tox_friend_get_connection_status(toxes[n], friendnumber, nullptr) == TOX_CONNECTION_NONE); } static void do_invite(Tox **toxes, State *state, uint32_t inviter, uint32_t invitee, uint32_t friendnum) { fprintf(stderr, "#%u inviting #%u\n", state[inviter].index, state[invitee].index); Tox_Err_Conference_Invite err; tox_conference_invite(toxes[inviter], friendnum, state[inviter].conference, &err); ck_assert_msg(err == TOX_ERR_CONFERENCE_INVITE_OK, "#%u attempting to invite #%u (friendnumber %u) returned with an error: %d", state[inviter].index, state[invitee].index, friendnum, err); do { iterate_alive(toxes, state); } while (!state[invitee].connected); } static bool group_complete(Tox **toxes, State *state) { int c = -1, size = 0; for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { if (!state[i].alive) { continue; } const int ct = tox_conference_peer_count(toxes[i], state[i].conference, nullptr); if (c == -1) { c = ct; } else if (c != ct) { return false; } ++size; } return (c == size); } static void wait_group_complete(Tox **toxes, State *state) { do { iterate_alive(toxes, state); } while (!group_complete(toxes, state)); } static void conference_invite_merge_test(Tox **toxes, State *state) { // 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(toxes[i], handle_conference_invite); tox_callback_conference_connected(toxes[i], &handle_conference_connected); state[i].alive = true; state[i].save_state = nullptr; } { // Create new conference, tox 2 is the founder. Tox_Err_Conference_New err; state[2].conference = tox_conference_new(toxes[2], &err); state[2].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", state[2].conference); } save(toxes, state, 2); do_invite(toxes, state, 2, 1, 0); do_invite(toxes, state, 1, 0, 0); save(toxes, state, 1); kill(toxes, state, 1); do { iterate_alive(toxes, state); } while (tox_conference_peer_count(toxes[2], state[2].conference, nullptr) != 1); do_invite(toxes, state, 2, 3, 1); do_invite(toxes, state, 3, 4, 1); kill(toxes, state, 2); reload(toxes, state, 1); uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; tox_self_get_public_key(toxes[1], public_key); tox_friend_add_norequest(toxes[3], public_key, nullptr); tox_self_get_public_key(toxes[3], public_key); tox_friend_add_norequest(toxes[1], public_key, nullptr); wait_connected(toxes, state, 1, 2); do_invite(toxes, state, 1, 3, 2); fprintf(stderr, "Waiting for group to merge\n"); wait_group_complete(toxes, state); fprintf(stderr, "Group merged\n"); reload(toxes, state, 2); wait_connected(toxes, state, 2, 0); do_invite(toxes, state, 2, 1, 0); fprintf(stderr, "Waiting for #2 to rejoin\n"); wait_group_complete(toxes, state); kill(toxes, state, 2); wait_group_complete(toxes, state); reload(toxes, state, 2); wait_connected(toxes, state, 2, 0); wait_connected(toxes, state, 1, 1); do_invite(toxes, state, 1, 2, 1); fprintf(stderr, "Waiting for #2 to rejoin\n"); wait_group_complete(toxes, state); for (int i = 0; i < NUM_INVITE_MERGE_TOX; i++) { if (state[i].save_state != nullptr) { free(state[i].save_state); } } } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); run_auto_test(NUM_INVITE_MERGE_TOX, conference_invite_merge_test, true); return 0; } c-toxcore-0.2.13/auto_tests/conference_peer_nick_test.c000066400000000000000000000120001415350724400232210ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include typedef struct State { uint32_t index; uint64_t clock; bool self_online; bool friend_online; bool friend_in_group; bool joined; uint32_t conference; } State; #include "run_auto_test.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) { State *state = (State *)user_data; fprintf(stderr, "handle_conference_invite(#%u, %u, %d, uint8_t[%u], _)\n", state->index, friend_number, type, (unsigned)length); fprintf(stderr, "tox%u joining conference\n", state->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", state->index, state->conference); state->joined = true; } static void handle_peer_list_changed(Tox *tox, uint32_t conference_number, void *user_data) { State *state = (State *)user_data; fprintf(stderr, "handle_peer_list_changed(#%u, %u, _)\n", state->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", state->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(Tox **toxes, State *state) { // Conference callbacks. tox_callback_conference_invite(toxes[0], handle_conference_invite); tox_callback_conference_invite(toxes[1], handle_conference_invite); tox_callback_conference_peer_list_changed(toxes[0], handle_peer_list_changed); tox_callback_conference_peer_list_changed(toxes[1], handle_peer_list_changed); // Set the names of the toxes. tox_self_set_name(toxes[0], (const uint8_t *)"test-tox-0", 10, nullptr); tox_self_set_name(toxes[1], (const uint8_t *)"test-tox-1", 10, nullptr); { // Create new conference, tox0 is the founder. Tox_Err_Conference_New err; state[0].conference = tox_conference_new(toxes[0], &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(toxes[0], 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(2, toxes, state, 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(1, toxes, state, 1000); // Rebuild peer list after every iteration. rebuild_peer_list(toxes[0]); } while (state[0].friend_in_group); fprintf(stderr, "Invitations accepted\n"); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); run_auto_test(2, conference_peer_nick_test, false); return 0; } c-toxcore-0.2.13/auto_tests/conference_simple_test.c000066400000000000000000000215461415350724400225720ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "../testing/misc_tools.h" #include "../toxcore/tox.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; } c-toxcore-0.2.13/auto_tests/conference_test.c000066400000000000000000000350301415350724400212120ustar00rootroot00000000000000/* Auto Tests: Conferences. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #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 { uint32_t index; uint64_t clock; bool invited_next; } State; #include "run_auto_test.h" static void handle_self_connection_status( Tox *tox, Tox_Connection connection_status, void *user_data) { const State *state = (State *)user_data; if (connection_status != TOX_CONNECTION_NONE) { printf("tox #%u: is now connected\n", state->index); } else { printf("tox #%u: is now disconnected\n", state->index); } } static void handle_friend_connection_status( Tox *tox, uint32_t friendnumber, Tox_Connection connection_status, void *user_data) { const State *state = (State *)user_data; if (connection_status != TOX_CONNECTION_NONE) { printf("tox #%u: is now connected to friend %u\n", state->index, friendnumber); } else { printf("tox #%u: is now disconnected from friend %u\n", state->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 State *state = (State *)user_data; ck_assert_msg(type == TOX_CONFERENCE_TYPE_TEXT, "tox #%u: wrong conference type: %d", state->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", state->index, err); ck_assert_msg(g_num == 0, "tox #%u: group number was not 0", state->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.", state->index); } static void handle_conference_connected( Tox *tox, uint32_t conference_number, void *user_data) { State *state = (State *)user_data; 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", state->index, err); printf("tox #%u: invited next friend\n", state->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, Tox **toxes, 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(toxes[i], 0, nullptr) > tox_count - num_disconnected) { return false; } } return true; } static void disconnect_toxes(uint32_t tox_count, Tox **toxes, State *state, 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(toxes[i], &state[i]); state[i].clock += 1000; } } c_sleep(20); } while (!toxes_are_disconnected_from_group(tox_count, toxes, disconnect_now)); invert = !invert; } while (invert); } static bool all_connected_to_group(uint32_t tox_count, Tox **toxes) { for (uint32_t i = 0; i < tox_count; i++) { if (tox_conference_peer_count(toxes[i], 0, nullptr) < tox_count) { return false; } } return true; } static bool names_propagated(uint32_t tox_count, Tox **toxes, State *state) { 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(toxes[i], 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(bool *list, const uint32_t length) { uint32_t index; do { index = random_u32() % length; } while (list[index]); return index; } static void run_conference_tests(Tox **toxes, State *state) { /* 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(toxes[i], 0, max_frozen, &err); ck_assert_msg(err == TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_OK, "tox #%u failed to set max offline: err = %d", state[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(disconnected, NUM_GROUP_TOX); disconnected[disconnect] = true; if (i < NUM_DISCONNECT / 2) { restarting[disconnect] = true; printf("Restarting #%u\n", state[disconnect].index); } else { printf("Disconnecting #%u\n", state[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(toxes[i]); 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(toxes[i], save[i]); tox_kill(toxes[i]); } } disconnect_toxes(NUM_GROUP_TOX, toxes, state, 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]); toxes[i] = tox_new_log(options, nullptr, &state[i].index); ck_assert(toxes[i] != nullptr); tox_options_free(options); free(save[i]); set_mono_time_callback(toxes[i], &state[i]); tox_conference_set_max_offline(toxes[i], 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, state[i].index); tox_self_set_name(toxes[i], (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(toxes[i], 0, nullptr); ck_assert_msg(num_frozen <= max_frozen, "tox #%u has too many offline peers: %u\n", state[i].index, num_frozen); } printf("reconnecting toxes\n"); do { iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); } while (!all_connected_to_group(NUM_GROUP_TOX, toxes)); printf("running conference tests\n"); for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { tox_callback_conference_message(toxes[i], &handle_conference_message); iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); } Tox_Err_Conference_Send_Message err; ck_assert_msg( tox_conference_send_message( toxes[random_u32() % NUM_GROUP_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(NUM_GROUP_TOX, toxes, state, 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(toxes[i], 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", state[j].index, state[i].index); } } } for (uint32_t k = NUM_GROUP_TOX; k != 0 ; --k) { tox_conference_delete(toxes[k - 1], 0, nullptr); for (uint8_t j = 0; j < 10 || j < NUM_GROUP_TOX; ++j) { iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); } for (uint32_t i = 0; i < k - 1; ++i) { uint32_t peer_count = tox_conference_peer_count(toxes[i], 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(Tox **toxes, State *state) { const time_t test_start_time = time(nullptr); for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { tox_callback_self_connection_status(toxes[i], &handle_self_connection_status); tox_callback_friend_connection_status(toxes[i], &handle_friend_connection_status); tox_callback_conference_invite(toxes[i], &handle_conference_invite); tox_callback_conference_connected(toxes[i], &handle_conference_connected); char name[NAMELEN + 1]; snprintf(name, NAMELEN + 1, NAME_FORMAT_STR, state[i].index); tox_self_set_name(toxes[i], (const uint8_t *)name, NAMELEN, nullptr); } ck_assert_msg(tox_conference_new(toxes[0], nullptr) != UINT32_MAX, "failed to create group"); printf("tox #%u: inviting its first friend\n", state[0].index); ck_assert_msg(tox_conference_invite(toxes[0], 0, 0, nullptr) != 0, "failed to invite friend"); state[0].invited_next = true; ck_assert_msg(tox_conference_set_title(toxes[0], 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(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); invited_count = 0; for (uint32_t i = 0; i < NUM_GROUP_TOX; ++i) { invited_count += state[i].invited_next; } } while (invited_count != NUM_GROUP_TOX - 1); uint64_t pregroup_clock = state[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(NUM_GROUP_TOX, toxes, state, 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(toxes[i], 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(toxes[i], 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(toxes[i], 0, nullptr); ck_assert_msg(ret == sizeof("Gentoo") - 1, "Wrong title length"); tox_conference_get_title(toxes[i], 0, title, nullptr); ck_assert_msg(memcmp("Gentoo", title, ret) == 0, "Wrong title"); } printf("waiting for names to propagate\n"); do { iterate_all_wait(NUM_GROUP_TOX, toxes, state, ITERATION_INTERVAL); } while (!names_propagated(NUM_GROUP_TOX, toxes, state)); printf("group connected, took %d seconds\n", (int)((state[0].clock - pregroup_clock) / 1000)); run_conference_tests(toxes, state); 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_test(NUM_GROUP_TOX, test_many_group, true); return 0; } c-toxcore-0.2.13/auto_tests/conference_two_test.c000066400000000000000000000015241415350724400221040ustar00rootroot00000000000000// 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. #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "../testing/misc_tools.h" #include "../toxcore/tox.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; } c-toxcore-0.2.13/auto_tests/crypto_test.c000066400000000000000000000275141415350724400204330ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #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(uint8_t *b, size_t blen) { size_t i; for (i = 0; i < blen; i++) { b[i] = random_u08(); } } // These test vectors are from libsodium's test suite static const unsigned char 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 unsigned char 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 unsigned char 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 unsigned char 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 unsigned char 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 }; START_TEST(test_known) { unsigned char c[147]; unsigned char m[131]; uint16_t clen, mlen; ck_assert_msg(sizeof(c) == sizeof(m) + CRYPTO_MAC_SIZE * sizeof(unsigned char), "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(unsigned char), c); ck_assert_msg(memcmp(test_c, c, sizeof(c)) == 0, "cyphertext doesn't match test vector"); ck_assert_msg(clen == sizeof(c) / sizeof(unsigned char), "wrong ciphertext length"); mlen = decrypt_data(bobpk, alicesk, test_nonce, test_c, sizeof(test_c) / sizeof(unsigned char), 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(unsigned char), "wrong plaintext length"); } END_TEST START_TEST(test_fast_known) { unsigned char k[CRYPTO_SHARED_KEY_SIZE]; unsigned char c[147]; unsigned char m[131]; uint16_t clen, mlen; encrypt_precompute(bobpk, alicesk, k); ck_assert_msg(sizeof(c) == sizeof(m) + CRYPTO_MAC_SIZE * sizeof(unsigned char), "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(unsigned char), c); ck_assert_msg(memcmp(test_c, c, sizeof(c)) == 0, "cyphertext doesn't match test vector"); ck_assert_msg(clen == sizeof(c) / sizeof(unsigned char), "wrong ciphertext length"); mlen = decrypt_data_symmetric(k, test_nonce, test_c, sizeof(test_c) / sizeof(unsigned char), 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(unsigned char), "wrong plaintext length"); } END_TEST START_TEST(test_endtoend) { unsigned char pk1[CRYPTO_PUBLIC_KEY_SIZE]; unsigned char sk1[CRYPTO_SECRET_KEY_SIZE]; unsigned char pk2[CRYPTO_PUBLIC_KEY_SIZE]; unsigned char sk2[CRYPTO_SECRET_KEY_SIZE]; unsigned char k1[CRYPTO_SHARED_KEY_SIZE]; unsigned char k2[CRYPTO_SHARED_KEY_SIZE]; unsigned char n[CRYPTO_NONCE_SIZE]; unsigned char m[500]; unsigned char c1[sizeof(m) + CRYPTO_MAC_SIZE]; unsigned char c2[sizeof(m) + CRYPTO_MAC_SIZE]; unsigned char c3[sizeof(m) + CRYPTO_MAC_SIZE]; unsigned char c4[sizeof(m) + CRYPTO_MAC_SIZE]; unsigned char m1[sizeof(m)]; unsigned char m2[sizeof(m)]; unsigned char m3[sizeof(m)]; unsigned char m4[sizeof(m)]; uint16_t mlen; uint16_t c1len, c2len, c3len, c4len; uint16_t m1len, m2len, m3len, m4len; uint8_t testno; // Test 100 random messages and keypairs for (testno = 0; testno < 100; testno++) { //Generate random message (random length from 100 to 500) mlen = (random_u32() % 400) + 100; rand_bytes(m, mlen); rand_bytes(n, CRYPTO_NONCE_SIZE); //Generate keypairs crypto_new_keypair(pk1, sk1); crypto_new_keypair(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 c1len = encrypt_data(pk2, sk1, n, m, mlen, c1); c2len = encrypt_data(pk1, sk2, n, m, mlen, c2); c3len = encrypt_data_symmetric(k1, n, m, mlen, c3); 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 m1len = decrypt_data(pk2, sk1, n, c1, c1len, m1); m2len = decrypt_data(pk1, sk2, n, c1, c1len, m2); m3len = decrypt_data_symmetric(k1, n, c1, c1len, m3); 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"); } } END_TEST START_TEST(test_large_data) { unsigned char k[CRYPTO_SHARED_KEY_SIZE]; unsigned char n[CRYPTO_NONCE_SIZE]; unsigned char m1[MAX_CRYPTO_PACKET_SIZE - CRYPTO_MAC_SIZE]; unsigned char c1[sizeof(m1) + CRYPTO_MAC_SIZE]; unsigned char m1prime[sizeof(m1)]; unsigned char m2[MAX_CRYPTO_PACKET_SIZE]; unsigned char c2[sizeof(m2) + CRYPTO_MAC_SIZE]; uint16_t c1len, c2len; uint16_t m1plen; //Generate random messages rand_bytes(m1, sizeof(m1)); rand_bytes(m2, sizeof(m2)); rand_bytes(n, CRYPTO_NONCE_SIZE); //Generate key rand_bytes(k, CRYPTO_SHARED_KEY_SIZE); c1len = encrypt_data_symmetric(k, n, m1, sizeof(m1), c1); c2len = encrypt_data_symmetric(k, n, m2, sizeof(m2), c2); ck_assert_msg(c1len == sizeof(m1) + CRYPTO_MAC_SIZE, "could not encrypt"); ck_assert_msg(c2len == sizeof(m2) + CRYPTO_MAC_SIZE, "could not encrypt"); m1plen = decrypt_data_symmetric(k, n, c1, c1len, m1prime); ck_assert_msg(m1plen == sizeof(m1), "decrypted text lengths differ"); ck_assert_msg(memcmp(m1prime, m1, sizeof(m1)) == 0, "decrypted texts differ"); } END_TEST START_TEST(test_large_data_symmetric) { unsigned char k[CRYPTO_SYMMETRIC_KEY_SIZE]; unsigned char n[CRYPTO_NONCE_SIZE]; unsigned char m1[16 * 16 * 16]; unsigned char c1[sizeof(m1) + CRYPTO_MAC_SIZE]; unsigned char m1prime[sizeof(m1)]; uint16_t c1len; uint16_t m1plen; //Generate random messages rand_bytes(m1, sizeof(m1)); rand_bytes(n, CRYPTO_NONCE_SIZE); //Generate key new_symmetric_key(k); c1len = encrypt_data_symmetric(k, n, m1, sizeof(m1), c1); ck_assert_msg(c1len == sizeof(m1) + CRYPTO_MAC_SIZE, "could not encrypt data"); m1plen = decrypt_data_symmetric(k, n, c1, c1len, m1prime); ck_assert_msg(m1plen == sizeof(m1), "decrypted text lengths differ"); ck_assert_msg(memcmp(m1prime, m1, sizeof(m1)) == 0, "decrypted texts differ"); } END_TEST 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)); } START_TEST(test_increment_nonce) { uint32_t i; uint8_t n[CRYPTO_NONCE_SIZE]; for (i = 0; i < CRYPTO_NONCE_SIZE; ++i) { n[i] = random_u08(); } 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(); 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"); } } END_TEST START_TEST(test_memzero) { 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"); } } END_TEST static Suite *crypto_suite(void) { Suite *s = suite_create("Crypto"); DEFTESTCASE(known); DEFTESTCASE(fast_known); DEFTESTCASE_SLOW(endtoend, 15); /* waiting up to 15 seconds */ DEFTESTCASE(large_data); DEFTESTCASE(large_data_symmetric); DEFTESTCASE_SLOW(increment_nonce, 20); DEFTESTCASE(memzero); return s; } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); Suite *crypto = crypto_suite(); SRunner *test_runner = srunner_create(crypto); uint8_t number_failed = 0; srunner_run_all(test_runner, CK_NORMAL); number_failed = srunner_ntests_failed(test_runner); srunner_free(test_runner); return number_failed; } c-toxcore-0.2.13/auto_tests/data/000077500000000000000000000000001415350724400166105ustar00rootroot00000000000000c-toxcore-0.2.13/auto_tests/data/save.tox000066400000000000000000000103731415350724400203060ustar00rootroot00000000000000DLv,}i*[ǽzv[0|oī[!5޻f!=Ix+ّެzw Yk%UvFkJRVƬQ~NFo':4f䂥O<Ϩwvy236'ߵbvh=E;]"w 6#ŰT3*@#_r]W{˱ȃY(G1 |A I;=(*e6v/Ղa[l)QL 6u^5wc25MQaAgi ʘ^(*`D2eh!, $$х7]긂Vk %kD/.UP3],_@W)L\vOp>I 22(^~(ֶtlsyt2ra_'ӮdႥNjp~T_Y9U/U uW<?uZONe{eǎ[gͷb_l=o;,wc3%I2ɉe FeQb5L٭/T`ݕUO%uUnZsI/Ղa[l)QL 6u^5wc25MQaAgi ʘ^(*`D2eh!W{˱ȃY(G1 |A I;=(*e6v4f䂥O<Ϩwvy236'ߵbvh=E;]"w 6#ŰT3*@#_r]Ne{eǎ[gͷb_l=o;٭/T`ݕUO%uUnZsI,wc3%I2ɉe FeQb5L>FkJRVƬQ~NFo':4f䂥O<Ϩwvy236'ߵbvh=W{˱ȃY(G1 |A I;=(*e6vMQaAgi ʘ^(*`D2eh!/Ղa[l)QL 6u^5wc25" [y'1|r讐aŠwCAdd me.Y*name Hello World 8 Ne{eǎ[gͷb_l=o;MQaAgi ʘ^(*`D2eh!/Ղa[l)QL 6u^5wc25T/ĵoyDq͎vH ^W{˱ȃY(G1 |A I;=(*e6vE;]"w 6#ŰT3*@#_r]>FkJRVƬQ~NFo':4f䂥O<Ϩwvy236'ߵbvh=c-toxcore-0.2.13/auto_tests/dht_test.c000066400000000000000000000667641415350724400177040ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "check_compat.h" #include "../testing/misc_tools.h" #include "../toxcore/crypto_core.h" #ifndef DHT_C_INCLUDED #include "../toxcore/DHT.c" #endif // DHT_C_INCLUDED #include "../toxcore/tox.h" // These tests currently fail. static bool enable_broken_tests = false; #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 mark_bad(const Mono_Time *mono_time, IPPTsPng *ipptp) { ipptp->timestamp = mono_time_get(mono_time) - 2 * BAD_NODE_TIMEOUT; ipptp->hardening.routes_requests_ok = 0; ipptp->hardening.send_nodes_ok = 0; ipptp->hardening.testing_requests = 0; } static void mark_possible_bad(const Mono_Time *mono_time, IPPTsPng *ipptp) { ipptp->timestamp = mono_time_get(mono_time); ipptp->hardening.routes_requests_ok = 0; ipptp->hardening.send_nodes_ok = 0; ipptp->hardening.testing_requests = 0; } static void mark_good(const Mono_Time *mono_time, IPPTsPng *ipptp) { ipptp->timestamp = mono_time_get(mono_time); ipptp->hardening.routes_requests_ok = (HARDENING_ALL_OK >> 0) & 1; ipptp->hardening.send_nodes_ok = (HARDENING_ALL_OK >> 1) & 1; ipptp->hardening.testing_requests = (HARDENING_ALL_OK >> 2) & 1; } static void mark_all_good(const Mono_Time *mono_time, Client_data *list, uint32_t length, uint8_t ipv6) { uint32_t i; for (i = 0; i < length; ++i) { if (ipv6) { mark_good(mono_time, &list[i].assoc6); } else { mark_good(mono_time, &list[i].assoc4); } } } /* Returns 1 if public_key has a furthest distance to comp_client_id than all public_key's in the list */ static uint8_t is_furthest(const uint8_t *comp_client_id, Client_data *list, uint32_t length, const uint8_t *public_key) { uint32_t i; for (i = 0; i < length; ++i) { if (id_closest(comp_client_id, public_key, list[i].public_key) == 1) { return 0; } } return 1; } static int client_in_list(Client_data *list, uint32_t length, const uint8_t *public_key) { uint32_t i; for (i = 0; i < length; ++i) { if (id_equal(public_key, list[i].public_key)) { return i; } } return -1; } static void test_addto_lists_update(DHT *dht, Client_data *list, uint32_t length, IP_Port *ip_port) { uint32_t used, test, test1, test2, found; IP_Port test_ipp; uint8_t test_id[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t ipv6 = net_family_is_ipv6(ip_port->ip.family) ? 1 : 0; // check id update for existing ip_port test = random_u32() % length; ipport_copy(&test_ipp, ipv6 ? &list[test].assoc6.ip_port : &list[test].assoc4.ip_port); random_bytes(test_id, sizeof(test_id)); used = addto_lists(dht, test_ipp, test_id); ck_assert_msg(used >= 1, "Wrong number of added clients"); // it is possible to have ip_port duplicates in the list, so ip_port @ found not always equal to ip_port @ test found = client_in_list(list, length, test_id); ck_assert_msg(found >= 0, "Client id is not in the list"); ck_assert_msg(ipport_equal(&test_ipp, ipv6 ? &list[found].assoc6.ip_port : &list[found].assoc4.ip_port), "Client IP_Port is incorrect"); // check ip_port update for existing id test = random_u32() % length; test_ipp.port = random_u32() % TOX_PORT_DEFAULT; id_copy(test_id, list[test].public_key); used = addto_lists(dht, test_ipp, test_id); ck_assert_msg(used >= 1, "Wrong number of added clients"); // it is not possible to have id duplicates in the list, so id @ found must be equal id @ test ck_assert_msg(client_in_list(list, length, test_id) == test, "Client id is not in the list"); ck_assert_msg(ipport_equal(&test_ipp, ipv6 ? &list[test].assoc6.ip_port : &list[test].assoc4.ip_port), "Client IP_Port is incorrect"); // check ip_port update for existing id and ip_port (... port ... id ...) test1 = random_u32() % (length / 2); test2 = random_u32() % (length / 2) + length / 2; ipport_copy(&test_ipp, ipv6 ? &list[test1].assoc6.ip_port : &list[test1].assoc4.ip_port); id_copy(test_id, list[test2].public_key); if (ipv6) { list[test2].assoc6.ip_port.port = -1; } else { list[test2].assoc4.ip_port.port = -1; } used = addto_lists(dht, test_ipp, test_id); ck_assert_msg(used >= 1, "Wrong number of added clients"); ck_assert_msg(client_in_list(list, length, test_id) == test2, "Client id is not in the list"); ck_assert_msg(ipport_equal(&test_ipp, ipv6 ? &list[test2].assoc6.ip_port : &list[test2].assoc4.ip_port), "Client IP_Port is incorrect"); // check ip_port update for existing id and ip_port (... id ... port ...) test1 = random_u32() % (length / 2); test2 = random_u32() % (length / 2) + length / 2; ipport_copy(&test_ipp, ipv6 ? &list[test2].assoc6.ip_port : &list[test2].assoc4.ip_port); id_copy(test_id, list[test1].public_key); if (ipv6) { list[test1].assoc6.ip_port.port = -1; } else { list[test1].assoc4.ip_port.port = -1; } used = addto_lists(dht, test_ipp, test_id); ck_assert_msg(used >= 1, "Wrong number of added clients"); ck_assert_msg(client_in_list(list, length, test_id) == test1, "Client id is not in the list"); ck_assert_msg(ipport_equal(&test_ipp, ipv6 ? &list[test1].assoc6.ip_port : &list[test1].assoc4.ip_port), "Client IP_Port is incorrect"); } static void test_addto_lists_bad(DHT *dht, Client_data *list, uint32_t length, IP_Port *ip_port) { // check "bad" clients replacement int used, test1, test2, test3; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE], test_id1[CRYPTO_PUBLIC_KEY_SIZE], test_id2[CRYPTO_PUBLIC_KEY_SIZE], test_id3[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t ipv6 = net_family_is_ipv6(ip_port->ip.family) ? 1 : 0; random_bytes(public_key, sizeof(public_key)); mark_all_good(dht->mono_time, list, length, ipv6); test1 = random_u32() % (length / 3); test2 = random_u32() % (length / 3) + length / 3; test3 = random_u32() % (length / 3) + 2 * length / 3; ck_assert_msg(!(test1 == test2 || test1 == test3 || test2 == test3), "Wrong test indices are chosen"); id_copy((uint8_t *)&test_id1, list[test1].public_key); id_copy((uint8_t *)&test_id2, list[test2].public_key); id_copy((uint8_t *)&test_id3, list[test3].public_key); // mark nodes as "bad" if (ipv6) { mark_bad(dht->mono_time, &list[test1].assoc6); mark_bad(dht->mono_time, &list[test2].assoc6); mark_bad(dht->mono_time, &list[test3].assoc6); } else { mark_bad(dht->mono_time, &list[test1].assoc4); mark_bad(dht->mono_time, &list[test2].assoc4); mark_bad(dht->mono_time, &list[test3].assoc4); } ip_port->port += 1; used = addto_lists(dht, *ip_port, public_key); ck_assert_msg(used >= 1, "Wrong number of added clients"); ck_assert_msg(client_in_list(list, length, public_key) >= 0, "Client id is not in the list"); ck_assert_msg(client_in_list(list, length, test_id2) >= 0, "Wrong bad client removed"); ck_assert_msg(client_in_list(list, length, test_id3) >= 0, "Wrong bad client removed"); } static void test_addto_lists_possible_bad(DHT *dht, Client_data *list, uint32_t length, IP_Port *ip_port, const uint8_t *comp_client_id) { // check "possibly bad" clients replacement uint32_t used, test1, test2, test3; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE], test_id1[CRYPTO_PUBLIC_KEY_SIZE], test_id2[CRYPTO_PUBLIC_KEY_SIZE], test_id3[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t ipv6 = net_family_is_ipv6(ip_port->ip.family) ? 1 : 0; random_bytes(public_key, sizeof(public_key)); mark_all_good(dht->mono_time, list, length, ipv6); test1 = random_u32() % (length / 3); test2 = random_u32() % (length / 3) + length / 3; test3 = random_u32() % (length / 3) + 2 * length / 3; ck_assert_msg(!(test1 == test2 || test1 == test3 || test2 == test3), "Wrong test indices are chosen"); id_copy((uint8_t *)&test_id1, list[test1].public_key); id_copy((uint8_t *)&test_id2, list[test2].public_key); id_copy((uint8_t *)&test_id3, list[test3].public_key); // mark nodes as "possibly bad" if (ipv6) { mark_possible_bad(dht->mono_time, &list[test1].assoc6); mark_possible_bad(dht->mono_time, &list[test2].assoc6); mark_possible_bad(dht->mono_time, &list[test3].assoc6); } else { mark_possible_bad(dht->mono_time, &list[test1].assoc4); mark_possible_bad(dht->mono_time, &list[test2].assoc4); mark_possible_bad(dht->mono_time, &list[test3].assoc4); } ip_port->port += 1; used = addto_lists(dht, *ip_port, public_key); ck_assert_msg(used >= 1, "Wrong number of added clients"); ck_assert_msg(client_in_list(list, length, public_key) >= 0, "Client id is not in the list"); bool inlist_id1 = client_in_list(list, length, test_id1) >= 0; bool inlist_id2 = client_in_list(list, length, test_id2) >= 0; bool inlist_id3 = client_in_list(list, length, test_id3) >= 0; ck_assert_msg(inlist_id1 + inlist_id2 + inlist_id3 == 2, "Wrong client removed"); if (!inlist_id1) { ck_assert_msg(id_closest(comp_client_id, test_id2, test_id1) == 1, "Id has been removed but is closer to than another one"); ck_assert_msg(id_closest(comp_client_id, test_id3, test_id1) == 1, "Id has been removed but is closer to than another one"); } else if (!inlist_id2) { ck_assert_msg(id_closest(comp_client_id, test_id1, test_id2) == 1, "Id has been removed but is closer to than another one"); ck_assert_msg(id_closest(comp_client_id, test_id3, test_id2) == 1, "Id has been removed but is closer to than another one"); } else if (!inlist_id3) { ck_assert_msg(id_closest(comp_client_id, test_id1, test_id3) == 1, "Id has been removed but is closer to than another one"); ck_assert_msg(id_closest(comp_client_id, test_id2, test_id3) == 1, "Id has been removed but is closer to than another one"); } } static void test_addto_lists_good(DHT *dht, Client_data *list, uint32_t length, IP_Port *ip_port, const uint8_t *comp_client_id) { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t ipv6 = net_family_is_ipv6(ip_port->ip.family) ? 1 : 0; mark_all_good(dht->mono_time, list, length, ipv6); // check "good" client id replacement do { random_bytes(public_key, sizeof(public_key)); } while (is_furthest(comp_client_id, list, length, public_key)); ip_port->port += 1; addto_lists(dht, *ip_port, public_key); ck_assert_msg(client_in_list(list, length, public_key) >= 0, "Good client id is not in the list"); // check "good" client id skip do { random_bytes(public_key, sizeof(public_key)); } while (!is_furthest(comp_client_id, list, length, public_key)); ip_port->port += 1; addto_lists(dht, *ip_port, public_key); ck_assert_msg(client_in_list(list, length, public_key) == -1, "Good client id is in the list"); } #ifndef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif static void test_addto_lists(IP ip) { Logger *log = logger_new(); uint32_t index = 1; logger_callback_log(log, (logger_cb *)print_debug_log, nullptr, &index); Mono_Time *mono_time = mono_time_new(); ck_assert_msg(mono_time != nullptr, "Failed to create Mono_Time"); Networking_Core *net = new_networking(log, ip, TOX_PORT_DEFAULT); ck_assert_msg(net != nullptr, "Failed to create Networking_Core"); DHT *dht = new_dht(log, mono_time, net, true); ck_assert_msg(dht != nullptr, "Failed to create DHT"); IP_Port ip_port; ip_port.ip = ip; ip_port.port = TOX_PORT_DEFAULT; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint16_t i, used; // check lists filling for (i = 0; i < MAX(LCLIENT_LIST, MAX_FRIEND_CLIENTS); ++i) { random_bytes(public_key, sizeof(public_key)); used = addto_lists(dht, ip_port, public_key); ck_assert_msg(used == dht->num_friends + 1, "Wrong number of added clients with existing ip_port"); } for (i = 0; i < MAX(LCLIENT_LIST, MAX_FRIEND_CLIENTS); ++i) { ip_port.port += 1; used = addto_lists(dht, ip_port, public_key); ck_assert_msg(used == dht->num_friends + 1, "Wrong number of added clients with existing public_key"); } for (i = 0; i < MAX(LCLIENT_LIST, MAX_FRIEND_CLIENTS); ++i) { ip_port.port += 1; random_bytes(public_key, sizeof(public_key)); used = addto_lists(dht, ip_port, public_key); ck_assert_msg(used >= 1, "Wrong number of added clients"); } /*check: Current behavior if there are two clients with the same id is * to replace the first ip by the second. */ test_addto_lists_update(dht, dht->close_clientlist, LCLIENT_LIST, &ip_port); for (i = 0; i < dht->num_friends; ++i) { test_addto_lists_update(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &ip_port); } // check "bad" entries test_addto_lists_bad(dht, dht->close_clientlist, LCLIENT_LIST, &ip_port); for (i = 0; i < dht->num_friends; ++i) { test_addto_lists_bad(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &ip_port); } // check "possibly bad" entries if (enable_broken_tests) { test_addto_lists_possible_bad(dht, dht->close_clientlist, LCLIENT_LIST, &ip_port, dht->self_public_key); for (i = 0; i < dht->num_friends; ++i) { test_addto_lists_possible_bad(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &ip_port, dht->friends_list[i].public_key); } } // check "good" entries test_addto_lists_good(dht, dht->close_clientlist, LCLIENT_LIST, &ip_port, dht->self_public_key); for (i = 0; i < dht->num_friends; ++i) { test_addto_lists_good(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &ip_port, dht->friends_list[i].public_key); } kill_dht(dht); kill_networking(net); logger_kill(log); } static void test_addto_lists_ipv4(void) { IP ip; ip_init(&ip, 0); test_addto_lists(ip); } static void test_addto_lists_ipv6(void) { IP ip; ip_init(&ip, 1); test_addto_lists(ip); } #define DHT_DEFAULT_PORT (TOX_PORT_DEFAULT + 1000) static void print_pk(uint8_t *public_key) { uint32_t j; for (j = 0; j < CRYPTO_PUBLIC_KEY_SIZE; j++) { printf("%02X", public_key[j]); } printf("\n"); } static void test_add_to_list(uint8_t cmp_list[][CRYPTO_PUBLIC_KEY_SIZE + 1], uint16_t length, const uint8_t *pk, const uint8_t *cmp_pk) { uint8_t p_b[CRYPTO_PUBLIC_KEY_SIZE]; uint16_t i; for (i = 0; i < length; ++i) { if (!cmp_list[i][CRYPTO_PUBLIC_KEY_SIZE]) { memcpy(cmp_list[i], pk, CRYPTO_PUBLIC_KEY_SIZE); cmp_list[i][CRYPTO_PUBLIC_KEY_SIZE] = 1; return; } if (memcmp(cmp_list[i], pk, CRYPTO_PUBLIC_KEY_SIZE) == 0) { return; } } for (i = 0; i < length; ++i) { if (id_closest(cmp_pk, cmp_list[i], pk) == 2) { memcpy(p_b, cmp_list[i], CRYPTO_PUBLIC_KEY_SIZE); memcpy(cmp_list[i], pk, CRYPTO_PUBLIC_KEY_SIZE); test_add_to_list(cmp_list, length, p_b, cmp_pk); break; } } } #define NUM_DHT 100 static void test_list_main(void) { DHT *dhts[NUM_DHT]; Logger *logs[NUM_DHT]; Mono_Time *mono_times[NUM_DHT]; uint32_t index[NUM_DHT]; uint8_t cmp_list1[NUM_DHT][MAX_FRIEND_CLIENTS][CRYPTO_PUBLIC_KEY_SIZE + 1]; memset(cmp_list1, 0, sizeof(cmp_list1)); uint16_t i, j, k, l; for (i = 0; i < NUM_DHT; ++i) { IP ip; ip_init(&ip, 1); logs[i] = logger_new(); index[i] = i + 1; logger_callback_log(logs[i], (logger_cb *)print_debug_log, nullptr, &index[i]); mono_times[i] = mono_time_new(); dhts[i] = new_dht(logs[i], mono_times[i], new_networking(logs[i], ip, DHT_DEFAULT_PORT + i), true); ck_assert_msg(dhts[i] != nullptr, "Failed to create dht instances %u", i); ck_assert_msg(net_port(dhts[i]->net) != DHT_DEFAULT_PORT + i, "Bound to wrong port: %d", net_port(dhts[i]->net)); } for (i = 0; i < NUM_DHT; ++i) { for (j = 1; j < NUM_DHT; ++j) { test_add_to_list(cmp_list1[i], MAX_FRIEND_CLIENTS, dhts[(i + j) % NUM_DHT]->self_public_key, dhts[i]->self_public_key); } } for (i = 0; i < NUM_DHT; ++i) { for (j = 0; j < NUM_DHT; ++j) { if (i == j) { continue; } IP_Port ip_port; ip_init(&ip_port.ip, 0); ip_port.ip.ip.v4.uint32 = random_u32(); ip_port.port = random_u32() % (UINT16_MAX - 1); ++ip_port.port; addto_lists(dhts[i], ip_port, dhts[j]->self_public_key); } } #if 0 print_pk(dhts[0]->self_public_key); for (i = 0; i < MAX_FRIEND_CLIENTS; ++i) { printf("----Entry %u----\n", i); print_pk(cmp_list1[i]); } #endif uint16_t m_count = 0; for (l = 0; l < NUM_DHT; ++l) { for (i = 0; i < MAX_FRIEND_CLIENTS; ++i) { for (j = 1; j < NUM_DHT; ++j) { if (memcmp(cmp_list1[l][i], dhts[(l + j) % NUM_DHT]->self_public_key, CRYPTO_PUBLIC_KEY_SIZE) != 0) { continue; } uint16_t count = 0; for (k = 0; k < LCLIENT_LIST; ++k) { if (memcmp(dhts[l]->self_public_key, dhts[(l + j) % NUM_DHT]->close_clientlist[k].public_key, CRYPTO_PUBLIC_KEY_SIZE) == 0) { ++count; } } if (count != 1) { print_pk(dhts[l]->self_public_key); for (k = 0; k < MAX_FRIEND_CLIENTS; ++k) { printf("----Entry %u----\n", k); print_pk(cmp_list1[l][k]); } for (k = 0; k < LCLIENT_LIST; ++k) { printf("----Closel %u----\n", k); print_pk(dhts[(l + j) % NUM_DHT]->close_clientlist[k].public_key); } print_pk(dhts[(l + j) % NUM_DHT]->self_public_key); } ck_assert_msg(count == 1, "Nodes in search don't know ip of friend. %u %u %u", i, j, count); Node_format ln[MAX_SENT_NODES]; uint16_t n = get_close_nodes(dhts[(l + j) % NUM_DHT], dhts[l]->self_public_key, ln, net_family_unspec, 1, 0); ck_assert_msg(n == MAX_SENT_NODES, "bad num close %u | %u %u", n, i, j); count = 0; for (k = 0; k < MAX_SENT_NODES; ++k) { if (memcmp(dhts[l]->self_public_key, ln[k].public_key, CRYPTO_PUBLIC_KEY_SIZE) == 0) { ++count; } } ck_assert_msg(count == 1, "Nodes in search don't know ip of friend. %u %u %u", i, j, count); #if 0 for (k = 0; k < MAX_SENT_NODES; ++k) { printf("----gn %u----\n", k); print_pk(ln[k].public_key); } #endif ++m_count; } } } ck_assert_msg(m_count == (NUM_DHT) * (MAX_FRIEND_CLIENTS), "Bad count. %u != %u", m_count, (NUM_DHT) * (MAX_FRIEND_CLIENTS)); for (i = 0; i < NUM_DHT; ++i) { Networking_Core *n = dhts[i]->net; kill_dht(dhts[i]); kill_networking(n); mono_time_free(mono_times[i]); logger_kill(logs[i]); } } static void test_list(void) { uint8_t i; for (i = 0; i < 10; ++i) { test_list_main(); } } static void ip_callback(void *data, int32_t number, IP_Port ip_port) { } #define NUM_DHT_FRIENDS 20 static uint64_t get_clock_callback(Mono_Time *mono_time, void *user_data) { const uint64_t *clock = (const uint64_t *)user_data; return *clock; } static void test_DHT_test(void) { uint32_t to_comp = 8394782; DHT *dhts[NUM_DHT]; Logger *logs[NUM_DHT]; Mono_Time *mono_times[NUM_DHT]; uint64_t clock[NUM_DHT]; uint32_t index[NUM_DHT]; uint32_t i, j; for (i = 0; i < NUM_DHT; ++i) { IP ip; ip_init(&ip, 1); logs[i] = logger_new(); index[i] = i + 1; logger_callback_log(logs[i], (logger_cb *)print_debug_log, nullptr, &index[i]); mono_times[i] = mono_time_new(); clock[i] = current_time_monotonic(mono_times[i]); mono_time_set_current_time_callback(mono_times[i], get_clock_callback, &clock[i]); dhts[i] = new_dht(logs[i], mono_times[i], new_networking(logs[i], ip, DHT_DEFAULT_PORT + i), true); ck_assert_msg(dhts[i] != nullptr, "Failed to create dht instances %u", i); ck_assert_msg(net_port(dhts[i]->net) != DHT_DEFAULT_PORT + i, "Bound to wrong port"); } struct { uint16_t tox1; uint16_t tox2; } pairs[NUM_DHT_FRIENDS]; for (i = 0; i < NUM_DHT_FRIENDS; ++i) { // TODO(hugbubby): remove use of goto. loop_top: pairs[i].tox1 = random_u32() % NUM_DHT; pairs[i].tox2 = (pairs[i].tox1 + (random_u32() % (NUM_DHT - 1)) + 1) % NUM_DHT; for (j = 0; j < i; ++j) { if (pairs[j].tox2 == pairs[i].tox2 && pairs[j].tox1 == pairs[i].tox1) { goto loop_top; } } uint16_t lock_count = 0; ck_assert_msg(dht_addfriend(dhts[pairs[i].tox2], dhts[pairs[i].tox1]->self_public_key, &ip_callback, &to_comp, 1337, &lock_count) == 0, "Failed to add friend"); ck_assert_msg(lock_count == 1, "bad lock count: %u %u", lock_count, i); } for (i = 0; i < NUM_DHT; ++i) { IP_Port ip_port; ip_port.ip = get_loopback(); ip_port.port = net_htons(DHT_DEFAULT_PORT + i); dht_bootstrap(dhts[(i - 1) % NUM_DHT], ip_port, dhts[i]->self_public_key); } while (true) { uint16_t counter = 0; for (i = 0; i < NUM_DHT_FRIENDS; ++i) { IP_Port a; if (dht_getfriendip(dhts[pairs[i].tox2], dhts[pairs[i].tox1]->self_public_key, &a) == 1) { ++counter; } } if (counter == NUM_DHT_FRIENDS) { break; } for (i = 0; i < NUM_DHT; ++i) { mono_time_update(mono_times[i]); networking_poll(dhts[i]->net, nullptr); do_dht(dhts[i]); clock[i] += 500; } c_sleep(20); } for (i = 0; i < NUM_DHT; ++i) { Networking_Core *n = dhts[i]->net; kill_dht(dhts[i]); kill_networking(n); mono_time_free(mono_times[i]); logger_kill(logs[i]); } } static void test_dht_create_packet(void) { uint8_t plain[100] = {0}; uint8_t pkt[1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE]; uint8_t key[CRYPTO_SYMMETRIC_KEY_SIZE]; new_symmetric_key(key); uint16_t length = dht_create_packet(key, key, NET_PACKET_GET_NODES, plain, sizeof(plain), pkt); ck_assert_msg(pkt[0] == NET_PACKET_GET_NODES, "Malformed packet."); ck_assert_msg(memcmp(pkt + 1, key, CRYPTO_SYMMETRIC_KEY_SIZE) == 0, "Malformed packet."); ck_assert_msg(length == 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE, "Invalid size. Should be %u got %d", (unsigned)sizeof(pkt), length); printf("Create Packet Successful!\n"); } #define MAX_COUNT 3 static void dht_pack_unpack(const Node_format *nodes, size_t size, uint8_t *data, size_t length) { int16_t packed_size = pack_nodes(data, length, nodes, size); ck_assert_msg(packed_size != -1, "Wrong pack_nodes result"); uint16_t processed = 0; VLA(Node_format, nodes_unpacked, size); const uint8_t tcp_enabled = 1; uint16_t unpacked_count = unpack_nodes(nodes_unpacked, size, &processed, data, length, tcp_enabled); ck_assert_msg(unpacked_count == size, "Wrong unpack_nodes result"); ck_assert_msg(processed == packed_size, "unpack_nodes did not process all data"); for (size_t i = 0; i < size; i++) { const IP_Port *ipp1 = &nodes[i].ip_port; const IP_Port *ipp2 = &nodes_unpacked[i].ip_port; ck_assert_msg(ip_equal(&ipp1->ip, &ipp2->ip), "Unsuccessful ip unpack"); ck_assert_msg(ipp1->port == ipp2->port, "Unsuccessful port unpack"); const uint8_t *pk1 = nodes[i].public_key; const uint8_t *pk2 = nodes_unpacked[i].public_key; ck_assert_msg(!memcmp(pk1, pk2, CRYPTO_PUBLIC_KEY_SIZE), "Unsuccessful pk unpack"); } } static void random_ip(IP_Port *ipp, int family) { uint8_t *ip = nullptr; size_t size; if (family == TOX_AF_INET || family == TCP_INET) { ip = (uint8_t *)&ipp->ip.ip.v4; size = sizeof(ipp->ip.ip.v4); } else if (family == TOX_AF_INET6 || family == TCP_INET6) { ip = (uint8_t *)&ipp->ip.ip.v6; size = sizeof(ipp->ip.ip.v6); } else { return; } uint8_t *port = (uint8_t *)&ipp->port; random_bytes(port, sizeof(ipp->port)); random_bytes(ip, size); // TODO(iphydf): Pass the net_family variant to random_ip. ipp->ip.family.value = family; } #define PACKED_NODES_SIZE (SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE) static void test_dht_node_packing(void) { const uint16_t length = MAX_COUNT * PACKED_NODES_SIZE; uint8_t *data = (uint8_t *)malloc(length); Node_format nodes[MAX_COUNT]; const size_t pk_size = sizeof(nodes[0].public_key); random_bytes(nodes[0].public_key, pk_size); random_bytes(nodes[1].public_key, pk_size); random_bytes(nodes[2].public_key, pk_size); random_ip(&nodes[0].ip_port, TOX_AF_INET); random_ip(&nodes[1].ip_port, TOX_AF_INET); random_ip(&nodes[2].ip_port, TOX_AF_INET); dht_pack_unpack(nodes, 3, data, length); random_ip(&nodes[0].ip_port, TOX_AF_INET); random_ip(&nodes[1].ip_port, TOX_AF_INET); random_ip(&nodes[2].ip_port, TCP_INET); dht_pack_unpack(nodes, 3, data, length); random_ip(&nodes[0].ip_port, TOX_AF_INET); random_ip(&nodes[1].ip_port, TOX_AF_INET6); random_ip(&nodes[2].ip_port, TCP_INET6); dht_pack_unpack(nodes, 3, data, length); random_ip(&nodes[0].ip_port, TCP_INET); random_ip(&nodes[1].ip_port, TCP_INET6); random_ip(&nodes[2].ip_port, TCP_INET); dht_pack_unpack(nodes, 3, data, length); random_ip(&nodes[0].ip_port, TOX_AF_INET6); random_ip(&nodes[1].ip_port, TOX_AF_INET6); random_ip(&nodes[2].ip_port, TOX_AF_INET6); dht_pack_unpack(nodes, 3, data, length); free(data); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); test_dht_create_packet(); test_dht_node_packing(); test_list(); test_DHT_test(); if (enable_broken_tests) { test_addto_lists_ipv4(); test_addto_lists_ipv6(); } return 0; } c-toxcore-0.2.13/auto_tests/encryptsave_test.c000066400000000000000000000237731415350724400214610ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #ifdef VANILLA_NACL #include "../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_pwhash_scryptsalsa208sha256.h" #else #include #endif #include "../testing/misc_tools.h" #include "../toxcore/ccompat.h" #include "../toxcore/crypto_core.h" #include "../toxcore/tox.h" #include "../toxencryptsave/toxencryptsave.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); VLA(uint8_t, data, size); tox_get_savedata(tox1, data); size_t size2 = size + TOX_PASS_ENCRYPTION_EXTRA_LENGTH; VLA(uint8_t, enc_data, size2); 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"); VLA(uint8_t, dec_data, size); 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); VLA(uint8_t, data2, size); 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; VLA(uint8_t, encdata2, size2); 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"); VLA(uint8_t, out1, size); VLA(uint8_t, out2, size); 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); } 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); uint8_t *in_plaintext2a = (uint8_t *)malloc(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); 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; } c-toxcore-0.2.13/auto_tests/file_saving_test.c000066400000000000000000000066201415350724400213740ustar00rootroot00000000000000/* 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/ccompat.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[TOX_MAX_NAME_LENGTH]; tox_self_get_name(t, readname); readname[tox_self_get_name_size(t)] = '\0'; ck_assert_msg(strcmp((const char *)readname, name) == 0, "name returned by tox_self_get_name does not match expected result"); tox_kill(t); free(clear); free(cipher); 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; } c-toxcore-0.2.13/auto_tests/file_transfer_test.c000066400000000000000000000352551415350724400217370ustar00rootroot00000000000000/* File transfer test. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/ccompat.h" #include "../toxcore/tox.h" #include "../toxcore/util.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; } c-toxcore-0.2.13/auto_tests/friend_connection_test.c000066400000000000000000000012311415350724400225650ustar00rootroot00000000000000/* 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include typedef struct State { uint32_t index; uint64_t clock; } State; #include "run_auto_test.h" static void friend_connection_test(Tox **toxes, State *state) { // Nothing to do here. When copying this test, add test-specific code here. } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); run_auto_test(2, friend_connection_test, false); return 0; } c-toxcore-0.2.13/auto_tests/friend_request_test.c000066400000000000000000000053521415350724400221260ustar00rootroot00000000000000/* Tests that we can add friends. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../toxcore/ccompat.h" #include "../toxcore/tox.h" #include "../toxcore/util.h" #include "../testing/misc_tools.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; } c-toxcore-0.2.13/auto_tests/invalid_tcp_proxy_test.c000066400000000000000000000031421415350724400226370ustar00rootroot00000000000000// 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. #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "../testing/misc_tools.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; } c-toxcore-0.2.13/auto_tests/invalid_udp_proxy_test.c000066400000000000000000000030621415350724400226420ustar00rootroot00000000000000// Test that if UDP is enabled, and a proxy is provided that does not support // UDP proxying, we disable UDP. #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "../testing/misc_tools.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; } c-toxcore-0.2.13/auto_tests/lan_discovery_test.c000066400000000000000000000017021415350724400217430ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "../testing/misc_tools.h" #include "../toxcore/ccompat.h" 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); 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(1000); } 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; } c-toxcore-0.2.13/auto_tests/lossless_packet_test.c000066400000000000000000000034661415350724400223110ustar00rootroot00000000000000/* Tests that we can send lossless packets. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #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 { uint32_t index; uint64_t clock; bool custom_packet_received; } State; #include "run_auto_test.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) { State *state = (State *)user_data; uint8_t cmp_packet[TOX_MAX_CUSTOM_PACKET_SIZE]; memset(cmp_packet, LOSSLESS_PACKET_FILLER, sizeof(cmp_packet)); if (length == TOX_MAX_CUSTOM_PACKET_SIZE && memcmp(data, cmp_packet, sizeof(cmp_packet)) == 0) { state->custom_packet_received = true; } } static void test_lossless_packet(Tox **toxes, State *state) { tox_callback_friend_lossless_packet(toxes[1], &handle_lossless_packet); uint8_t packet[TOX_MAX_CUSTOM_PACKET_SIZE + 1]; memset(packet, LOSSLESS_PACKET_FILLER, sizeof(packet)); bool ret = tox_friend_send_lossless_packet(toxes[0], 0, packet, sizeof(packet), nullptr); ck_assert_msg(ret == false, "should not be able to send custom packets this big %i", ret); ret = tox_friend_send_lossless_packet(toxes[0], 0, packet, TOX_MAX_CUSTOM_PACKET_SIZE, nullptr); ck_assert_msg(ret == true, "tox_friend_send_lossless_packet fail %i", ret); do { iterate_all_wait(2, toxes, state, ITERATION_INTERVAL); } while (!state[1].custom_packet_received); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); run_auto_test(2, test_lossless_packet, false); return 0; } c-toxcore-0.2.13/auto_tests/lossy_packet_test.c000066400000000000000000000032661415350724400216110ustar00rootroot00000000000000/* Tests that we can send lossy packets. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/util.h" #include "check_compat.h" typedef struct State { uint32_t index; uint64_t clock; bool custom_packet_received; } State; #include "run_auto_test.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[TOX_MAX_CUSTOM_PACKET_SIZE]; memset(cmp_packet, LOSSY_PACKET_FILLER, sizeof(cmp_packet)); if (length == TOX_MAX_CUSTOM_PACKET_SIZE && memcmp(data, cmp_packet, sizeof(cmp_packet)) == 0) { State *state = (State *)user_data; state->custom_packet_received = true; } } static void test_lossy_packet(Tox **toxes, State *state) { tox_callback_friend_lossy_packet(toxes[1], &handle_lossy_packet); uint8_t packet[TOX_MAX_CUSTOM_PACKET_SIZE + 1]; memset(packet, LOSSY_PACKET_FILLER, sizeof(packet)); bool ret = tox_friend_send_lossy_packet(toxes[0], 0, packet, sizeof(packet), nullptr); ck_assert_msg(ret == false, "should not be able to send custom packets this big %i", ret); ret = tox_friend_send_lossy_packet(toxes[0], 0, packet, TOX_MAX_CUSTOM_PACKET_SIZE, nullptr); ck_assert_msg(ret == true, "tox_friend_send_lossy_packet fail %i", ret); do { iterate_all_wait(2, toxes, state, ITERATION_INTERVAL); } while (!state[1].custom_packet_received); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); run_auto_test(2, test_lossy_packet, false); return 0; } c-toxcore-0.2.13/auto_tests/messenger_test.c000066400000000000000000000272301415350724400210760ustar00rootroot00000000000000/* unit tests for /core/Messenger.c * Design: * Just call every non-static function in Messenger.c, checking that * they return as they should with check calls. "Bad" calls of the type * function(bad_data, good_length) are _not_ checked for, this type * of call is the fault of the client code. * * Note: * None of the functions here test things that rely on the network, i.e. * checking that status changes are received, messages can be sent, etc. * All of that is done in a separate test, with two local clients running. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #ifdef VANILLA_NACL #include // crypto_box_PUBLICKEYBYTES and other defines. #else #include #endif #include "check_compat.h" #include "../testing/misc_tools.h" #include "../toxcore/Messenger.h" #define REALLY_BIG_NUMBER ((1) << (sizeof(uint16_t) * 7)) static bool enable_broken_tests = false; static const char *friend_id_str = "e4b3d5030bc99494605aecc33ceec8875640c1d74aa32790e821b17e98771c4a00000000f1db"; static const char *good_id_str = "d3f14b6d384d8f5f2a66cff637e69f28f539c5de61bc29744785291fa4ef4d64"; static const char *bad_id_str = "9B569D14ff637e69f2"; static uint8_t *friend_id = nullptr; static uint8_t *good_id = nullptr; static uint8_t *bad_id = nullptr; static int friend_id_num = 0; static Messenger *m; START_TEST(test_m_sendmesage) { const char *message = "h-hi :3"; int good_len = strlen(message); int bad_len = MAX_CRYPTO_PACKET_SIZE; ck_assert(m_send_message_generic( m, -1, MESSAGE_NORMAL, (const uint8_t *)message, good_len, nullptr) == -1); ck_assert(m_send_message_generic( m, REALLY_BIG_NUMBER, MESSAGE_NORMAL, (const uint8_t *)message, good_len, nullptr) == -1); ck_assert(m_send_message_generic( m, 17, MESSAGE_NORMAL, (const uint8_t *)message, good_len, nullptr) == -1); ck_assert(m_send_message_generic( m, friend_id_num, MESSAGE_NORMAL, (const uint8_t *)message, bad_len, nullptr) == -2); } END_TEST START_TEST(test_m_get_userstatus_size) { int rc = 0; ck_assert_msg((m_get_statusmessage_size(m, -1) == -1), "m_get_statusmessage_size did NOT catch an argument of -1"); ck_assert_msg((m_get_statusmessage_size(m, REALLY_BIG_NUMBER) == -1), "m_get_statusmessage_size did NOT catch the following argument: %d\n", REALLY_BIG_NUMBER); rc = m_get_statusmessage_size(m, friend_id_num); /* this WILL error if the original m_addfriend_norequest() failed */ ck_assert_msg((rc >= 0 && rc <= MAX_STATUSMESSAGE_LENGTH), "m_get_statusmessage_size is returning out of range values! (%i)\n" "(this can be caused by the error of m_addfriend_norequest" " in the beginning of the suite)\n", rc); } END_TEST START_TEST(test_m_set_userstatus) { const char *status = "online!"; uint16_t good_length = strlen(status); uint16_t bad_length = REALLY_BIG_NUMBER; ck_assert_msg((m_set_statusmessage(m, (const uint8_t *)status, bad_length) == -1), "m_set_userstatus did NOT catch the following length: %d\n", REALLY_BIG_NUMBER); ck_assert_msg((m_set_statusmessage(m, (const uint8_t *)status, good_length) == 0), "m_set_userstatus did NOT return 0 on the following length: %d\n" "MAX_STATUSMESSAGE_LENGTH: %d\n", good_length, MAX_STATUSMESSAGE_LENGTH); } END_TEST START_TEST(test_m_get_friend_connectionstatus) { ck_assert_msg((m_get_friend_connectionstatus(m, -1) == -1), "m_get_friend_connectionstatus did NOT catch an argument of -1.\n"); ck_assert_msg((m_get_friend_connectionstatus(m, REALLY_BIG_NUMBER) == -1), "m_get_friend_connectionstatus did NOT catch an argument of %d.\n", REALLY_BIG_NUMBER); } END_TEST START_TEST(test_m_friend_exists) { ck_assert_msg((m_friend_exists(m, -1) == 0), "m_friend_exists did NOT catch an argument of -1.\n"); ck_assert_msg((m_friend_exists(m, REALLY_BIG_NUMBER) == 0), "m_friend_exists did NOT catch an argument of %d.\n", REALLY_BIG_NUMBER); } END_TEST START_TEST(test_m_delfriend) { ck_assert_msg((m_delfriend(m, -1) == -1), "m_delfriend did NOT catch an argument of -1\n"); ck_assert_msg((m_delfriend(m, REALLY_BIG_NUMBER) == -1), "m_delfriend did NOT catch the following number: %d\n", REALLY_BIG_NUMBER); } END_TEST START_TEST(test_m_addfriend) { const char *good_data = "test"; const char *bad_data = ""; int good_len = strlen(good_data); int bad_len = strlen(bad_data); int really_bad_len = (MAX_CRYPTO_PACKET_SIZE - crypto_box_PUBLICKEYBYTES - crypto_box_NONCEBYTES - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES + 100); /* TODO(irungentoo): Update this properly to latest master */ if (m_addfriend(m, friend_id, (const uint8_t *)good_data, really_bad_len) != FAERR_TOOLONG) { ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", really_bad_len); } /* this will return an error if the original m_addfriend_norequest() failed */ if (m_addfriend(m, friend_id, (const uint8_t *)good_data, good_len) != FAERR_ALREADYSENT) { ck_abort_msg("m_addfriend did NOT catch adding a friend we already have.\n" "(this can be caused by the error of m_addfriend_norequest in" " the beginning of the suite)\n"); } if (m_addfriend(m, good_id, (const uint8_t *)bad_data, bad_len) != FAERR_NOMESSAGE) { ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", bad_len); } /* this should REALLY return an error */ /* TODO(irungentoo): validate client_id in m_addfriend? */ if (m_addfriend(m, bad_id, (const uint8_t *)good_data, good_len) >= 0) { ck_abort_msg("The following ID passed through " "m_addfriend without an error:\n'%s'\n", bad_id_str); } } END_TEST START_TEST(test_setname) { const char *good_name = "consensualCorn"; int good_length = strlen(good_name); int bad_length = REALLY_BIG_NUMBER; ck_assert_msg((setname(m, (const uint8_t *)good_name, bad_length) == -1), "setname() did NOT error on %d as a length argument!\n", bad_length); ck_assert_msg((setname(m, (const uint8_t *)good_name, good_length) == 0), "setname() did NOT return 0 on good arguments!\n"); } END_TEST START_TEST(test_getself_name) { const char *nickname = "testGallop"; size_t len = strlen(nickname); char *nick_check = (char *)calloc(len + 1, 1); setname(m, (const uint8_t *)nickname, len); getself_name(m, (uint8_t *)nick_check); ck_assert_msg((memcmp(nickname, nick_check, len) == 0), "getself_name failed to return the known name!\n" "known name: %s\nreturned: %s\n", nickname, nick_check); free(nick_check); } END_TEST /* this test is excluded for now, due to lack of a way * to set a friend's status for now. * ideas: * if we have access to the friends list, we could * just add a status manually ourselves. */ #if 0 START_TEST(test_m_copy_userstatus) { assert(m_copy_userstatus(-1, buf, MAX_USERSTATUS_LENGTH) == -1); assert(m_copy_userstatus(REALLY_BIG_NUMBER, buf, MAX_USERSTATUS_LENGTH) == -1); m_copy_userstatus(friend_id_num, buf, MAX_USERSTATUS_LENGTH + 6); assert(strcmp(name_buf, friend_id_status) == 0); } END_TEST #endif START_TEST(test_getname) { uint8_t name_buf[MAX_NAME_LENGTH]; uint8_t test_name[] = {'f', 'o', 'o'}; ck_assert(getname(m, -1, name_buf) == -1); ck_assert(getname(m, REALLY_BIG_NUMBER, name_buf) == -1); memcpy(m->friendlist[0].name, &test_name[0], 3); m->friendlist[0].name_length = 4; ck_assert(getname(m, 0, &name_buf[0]) == 4); ck_assert(strcmp((char *)&name_buf[0], "foo") == 0); } END_TEST START_TEST(test_dht_state_saveloadsave) { /* validate that: * a) saving stays within the confined space * b) a save()d state can be load()ed back successfully * c) a second save() is of equal size * d) the second save() is of equal content */ const size_t extra = 64; const size_t size = dht_size(m->dht); VLA(uint8_t, buffer, size + 2 * extra); memset(buffer, 0xCD, extra); memset(buffer + extra + size, 0xCD, extra); dht_save(m->dht, buffer + extra); for (size_t i = 0; i < extra; i++) { ck_assert_msg(buffer[i] == 0xCD, "Buffer underwritten from dht_save() @%u", (unsigned)i); ck_assert_msg(buffer[extra + size + i] == 0xCD, "Buffer overwritten from dht_save() @%u", (unsigned)i); } const int res = dht_load(m->dht, buffer + extra, size); if (res == -1) { ck_assert_msg(res == 0, "Failed to load back stored buffer: res == -1"); } else { const size_t offset = res >> 4; const uint8_t *ptr = buffer + extra + offset; ck_assert_msg(res == 0, "Failed to load back stored buffer: 0x%02x%02x%02x%02x%02x%02x%02x%02x @%u/%u, code %d", ptr[-2], ptr[-1], ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], (unsigned)offset, (unsigned)size, res & 0x0F); } const size_t size2 = dht_size(m->dht); ck_assert_msg(size == size2, "Messenger \"grew\" in size from a store/load cycle: %u -> %u", (unsigned)size, (unsigned)size2); VLA(uint8_t, buffer2, size2); dht_save(m->dht, buffer2); ck_assert_msg(!memcmp(buffer + extra, buffer2, size), "DHT state changed by store/load/store cycle"); } END_TEST static Suite *messenger_suite(void) { Suite *s = suite_create("Messenger"); DEFTESTCASE(dht_state_saveloadsave); DEFTESTCASE(getself_name); DEFTESTCASE(m_get_userstatus_size); DEFTESTCASE(m_set_userstatus); if (enable_broken_tests) { DEFTESTCASE(m_addfriend); } DEFTESTCASE(m_friend_exists); DEFTESTCASE(m_get_friend_connectionstatus); DEFTESTCASE(m_delfriend); DEFTESTCASE(setname); DEFTESTCASE(getname); DEFTESTCASE(m_sendmesage); return s; } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); friend_id = hex_string_to_bin(friend_id_str); good_id = hex_string_to_bin(good_id_str); bad_id = hex_string_to_bin(bad_id_str); Mono_Time *mono_time = mono_time_new(); /* IPv6 status from global define */ Messenger_Options options = {0}; options.ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; options.port_range[0] = 41234; options.port_range[1] = 44234; options.log_callback = (logger_cb *)print_debug_log; m = new_messenger(mono_time, &options, nullptr); /* setup a default friend and friendnum */ if (m_addfriend_norequest(m, friend_id) < 0) { fputs("m_addfriend_norequest() failed on a valid ID!\n" "this was CRITICAL to the test, and the build WILL fail.\n" "the tests will continue now...\n\n", stderr); } if ((friend_id_num = getfriend_id(m, friend_id)) < 0) { fputs("getfriend_id() failed on a valid ID!\n" "this was CRITICAL to the test, and the build WILL fail.\n" "the tests will continue now...\n\n", stderr); } Suite *messenger = messenger_suite(); SRunner *test_runner = srunner_create(messenger); int number_failed = 0; srunner_run_all(test_runner, CK_NORMAL); number_failed = srunner_ntests_failed(test_runner); srunner_free(test_runner); free(friend_id); free(good_id); free(bad_id); kill_messenger(m); mono_time_free(mono_time); return number_failed; } c-toxcore-0.2.13/auto_tests/network_test.c000066400000000000000000000141471415350724400206020ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "../testing/misc_tools.h" #include "../toxcore/network.h" #include "check_compat.h" #ifndef USE_IPV6 #define USE_IPV6 1 #endif START_TEST(test_addr_resolv_localhost) { #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 char localhost[] = "localhost"; IP ip; ip_init(&ip, 0); // ipv6enabled = 0 int res = addr_resolve(localhost, &ip, nullptr); int error = net_error(); const char *strerror = net_new_strerror(error); ck_assert_msg(res > 0, "Resolver failed: %d, %s", error, strerror); net_kill_strerror(strerror); char ip_str[IP_NTOA_LEN]; 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.", ip_ntoa(&ip, ip_str, sizeof(ip_str))); ip_init(&ip, 1); // ipv6enabled = 1 res = addr_resolve(localhost, &ip, nullptr); #if USE_IPV6 int localhost_split = 0; if (!(res & TOX_ADDR_RESOLVE_INET6)) { res = addr_resolve("ip6-localhost", &ip, nullptr); localhost_split = 1; } error = net_error(); strerror = net_new_strerror(error); ck_assert_msg(res > 0, "Resolver failed: %d, %s", error, strerror); net_kill_strerror(strerror); ck_assert_msg(net_family_is_ipv6(ip.family), "Expected family TOX_AF_INET6 (%u), 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.", ip_ntoa(&ip, ip_str, sizeof(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(localhost, &ip, &extra); error = net_error(); strerror = net_new_strerror(error); ck_assert_msg(res > 0, "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 (%u), got %u.", TOX_AF_INET6, ip.family.value); ck_assert_msg(!memcmp(&ip.ip.v6, &ip6_loopback, sizeof(IP6)), "Expected ::1, got %s.", ip_ntoa(&ip, ip_str, sizeof(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.", ip_ntoa(&ip, ip_str, sizeof(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.", ip_ntoa(&ip, ip_str, sizeof(ip_str))); #endif } END_TEST START_TEST(test_ip_equal) { 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); } END_TEST static Suite *network_suite(void) { Suite *s = suite_create("Network"); networking_at_startup(); DEFTESTCASE(addr_resolv_localhost); DEFTESTCASE(ip_equal); return s; } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); Suite *network = network_suite(); SRunner *test_runner = srunner_create(network); int number_failed = 0; srunner_run_all(test_runner, CK_NORMAL); number_failed = srunner_ntests_failed(test_runner); srunner_free(test_runner); return number_failed; } c-toxcore-0.2.13/auto_tests/onion_test.c000066400000000000000000000426021415350724400202300ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #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 "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(Onion *onion) { mono_time_update(onion->mono_time); networking_poll(onion->net, nullptr); do_dht(onion->dht); } static int handled_test_1; static int handle_test_1(void *object, 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, 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, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion *onion = (Onion *)object; if (length != (1 + CRYPTO_NONCE_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + 1 + CRYPTO_SHA256_SIZE + CRYPTO_MAC_SIZE)) { return 1; } uint8_t plain[1 + 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, 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; } static void test_basic(void) { uint32_t index[] = { 1, 2, 3 }; Logger *log1 = logger_new(); logger_callback_log(log1, (logger_cb *)print_debug_log, nullptr, &index[0]); Logger *log2 = logger_new(); logger_callback_log(log2, (logger_cb *)print_debug_log, nullptr, &index[1]); Mono_Time *mono_time1 = mono_time_new(); Mono_Time *mono_time2 = mono_time_new(); IP ip = get_loopback(); Onion *onion1 = new_onion(mono_time1, new_dht(log1, mono_time1, new_networking(log1, ip, 36567), true)); Onion *onion2 = new_onion(mono_time2, new_dht(log2, mono_time2, new_networking(log2, ip, 36568), true)); 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(onion1->dht, &path, nodes); int ret = send_onion_packet(onion1->net, &path, nodes[3].ip_port, req_packet, sizeof(req_packet)); ck_assert_msg(ret == 0, "Failed to create/send onion packet."); handled_test_1 = 0; do { do_onion(onion1); do_onion(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(onion1); do_onion(onion2); } while (handled_test_2 == 0); Onion_Announce *onion1_a = new_onion_announce(mono_time1, onion1->dht); Onion_Announce *onion2_a = new_onion_announce(mono_time2, onion2->dht); networking_registerhandler(onion1->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_test_3, onion1); ck_assert_msg((onion1_a != nullptr) && (onion2_a != nullptr), "Onion_Announce failed initializing."); uint8_t zeroes[64] = {0}; random_bytes(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); ret = send_announce_request(onion1->net, &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(onion1); do_onion(onion2); c_sleep(50); } while (handled_test_3 == 0); random_bytes(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, &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(onion1); do_onion(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, (logger_cb *)print_debug_log, nullptr, &index[2]); Mono_Time *mono_time3 = mono_time_new(); Onion *onion3 = new_onion(mono_time3, new_dht(log3, mono_time3, new_networking(log3, ip, 36569), true)); ck_assert_msg((onion3 != nullptr), "Onion failed initializing."); random_nonce(nonce); ret = send_data_request(onion3->net, &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(onion1); do_onion(onion2); c_sleep(50); } while (handled_test_4 == 0); 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(uint16_t port, uint32_t *index) { IP ip = get_loopback(); ip.ip.v6.uint8[15] = 1; 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, (logger_cb *)print_debug_log, nullptr, index); on->mono_time = mono_time_new(); if (!on->mono_time) { logger_kill(on->log); free(on); return nullptr; } Networking_Core *net = new_networking(on->log, ip, port); if (!net) { mono_time_free(on->mono_time); logger_kill(on->log); free(on); return nullptr; } DHT *dht = new_dht(on->log, on->mono_time, net, true); if (!dht) { kill_networking(net); mono_time_free(on->mono_time); logger_kill(on->log); free(on); return nullptr; } on->onion = new_onion(on->mono_time, 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->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, on->mono_time, new_net_crypto(on->log, 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, 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; uint16_t count = 0; int ret = dht_addfriend(on->onion->dht, dht_public_key, &dht_ip_callback, object, number, &count); ck_assert_msg(ret == 0, "dht_addfriend() did not return 0"); ck_assert_msg(count == 1, "Count not 1, count is %u", count); 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]; for (i = 0; i < NUM_ONIONS; ++i) { index[i] = i + 1; onions[i] = new_onions(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; } c-toxcore-0.2.13/auto_tests/overflow_recvq_test.c000066400000000000000000000035641415350724400221550ustar00rootroot00000000000000/* Try to overflow the net_crypto packet buffer. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include typedef struct State { uint32_t index; uint64_t clock; uint32_t recv_count; } State; #include "run_auto_test.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) { State *state = (State *)user_data; state->recv_count++; } static void net_crypto_overflow_test(Tox **toxes, State *state) { tox_callback_friend_message(toxes[0], 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(toxes[tox_index], 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(3, toxes, state, ITERATION_INTERVAL); } printf("tox%u received %u messages\n", state[0].index, state[0].recv_count); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); run_auto_test(3, net_crypto_overflow_test, false); return 0; } c-toxcore-0.2.13/auto_tests/overflow_sendq_test.c000066400000000000000000000025121415350724400221370ustar00rootroot00000000000000/* Try to overflow the net_crypto packet buffer. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include typedef struct State { uint32_t index; uint64_t clock; } State; #include "run_auto_test.h" #define NUM_MSGS 40000 static void net_crypto_overflow_test(Tox **toxes, State *state) { 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(toxes[0], 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_test(2, net_crypto_overflow_test, false); return 0; } c-toxcore-0.2.13/auto_tests/reconnect_test.c000066400000000000000000000055201415350724400210640ustar00rootroot00000000000000/* 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #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) typedef struct State { uint32_t index; uint64_t clock; } State; #include "run_auto_test.h" static uint32_t tox_connected_count(uint32_t tox_count, Tox **toxes, State *state, uint32_t index) { const size_t friend_count = tox_self_get_friend_list_size(toxes[index]); uint32_t connected_count = 0; for (size_t j = 0; j < friend_count; j++) { if (tox_friend_get_connection_status(toxes[index], j, nullptr) != TOX_CONNECTION_NONE) { ++connected_count; } } return connected_count; } static bool all_disconnected_from(uint32_t tox_count, Tox **toxes, State *state, uint32_t index) { for (uint32_t i = 0; i < tox_count; i++) { if (i == index) { continue; } if (tox_connected_count(tox_count, toxes, state, i) >= tox_count - 1) { return false; } } return true; } static void test_reconnect(Tox **toxes, State *state) { const time_t test_start_time = time(nullptr); printf("letting connections settle\n"); do { iterate_all_wait(TOX_COUNT, toxes, state, ITERATION_INTERVAL); } while (time(nullptr) - test_start_time < 2); uint16_t disconnect = random_u16() % TOX_COUNT; printf("disconnecting #%u\n", state[disconnect].index); do { for (uint16_t i = 0; i < TOX_COUNT; ++i) { if (i != disconnect) { tox_iterate(toxes[i], &state[i]); state[i].clock += 1000; } } c_sleep(20); } while (!all_disconnected_from(TOX_COUNT, toxes, state, disconnect)); const uint64_t reconnect_start_time = state[0].clock; printf("reconnecting\n"); do { iterate_all_wait(TOX_COUNT, toxes, state, ITERATION_INTERVAL); } while (!all_friends_connected(TOX_COUNT, toxes)); const uint64_t reconnect_time = state[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_test(TOX_COUNT, test_reconnect, false); return 0; } c-toxcore-0.2.13/auto_tests/run_auto_test.h000066400000000000000000000103221415350724400207410ustar00rootroot00000000000000#include // calloc, free #include "check_compat.h" #include "../testing/misc_tools.h" #include "../toxcore/Messenger.h" #include "../toxcore/mono_time.h" static bool all_connected(uint32_t tox_count, Tox **toxes) { for (uint32_t i = 0; i < tox_count; i++) { if (tox_self_get_connection_status(toxes[i]) == TOX_CONNECTION_NONE) { return false; } } return true; } static bool all_friends_connected(uint32_t tox_count, Tox **toxes) { for (uint32_t i = 0; i < tox_count; i++) { const size_t friend_count = tox_self_get_friend_list_size(toxes[i]); for (size_t j = 0; j < friend_count; j++) { if (tox_friend_get_connection_status(toxes[i], j, nullptr) == TOX_CONNECTION_NONE) { return false; } } } return true; } static void iterate_all_wait(uint32_t tox_count, Tox **toxes, State *state, uint32_t wait) { for (uint32_t i = 0; i < tox_count; i++) { tox_iterate(toxes[i], &state[i]); state[i].clock += wait; } /* Also actually sleep a little, to allow for local network processing */ c_sleep(5); } static uint64_t get_state_clock_callback(Mono_Time *mono_time, void *user_data) { const State *state = (const State *)user_data; return state->clock; } static void set_mono_time_callback(Tox *tox, State *state) { // TODO(iphydf): Don't rely on toxcore internals. Mono_Time *mono_time = ((Messenger *)tox)->mono_time; state->clock = current_time_monotonic(mono_time); mono_time_set_current_time_callback(mono_time, get_state_clock_callback, state); } static void run_auto_test(uint32_t tox_count, void test(Tox **toxes, State *state), bool chain) { printf("initialising %u toxes\n", tox_count); Tox **toxes = (Tox **)calloc(tox_count, sizeof(Tox *)); State *state = (State *)calloc(tox_count, sizeof(State)); ck_assert(toxes != nullptr); ck_assert(state != nullptr); for (uint32_t i = 0; i < tox_count; i++) { state[i].index = i; toxes[i] = tox_new_log(nullptr, nullptr, &state[i].index); ck_assert_msg(toxes[i], "failed to create %u tox instances", i + 1); set_mono_time_callback(toxes[i], &state[i]); } if (chain) { printf("each tox adds adjacent toxes as friends\n"); for (uint32_t i = 0; i < tox_count; i++) { for (uint32_t j = i - 1; j != i + 3; j += 2) { if (j >= tox_count) { continue; } uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; tox_self_get_public_key(toxes[j], public_key); Tox_Err_Friend_Add err; tox_friend_add_norequest(toxes[i], public_key, &err); ck_assert(err == TOX_ERR_FRIEND_ADD_OK); } } } else { printf("toxes all add each other as friends\n"); for (uint32_t i = 0; i < tox_count; i++) { for (uint32_t j = 0; j < tox_count; j++) { if (i != j) { uint8_t public_key[TOX_PUBLIC_KEY_SIZE]; tox_self_get_public_key(toxes[j], public_key); Tox_Err_Friend_Add err; tox_friend_add_norequest(toxes[i], public_key, &err); ck_assert(err == TOX_ERR_FRIEND_ADD_OK); } } } } printf("bootstrapping all toxes off toxes[0]\n"); uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; tox_self_get_dht_id(toxes[0], dht_key); const uint16_t dht_port = tox_self_get_udp_port(toxes[0], nullptr); for (uint32_t i = 1; i < tox_count; i++) { Tox_Err_Bootstrap err; tox_bootstrap(toxes[i], "localhost", dht_port, dht_key, &err); ck_assert(err == TOX_ERR_BOOTSTRAP_OK); } do { iterate_all_wait(tox_count, toxes, state, ITERATION_INTERVAL); } while (!all_connected(tox_count, toxes)); printf("toxes are online\n"); do { iterate_all_wait(tox_count, toxes, state, ITERATION_INTERVAL); } while (!all_friends_connected(tox_count, toxes)); printf("tox clients connected\n"); test(toxes, state); for (uint32_t i = 0; i < tox_count; i++) { tox_kill(toxes[i]); } free(state); free(toxes); } c-toxcore-0.2.13/auto_tests/save_compatibility_test.c000066400000000000000000000113171415350724400227740ustar00rootroot00000000000000// Tests to make sure new save code is compatible with old save files #include "../testing/misc_tools.h" #include "../toxcore/tox.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[TOX_MAX_NAME_LENGTH]; 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[TOX_MAX_STATUS_MESSAGE_LENGTH]; 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); tox_kill(tox); } int main(int argc, char *argv[]) { char base_path[4096]; if (argc <= 1) { const char *srcdir = getenv("srcdir"); if (srcdir == nullptr) { srcdir = "."; } strcpy(base_path, srcdir); } else { strcpy(base_path, 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; } c-toxcore-0.2.13/auto_tests/save_friend_test.c000066400000000000000000000106451415350724400213750ustar00rootroot00000000000000/* Auto Tests: Save and load friends. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/ccompat.h" #include "../toxcore/crypto_core.h" #include "../toxcore/tox.h" #include "check_compat.h" struct test_data { uint8_t name[TOX_MAX_NAME_LENGTH]; uint8_t status_message[TOX_MAX_STATUS_MESSAGE_LENGTH]; bool received_name; bool received_status_message; }; static void set_random(Tox *m, 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(); } setter(m, text, SIZEOF_VLA(text), nullptr); } 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; memcpy(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; memcpy(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 = {{0}}; 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[TOX_MAX_NAME_LENGTH] = { 0 }; uint8_t reference_status[TOX_MAX_STATUS_MESSAGE_LENGTH] = { 0 }; set_random(tox1, tox_self_set_name, TOX_MAX_NAME_LENGTH); set_random(tox2, tox_self_set_name, TOX_MAX_NAME_LENGTH); set_random(tox1, tox_self_set_status_message, TOX_MAX_STATUS_MESSAGE_LENGTH); set_random(tox2, 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); VLA(uint8_t, savedata, 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); tox_friend_get_name(tox_to_compare, 0, to_compare.name, 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); return 0; } c-toxcore-0.2.13/auto_tests/save_load_test.c000066400000000000000000000162201415350724400210400ustar00rootroot00000000000000/* Tests that we can save and load Tox data. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/ccompat.h" #include "../toxcore/tox.h" #include "../toxcore/util.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 33430 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); } 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 *tox1 = tox_new_log(opts1, nullptr, &index[0]); 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, nullptr, &index[1]); struct Tox_Options *opts3 = tox_options_new(nullptr); tox_options_set_local_discovery_enabled(opts3, false); Tox *tox3 = tox_new_log(opts3, nullptr, &index[2]); ck_assert_msg(tox1 && tox2 && tox3, "Failed to create 3 tox instances"); 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: %i", 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; } } c_sleep(ITERATION_INTERVAL); } 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)); 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; } } c_sleep(ITERATION_INTERVAL); } 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; } c-toxcore-0.2.13/auto_tests/send_message_test.c000066400000000000000000000033561415350724400215460ustar00rootroot00000000000000/* Tests that we can send messages to friends. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include typedef struct State { uint32_t index; uint64_t clock; bool message_received; } State; #include "run_auto_test.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 *userdata) { State *state = (State *)userdata; if (type != TOX_MESSAGE_TYPE_NORMAL) { ck_abort_msg("Bad type"); } uint8_t cmp_msg[TOX_MAX_MESSAGE_LENGTH]; memset(cmp_msg, MESSAGE_FILLER, sizeof(cmp_msg)); if (length == TOX_MAX_MESSAGE_LENGTH && memcmp(string, cmp_msg, sizeof(cmp_msg)) == 0) { state->message_received = true; } } static void send_message_test(Tox **toxes, State *state) { tox_callback_friend_message(toxes[1], &message_callback); uint8_t msgs[TOX_MAX_MESSAGE_LENGTH + 1]; memset(msgs, MESSAGE_FILLER, sizeof(msgs)); Tox_Err_Friend_Send_Message errm; tox_friend_send_message(toxes[0], 0, TOX_MESSAGE_TYPE_NORMAL, msgs, TOX_MAX_MESSAGE_LENGTH + 1, &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(toxes[0], 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); do { iterate_all_wait(2, toxes, state, ITERATION_INTERVAL); } while (!state[1].message_received); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); run_auto_test(2, send_message_test, false); return 0; } c-toxcore-0.2.13/auto_tests/set_name_test.c000066400000000000000000000065231415350724400207030ustar00rootroot00000000000000/* Tests that we can set our name. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/ccompat.h" #include "../toxcore/tox.h" #include "../toxcore/util.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) && memcmp(string, NICKNAME, sizeof(NICKNAME)) == 0, "Name not correct"); 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 %u\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; } c-toxcore-0.2.13/auto_tests/set_status_message_test.c000066400000000000000000000072031415350724400230060ustar00rootroot00000000000000/* Tests that we can set our status message */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/ccompat.h" #include "../toxcore/tox.h" #include "../toxcore/util.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 %u\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; } c-toxcore-0.2.13/auto_tests/skeleton_test.c000066400000000000000000000016521415350724400207320ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "../testing/misc_tools.h" #include "check_compat.h" /* #include "../" */ START_TEST(test_creativetestnamegoeshere) { uint8_t test = 0; ck_assert_msg(test == 0, "test: expected result 0, got %u.", test); } END_TEST static Suite *creativesuitenamegoeshere_suite(void) { Suite *s = suite_create("creativesuitedescritptiongoeshere"); DEFTESTCASE(/* remove test_ from test function names */ creativetestnamegoeshere); return s; } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); Suite *creativesuitenamegoeshere = creativesuitenamegoeshere_suite(); SRunner *test_runner = srunner_create(creativesuitenamegoeshere); int number_failed = 0; srunner_run_all(test_runner, CK_NORMAL); number_failed = srunner_ntests_failed(test_runner); srunner_free(test_runner); return number_failed; } c-toxcore-0.2.13/auto_tests/tcp_relay_test.c000066400000000000000000000037371415350724400210760ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "../testing/misc_tools.h" #include "check_compat.h" static uint8_t const key1[] = { 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, }; static uint8_t const key2[] = { 0x3F, 0x0A, 0x45, 0xA2, 0x68, 0x36, 0x7C, 0x1B, 0xEA, 0x65, 0x2F, 0x25, 0x8C, 0x85, 0xF4, 0xA6, 0x6D, 0xA7, 0x6B, 0xCA, 0xA6, 0x67, 0xA4, 0x9E, 0x77, 0x0B, 0xCC, 0x49, 0x17, 0xAB, 0x6A, 0x25, }; 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); tox_bootstrap(tox_tcp, "78.46.73.141", 33445, key1, nullptr); tox_bootstrap(tox_tcp, "tox.initramfs.io", 33445, key2, nullptr); Tox_Err_Bootstrap tcp_err; tox_add_tcp_relay(tox_tcp, "78.46.73.141", 33445, key1, &tcp_err); ck_assert_msg(tcp_err == TOX_ERR_BOOTSTRAP_OK, "attempting to add tcp relay returned with an error: %d", tcp_err); tox_add_tcp_relay(tox_tcp, "tox.initramfs.io", 33445, key2, &tcp_err); ck_assert_msg(tcp_err == TOX_ERR_BOOTSTRAP_OK, "attempting to add tcp relay returned with an error: %d", tcp_err); 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; } c-toxcore-0.2.13/auto_tests/tox_many_tcp_test.c000066400000000000000000000171531415350724400216150ustar00rootroot00000000000000/* Auto Tests: Many TCP. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/crypto_core.h" #include "../toxcore/tox.h" #include "../toxcore/util.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 #ifdef TCP_RELAY_PORT #undef TCP_RELAY_PORT #endif #define TCP_RELAY_PORT 33448 START_TEST(test_many_clients_tcp) { 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; 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[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() % NUM_TOXES_TCP; pairs[i].tox2 = (pairs[i].tox1 + random_u32() % (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); } END_TEST #define NUM_TCP_RELAYS 3 START_TEST(test_many_clients_tcp_b) { 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() % NUM_TOXES_TCP; pairs[i].tox2 = (pairs[i].tox1 + random_u32() % (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); } END_TEST static Suite *tox_suite(void) { Suite *s = suite_create("Tox many tcp"); /* Each tox connects to a single tox TCP */ DEFTESTCASE(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. */ DEFTESTCASE(many_clients_tcp_b); } return s; } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); Suite *tox = tox_suite(); SRunner *test_runner = srunner_create(tox); int number_failed = 0; srunner_run_all(test_runner, CK_NORMAL); number_failed = srunner_ntests_failed(test_runner); srunner_free(test_runner); return number_failed; } c-toxcore-0.2.13/auto_tests/tox_many_test.c000066400000000000000000000073461415350724400207520ustar00rootroot00000000000000/* Auto Tests: Many clients. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../testing/misc_tools.h" #include "../toxcore/crypto_core.h" #include "../toxcore/tox.h" #include "../toxcore/util.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) { 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() % TCP_TEST_NUM_TOXES; pairs[i].tox2 = (pairs[i].tox1 + random_u32() % (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; } c-toxcore-0.2.13/auto_tests/tox_one_test.c000066400000000000000000000130431415350724400205560ustar00rootroot00000000000000/* Auto Tests: One instance. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "../testing/misc_tools.h" #include "../toxcore/ccompat.h" #include "../toxcore/crypto_core.h" #include "../toxcore/tox.h" #include "../toxcore/util.h" #include "check_compat.h" static void set_random_name_and_status_message(Tox *tox, uint8_t *name, uint8_t *status_message) { int i; for (i = 0; i < TOX_MAX_NAME_LENGTH; ++i) { name[i] = random_u08(); } for (i = 0; i < TOX_MAX_STATUS_MESSAGE_LENGTH; ++i) { status_message[i] = random_u08(); } } static void test_one(void) { uint8_t name[TOX_MAX_NAME_LENGTH]; uint8_t status_message[TOX_MAX_STATUS_MESSAGE_LENGTH]; uint8_t name2[TOX_MAX_NAME_LENGTH]; uint8_t status_message2[TOX_MAX_STATUS_MESSAGE_LENGTH]; uint32_t index[] = { 1, 2 }; Tox *tox1 = tox_new_log(nullptr, nullptr, &index[0]); ck_assert(tox1 != nullptr); set_random_name_and_status_message(tox1, name, status_message); Tox *tox2 = tox_new_log(nullptr, nullptr, &index[1]); ck_assert(tox2 != nullptr); set_random_name_and_status_message(tox2, name2, status_message2); uint8_t address[TOX_ADDRESS_SIZE]; tox_self_get_address(tox1, address); Tox_Err_Friend_Add error; uint32_t ret = tox_friend_add(tox1, address, (const uint8_t *)"m", 1, &error); ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_OWN_KEY, "Adding own address worked."); tox_self_get_address(tox2, address); uint8_t message[TOX_MAX_FRIEND_REQUEST_LENGTH + 1] = {0}; ret = tox_friend_add(tox1, address, nullptr, 0, &error); ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_NULL, "Sending request with no message worked."); ret = tox_friend_add(tox1, address, message, 0, &error); ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_NO_MESSAGE, "Sending request with no message worked."); ret = tox_friend_add(tox1, address, message, sizeof(message), &error); ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_TOO_LONG, "TOX_MAX_FRIEND_REQUEST_LENGTH is too big."); address[0]++; ret = tox_friend_add(tox1, address, (const uint8_t *)"m", 1, &error); ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_BAD_CHECKSUM, "Adding address with bad checksum worked."); tox_self_get_address(tox2, address); ret = tox_friend_add(tox1, address, message, TOX_MAX_FRIEND_REQUEST_LENGTH, &error); ck_assert_msg(ret == 0 && error == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend."); ret = tox_friend_add(tox1, address, message, TOX_MAX_FRIEND_REQUEST_LENGTH, &error); ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_ALREADY_SENT, "Adding friend twice worked."); tox_self_set_name(tox1, name, sizeof(name), nullptr); ck_assert_msg(tox_self_get_name_size(tox1) == sizeof(name), "Can't set name of TOX_MAX_NAME_LENGTH"); tox_self_set_status_message(tox1, status_message, sizeof(status_message), nullptr); ck_assert_msg(tox_self_get_status_message_size(tox1) == sizeof(status_message), "Can't set status message of TOX_MAX_STATUS_MESSAGE_LENGTH"); tox_self_get_address(tox1, address); size_t save_size = tox_get_savedata_size(tox1); VLA(uint8_t, data, save_size); tox_get_savedata(tox1, data); tox_kill(tox2); Tox_Err_New err_n; 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, data, save_size); tox2 = tox_new_log(options, &err_n, &index[1]); ck_assert_msg(err_n == TOX_ERR_NEW_OK, "Load failed"); ck_assert_msg(tox_self_get_name_size(tox2) == sizeof name, "Wrong name size."); ck_assert_msg(tox_self_get_status_message_size(tox2) == sizeof status_message, "Wrong status message size"); uint8_t name_loaded[TOX_MAX_NAME_LENGTH] = { 0 }; tox_self_get_name(tox2, name_loaded); ck_assert_msg(!memcmp(name, name_loaded, sizeof name), "Wrong name."); uint8_t status_message_loaded[TOX_MAX_STATUS_MESSAGE_LENGTH] = { 0 }; tox_self_get_status_message(tox2, status_message_loaded); ck_assert_msg(!memcmp(status_message, status_message_loaded, sizeof status_message_loaded), "Wrong status message."); uint8_t address2[TOX_ADDRESS_SIZE] = { 0 }; tox_self_get_address(tox2, address2); ck_assert_msg(memcmp(address2, address, TOX_ADDRESS_SIZE) == 0, "Wrong address."); uint8_t new_name[TOX_MAX_NAME_LENGTH] = { 0 }; tox_self_get_name(tox2, new_name); ck_assert_msg(memcmp(name, new_name, TOX_MAX_NAME_LENGTH) == 0, "Wrong name"); uint8_t sk[TOX_SECRET_KEY_SIZE]; tox_self_get_secret_key(tox2, sk); tox_kill(tox2); tox_options_default(options); tox_options_set_savedata_type(options, TOX_SAVEDATA_TYPE_SECRET_KEY); tox_options_set_savedata_data(options, sk, sizeof(sk)); tox2 = tox_new_log(options, &err_n, &index[1]); ck_assert_msg(err_n == TOX_ERR_NEW_OK, "Load failed"); uint8_t address3[TOX_ADDRESS_SIZE]; tox_self_get_address(tox2, address3); ck_assert_msg(memcmp(address3, address, TOX_PUBLIC_KEY_SIZE) == 0, "Wrong public key."); uint8_t pk[TOX_PUBLIC_KEY_SIZE]; tox_self_get_public_key(tox2, pk); ck_assert_msg(memcmp(pk, address, TOX_PUBLIC_KEY_SIZE) == 0, "Wrong public key."); tox_options_free(options); tox_kill(tox1); tox_kill(tox2); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); test_one(); return 0; } c-toxcore-0.2.13/auto_tests/tox_strncasecmp_test.c000066400000000000000000000132101415350724400223130ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #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]); } START_TEST(test_general) { // 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); } END_TEST static Suite *tox_strncasecmp_suite(void) { Suite *s = suite_create("tox_strncasecmp"); DEFTESTCASE(general); return s; } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); Suite *s = tox_strncasecmp_suite(); SRunner *test_runner = srunner_create(s); int number_failed = 0; srunner_run_all(test_runner, CK_NORMAL); number_failed = srunner_ntests_failed(test_runner); srunner_free(test_runner); return number_failed; } c-toxcore-0.2.13/auto_tests/toxav_basic_test.c000066400000000000000000000422541415350724400214130ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #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 "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: %d\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 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); if (call_err != TOXAV_ERR_CALL_OK) { printf("toxav_call failed: %d\n", call_err); ck_assert(0); } 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); if (answer_err != TOXAV_ERR_ANSWER_OK) { printf("toxav_answer failed: %d\n", answer_err); ck_assert(0); } 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); if (cc_err != TOXAV_ERR_CALL_CONTROL_OK) { printf("toxav_call_control failed: %d\n", cc_err); ck_assert(0); } } } 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); if (rc != TOXAV_ERR_CALL_OK) { printf("toxav_call failed: %d\n", rc); ck_assert(0); } } 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); if (rc != TOXAV_ERR_CALL_CONTROL_OK) { printf("toxav_call_control failed: %d\n", rc); ck_assert(0); } } 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); if (rc != TOXAV_ERR_CALL_OK) { printf("toxav_call failed: %d\n", rc); ck_assert(0); } } 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); if (rc != TOXAV_ERR_CALL_CONTROL_OK) { printf("toxav_call_control failed: %d\n", rc); ck_assert(0); } } /* 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); if (rc != TOXAV_ERR_CALL_OK) { printf("toxav_call failed: %d\n", rc); ck_assert(0); } } 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); if (rc != TOXAV_ERR_ANSWER_OK) { printf("toxav_answer failed: %d\n", rc); ck_assert(0); } } 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); if (rc != TOXAV_ERR_CALL_CONTROL_OK) { printf("toxav_call_control failed: %d\n", rc); ck_assert(0); } } 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); if (rc != TOXAV_ERR_CALL_OK) { printf("toxav_call failed: %d\n", rc); ck_assert(0); } } do { iterate_tox(bootstrap, Alice, Bob); } while (!BobCC.incoming); { Toxav_Err_Answer rc; toxav_answer(BobAV, 0, 48, 0, &rc); if (rc != TOXAV_ERR_ANSWER_OK) { printf("toxav_answer failed: %d\n", rc); ck_assert(0); } } 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); if (rc != TOXAV_ERR_CALL_CONTROL_OK) { printf("toxav_call_control failed: %d\n", rc); ck_assert(0); } } 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); if (rc != TOXAV_ERR_CALL_OK) { printf("toxav_call failed: %d\n", rc); ck_assert(0); } } do { iterate_tox(bootstrap, Alice, Bob); } while (!BobCC.incoming); { Toxav_Err_Answer rc; toxav_answer(BobAV, 0, 48, 0, &rc); if (rc != TOXAV_ERR_ANSWER_OK) { printf("toxav_answer failed: %d\n", rc); ck_assert(0); } } int16_t PCM[5670]; 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(AliceAV, 0, PCM, 960, 1, 48000, nullptr)); ck_assert(!toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, nullptr)); ck_assert_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME); iterate_tox(bootstrap, Alice, Bob); ck_assert(toxav_audio_send_frame(AliceAV, 0, PCM, 960, 1, 48000, nullptr)); ck_assert(toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, nullptr)); iterate_tox(bootstrap, Alice, Bob); { Toxav_Err_Call_Control rc; toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); if (rc != TOXAV_ERR_CALL_CONTROL_OK) { printf("toxav_call_control failed: %d\n", rc); ck_assert(0); } } 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; } c-toxcore-0.2.13/auto_tests/toxav_many_test.c000066400000000000000000000241041415350724400212700ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #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/util.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: %d %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; } 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]; { 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); Bobs[0] = tox_new_log(nullptr, &error, &index[2]); ck_assert(error == TOX_ERR_NEW_OK); Bobs[1] = tox_new_log(nullptr, &error, &index[3]); ck_assert(error == TOX_ERR_NEW_OK); Bobs[2] = tox_new_log(nullptr, &error, &index[4]); ck_assert(error == TOX_ERR_NEW_OK); } 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; } c_sleep(20); } 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); c_sleep(20); } 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; } } c_sleep(20); } 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); c_sleep(20); } 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); printf("\nTest successful!\n"); } int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); test_av_three_calls(); return 0; } c-toxcore-0.2.13/auto_tests/typing_test.c000066400000000000000000000033061415350724400204160ustar00rootroot00000000000000/* Tests that our typing notifications work. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #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 { uint32_t index; uint64_t clock; bool friend_is_typing; } State; #include "run_auto_test.h" static void typing_callback(Tox *m, uint32_t friendnumber, bool typing, void *userdata) { State *state = (State *)userdata; state->friend_is_typing = typing; } static void test_typing(Tox **toxes, State *state) { time_t cur_time = time(nullptr); tox_callback_friend_typing(toxes[1], &typing_callback); tox_self_set_typing(toxes[0], 0, true, nullptr); do { iterate_all_wait(2, toxes, state, 200); } while (!state[1].friend_is_typing); ck_assert_msg(tox_friend_get_typing(toxes[1], 0, nullptr) == 1, "tox_friend_get_typing should have returned true, but it didn't"); tox_self_set_typing(toxes[0], 0, false, nullptr); do { iterate_all_wait(2, toxes, state, 200); } while (state[1].friend_is_typing); Tox_Err_Friend_Query err_t; ck_assert_msg(tox_friend_get_typing(toxes[1], 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_test(2, test_typing, false); return 0; } c-toxcore-0.2.13/auto_tests/version_test.c000066400000000000000000000055261415350724400205770ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #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; } c-toxcore-0.2.13/autogen.sh000077500000000000000000000001041415350724400155010ustar00rootroot00000000000000#!/bin/sh -e echo 'Running autoreconf -if...' ( autoreconf -if ) c-toxcore-0.2.13/build/000077500000000000000000000000001415350724400146045ustar00rootroot00000000000000c-toxcore-0.2.13/build/Makefile.am000066400000000000000000000007761415350724400166520ustar00rootroot00000000000000bin_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 c-toxcore-0.2.13/cmake/000077500000000000000000000000001415350724400145655ustar00rootroot00000000000000c-toxcore-0.2.13/cmake/ApiDsl.cmake000066400000000000000000000024441415350724400167470ustar00rootroot00000000000000################################################################################ # # :: APIDSL regeneration # ################################################################################ find_program(APIDSL NAMES apidsl apidsl.native apidsl.byte ${CMAKE_SOURCE_DIR}/../apidsl/apigen.native) find_program(ASTYLE NAMES astyle $ENV{ASTYLE}) function(apidsl) if(APIDSL AND ASTYLE) foreach(in_file ${ARGN}) # Get the directory component of the input file name. if(CMAKE_VERSION VERSION_LESS 3.0) execute_process( COMMAND dirname ${in_file} OUTPUT_VARIABLE dirname OUTPUT_STRIP_TRAILING_WHITESPACE) else() get_filename_component(dirname ${in_file} DIRECTORY) endif() # Get the name without extension (i.e. without ".api.h"). get_filename_component(filename ${in_file} NAME_WE) # Put them together, with the new extension that is ".h". set(out_file ${CMAKE_SOURCE_DIR}/${dirname}/${filename}.h) # Run apidsl. add_custom_command( OUTPUT ${out_file} COMMAND "${APIDSL}" "${CMAKE_SOURCE_DIR}/${in_file}" | "${ASTYLE}" --options="${CMAKE_SOURCE_DIR}/other/astyle/astylerc" > "${out_file}" DEPENDS ${in_file}) endforeach() endif() endfunction() c-toxcore-0.2.13/cmake/CompileGTest.cmake000066400000000000000000000042031415350724400201250ustar00rootroot00000000000000# 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() c-toxcore-0.2.13/cmake/Dependencies.cmake000066400000000000000000000045461415350724400201660ustar00rootroot00000000000000############################################################################### # # :: 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 tox-spectest. pkg_use_module(MSGPACK msgpack ) ############################################################################### # # :: 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() c-toxcore-0.2.13/cmake/MacRpath.cmake000066400000000000000000000017361415350724400172750ustar00rootroot00000000000000# 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() c-toxcore-0.2.13/cmake/ModulePackage.cmake000066400000000000000000000137141415350724400202760ustar00rootroot00000000000000option(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() c-toxcore-0.2.13/cmake/StrictAbi.cmake000066400000000000000000000045101415350724400174530ustar00rootroot00000000000000################################################################################ # # :: 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() c-toxcore-0.2.13/codecov.yml000066400000000000000000000005341415350724400156540ustar00rootroot00000000000000--- 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% c-toxcore-0.2.13/conanfile.py000066400000000000000000000044601415350724400160210ustar00rootroot00000000000000# 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 = {"with_tests": [True, False]} default_options = {"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["MUST_BUILD_TOXAV"] = True if self.settings.compiler == "Visual Studio": self._cmake.definitions["MSVC_STATIC_SODIUM"] = True 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() 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"] c-toxcore-0.2.13/configure.ac000066400000000000000000000377641415350724400160140ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) AC_INIT([tox], [0.2.13]) AC_CONFIG_AUX_DIR(configure_aux) AC_CONFIG_SRCDIR([toxcore/net_crypto.c]) AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([foreign 1.10 -Wall 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 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 c-toxcore-0.2.13/docs/000077500000000000000000000000001415350724400144355ustar00rootroot00000000000000c-toxcore-0.2.13/docs/Group-Chats.md000066400000000000000000000046031415350724400171160ustar00rootroot00000000000000Group chats. Note: we assume everyone in the chat trusts each other. These group chats work by temporarily adding the 4 "closest" people defined by a distance function in group.c in order to form a circle of connected peers. These peers then relay messages to each other. A friend invites another friend to a group chat by sending them an invite packet. The friend either ignores the invite or responds with a response packet if he wants to join the chat. The friend invite contains the type of groupchat (text only, A/V) the friend is being invited to. TODO(irungentoo): write more of this. ## Protocol Invite packets: Invite packet: [uint8_t id 96][uint8_t id 0][uint16_t group chat number][33 bytes group chat identifier[1 byte type][32 bytes id]] Response packet [uint8_t id 96][uint8_t id 1][uint16_t group chat number(local)][uint16_t group chat number to join][33 bytes group chat identifier[1 byte type][32 bytes id]] Peer online packet: [uint8_t id 97][uint16_t group chat number (local)][33 bytes group chat identifier[1 byte type][32 bytes id]] Peer leave packet: [uint8_t id 98][uint16_t group chat number][uint8_t id 1] Peer query packet: [uint8_t id 98][uint16_t group chat number][uint8_t id 8] Peer response packet: [uint8_t id 98][uint16_t group chat number][uint8_t id 9][Repeated times number of peers: [uint16_t peer num][uint8_t 32bytes real public key][uint8_t 32bytes temp DHT public key][uint8_t name length][name]] Title response packet: [uint8_t id 98][uint16_t group chat number][uint8_t id 10][title] Message packets: [uint8_t id 99][uint16_t group chat number][uint16_t peer number][uint32_t message number][uint8_t with a value representing id of message][data] Lossy Message packets: [uint8_t id 199][uint16_t group chat number][uint16_t peer number][uint16_t message number][uint8_t with a value representing id of message][data] Group chat types: 0: text 1: AV Note: the message number is increased by 1 for each sent message. message ids: 0 - ping sent every ~60 seconds by every peer. No data. 16 - new_peer Tell everyone about a new peer in the chat. [uint16_t peer_num][uint8_t 32bytes real public key][uint8_t 32bytes temp DHT public key] 17 - kill_peer [uint16_t peer_num] 48 - name change [uint8_t name[namelen]] 49 - groupchat title change [uint8_t title[titlelen]] 64 - chat message [uint8_t message[messagelen]] 65 - action (/me) [uint8_t message[messagelen]] c-toxcore-0.2.13/docs/Hardening.txt000066400000000000000000000053531415350724400171030ustar00rootroot00000000000000Currently an attacker with sufficient resources could launch a large scale denial of service type attack by flooding the Tox network with a bunch of nodes that do not act like real nodes to prevent people from finding each other. Due to the design of Tox, this is the worst thing an attacker can do to disrupt the network. This solution's goal is to make these denial of service attack very very hard to accomplish. For the network to work every Tox node must: 1. Respond to ping requests. 2. Respond to get node requests with the ids of nodes closest to a queried id (It is assumed each nodes know at least the 32 nodes closest to them.) 3. Properly send crypto request packets to their intended destination. Currently the only thing a node needs to do to be part of the network is respond correctly to ping requests. The only people we really trust on the network are the nodes in our friends list. The behavior of each Tox node is easily predictable. This means that it possible for Tox nodes to test the nodes that they are connected to to see if they behave like normal Tox nodes and only send nodes that are confirmed to behave like real Tox nodes as part of send node replies when other nodes query them. If correctly done, this means that to poison the network an attacker can only infiltrate the network if his "fake" nodes behave exactly like real nodes completely defeating the purpose of the attack. Of course nodes must be rechecked regularly to defeat an attack where someone floods the network with many good nodes then suddenly turns them all bad. This also prevents someone from accidentally killing the tox network with a bad implementation of the protocol. Implementation ideas (In Progress): 1. Use our friends to check if the nodes in our close list are good. EX: If our friend queries a node close to us and it correctly returns our ip/port and then sends a crypto request packet to it and it routes it correctly to us then it is good. Problems with this: People don't always have at least one online friend. 2. Pick random nodes (add ourselves some random (fake) friends to increase the pool of available nodes) and make then send requests to other nodes, the response is then relayed back to us and compared to how the node should have behaved. If the node is found to be behaving correctly, it is set as trusted. Only trusted nodes are sent in send node packets, that is unless the exact node being queried for in the getnode packet is present, it will be sent in the sendnode packet even if it is not trusted. The hypothesis is that if to be part of the network nodes have to behave correctly it should prevent disruption from nodes that behave incorrectly. (This idea is currently being implemented in the harden branch.) ... c-toxcore-0.2.13/docs/Hardening_docs.txt000066400000000000000000000030651415350724400201110ustar00rootroot00000000000000Hardening request packets are sent as crypto request packets (see crypto docs.) NOTE: currently only get nodes requests are tested in the code which is why there is only one test (more will be added soon.) All hardening requests must contain exactly 768 bytes of data. (The data sent must be padded with zeros if it is smaller than that.) 1. Get the information (IP_port, client_id) of the node we want to test. 2. Find a couple random nodes that is not that node (one for each test.) 3. Send crypto request packets to each of these random nodes with the data being: [byte with value: 02 (get nodes test request)][struct Node_format (the node to test.)][client_id(32 bytes) the id to query the node with.][padding] 4. The random node receives a packet. -The packet is a get nodes test request: send a get_node request to that node with the id to query in the request. when a send_node response is received, send the following response to the person who sent us the get nodes test request packet: [byte with value: 03 (get nodes test response)][client_id(32 bytes): the id of the tested node][The list of nodes it responded with in IPv6 Node format (struct Node_Format)] PROTIP: (get node requests and response contain an encrypted part that you can use to store information so that you don't have to store in your memory where/if to send back the response from the send node) 5. Receive the test responses. -If the test(s) pass (the nodes behave in a satisfactory manner), make these nodes have priority over those who don't pass the test(s). c-toxcore-0.2.13/docs/Prevent_Tracking.txt000066400000000000000000000201421415350724400204420ustar00rootroot00000000000000Current privacy issues with the Tox DHT: 1. It makes tracking people across different IPs very easy. Solution: Have each new DHT use a temporary public/private key pair not related to the long term public/private key pair. 2. Metadata on which key is friends to which can be collected (The hardening makes this somewhat harder by introducing a bunch of random traffic but not impossible.). Solution: If no long term keys were used in the DHT it would solve this problem. (possibly knowing which ip is connected to which is much less precious.) So, it seems all our privacy problems are solved if we can manage to make every node in the DHT have a keypair that is not related to the long term keys and is generated every session. So, every node in the DHT now has a temporary keypair not related to their real long term one. But, how do people find themselves then? We have to add a way for people to tell their friends what their DHT public key is. We also have to somehow make it so people can send/receive friend requests. This has to be done without non-friends being able to find out where a node is. The solution: Onion routing + enable the storage of some small amount of data on DHT nodes. Alice and bob are friends. Before joining the DHT they generate temporary session keypairs to be used for the DHT instead of their long term keys. Bob finds a bunch of random nodes then picks 3 random working ones (A, B, C). Bob gets the known working node with an id closest to his real one from his list (D) Bob then creates an onion (the packet will go through A, B, C and will end up at D) announce request packet with his real public key, ping_id as zeros and searching for his real public key. Bob will announce response packets and will recursively send onion announce request packets to closer and closer nodes until he finds the ones closest to his real public key. Once he has done this, he will send some onion announce request packets with the right ping_id previously received from the node when he queried it to announce himself to the node. The nodes he announces himself to keep the information to send onion packets to that node in memory. Alice meanwhile searches for the nodes closest to Bobs real id using a temporary keypair and announce request packets. She does this until she finds nodes that respond with a ping_id of zero. She sends data to route request packet with information telling Bob her temporary id in the DHT (or a friend request if she is not friends with him). Bob finds her by using her temporary id and they connect to each other. NOTE: crypto_box is used for all the asymmetric encryption and crypto_secretbox is used for all the symmetric. Also every DHT node have a random symmetric key which they use to encrypt the stuff in normal get node request that is used to encrypt stuff in the following. Onion packet (request): initial (sent from us to node A): [uint8_t packet id (128)][nonce] [our temp DHT public key]encrypted with our temp DHT private key and the pub key of Node A and the nonce:[ [IP_Port of node B][a random public key]encrypted with the random private key and the pub key of Node B and the nonce:[ [IP_Port of node C][a random public key]encrypted with the random private key and the pub key of Node C and the nonce:[ [IP_Port of node D][data to send to Node D]]]] (sent from node A to node B): [uint8_t packet id (129)][nonce] [a random public key]encrypted with the random private key and the pub key of Node B and the nonce:[ [IP_Port of node C][a random public key]encrypted with the random private key and the pub key of Node C and the nonce:[ [IP_Port of node D][data to send to Node D]]][nonce (for the following symmetric encryption)]encrypted with temp symmetric key of Node A: [IP_Port (of us)] (sent from node B to node C): [uint8_t packet id (130)][nonce] [a random public key]encrypted with the random private key and the pub key of Node C and the nonce:[ [IP_Port of node D][data to send to Node D]][nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node B:[IP_Port (of Node A)[nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node A: [IP_Port (of us)]] (sent from node C to node D): [data to send to Node D][nonce (for the following symmetric encryption)]encrypted with temp symmetric key of Node C: [IP_Port (of Node B)[nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node B:[IP_Port (of Node A)[nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node A: [IP_Port (of us)]]] Data sent to Node D: announce request packet: [uint8_t packet id (131)][nonce][our real long term public key or a temporary one (see next)] encrypted (with our real long term private key if we want to announce ourselves, a temporary one if we are searching for friends) and the pub key of Node D and the nonce: [[(32 bytes) ping_id][client id we are searching for][public key that we want those sending back data packets to use.][data to send back in response(fixed size)]] (if the ping id is zero, respond with a announce response packet) (If the ping id matches the one the node sent in the announce response and the public key matches the one being searched for, add the part used to send data to our list (if the list is full make it replace the furthest entry)) data to route request packet: [uint8_t packet id (133)][public key of destination node][nonce][temporary just generated public key] encrypted with that temporary private key and the nonce and the public key from the announce response packet of the destination node:[data] (if Node D contains the ret data for the node, it sends the stuff in this packet as a data to route response packet to the right node) The data in the previous packet is in format: [real public key of sender] encrypted with real private key of the sender, the nonce in the data packet and the real public key of the receiver:[[uint8_t id][data (optional)]] Data sent to us: announce response packet: [uint8_t packet id (132)][data to send back in response(fixed size)][nonce] encrypted with the DHT private key of Node D, the public key in the request and the nonce:[[uint8_t is_stored] [(32 bytes) ping_id if is_stored is 0 or 2, public key that must be used to send data packets if is_stored is 1][Node_Format * (maximum of 8)]] (if the is_stored is not 0, it means the information to reach the client id we are searching for is stored on this node) is_stored is 2 as a response to a peer trying to announce himself to tell the peer that he is currently announced successfully. data to route response packet: [uint8_t packet id (134)][nonce][temporary just generated public key] encrypted with that temporary private key, the nonce and the public key from the announce response packet of the destination node:[data] Onion packet (response): initial (sent from node D to node C): [uint8_t packet id (140)][nonce (for the following symmetric encryption)]encrypted with temp symmetric key of Node C: [IP_Port (of Node B)[nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node B:[IP_Port (of Node A)[nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node A: [IP_Port (of us)]]][data to send back] (sent from node C to node B): [uint8_t packet id (141)][nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node B:[IP_Port (of Node A)[nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node A: [IP_Port (of us)]][data to send back] (sent from node B to node A): [uint8_t packet id (142)][nonce (for the following symmetric encryption)] encrypted with temp symmetric key of Node A: [IP_Port (of us)][data to send back] (sent from node A to us): [data to send back] Data packets: To tell our friend what our DHT public key is so that he can connect to us we send a data packet with id 156 and the data being:[uint64_t (in network byte order) no_replay, the packet will only be accepted if this number is bigger than the last one received] [our dht public key][Node_Format * ( maximum of 8) nodes closest to us so that the friend can find us faster] c-toxcore-0.2.13/docs/TCP_Network.txt000066400000000000000000000141731415350724400173430ustar00rootroot00000000000000It has come to our attention that to achieve decent market penetration Tox must work behind ALL internet connections, may they be behind enterprise NATs or any other bad network conditions. The people who have issues with the UDP direct connection approach seem to be a small minority though it is hard to estimate how many. This means that routing their packets using good nodes on the network will probably not take a huge toll on the network and will assure that people can use Tox regardless of the quality of their internet connection. How it's going to work: 1. Alice, a Tox client on a TCP only network generates a temporary public key and connects to a bootstrap node. 2. Using the bootstrap node she finds and connects to a couple (exact number to be determined later) number of random nodes that have TCP relay support. 3. She uses the onion through the TCP relay connections to send friend requests or tell online friends which TCP nodes she is connected to and her temporary public key. 4. Bob receives an onion packet from Alice telling him which nodes she is connected to. Bob connects to these nodes and establishes a routed connection with Alice using that temporary public key. 5. That connection is used by both to transmit encrypted Messenger and A/V packets. 6. If one of the nodes shuts down while it is currently routing traffic, Alice and bob just switch to one of the other nodes they are both connected to. Detailed implementation details: There are two distinct parts for TCP relays, the client part and the server part. The server acts as the actual relay. Servers must have fully forwarded TCP ports (NAT-PMP and uPNP can help here). The first port the server will try binding to is 443 followed by port 3389 and possibly some others. Onion packets can be sent/received through the TCP servers. Server: The public/private key pair the TCP server uses is the same one he uses for the DHT. all crypto for communication with the server uses the crypto_box() function of NaCl. TCP doesn't have packets so what we will refer to as packets are sent this way: [[uint16_t (length of data)][data]] So if you would inspect the TCP stream you would see: [[uint16_t (length of data)][data]][[uint16_t (length of data)][data]][[uint16_t (length of data)][data]] Note that both handshake packets don't have this format (the length for them is always the same so we don't need to specify it.) When the client connects to the server, he sends this packet: [public key of client (32 bytes)][nonce for the encrypted data [24 bytes]][encrypted with the private key of the client and public key of the server and the nonce:[public key (32 bytes) and][base nonce we want the server to use to encrypt the packets sent to us (24 bytes)]] The server responds with: [nonce for the encrypted data [24 bytes]][encrypted with the public key of the client and private key of the server and the nonce:[public key (32 bytes) and][base nonce we want the client to use to encrypt the packets sent to us (24 bytes)]] All packets to the server are end to end encrypted with the information received (and sent) in the handshake. (first packet is encrypted with the base nonce the private key for which the client sent the server the public key and the public key we sent to the client, the next with base nonce + 1...) The connection is set to an unconfirmed state until a packet is received and decrypted correctly using the information in the handshake. each packet sent to/from the server has an id (the first byte of the plain text data of the packet.) ids 0 to 15 are reserved for special packets, ids 16 to 255 are used to denote who we want the data to be routed to/who the packet is from. special ids and packets: 0 - Routing request. [uint8_t id (0)][public key (32 bytes)] 1 - Routing request response. [uint8_t id (1)][uint8_t (rpid) 0 if refused, packet id if accepted][public key (32 bytes)] 2 - Connect notification: [uint8_t id (2)][uint8_t (packet id of connection that got connected)] 3 - Disconnect notification: [uint8_t id (3)][uint8_t (packet id of connection that got disconnected)] 4 - ping packet [uint8_t id (4)][uint64_t ping_id (0 is invalid)] 5 - ping response (pong) [uint8_t id (5)][uint64_t ping_id (0 is invalid)] 6 - OOB send [uint8_t id (6)][destination public key (32 bytes)][data] 7 - OOB recv [uint8_t id (7)][senders public key (32 bytes)][data] 8 - onion packet (same format as initial onion packet (See: Prevent tracking.txt) but packet id is 8 instead of 128) 9 - onion packet response (same format as onion packet with id 142 but id is 9 instead.) The rest of the special ids are reserved for possible future usage. If the server receives a routing request he stores server side that the client wants to connect to the person with that public key and sends back a Routing request response with the rpid along with the public key sent in the request. If for some reason the server must refuse the routing request (too many) he sends the response with a rpid of 0. If the person who the client wants to connect to is also online and wants to connect to the client a connect notification is sent to both with the appropriate packet id. If either one disconnects, a disconnect notification is sent to the other with appropriate packet id. If a client sends a disconnect notification, the entry on the server for that routed connection is cleared and a disconnect notification is sent to the peer (if he was online) If the server receives an onion packet he handles it the same as he would if it was one received normally via UDP, he must also assure himself that any responses must be sent to the proper client. Ping responses must have the same ping_id as the request. If the server receives a ping packet he must respond with a ping response. The server will send a ping packet to clients every 30 seconds, they have 30 seconds to respond, if they don't the connection is deleted. OOB send packets will be sent to the peer connected to the TCP server with the destination public key as a OOB recv packet. The client sending this packet has no way of knowing if the packet reached its destination. Client: Implementation details coming soon. c-toxcore-0.2.13/docs/Tox_middle_level_network_protocol.txt000066400000000000000000000131341415350724400241510ustar00rootroot00000000000000The TCP client and TCP server part are in a state that can be considered feature complete. Why doesn't Tox support TCP yet even if those parts are complete? The answer is that a way to ensure a smooth switchover between the TCP and UDP needs to be added. If Tox first connects to the other user using TCP but then, due to pure chance, manages to connect using the faster direct UDP connection, Tox must switch seamlessly from the TCP to the UDP connection without there being any data loss or the other user going offline and then back online. The transition must be seamless whatever both connected users are doing - be it transferring files or simply chatting together. Possible evil/bad or simply TCP relays going offline must not impact the connection between both clients. Typically, Tox will use more than one TCP relay to connect to other peers for maximum connection stability, which means there must be a way for Tox to take advantage of multiple relays in a way that the user will never be aware of, if one of them goes offline/tries to slow down the connection/decides to corrupt packets/etc. To accomplish this, Tox needs something between the low level protocol (TCP) and high level Tox messaging protocol; hence the name middle level. The plan is to move some functionality from lossless_UDP to a higher level: more specifically, the functionality for detecting which packets a peer is missing, and the ability to request and send them again. Lossless UDP uses plain text packets to request missing packets from the other peer, while Tox is currently designed to kill the connection if any packet tampering is detected. This works very well when connecting directly with someone because if the attacker can modify packets, it means he can kill your connection anyway. With TCP relays, however, that is not the case. As such the packets used to request missing packets must be encrypted. If it is detected that a packet has been tampered, the connection must stay intact while the evil relay must be disconnected from and replaced with a good relay; the behavior must be the same as if the relay had just suddenly gone offline. Of course, something to protect from evil "friends" framing relays must also be implemented. Detailed implementation details: cookie request packet: [uint8_t 24][Sender's DHT Public key (32 bytes)][Random nonce (24 bytes)][Encrypted message containing: [Sender's real public key (32 bytes)][padding (32 bytes)][uint64_t number (must be sent back untouched in cookie response)]] Encrypted message is encrypted with sender's DHT private key, receiver's DHT public key and the nonce. cookie response packet: [uint8_t 25][Random nonce (24 bytes)][Encrypted message containing: [Cookie][uint64_t number (that was sent in the request)]] Encrypted message is encrypted with sender's DHT private key, receiver's DHT public key and the nonce. The Cookie should be basically: [nonce][encrypted data:[uint64_t time][Sender's real public key (32 bytes)][Sender's DHT public key (32 bytes)]] Handshake packet: [uint8_t 26][Cookie][nonce][Encrypted message containing: [random 24 bytes base nonce][session public key of the peer (32 bytes)][sha512 hash of the entire Cookie sitting outside the encrypted part][Other Cookie (used by the other to respond to the handshake packet)]] The handshake packet is encrypted using the real private key of the sender, the real public key of the receiver and the nonce. Alice wants to connect to Bob: Alice sends a cookie request packet to Bob and gets a cookie response back. Alice then generates a nonce and a temporary public/private keypair. Alice then takes that nonce and just generated private key, the obtained cookie, creates a new cookie and puts them in a handshake packet, which she sends to Bob. Bob gets the handshake packet, accepts the connection request, then generates a nonce and a temporary public/private keypair and sends a handshake packet back with this just generated information and with the cookie field being the Other Cookie contained in the received handshake. Both then use these temporary keys to generate the session key, with which every data packet sent and received will be encrypted and decrypted. The nonce sent in the handshake will be used to encrypt the first data packet sent, the nonce + 1 for the second, the nonce + 2 for the third, and so on. Data packets: [uint8_t 27][uint16_t (in network byte order) the last 2 bytes of the nonce used to encrypt this][encrypted with the session key and a nonce:[plain data]] Plain data in the data packets: [uint32_t our recvbuffers buffer_start, (highest packet number handled + 1)][uint32_t packet number if lossless, our sendbuffer buffer_end if lossy][data] data ids: 0: padding (skipped until we hit a non zero (data id) byte) 1: packet request packet (lossy packet) 2: connection kill packet (lossy packet) (tells the other that the connection is over) ... 16+: reserved for Messenger usage (lossless packets). 192+: reserved for Messenger usage (lossy packets). 255: reserved for Messenger usage (lossless packet) packet request packet: [uint8_t (1)][uint8_t num][uint8_t num][uint8_t num]...[uint8_t num] The list of nums are a list of packet numbers the other is requesting. In order to get the real packet numbers from this list, take the recvbuffers buffer_start from the packet, subtract 1 from it and put it in packet_num, then start from the beginning of the num list: if num is zero, add 255 to packet_num, then do the next num. If num isn't zero, add its value to packet_num, note that the other has requested we send this packet again to them, then continue to the next num in the list. c-toxcore-0.2.13/docs/apidsl.md000066400000000000000000000032241415350724400162340ustar00rootroot00000000000000This folder contains the input file (``tox.in.h``) that has to be used to generate the ``tox.h`` api with: https://github.com/TokTok/apidsl # Minimal requirements There are some minimal requirements to contribute to ``tox.h``: * unix environment * ``astyle`` ``>=2.03`` * [``apidsl``](https://github.com/TokTok/apidsl) (you can use provided service with curl instead) ## Quick way If you want to do it quickly and you don't have time for anything other than copypasting commands, you should have ``curl`` installed. 1. Make sure that you have ``curl`` and ``>=astyle-2.03`` installed 2. Modify [``tox.api.h``](/toxcore/tox.api.h) 3. Run command below ↓ Command to run from ``toxcore`` directory (quick way, involves using curl): ```bash # For tox.h: curl -X POST --data-binary @- https://apidsl.herokuapp.com/apidsl \ < toxcore/tox.api.h \ | astyle --options=other/astyle/astylerc \ > toxcore/tox.h # For toxav.h: curl -X POST --data-binary @- https://apidsl.herokuapp.com/apidsl \ < toxav/toxav.api.h \ | astyle --options=other/astyle/astylerc \ > toxav/toxav.h ``` You may want to make sure with ``git diff`` that changes made in ``tox.h`` reflect changes in ``tox.in.h``. And you're done. ## Manually If you prefer to have more control over what is happening, there are steps below: 1. Install [``apidsl``](https://github.com/TokTok/apidsl) 2. Install ``astyle``, version 2.03 or later. 3. Modify [``tox.api.h``](/toxcore/tox.api.h) 4. Use ``apidsl`` ``??`` 5. Parse generated ``tox.h`` with astyle, minimal command for it would be: ```bash astyle --options=other/astyle/astylerc toxcore/tox.h ``` **Always pass output from ``apidsl`` through astyle.** c-toxcore-0.2.13/docs/av_api.md000066400000000000000000000161441415350724400162240ustar00rootroot00000000000000# A/V API reference ## Take toxmsi/phone.c as a reference ### Initialization: ``` phone_t* initPhone(uint16_t _listen_port, uint16_t _send_port); ``` function initializes sample phone. _listen_port and _send_port are variables only meant for local testing. You will not have to do anything regarding to that since everything will be started within a messenger. Phone requires one msi session and two rtp sessions ( one for audio and one for video ). ``` msi_session_t* msi_init_session( void* _core_handler, const uint8_t* _user_agent ); ``` initializes msi session. Params: ``` void* _core_handler - pointer to an object handling networking, const uint8_t* _user_agent - string describing phone client version. ``` Return value: msi_session_t* - pointer to a newly created msi session handler. ### msi_session_t reference: How to handle msi session: Controlling is done via callbacks and action handlers. First register callbacks for every state/action received and make sure NOT TO PLACE SOMETHING LIKE LOOPS THAT TAKES A LOT OF TIME TO EXECUTE; every callback is being called directly from event loop. You can find examples in phone.c. Register callbacks: ``` void msi_register_callback_call_started ( MCALLBACK ); void msi_register_callback_call_canceled ( MCALLBACK ); void msi_register_callback_call_rejected ( MCALLBACK ); void msi_register_callback_call_ended ( MCALLBACK ); void msi_register_callback_recv_invite ( MCALLBACK ); void msi_register_callback_recv_ringing ( MCALLBACK ); void msi_register_callback_recv_starting ( MCALLBACK ); void msi_register_callback_recv_ending ( MCALLBACK ); void msi_register_callback_recv_error ( MCALLBACK ); void msi_register_callback_requ_timeout ( MCALLBACK ); ``` MCALLBACK is defined as: void (*callback) (void* _arg) msi_session_t* handler is being thrown as \_arg so you can use that and \_agent_handler to get to your own phone handler directly from callback. Actions: ``` int msi_invite ( msi_session_t* _session, call_type _call_type, uint32_t _timeoutms ); ``` Sends call invite. Before calling/sending invite msi_session_t::_friend_id is needed to be set or else it will not work. _call_type is type of the call ( Audio/Video ) and _timeoutms is how long will poll wait until request is terminated. ``` int msi_hangup ( msi_session_t* _session ); ``` Hangs up active call ``` int msi_answer ( msi_session_t* _session, call_type _call_type ); ``` Answer incoming call. _call_type set's callee call type. ``` int msi_cancel ( msi_session_t* _session ); ``` Cancel current request. ``` int msi_reject ( msi_session_t* _session ); ``` Reject incoming call. ### Now for rtp: You will need 2 sessions; one for audio one for video. You start them with: ``` rtp_session_t* rtp_init_session ( int _max_users, int _multi_session ); ``` Params: ``` int _max_users - max users. -1 if undefined int _multi_session - any positive number means uses multi session; -1 if not. ``` Return value: ``` rtp_session_t* - pointer to a newly created rtp session handler. ``` ### How to handle rtp session: Take a look at ``` void* phone_handle_media_transport_poll ( void* _hmtc_args_p ) in phone.c ``` on example. Basically what you do is just receive a message via: ``` struct rtp_msg_s* rtp_recv_msg ( rtp_session_t* _session ); ``` and then you use payload within the rtp_msg_s struct. Don't forget to deallocate it with: void rtp_free_msg ( rtp_session_t* _session, struct rtp_msg_s* _msg ); Receiving should be thread safe so don't worry about that. When you capture and encode a payload you want to send it ( obviously ). first create a new message with: ``` struct rtp_msg_s* rtp_msg_new ( rtp_session_t* _session, const uint8_t* _data, uint32_t _length ); ``` and then send it with: ``` int rtp_send_msg ( rtp_session_t* _session, struct rtp_msg_s* _msg, void* _core_handler ); ``` _core_handler is the same network handler as in msi_session_s struct. ## A/V initialization: ``` int init_receive_audio(codec_state *cs); int init_receive_video(codec_state *cs); Initialises the A/V decoders. On failure it will print the reason and return 0. On success it will return 1. int init_send_audio(codec_state *cs); int init_send_video(codec_state *cs); Initialises the A/V encoders. On failure it will print the reason and return 0. On success it will return 1. init_send_audio will also let the user select an input device. init_send_video will determine the webcam's output codec and initialise the appropriate decoder. int video_encoder_refresh(codec_state *cs, int bps); Reinitialises the video encoder with a new bitrate. ffmpeg does not expose the needed VP8 feature to change the bitrate on the fly, so this serves as a workaround. In the future, VP8 should be used directly and ffmpeg should be dropped from the dependencies. The variable bps is the required bitrate in bits per second. ``` ### A/V encoding/decoding: ``` void *encode_video_thread(void *arg); ``` Spawns the video encoding thread. The argument should hold a pointer to a codec_state. This function should only be called if video encoding is supported (when init_send_video returns 1). Each video frame gets encoded into a packet, which is sent via RTP. Every 60 frames a new bidirectional interframe is encoded. ``` void *encode_audio_thread(void *arg); ``` Spawns the audio encoding thread. The argument should hold a pointer to a codec_state. This function should only be called if audio encoding is supported (when init_send_audio returns 1). Audio frames are read from the selected audio capture device during initialisation. This audio capturing can be rerouted to a different device on the fly. Each audio frame is encoded into a packet, and sent via RTP. All audio frames have the same amount of samples, which is defined in AV_codec.h. ``` int video_decoder_refresh(codec_state *cs, int width, int height); ``` Sets the SDL window dimensions and creates a pixel buffer with the requested size. It also creates a scaling context, which will be used to convert the input image format to YUV420P. ``` void *decode_video_thread(void *arg); ``` Spawns a video decoding thread. The argument should hold a pointer to a codec_state. The codec_state is assumed to contain a successfully initialised video decoder. This function reads video packets and feeds them to the video decoder. If the video frame's resolution has changed, video_decoder_refresh() is called. Afterwards, the frame is displayed on the SDL window. ``` void *decode_audio_thread(void *arg); ``` Spawns an audio decoding thread. The argument should hold a pointer to a codec_state. The codec_state is assumed to contain a successfully initialised audio decoder. All received audio packets are pushed into a jitter buffer and are reordered. If there is a missing packet, or a packet has arrived too late, it is treated as a lost packet and the audio decoder is informed of the packet loss. The audio decoder will then try to reconstruct the lost packet, based on information from previous packets. Audio is played on the default OpenAL output device. If you have any more qustions/bug reports/feature request contact the following users on the irc channel #tox-dev on irc.freenode.net: For RTP and MSI: mannol For audio and video: Martijnvdc c-toxcore-0.2.13/docs/minpgc.md000066400000000000000000000131141415350724400162340ustar00rootroot00000000000000# Persistent conferences This document describes the "minpgc" simple persistent conferences implementation of PR #1069. Many of the ideas derive from isotoxin's persistent conferences implementation, PR #826. ## Specification of changes from pre-existing conference specification We add one new packet type: Rejoin Conference packet | Length | Contents | |:-------|:--------------------------------| | `1` | `uint8_t` (0x64) | | `33` | Group chat identifier | A peer times out from a group if it has been inactive for 60s. When a peer times out, we flag it as _frozen_. Frozen peers are disregarded for all purposes except those discussed below - in particular no packets are sent to them except as described below, they are omitted from the peer lists sent to the client or in a Peer Response packet, and they are not considered when determining closest peers for establishing direct connections. A peer is considered to be active if we receive a group message or Rejoin packet from it, or a New Peer message for it. If a frozen peer is seen to be active, we remove its 'frozen' flag and send a Name group message. (We can hold off on sending this message until the next `tox_iterate`, and only send one message if many frozen peers become active at once). If we receive a New Peer message for a peer, we update its DHT pubkey. If we receive a group message originating from an unknown peer, we drop the message but send a Peer Query packet back to the peer who directly sent us the message. (This is current behaviour; it's mentioned here because it's important and not currently mentioned in the spec.) If we receive a Rejoin packet from a peer we update its DHT pubkey, add a temporary groupchat connection for the peer, and, once the connection is online, send out a New Peer message announcing the peer, and a Name message. Whenever we make a new friend connection, we check if the public key is that of any frozen peer. If so, we send it a Rejoin packet, add a temporary groupchat connection for it, and, once the connection is online, send the peer a Peer Query packet. We do the same with a peer when we are setting it as frozen if we have a friend connection to it. The temporary groupchat connections established in sending and handling Rejoin packets are not immediately operational (because group numbers are not known); rather, an Online packet is sent when we handle a Rejoin packet. When a connection is set as online as a result of an Online packet, we ping the group. When processing the reply to a Peer Query, we update the DHT pubkey of an existing peer if and only if it is frozen or has not had its DHT pubkey updated since it last stopped being frozen. When we receive a Title Response packet, we set the title if it has never been set or if at some point since it was last set, there were no unfrozen peers (except us). ## Discussion ### Overview The intention is to recover seamlessly from splits in the group, the most common form of which is a single peer temporarily losing all connectivity. To see how this works, first note that groups (even before the changes discussed here) have the property that for a group to be connected in the sense that any peer will receive the messages of any other peer and have them in their peerlist, it is necessary and sufficient that there is a path of direct group connections between any two peers. Now suppose the group is split into two connected components, with each member of one component frozen according to the members of the other. Suppose there are two peers, one in each component, which are using the above protocol, and suppose they establish a friend connection. Then each will rejoin the other, forming a direct group connection. Hence the whole group will become connected (even if all other peers are using the unmodified protocol). The Peer Query packet sent on rejoining hastens this process. Peers who leave the group during a split will not be deleted by all peers after the merge - but they will be set as frozen due to ping timeouts, which is sufficient. ### Titles If we have a split into components each containing multiple peers, and the title is changed in one component, then peers will continue to disagree on the title after the split. Short of a complicated voting system, this seems the only reasonable behaviour. ### Implementation notes Although I've described the logic in terms of an 'frozen' flag, it might actually make more sense in the implementation to have a separate list for frozen peers. ## Saving Saving is implemented by simply saving all live groups with their group numbers and full peer info for all peers. On reload, all peers are set as frozen. Clients needs to support this by understanding that groups may exist on start-up. Clients should call `tox_conference_get_chatlist` to obtain them. A group which is deleted (with `tox_conference_delete`) is removed permanently and will not be saved. ## Limitations If a peer disconnects from the group for a period short enough that group timeouts do not occur, and a name change occurs during this period, then the name change will never be propagated. One way to deal with this would be a general mechanism for storing and requesting missed group messages. But this is considered out of scope of this PR. If a peer changes its DHT pubkey, the change might not be properly propagated under various circumstances - in particular, if connections do not go down long enough for the peer to become frozen. One way to deal with this would be to add a group message announcing the sending peer's current DHT pubkey, and treat it analogously to the Name message. c-toxcore-0.2.13/docs/updates/000077500000000000000000000000001415350724400161025ustar00rootroot00000000000000c-toxcore-0.2.13/docs/updates/Crypto.md000066400000000000000000000044041415350724400177060ustar00rootroot00000000000000Encryption library used: http://nacl.cr.yp.to/ When running the program for the first time the crypto_box_keypair() function is used to generate the users public-private key pair. (32 bytes each) The generated public key is set as the client_id of the peer. Adding a friend --------------- Alice adds Bob to her friend list by adding his 32 byte public key (client_id) to her friend list. 2 cases: case 1: Alice adds the public key of Bob, then Bob waits for Alice to attempt to connect to him. case 2: Bob and Alice add their respective public keys to their friend lists at the same time. case 1: Alice sends an onion data (see: Prevent_tracking.txt) packet to Bob with the encrypted part containing the friend request like so: ``` [char with a value of 32][nospam number (4 bytes)][Message] ``` Ex message: hello Bob it's me Alice -_- add me pl0x. For more info on the nospam see: Spam_Prevention.txt Bob receives the request and decrypts the message using the function crypto_box_open() If the message decrypts successfully: If Alice is already in Bob's friend list: case 2 If Alice is not in Bob's friend list and the nospam is good: Bob is prompt to add Alice and is shown the message from her. If Bob accepts Alice friend request he adds her public key to his friend list. case 2: Bob and Alice both have the others public key in their friend list, they are ready for the next step: Connecting to an already added friend In the next step only crypto_box() is used for encryption and only crypto_box_open() for decryption (just like in the last step.) Connecting to an already added friend ------------------------------------- see: Tox_middle_level_network_protocol.txt Crypto request packets -------------------------------------- ``` [char with a value of 32][Bob (The receiver's) Public key (client_id) (32 bytes))][Alice's (The sender's) Public key (client_id) (32 bytes)][Random nonce (24 bytes)][Encrypted message] ``` The encrypted message is encrypted with crypto_box() (using Bob's public key, Alice's private key and the nonce (randomly generated 24 bytes)) and is a message from Alice in which she tells Bob who she is. Each node can route the request to the receiver if they are connected to him. This is to bypass bad NATs. c-toxcore-0.2.13/docs/updates/DHT.md000066400000000000000000000131731415350724400170500ustar00rootroot00000000000000DHT protocol ============ NOTE: only the protocol section is up to date, the rest needs to be rewritten. Follows pretty much the principle of the torrent DHT: http://www.bittorrent.org/beps/bep_0005.html (READ IT) But: Vastly simplified packet format and encryption. Boostrapping: The first time you install the client we bootstrap it with a node. (bandwidth should not be a problem as the client only needs to be sent one reply.) Basics ------ (All the numbers here are just guesses and are probably not optimal values) client list: A list of node ids closest (mathematically see bittorrent doc) to ours matched with ip addresses + port number corresponding to that id and a timestamp containing the time or time since the client was successfully pinged. "friends" list: A list containing the node_ids of all our "friends" or clients we want to connect to. Also contains the ip addresses + port + node_ids + timestamp(of last ping like in the client list) of the 8 clients closest (mathematically see bittorrent doc) to each "friend" One pinged lists: -One for storing a list of ips along with their ping_ids and a timestamp for the ping requests Entries in the pinged lists expire after 5 seconds. If one of the lists becomes full, the expire rate reduces itself one second or the new ping takes the place of the oldest one. Entries in client list and "friends" list expire after 300 seconds without ping response. Each client stores a maximum of 32 entries in its client list. Each client in the client list and "friends" list is pinged every 60 seconds. Each client in the client list and "friends" list has a timestamp which denote the last time it was successfully pinged. If the corresponding clients timestamp is more than 130 seconds old it is considered bad. Send a get nodes request every 20 seconds to a random good node for each "friend" in our "friends" list. Send a get nodes request every 20 seconds to a random good node in the client list. When a client receives any request from another ----------------------------------------------- - Respond to the request - Ping request is replied to with with a ping response containing the same encrypted data - Get nodes request is replied with a send nodes reply containing the same encrypted data and the good nodes from the client list and/or the "friends" list that are closest to the requested_node_id - If the requesting client is not in the client list: - If there are no bad clients in the list and the list is full: - If the id of the other client is closer (mathematically see bittorrent doc) than at least one of the clients in the list or our "friends" list: - Send a ping request to the client. - if not forget about the client. - If there are bad clients and/or the list isn't full: - Send a ping request to the client When a client receives a response --------------------------------- - Ping response - If the node was previously pinged with a matching ping_id (check in the corresponding pinged list.) - If the node is in the client list the matching client's timestamp is set to current time. - If the node is in the "friends" list the matching client's timestamp is set to current time for every occurrence. - If the node is not in the client list: - If the list isn't full, add it to the list. - If the list is full, the furthest away (mathematically see bittorrent doc) bad client is replaced by the new one. - If the list is filled with good nodes replace the furthest client with it only if it is closer than the replaced node. - for each friend in the "friends" list: - If that friend's client list isn't full, add that client to it - If that friend's client list contains bad clients, replace the furthest one with that client. - If that friend's client list contains only good clients - If the client is closer to the friend than one of the other clients, it replaces the farthest one - If not, nothing happens. - Send nodes - If the ping_id matches what we sent previously (check in the corresponding pinged list.): - Each node in the response is pinged. Protocol -------- Node format: ``` [uint8_t family (2 == IPv4, 10 == IPv6, 130 == TCP IPv4, 138 == TCP IPv6)][ip (in network byte order), length=4 bytes if ipv4, 16 bytes if ipv6][port (in network byte order), length=2 bytes][char array (node_id), length=32 bytes] ``` see also: DHT.h (pack_nodes() and unpack_nodes()) Valid queries and Responses: Ping(Request and response): ``` [byte with value: 00 for request, 01 for response][char array (client node_id), length=32 bytes][random 24 byte nonce][Encrypted with the nonce and private key of the sender: [1 byte type (0 for request, 1 for response)][random 8 byte (ping_id)]] ``` ping_id = a random integer, the response must contain the exact same number as the request Get nodes (Request): Packet contents: ``` [byte with value: 02][char array (client node_id), length=32 bytes][random 24 byte nonce][Encrypted with the nonce and private key of the sender:[char array: requested_node_id (node_id of which we want the ip), length=32 bytes][Sendback data (must be sent back unmodified by in the response), length=8 bytes]] ``` Valid replies: a send_nodes packet Send_nodes (response (for all addresses)): ``` [byte with value: 04][char array (client node_id), length=32 bytes][random 24 byte nonce][Encrypted with the nonce and private key of the sender:[uint8_t number of nodes in this packet][Nodes in node format, length=?? * (number of nodes (maximum of 4 nodes)) bytes][Sendback data, length=8 bytes]] ``` c-toxcore-0.2.13/docs/updates/Spam-Prevention.md000066400000000000000000000005001415350724400214460ustar00rootroot00000000000000 Situation 1: Someone randomly goes around the DHT sending friend requests to everyone. Prevented by: Every friend address: [client_id (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)] contains a number (nospam). The nospam in every friend request to that friend must be that number. If not it is rejected. c-toxcore-0.2.13/docs/updates/Symmetric-NAT-Transversal.md000066400000000000000000000023671415350724400233320ustar00rootroot00000000000000Notes: Friend requests need to be routed. The current DHT should be capable of punching all NATs except symmetric ones. ###### Symmetric NAT hole punching: If we are not connected to the friend and if the DHT is queried and ips returned for the friend are the same but the port is different, the friend is assumed to be behind a symmetric NAT. Before attempting the procedure we first send a routed ping request to the friend. This request is to be routed through the nodes who returned the ip of the peer. As soon as we receive one routed ping request from the other peer, we respond with a ping response. Ping request/response packet: See: Crypto request packets in [[Crypto]] Message: For the ping request: [char with a value of 254][char with 0][8 byte random number] For the ping response: [char with a value of 254][char with 1][8 byte random number (The same that was sent in the request)] As soon as we get a proper ping response from the other we run the different ports returned by the DHT through our port guessing algorithm. ###### Port guessing algorithm: Right now it just tries all the ports directly beside the known ports.(A better one is needed) ###### We send DHT ping requests to all the guessed ports, only a couple at a time. c-toxcore-0.2.13/libtoxav.pc.in000066400000000000000000000003531415350724400162670ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libtoxav Description: Tox A/V library Requires: libtoxcore Version: @PACKAGE_VERSION@ Libs: -L${libdir} -ltoxav @AV_LIBS@ Cflags: -I${includedir} c-toxcore-0.2.13/libtoxcore.pc.in000066400000000000000000000005141415350724400166100ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libtoxcore Description: Tox protocol library Requires: Version: @PACKAGE_VERSION@ Libs: @NACL_OBJECTS_PKGCONFIG@ -L${libdir} -ltoxcore @NACL_LDFLAGS@ -ltoxencryptsave @NACL_LIBS@ @LIBS@ @MATH_LDFLAGS@ @PTHREAD_LDFLAGS@ Cflags: -I${includedir} c-toxcore-0.2.13/m4/000077500000000000000000000000001415350724400140255ustar00rootroot00000000000000c-toxcore-0.2.13/m4/ax_have_epoll.m4000066400000000000000000000070551415350724400171040ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_have_epoll.html # =========================================================================== # # SYNOPSIS # # AX_HAVE_EPOLL([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # AX_HAVE_EPOLL_PWAIT([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # DESCRIPTION # # This macro determines whether the system supports the epoll I/O event # interface. A neat usage example would be: # # AX_HAVE_EPOLL( # [AX_CONFIG_FEATURE_ENABLE(epoll)], # [AX_CONFIG_FEATURE_DISABLE(epoll)]) # AX_CONFIG_FEATURE( # [epoll], [This platform supports epoll(7)], # [HAVE_EPOLL], [This platform supports epoll(7).]) # # The epoll interface was added to the Linux kernel in version 2.5.45, and # the macro verifies that a kernel newer than this is installed. This # check is somewhat unreliable if doesn't match the # running kernel, but it is necessary regardless, because glibc comes with # stubs for the epoll_create(), epoll_wait(), etc. that allow programs to # compile and link even if the kernel is too old; the problem would then # be detected only at runtime. # # Linux kernel version 2.6.19 adds the epoll_pwait() call in addition to # epoll_wait(). The availability of that function can be tested with the # second macro. Generally speaking, it is safe to assume that # AX_HAVE_EPOLL would succeed if AX_HAVE_EPOLL_PWAIT has, but not the # other way round. # # LICENSE # # Copyright (c) 2008 Peter Simons # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 AC_DEFUN([AX_HAVE_EPOLL], [dnl ax_have_epoll_cppflags="${CPPFLAGS}" AC_CHECK_HEADER([linux/version.h], [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"]) AC_MSG_CHECKING([for Linux epoll(7) interface]) AC_CACHE_VAL([ax_cv_have_epoll], [dnl AC_LINK_IFELSE([dnl AC_LANG_PROGRAM([dnl #include #ifdef HAVE_LINUX_VERSION_H # include # if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,45) # error linux kernel version is too old to have epoll # endif #endif ], [dnl int fd, rc; struct epoll_event ev; fd = epoll_create(128); rc = epoll_wait(fd, &ev, 1, 0);])], [ax_cv_have_epoll=yes], [ax_cv_have_epoll=no])]) CPPFLAGS="${ax_have_epoll_cppflags}" AS_IF([test "${ax_cv_have_epoll}" = "yes"], [AC_MSG_RESULT([yes]) $1],[AC_MSG_RESULT([no]) $2]) ])dnl AC_DEFUN([AX_HAVE_EPOLL_PWAIT], [dnl ax_have_epoll_cppflags="${CPPFLAGS}" AC_CHECK_HEADER([linux/version.h], [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"]) AC_MSG_CHECKING([for Linux epoll(7) interface with signals extension]) AC_CACHE_VAL([ax_cv_have_epoll_pwait], [dnl AC_LINK_IFELSE([dnl AC_LANG_PROGRAM([dnl #ifdef HAVE_LINUX_VERSION_H # include # if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) # error linux kernel version is too old to have epoll_pwait # endif #endif #include #include ], [dnl int fd, rc; struct epoll_event ev; fd = epoll_create(128); rc = epoll_wait(fd, &ev, 1, 0); rc = epoll_pwait(fd, &ev, 1, 0, (sigset_t const *)(0));])], [ax_cv_have_epoll_pwait=yes], [ax_cv_have_epoll_pwait=no])]) CPPFLAGS="${ax_have_epoll_cppflags}" AS_IF([test "${ax_cv_have_epoll_pwait}" = "yes"], [AC_MSG_RESULT([yes]) $1],[AC_MSG_RESULT([no]) $2]) ])dnl c-toxcore-0.2.13/m4/ax_pthread.m4000066400000000000000000000312671415350724400164170ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC to any special C compiler that is needed for # multi-threaded programs (defaults to the value of CC otherwise). (This # is necessary on AIX to use the special cc_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also link it with them as well. e.g. you should link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threads programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name # (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # # 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 20 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on True64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) AC_MSG_RESULT($ax_pthread_ok) if test x"$ax_pthread_ok" = xno; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items starting with a "-" are # C compiler flags, and other items are library names, except for "none" # which indicates that we try without any flags at all, and "pthread-config" # which is a program returning the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) # -pthreads: Solaris/gcc # -mthreads: Mingw32/gcc, Lynx/gcc # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads too; # also defines -D_REENTRANT) # ... -mt is also the pthreads flag for HP/aCC # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case ${host_os} in solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (We need to link with -pthreads/-mt/ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather # a function called by this macro, so we could check for that, but # who knows whether they'll stub that too in a future libc.) So, # we'll just look for -pthreads and -lpthread first: ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" ;; darwin*) ax_pthread_flags="-pthread $ax_pthread_flags" ;; esac if test x"$ax_pthread_ok" = xno; then for flag in $ax_pthread_flags; do case $flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $flag]) PTHREAD_CFLAGS="$flag" ;; pthread-config) AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) if test x"$ax_pthread_config" = xno; then continue; fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$flag]) PTHREAD_LIBS="-l$flag" ;; esac save_LIBS="$LIBS" save_CFLAGS="$CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include static void routine(void *a) { a = 0; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" AC_MSG_RESULT($ax_pthread_ok) if test "x$ax_pthread_ok" = xyes; then break; fi PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Various other checks: if test "x$ax_pthread_ok" = xyes; then save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_MSG_CHECKING([for joinable pthread attribute]) attr_name=unknown for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $attr; return attr /* ; */])], [attr_name=$attr; break], []) done AC_MSG_RESULT($attr_name) if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, [Define to necessary symbol if this constant uses a non-standard name on your system.]) fi AC_MSG_CHECKING([if more special flags are required for pthreads]) flag=no case ${host_os} in aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; osf* | hpux*) flag="-D_REENTRANT";; solaris*) if test "$GCC" = "yes"; then flag="-D_REENTRANT" else flag="-mt -D_REENTRANT" fi ;; esac AC_MSG_RESULT(${flag}) if test "x$flag" != xno; then PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" fi AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], ax_cv_PTHREAD_PRIO_INHERIT, [ AC_LINK_IFELSE([ AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], 1, [Have PTHREAD_PRIO_INHERIT.])) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" # More AIX lossage: compile with *_r variant if test "x$GCC" != xyes; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" AC_SUBST(PTHREAD_LIBS) AC_SUBST(PTHREAD_CFLAGS) AC_SUBST(PTHREAD_CC) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test x"$ax_pthread_ok" = xyes; then ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD c-toxcore-0.2.13/m4/pkg.m4000066400000000000000000000162321415350724400150540ustar00rootroot00000000000000# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- # serial 1 (pkg-config-0.24) # # Copyright © 2004 Scott James Remnant . # # 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 2 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, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # PKG_PROG_PKG_CONFIG([MIN-VERSION]) # ---------------------------------- AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])# PKG_PROG_PKG_CONFIG # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # Check to see whether a particular set of modules exists. Similar # to PKG_CHECK_MODULES(), but does not set variables or print errors. # # Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) # only at the first occurrence in configure.ac, so if the first place # it's called might be skipped (such as if it is within an "if", you # have to call PKG_CHECK_EXISTS manually # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) # --------------------------------------------- m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])# _PKG_CONFIG # _PKG_SHORT_ERRORS_SUPPORTED # ----------------------------- AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])# _PKG_SHORT_ERRORS_SUPPORTED # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], # [ACTION-IF-NOT-FOUND]) # # # Note that if there is a possibility the first call to # PKG_CHECK_MODULES might not happen, you should be sure to include an # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac # # # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])# PKG_CHECK_MODULES # PKG_INSTALLDIR(DIRECTORY) # ------------------------- # Substitutes the variable pkgconfigdir as the location where a module # should install pkg-config .pc files. By default the directory is # $libdir/pkgconfig, but the default can be changed by passing # DIRECTORY. The user can override through the --with-pkgconfigdir # parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ]) dnl PKG_INSTALLDIR # PKG_NOARCH_INSTALLDIR(DIRECTORY) # ------------------------- # Substitutes the variable noarch_pkgconfigdir as the location where a # module should install arch-independent pkg-config .pc files. By # default the directory is $datadir/pkgconfig, but the default can be # changed by passing DIRECTORY. The user can override through the # --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ]) dnl PKG_NOARCH_INSTALLDIR c-toxcore-0.2.13/other/000077500000000000000000000000001415350724400146265ustar00rootroot00000000000000c-toxcore-0.2.13/other/BUILD.bazel000066400000000000000000000007651415350724400165140ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_binary") load("//tools:no_undefined.bzl", "cc_library") cc_library( name = "bootstrap_node_packets", srcs = ["bootstrap_node_packets.c"], hdrs = ["bootstrap_node_packets.h"], visibility = ["//c-toxcore/other/bootstrap_daemon:__pkg__"], deps = ["//c-toxcore/toxcore:network"], ) cc_binary( name = "DHT_bootstrap", srcs = ["DHT_bootstrap.c"], deps = [ "//c-toxcore/testing:misc_tools", "//c-toxcore/toxcore", ], ) c-toxcore-0.2.13/other/DHT_bootstrap.c000066400000000000000000000146411415350724400175140ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * DHT bootstrap * * A simple DHT boostrap node for tox. */ #include #include #include #include "../toxcore/DHT.h" #include "../toxcore/LAN_discovery.h" #include "../toxcore/friend_requests.h" #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/tox.h" #include "../toxcore/util.h" #define TCP_RELAY_ENABLED #ifdef TCP_RELAY_ENABLED #include "../toxcore/TCP_server.h" #endif #include "../testing/misc_tools.h" #ifdef DHT_NODE_EXTRA_PACKETS #include "./bootstrap_node_packets.h" #define DHT_VERSION_NUMBER 1 #define DHT_MOTD "This is a test motd" #endif #define PORT 33445 static void manage_keys(DHT *dht) { enum { KEYS_SIZE = CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE }; uint8_t keys[KEYS_SIZE]; FILE *keys_file = fopen("key", "r"); if (keys_file != nullptr) { /* If file was opened successfully -- load keys, otherwise save new keys */ size_t read_size = fread(keys, sizeof(uint8_t), KEYS_SIZE, keys_file); if (read_size != KEYS_SIZE) { printf("Error while reading the key file\nExiting.\n"); exit(1); } dht_set_self_public_key(dht, keys); dht_set_self_secret_key(dht, keys + CRYPTO_PUBLIC_KEY_SIZE); printf("Keys loaded successfully.\n"); } else { memcpy(keys, dht_get_self_public_key(dht), CRYPTO_PUBLIC_KEY_SIZE); memcpy(keys + CRYPTO_PUBLIC_KEY_SIZE, dht_get_self_secret_key(dht), CRYPTO_SECRET_KEY_SIZE); keys_file = fopen("key", "w"); if (keys_file == nullptr) { printf("Error opening key file in write mode.\nKeys will not be saved.\n"); return; } if (fwrite(keys, sizeof(uint8_t), KEYS_SIZE, keys_file) != KEYS_SIZE) { printf("Error while writing the key file.\nExiting.\n"); exit(1); } printf("Keys saved successfully.\n"); } fclose(keys_file); } static void print_log(void *context, Logger_Level level, const char *file, int line, const char *func, const char *message, void *userdata) { const char *strlevel; switch (level) { case LOGGER_LEVEL_TRACE: strlevel = "TRACE"; break; case LOGGER_LEVEL_DEBUG: strlevel = "DEBUG"; break; case LOGGER_LEVEL_INFO: strlevel = "INFO"; break; case LOGGER_LEVEL_WARNING: strlevel = "WARNING"; break; case LOGGER_LEVEL_ERROR: strlevel = "ERROR"; break; default: strlevel = ""; break; } fprintf(stderr, "[%s] %s:%d(%s) %s\n", strlevel, file, line, func, message); } int main(int argc, char *argv[]) { if (argc == 2 && !tox_strncasecmp(argv[1], "-h", 3)) { printf("Usage (connected) : %s [--ipv4|--ipv6] IP PORT KEY\n", argv[0]); printf("Usage (unconnected): %s [--ipv4|--ipv6]\n", argv[0]); exit(0); } /* let user override default by cmdline */ bool ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; /* x */ int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled); if (argvoffset < 0) { exit(1); } /* Initialize networking - Bind to ip 0.0.0.0 / [::] : PORT */ IP ip; ip_init(&ip, ipv6enabled); Logger *logger = logger_new(); if (MIN_LOGGER_LEVEL == LOGGER_LEVEL_TRACE || MIN_LOGGER_LEVEL == LOGGER_LEVEL_DEBUG) { logger_callback_log(logger, print_log, nullptr, nullptr); } Mono_Time *mono_time = mono_time_new(); DHT *dht = new_dht(logger, mono_time, new_networking(logger, ip, PORT), true); Onion *onion = new_onion(mono_time, dht); Onion_Announce *onion_a = new_onion_announce(mono_time, dht); #ifdef DHT_NODE_EXTRA_PACKETS bootstrap_set_callbacks(dht_get_net(dht), DHT_VERSION_NUMBER, DHT_MOTD, sizeof(DHT_MOTD)); #endif if (!(onion && onion_a)) { printf("Something failed to initialize.\n"); exit(1); } perror("Initialization"); manage_keys(dht); printf("Public key: "); uint32_t i; #ifdef TCP_RELAY_ENABLED #define NUM_PORTS 3 uint16_t ports[NUM_PORTS] = {443, 3389, PORT}; TCP_Server *tcp_s = new_TCP_server(logger, ipv6enabled, NUM_PORTS, ports, dht_get_self_secret_key(dht), onion); if (tcp_s == nullptr) { printf("TCP server failed to initialize.\n"); exit(1); } #endif const char *const public_id_filename = "PUBLIC_ID.txt"; FILE *file = fopen(public_id_filename, "w"); if (file == nullptr) { printf("Could not open file \"%s\" for writing. Exiting...\n", public_id_filename); exit(1); } for (i = 0; i < 32; i++) { const uint8_t *const self_public_key = dht_get_self_public_key(dht); printf("%02X", self_public_key[i]); fprintf(file, "%02X", self_public_key[i]); } fclose(file); printf("\n"); printf("Port: %u\n", net_ntohs(net_port(dht_get_net(dht)))); if (argc > argvoffset + 3) { printf("Trying to bootstrap into the network...\n"); uint16_t port = net_htons(atoi(argv[argvoffset + 2])); uint8_t *bootstrap_key = hex_string_to_bin(argv[argvoffset + 3]); int res = dht_bootstrap_from_address(dht, argv[argvoffset + 1], ipv6enabled, port, bootstrap_key); free(bootstrap_key); if (!res) { printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]); exit(1); } } int is_waiting_for_dht_connection = 1; uint64_t last_LANdiscovery = 0; lan_discovery_init(dht); while (1) { mono_time_update(mono_time); if (is_waiting_for_dht_connection && dht_isconnected(dht)) { printf("Connected to other bootstrap node successfully.\n"); is_waiting_for_dht_connection = 0; } do_dht(dht); if (mono_time_is_timeout(mono_time, last_LANdiscovery, is_waiting_for_dht_connection ? 5 : LAN_DISCOVERY_INTERVAL)) { lan_discovery_send(net_htons(PORT), dht); last_LANdiscovery = mono_time_get(mono_time); } #ifdef TCP_RELAY_ENABLED do_TCP_server(tcp_s, mono_time); #endif networking_poll(dht_get_net(dht), nullptr); c_sleep(1); } } c-toxcore-0.2.13/other/DHTnodes000066400000000000000000000003011415350724400162130ustar00rootroot00000000000000As maintaining 2 separate lists of the same information seemed redundant, this list has been phased out. For a current DHT node list please visit https://wiki.tox.chat/doku.php?id=users:nodes c-toxcore-0.2.13/other/Makefile.inc000066400000000000000000000016151415350724400170410ustar00rootroot00000000000000if BUILD_DHT_BOOTSTRAP bin_PROGRAMS += DHT_bootstrap DHT_bootstrap_SOURCES = ../other/DHT_bootstrap.c \ ../toxcore/DHT.h \ ../toxcore/friend_requests.h \ ../other/bootstrap_node_packets.h \ ../other/bootstrap_node_packets.c DHT_bootstrap_CFLAGS = -I$(top_srcdir)/other \ $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) DHT_bootstrap_LDADD = $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ libmisc_tools.la \ libtoxcore.la \ $(LIBSODIUM_LIBS) \ $(NACL_OBJECTS) \ $(NACL_LIBS) \ $(WINSOCK2_LIBS) endif EXTRA_DIST += $(top_srcdir)/other/DHTnodes \ $(top_srcdir)/other/tox.png c-toxcore-0.2.13/other/analysis/000077500000000000000000000000001415350724400164515ustar00rootroot00000000000000c-toxcore-0.2.13/other/analysis/check_logger_levels000077500000000000000000000012111415350724400223600ustar00rootroot00000000000000#!/bin/sh # Make sure that logger levels in toxcore/logger.h, CMakeLists.txt and # configure.ac stay in sync. set -ex TMP_DIR="$(mktemp -d)" # ^\s+LOGGER_LEVEL_(\w+),?$ sed -n 's/^\s\+LOGGER_LEVEL_\(\w\+\),\?$/\1/p' toxcore/logger.h > "${TMP_DIR}/logger.h" # ^.*\$\{MIN_LOGGER_LEVEL\}" STREQUAL "(\w+)".*$ sed -n 's/^.*\${MIN_LOGGER_LEVEL}\" STREQUAL \"\(\w\+\)\".*$/\1/p' CMakeLists.txt > "${TMP_DIR}/CMakeLists.txt" # ^.*LOGGER_LEVEL_(\w+).*$ sed -n 's/^.*LOGGER_LEVEL_\(\w\+\).*$/\1/p' configure.ac > "${TMP_DIR}/configure.ac" diff -u "${TMP_DIR}/CMakeLists.txt" "${TMP_DIR}/logger.h" diff -u "${TMP_DIR}/configure.ac" "${TMP_DIR}/logger.h" c-toxcore-0.2.13/other/analysis/check_recursion000077500000000000000000000060311415350724400215450ustar00rootroot00000000000000#!/usr/bin/env python3 """ Tool to check for recursive calls in toxcore C code. Usage: cat toxav/*.c toxcore/*.c toxencryptsave/*.c \ | clang `pkg-config --cflags libsodium opus vpx` \ -Itoxav -Itoxcore -Itoxencryptsave -S -emit-llvm -xc - -o- \ | opt -analyze -print-callgraph 2>&1 \ | other/analysis/check_recursion """ import collections import fileinput import re import sys import time from typing import Dict from typing import List from typing import Set def load_callgraph() -> Dict: """ Parses the output from opt -print-callgraph from stdin or argv. Returns graph as dict[str, list[str]] containing nodes with their outgoing edges. """ graph = collections.defaultdict(set) cur = None for line in fileinput.input(): found = re.search("Call graph node for function: '(.*)'", line) if found: cur = found.group(1) if cur: found = re.search("calls function '(.*)'", line) if found: graph[cur].add(found.group(1)) return {k: sorted(v) for k, v in graph.items()} def walk( visited: Set, callgraph: Dict, cycles: Set, stack: List, cur: str, ) -> None: """ Detects cycles in the callgraph and adds them to the cycles parameter. """ if cur in visited: return stack.append(cur) for callee in callgraph.get(cur, ()): try: cycles.add(" -> ".join(stack[stack.index(callee):] + [callee])) except ValueError: walk(visited, callgraph, cycles, stack, callee) visited.add(callee) stack.pop() def get_time() -> int: """ Return the current time in milliseconds. """ return int(round(time.time() * 1000)) def find_recursion(expected: Set) -> None: """ Main function: detects cycles and prints them. Takes a set of expected cycles. If any of the expected cycles was not found, or any unexpected cycle was found, the program exits with an error. """ start = prev = get_time() print("[+0000=0000] Generating callgraph") callgraph = load_callgraph() now = get_time() print("[+%04d=%04d] Finding recursion" % (now - prev, now - start)) prev = now cycles = set() visited = set() for func in sorted(callgraph.keys()): walk(visited, callgraph, cycles, [], func) now = get_time() if cycles: print("[+%04d=%04d] Recursion detected:" % (now - prev, now - start)) for cycle in sorted(cycles): if cycle in expected: print(" - " + cycle + " (expected)") expected.remove(cycle) cycles.remove(cycle) else: print(" - " + cycle) else: print("[+%04d=%04d] No recursion detected" % (now - prev, now - start)) if expected: print("Expected recursion no longer present: " + str(list(expected))) if expected or cycles: sys.exit(1) find_recursion(expected={ "add_to_closest -> add_to_closest", "add_to_list -> add_to_list", }) c-toxcore-0.2.13/other/analysis/gen-file.sh000066400000000000000000000045401415350724400204760ustar00rootroot00000000000000#!/bin/bash CPPFLAGS="-DMIN_LOGGER_LEVEL=LOGGER_LEVEL_TRACE" CPPFLAGS+=("-isystem" "/usr/include/opus") CPPFLAGS+=("-Iauto_tests") CPPFLAGS+=("-Iother") CPPFLAGS+=("-Iother/bootstrap_daemon/src") CPPFLAGS+=("-Iother/fun") CPPFLAGS+=("-Itesting") CPPFLAGS+=("-Itesting/groupchats") CPPFLAGS+=("-Itoxcore") CPPFLAGS+=("-Itoxav") CPPFLAGS+=("-Itoxencryptsave") LDFLAGS=("-lopus" "-lsodium" "-lvpx" "-lpthread" "-lconfig") LDFLAGS+=("-fuse-ld=gold") LDFLAGS+=("-Wl,--detect-odr-violations") LDFLAGS+=("-Wl,--warn-common") LDFLAGS+=("-Wl,--warn-execstack") LDFLAGS+=("-Wl,-z,noexecstack") LDFLAGS+=("-Wl,-z,now") put() { if [ "$SKIP_LINES" = "" ]; then echo "#line 1 \"$1\"" >>amalgamation.cc fi cat "$1" >>amalgamation.cc } putmain() { echo "namespace ${1//[^a-zA-Z0-9_]/_} {" >>amalgamation.cc if [ "$SKIP_LINES" = "" ]; then echo "#line 1 \"$1\"" >>amalgamation.cc fi sed -e 's/^int main(/static &/' "$1" >>amalgamation.cc echo "} // namespace ${1//[^a-zA-Z0-9_]/_}" >>amalgamation.cc } callmain() { echo " call(${1//[^a-zA-Z0-9_]/_}::main, argc, argv);" >>amalgamation.cc } : >amalgamation.cc put auto_tests/check_compat.h FIND_QUERY="find . '-(' -name '*.c' '-)'" FIND_QUERY="$FIND_QUERY -and -not -wholename './_build/*'" FIND_QUERY="$FIND_QUERY -and -not -wholename './super_donators/*'" FIND_QUERY="$FIND_QUERY -and -not -name av_test.c" FIND_QUERY="$FIND_QUERY -and -not -name dht_test.c" FIND_QUERY="$FIND_QUERY -and -not -name version_test.c" readarray -t FILES <<<"$(eval "$FIND_QUERY")" (for i in "${FILES[@]}"; do grep -o '#include <[^>]*>' "$i" | grep -E -v '||>amalgamation.cc echo 'namespace {' >>amalgamation.cc for i in "${FILES[@]}"; do if ! grep -q '^int main(' "$i"; then put "$i" fi done for i in "${FILES[@]}"; do if grep -q '^int main(' "$i"; then putmain "$i" fi done echo "static void call(int m(), int argc, char **argv) { m(); }" >>amalgamation.cc echo "static void call(int m(int, char **), int argc, char **argv) { m(argc, argv); }" >>amalgamation.cc echo '} // namespace' >>amalgamation.cc echo "int main(int argc, char **argv) {" >>amalgamation.cc for i in "${FILES[@]}"; do if grep -q '^int main(' "$i"; then callmain "$i" fi done echo " return 0;" >>amalgamation.cc echo "}" >>amalgamation.cc c-toxcore-0.2.13/other/analysis/run-check-recursion000077500000000000000000000003711415350724400222660ustar00rootroot00000000000000#!/bin/sh cat toxav/*.c toxcore/*.c toxencryptsave/*.c | clang "$(pkg-config --cflags libsodium opus vpx)" \ -Itoxav -Itoxcore -Itoxencryptsave -S -emit-llvm -xc - -o- | opt -analyze -print-callgraph 2>&1 | other/analysis/check_recursion c-toxcore-0.2.13/other/analysis/run-clang000077500000000000000000000011301415350724400202600ustar00rootroot00000000000000#!/bin/bash . other/analysis/gen-file.sh echo "Running Clang compiler" clang++ -o /dev/null amalgamation.cc \ "${CPPFLAGS[@]}" \ "${LDFLAGS[@]}" \ -std=c++11 \ -Werror \ -Weverything \ -Wno-alloca \ -Wno-c++98-compat-pedantic \ -Wno-c99-extensions \ -Wno-cast-align \ -Wno-conversion \ -Wno-covered-switch-default \ -Wno-disabled-macro-expansion \ -Wno-documentation-deprecated-sync \ -Wno-missing-field-initializers \ -Wno-old-style-cast \ -Wno-padded \ -Wno-sign-compare \ -Wno-unreachable-code-return \ -Wno-unused-parameter \ -Wno-used-but-marked-unused c-toxcore-0.2.13/other/analysis/run-clang-analyze000077500000000000000000000002261415350724400217260ustar00rootroot00000000000000#!/bin/bash . other/analysis/gen-file.sh echo "Running Clang static analyzer" clang++ --analyze amalgamation.cc \ "${CPPFLAGS[@]}" \ -std=c++11 c-toxcore-0.2.13/other/analysis/run-cppcheck000077500000000000000000000001251415350724400207570ustar00rootroot00000000000000#!/bin/bash . other/analysis/gen-file.sh cppcheck amalgamation.cc "${CPPFLAGS[@]}" c-toxcore-0.2.13/other/analysis/run-gcc000077500000000000000000000025741415350724400177450ustar00rootroot00000000000000#!/bin/bash . other/analysis/gen-file.sh echo "Running GCC" # TODO(iphydf): Get rid of all VLAs, then enable -fstack-protector -Wstack-protector g++ -O3 -o /dev/null amalgamation.cc \ "${CPPFLAGS[@]}" \ "${LDFLAGS[@]}" \ -std=c++11 \ -pedantic \ -fdiagnostics-color=always \ -Wall \ -Wextra \ -Wno-aggregate-return \ -Wno-aggressive-loop-optimizations \ -Wno-float-conversion \ -Wno-format-signedness \ -Wno-missing-field-initializers \ -Wno-padded \ -Wno-sign-compare \ -Wno-sign-conversion \ -Wno-switch-default \ -Wno-unused-parameter \ -Wstrict-aliasing=0 \ -Wstrict-overflow=1 \ \ -Wmissing-declarations \ -Wbool-compare \ -Wcast-align \ -Wcast-qual \ -Wchar-subscripts \ -Wdouble-promotion \ -Wduplicated-cond \ -Wempty-body \ -Wenum-compare \ -Wfloat-equal \ -Wformat=2 \ -Wframe-address \ -Wframe-larger-than=133168 \ -Wignored-qualifiers \ -Wignored-attributes \ -Winit-self \ -Winline \ -Wlarger-than=133120 \ -Wmaybe-uninitialized \ -Wmemset-transposed-args \ -Wmisleading-indentation \ -Wnonnull \ -Wnonnull-compare \ -Wnull-dereference \ -Wodr \ -Wredundant-decls \ -Wreturn-type \ -Wshadow \ -Wsuggest-attribute=format \ -Wundef \ -Wunsafe-loop-optimizations \ -Wunused-label \ -Wunused-local-typedefs \ -Wunused-value \ -Wunused-but-set-parameter \ -Wunused-but-set-variable c-toxcore-0.2.13/other/analysis/run-infer000077500000000000000000000003101415350724400202760ustar00rootroot00000000000000#!/bin/bash # Infer ignores everything that's not in the "current file". SKIP_LINES=1 . other/analysis/gen-file.sh infer --no-progress-bar -- clang++ -fsyntax-only amalgamation.cc "${CPPFLAGS[@]}" c-toxcore-0.2.13/other/astyle/000077500000000000000000000000001415350724400161275ustar00rootroot00000000000000c-toxcore-0.2.13/other/astyle/README.md000066400000000000000000000015641415350724400174140ustar00rootroot00000000000000This directory can house various tools and utilities. # How to use astyle ## Manually ### For all files Run from ``toxcore`` directory: ```bash astyle --options=./other/astyle/astylerc ./toxcore/*.c ./toxcore/*.h ./testing/*.c ./toxav/*.c ./toxav/*.h ./other/*.c ./other/bootstrap_daemon/*.c ./toxencryptsave/*.c ./toxencryptsave/*.h ./auto_tests/*.c ``` ### For selected file Run from ``toxcore`` directory, e.g. for [``tox.h``](/toxcore/tox.h) file: ```bash astyle --options=./other/astyle/astylerc ./toxcore/tox.h ``` ## Automatically, as pre-commit hook (*NIX only) Copy [``astylerc``](/other/astyle/astylerc) to ``toxcore/.git/hooks`` # Why ``astylerc`` - this file can be used in the pre-commit hook to try its best at making the code conform to the coding style of toxcore. Furthermore, it is being used to format ``tox.h`` after using ``apidsl`` to generate it. c-toxcore-0.2.13/other/astyle/astylerc000066400000000000000000000005501415350724400177000ustar00rootroot00000000000000# Bracket Style Options --style=kr # Tab Options --indent=spaces=4 # Indentation Options --indent-switches # Padding Options --pad-header --break-blocks --pad-oper --unpad-paren --align-pointer=name --align-reference=name # Formatting Options --add-brackets --convert-tabs --max-code-length=120 # Other Options --preserve-date --formatted --lineend=linux c-toxcore-0.2.13/other/astyle/format-source000077500000000000000000000050521415350724400206450ustar00rootroot00000000000000#!/bin/bash set -ex SOURCE_DIR="$1" ASTYLE="$2" APIDSL="$3" # Go to the source root. if [ -z "$SOURCE_DIR" ]; then SOURCE_DIR=. fi cd "$SOURCE_DIR" if [ -z "$ASTYLE" ] || ! which "$ASTYLE"; then ASTYLE=astyle fi if ! which "$ASTYLE"; then # If we couldn't find or install an astyle binary, don't do anything. echo "Could not find an astyle binary; please install astyle." exit 1 fi if ! which "$APIDSL"; then if [ -f ../apidsl/apigen.native ]; then APIDSL=../apidsl/apigen.native else APIDSL=apidsl_curl fi fi TO_JSON='s/\\/\\\\/g;s/\n/\\n/g;s/"/\\"/g;s/^(.*)$/"$1"/' FROM_JSON='s/\\"/"/g;s/^"(.*)"$/$1/;s/\\\\/\\/g;s/\\n/\n/g' apidsl_request() { TMPFILE=$(mktemp /tmp/apidsl.XXXXXX) curl -s -o "$TMPFILE" -X POST --data @<( echo '["Request",' cat "$2" echo ']' ) "https://apidsl.herokuapp.com/$1" if grep '\[1,"' "$TMPFILE" >/dev/null; then echo "Error: $(grep -o '".*"' /tmp/apidsl-$$ | perl -0777 -pe "$FROM_JSON")" >&2 rm "$TMPFILE" exit 1 fi perl -0777 -pe 's/^\[0,(.*)\]$/$1/' "$TMPFILE" rm "$TMPFILE" } apidsl_curl() { echo "apidsl_curl $*" >&2 apidsl_request "c" <( apidsl_request "parse" <( perl -0777 -pe "$TO_JSON" "$1" ) ) | perl -0777 -pe "$FROM_JSON" } # Check if apidsl generated sources are up to date. set +x "$APIDSL" toxcore/LAN_discovery.api.h >toxcore/LAN_discovery.h & "$APIDSL" toxcore/crypto_core.api.h >toxcore/crypto_core.h & "$APIDSL" toxcore/ping.api.h >toxcore/ping.h & "$APIDSL" toxcore/ping_array.api.h >toxcore/ping_array.h & "$APIDSL" toxcore/tox.api.h >toxcore/tox.h & "$APIDSL" toxav/toxav.api.h >toxav/toxav.h & "$APIDSL" toxencryptsave/toxencryptsave.api.h >toxencryptsave/toxencryptsave.h & set -x wait wait wait wait wait wait wait if grep '' ./*/*.h; then echo "error: some apidsl references were unresolved" exit 1 fi readarray -t CC_SOURCES <<<"$(find . '(' -name '*.cc' ')')" CC_SOURCES+=(toxcore/crypto_core.c) CC_SOURCES+=(toxcore/ping_array.c) for bin in clang-format-6.0 clang-format-5.0 clang-format; do if which "$bin"; then "$bin" -i -style='{BasedOnStyle: Google, ColumnLimit: 100}' "${CC_SOURCES[@]}" break fi done FIND="find ." FIND="$FIND '(' -name '*.[ch]' ')'" FIND="$FIND -and -not -name '*.api.h'" FIND="$FIND -and -not -wholename './super_donators/*'" FIND="$FIND -and -not -wholename './third_party/*'" FIND="$FIND -and -not -wholename './toxencryptsave/crypto_pwhash*'" readarray -t C_SOURCES <<<"$(eval "$FIND")" "$ASTYLE" -n --options=other/astyle/astylerc "${C_SOURCES[@]}" git diff --color=always --exit-code c-toxcore-0.2.13/other/astyle/pre-commit000066400000000000000000000010241415350724400201230ustar00rootroot00000000000000#!/usr/bin/env sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". for file in `git diff-index --diff-filter=ACMR --name-only HEAD`; do if [[ $file == *.c || $file == *.h ]] then echo $file `which astyle` $file --options=tools/astylerc git add $file fi donec-toxcore-0.2.13/other/bootstrap_daemon/000077500000000000000000000000001415350724400201665ustar00rootroot00000000000000c-toxcore-0.2.13/other/bootstrap_daemon/BUILD.bazel000066400000000000000000000004261415350724400220460ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_binary") cc_binary( name = "bootstrap_daemon", srcs = glob([ "src/*.c", "src/*.h", ]), deps = [ "//c-toxcore/other:bootstrap_node_packets", "//c-toxcore/toxcore", "@libconfig", ], ) c-toxcore-0.2.13/other/bootstrap_daemon/README.md000066400000000000000000000230441415350724400214500ustar00rootroot00000000000000# Instructions - [For `systemd` users](#for-systemd-users) - [Setting up](#setting-up) - [Updating](#updating) - [Troubleshooting](#troubleshooting) - [For `SysVinit` users](#for-sysvinit-users) - [Setting up](#setting-up-1) - [Updating](#updating-1) - [Troubleshooting](#troubleshooting-1) - [For `Docker` users](#for-docker-users) - [Setting up](#setting-up-2) - [Updating](#updating-2) - [Troubleshooting](#troubleshooting-2) These instructions are primarily tested on Debian Linux, Wheezy for SysVinit and Jessie for systemd, but they should work on other POSIX-compliant systems too. ## For `systemd` users ### Setting up For security reasons we run the daemon under its own user. Create a new user by executing the following: ```sh 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 ``` Restrict access to home directory: ```sh sudo chmod 700 /var/lib/tox-bootstrapd ``` Copy `tox-bootstrapd.conf` file to where `ExecStart=` from `tox-bootstrapd.service` points to. By default it's `/etc/tox-bootstrapd.conf`. ```sh sudo cp tox-bootstrapd.conf /etc/tox-bootstrapd.conf ``` Go over everything in the copied `tox-bootstrapd.conf` file. Set options you want and add actual working nodes to the `bootstrap_nodes` list, instead of the example ones, if you want your node to connect to the Tox network. Make sure `pid_file_path` matches `PIDFile=` from `tox-bootstrapd.service`. Copy `tox-bootstrapd.service` to `/etc/systemd/system/`: ```sh sudo cp tox-bootstrapd.service /etc/systemd/system/ ``` You must uncomment the next line in tox-bootstrapd.service, if you want to use port number < 1024: ``` #CapabilityBoundingSet=CAP_NET_BIND_SERVICE ``` and, possibly, install `libcap2-bin` or `libcap2` package, depending of your distribution. Reload systemd units definitions, enable service for automatic start (if needed), start it and verify it's running: ```sh sudo systemctl daemon-reload sudo systemctl enable tox-bootstrapd.service sudo systemctl start tox-bootstrapd.service sudo systemctl status tox-bootstrapd.service ``` Get your public key and check that the daemon initialized correctly: ```sh sudo grep "tox-bootstrapd" /var/log/syslog ``` ### Updating You want to make sure that the daemon uses the newest toxcore, as there might have been some changes done to the DHT, so it's advised to update the daemon at least once every month. To update the daemon first stop it: ```sh sudo systemctl stop tox-bootstrapd.service ``` Then update your toxcore git repository, rebuild the toxcore and the daemon and make sure to install them. Check if `tox-bootstrapd.service` in toxcore git repository was modified since the last time you copied it, as you might need to update it too. Reload `tox-bootstrapd.service` if you have updated modified it: ```sh sudo systemctl daemon-reload ``` After all of this is done, simply start the daemon back again: ```sh sudo systemctl start tox-bootstrapd.service ``` ### Troubleshooting - Check daemon's status: ```sh sudo systemctl status tox-bootstrapd.service ``` - Check the log for errors: ```sh sudo grep "tox-bootstrapd" /var/log/syslog # or sudo journalctl --pager-end # or sudo journalctl -f _SYSTEMD_UNIT=tox-bootstrapd.service ``` - Make sure tox-bootstrapd user has write permission for keys and pid files. - Make sure tox-bootstrapd has read permission for the config file. - Make sure tox-bootstrapd location matches its path in tox-bootstrapd.service file. ## For `SysVinit` users ### Setting up For security reasons we run the daemon under its own user. Create a new user by executing the following: ```sh 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 ``` Restrict access to home directory: ```sh sudo chmod 700 /var/lib/tox-bootstrapd ``` Copy `tox-bootstrapd.conf` file to where `CFGFILE` variable from `tox-bootstrapd.sh` points to. By default it's `/etc/tox-bootstrapd.conf`. ```sh sudo cp tox-bootstrapd.conf /etc/tox-bootstrapd.conf ``` Go over everything in the copied `tox-bootstrapd.conf` file. Set options you want and add actual working nodes to the `bootstrap_nodes` list, instead of the example ones, if you want your node to connect to the Tox network. Make sure `pid_file_path` matches `PIDFILE` from `tox-bootstrapd.sh`. Look at the variable declarations in the beginning of `tox-bootstrapd.sh` init script to see if you need to change anything for it to work on your system. The default values must be fine for most users and we assume that you use those next. If you have configured the daemon to use any port numbers that are lower than 1024, you need to execute the command below, as by default non-privileged users cannot open ports <1024. The change persists through reboot: ```sh sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/tox-bootstrapd ``` Copy `tox-bootstrapd.sh` init script to `/etc/init.d/tox-bootstrapd` (note the disappearance of ".sh" ending): ```sh sudo cp tox-bootstrapd.sh /etc/init.d/tox-bootstrapd ``` Set permissions for the init system to run the script: ```sh sudo chmod 755 /etc/init.d/tox-bootstrapd ``` Make the init system aware of the script, start the daemon and verify it's running: ```sh sudo update-rc.d tox-bootstrapd defaults sudo service tox-bootstrapd start sudo service tox-bootstrapd status ``` Get your public key and check that the daemon initialized correctly: ```sh sudo grep "tox-bootstrapd" /var/log/syslog ``` ### Updating You want to make sure that the daemon uses the newest toxcore, as there might have been some changes done to the DHT, so it's advised to update the daemon at least once every month. To update the daemon first stop it: ```sh sudo service tox-bootstrapd stop ``` Then update your toxcore git repository, rebuild the toxcore and the daemon and make sure to install them. Check if `tox-bootstrapd.sh` in toxcore git repository was modified since the last time you copied it, as you might need to update it too. After all of this is done, simply start the daemon back again: ```sh sudo service tox-bootstrapd start ``` ### Troubleshooting - Check daemon's status: ```sh sudo service tox-bootstrapd status ``` - Check the log for errors: ```sh sudo grep "tox-bootstrapd" /var/log/syslog ``` - Check that variables in the beginning of `/etc/init.d/tox-bootstrapd` are valid. - Make sure tox-bootstrapd user has write permission for keys and pid files. - Make sure tox-bootstrapd has read permission for the config file. - Make sure tox-bootstrapd location matches its path in the `/etc/init.d/tox-bootstrapd` init script. ## For `Docker` users: ### Setting up If you are familiar with Docker and would rather run the daemon in a Docker container, you may download the latest official docker image. Check the GitHub [releases](https://github.com/TokTok/c-toxcore/releases) page for the latest version (e.g. v0.2.11), and run: ```sh docker pull toxchat/bootstrap-node:0.2.11 docker run --rm -it --entrypoint=sha256sum toxchat/bootstrap-node:latest /usr/local/bin/tox-bootstrapd ``` This will print the SHA256 checksum of the latest binary, which should agree with the SHA256 checksum in the Dockerfile. If you want to build the bootstrap node from source, check out the latest release: ```sh git checkout $(git tag --list | grep -P '^v(\d+).(\d+).(\d+)$' | \ sed 's/v/v /g' | sed 's/\./ /g' | \ sort -snk4,4 | sort -snk3,3 | sort -snk2,2 | tail -n 1 | \ sed 's/v /v/g' | sed 's/ /\./g') ``` and run the following from the top level c-toxcore directory: ```sh tar c $(git ls-files) | docker build -f other/bootstrap_daemon/docker/Dockerfile -t toxchat/bootstrap-node - 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 --restart always \ -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 ``` We create a new user and protect its home directory in order to mount it in the Docker image, so that the kyepair the daemon uses would be stored on the host system, which makes it less likely that you would loose the keypair while playing with or updating the Docker container. You can check logs for your public key or any errors: ```sh docker logs tox-bootstrapd ``` Note that the Docker container runs a script which pulls a list of bootstrap nodes off https://nodes.tox.chat/ and adds them in the config file. ### Updating You want to make sure that the daemon uses the newest toxcore, as there might have been some changes done to the DHT, so it's advised to update the daemon at least once every month. To update the daemon, all you need is to erase current container with its image: ```sh docker stop tox-bootstrapd docker rm tox-bootstrapd docker rmi toxchat/bootstrap-node ``` Then rebuild and run the image again: ```sh tar c $(git ls-files) | docker build -f other/bootstrap_daemon/docker/Dockerfile -t toxchat/bootstrap-node - docker run -d --name tox-bootstrapd --restart always \ -v /var/lib/tox-bootstrapd/:/var/lib/tox-bootstrapd/ \ -p 443:443 \ -p 3389:3389 \ -p 33445:33445 \ -p 33445:33445/udp \ toxchat/bootstrap-node ``` ### Troubleshooting - Check if the container is running: ```sh docker ps -a ``` - Check the log for errors: ```sh docker logs tox-bootstrapd ``` c-toxcore-0.2.13/other/bootstrap_daemon/docker/000077500000000000000000000000001415350724400214355ustar00rootroot00000000000000c-toxcore-0.2.13/other/bootstrap_daemon/docker/Dockerfile000066400000000000000000000053641415350724400234370ustar00rootroot00000000000000########################################################### # Builder image: we compile the code here (static build) FROM alpine:3.11.5 AS build RUN ["apk", "--no-cache", "add",\ "build-base",\ "cmake",\ "linux-headers",\ "libconfig-dev",\ "libconfig-static",\ "libsodium-dev",\ "libsodium-static",\ "ninja",\ "python3"\ ] WORKDIR /src/c-toxcore # Very selectively add files to the image, because we may have random stuff # lying around. In particular, we don't need to rebuild the docker image when # toxav changes or the Dockerfile changes down from the build. COPY cmake cmake COPY other/bootstrap_daemon/src other/bootstrap_daemon/src COPY other/bootstrap_node_packets.[ch] other/ COPY other/DHT_bootstrap.c other/ COPY other/pkgconfig other/pkgconfig COPY other/rpm other/rpm COPY testing/misc_tools.[ch] testing/ COPY toxcore toxcore COPY toxencryptsave toxencryptsave COPY CMakeLists.txt so.version ./ RUN ["cmake", "-B_build", "-H.",\ "-GNinja",\ "-DCMAKE_BUILD_TYPE=Release",\ "-DFULLY_STATIC=ON",\ "-DBUILD_TOXAV=OFF",\ "-DBOOTSTRAP_DAEMON=ON"\ ] RUN ["cmake", "--build", "_build", "--target", "install"] # Verify checksum from dev-built binary, so we can be sure Docker Hub doesn't # mess with your binaries. COPY other/bootstrap_daemon/docker/tox-bootstrapd.sha256 other/bootstrap_daemon/docker/ RUN ["sha256sum", "/usr/local/bin/tox-bootstrapd"] RUN ["sha256sum", "-c", "other/bootstrap_daemon/docker/tox-bootstrapd.sha256"] # Remove all the example bootstrap nodes from the config file. COPY other/bootstrap_daemon/tox-bootstrapd.conf other/bootstrap_daemon/ # hadolint ignore=SC2086,SC2154 RUN ["sed", "-i", "/^bootstrap_nodes = /,$d", "other/bootstrap_daemon/tox-bootstrapd.conf"] # Add bootstrap nodes from https://nodes.tox.chat/. COPY other/bootstrap_daemon/docker/get-nodes.py other/bootstrap_daemon/docker/ RUN ["other/bootstrap_daemon/docker/get-nodes.py", "other/bootstrap_daemon/tox-bootstrapd.conf"] ########################################################### # Final image build: this is what runs the bootstrap node FROM debian:buster-slim COPY --from=build /usr/local/bin/tox-bootstrapd /usr/local/bin/ COPY --from=build /src/c-toxcore/other/bootstrap_daemon/tox-bootstrapd.conf /etc/tox-bootstrapd.conf RUN ["useradd", "--home-dir", "/var/lib/tox-bootstrapd", "--create-home",\ "--system", "--shell", "/sbin/nologin",\ "--comment", "Account to run the Tox DHT bootstrap daemon",\ "--user-group", "tox-bootstrapd"\ ] RUN ["chmod", "644", "/etc/tox-bootstrapd.conf"] RUN ["chmod", "700", "/var/lib/tox-bootstrapd"] WORKDIR /var/lib/tox-bootstrapd USER tox-bootstrapd ENTRYPOINT ["/usr/local/bin/tox-bootstrapd",\ "--config", "/etc/tox-bootstrapd.conf",\ "--log-backend", "stdout",\ "--foreground"\ ] EXPOSE 443/tcp 3389/tcp 33445/tcp 33445/udp c-toxcore-0.2.13/other/bootstrap_daemon/docker/get-nodes.py000077500000000000000000000042511415350724400237010ustar00rootroot00000000000000#!/usr/bin/env python3 """ Copyright (c) 2016 by nurupo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ # Gets a list of nodes from https://nodes.tox.chat/json and prints them out # in the format of tox-bootstrapd config file. import json import sys import urllib.request response = urllib.request.urlopen('https://nodes.tox.chat/json') raw_json = response.read().decode('ascii', 'ignore') nodes = json.loads(raw_json)['nodes'] def node_to_string(node): node_output = ' { // ' + node['maintainer'] + '\n' node_output += ' public_key = "' + node['public_key'] + '"\n' node_output += ' port = ' + str(node['port']) + '\n' node_output += ' address = "' if len(node['ipv4']) > 4: return node_output + node['ipv4'] + '"\n }' if len(node['ipv6']) > 4: return node_output + node['ipv6'] + '"\n }' raise Exception('no IP address found for node ' + json.dumps(node)) output = ('bootstrap_nodes = (\n' + ',\n'.join(map(node_to_string, nodes)) + '\n)') if len(sys.argv) > 1: with open(sys.argv[1], 'a') as fh: fh.write(output + '\n') print("Wrote %d nodes to %s" % (len(nodes), sys.argv[1])) else: print(output) c-toxcore-0.2.13/other/bootstrap_daemon/docker/tox-bootstrapd.sha256000066400000000000000000000001401415350724400253530ustar00rootroot0000000000000072f7fffc82c5074c94e13a6aee5d6231c4a063bef4338bae1f8d3ab4624c5582 /usr/local/bin/tox-bootstrapd c-toxcore-0.2.13/other/bootstrap_daemon/docker/update-sha256000077500000000000000000000014071415350724400236550ustar00rootroot00000000000000#!/bin/bash set -eux docker_build() { readarray -t FILES <<<"$(git ls-files)" tar c "${FILES[@]}" | docker build -f other/bootstrap_daemon/docker/Dockerfile -t toxchat/bootstrap-node - } # Run Docker build once. If it succeeds, we're good. if docker_build; then exit 0 fi # We're not good. Run it again, but now capture the output. OUTPUT=$(docker_build || true 2>&1) if echo "$OUTPUT" | grep '/usr/local/bin/tox-bootstrapd: FAILED'; then # This is a checksum warning, so we need to update it. IMAGE=$(echo "$OUTPUT" | grep '^ ---> [0-9a-f]*$' | grep -o '[0-9a-f]*$' | tail -n1) docker run --rm "$IMAGE" sha256sum /usr/local/bin/tox-bootstrapd >other/bootstrap_daemon/docker/tox-bootstrapd.sha256 fi # Run once last time to complete the build. docker_build c-toxcore-0.2.13/other/bootstrap_daemon/src/000077500000000000000000000000001415350724400207555ustar00rootroot00000000000000c-toxcore-0.2.13/other/bootstrap_daemon/src/Makefile.inc000066400000000000000000000036641415350724400231760ustar00rootroot00000000000000if BUILD_DHT_BOOTSTRAP_DAEMON bin_PROGRAMS += tox-bootstrapd tox_bootstrapd_SOURCES = \ ../other/bootstrap_daemon/src/command_line_arguments.c \ ../other/bootstrap_daemon/src/command_line_arguments.h \ ../other/bootstrap_daemon/src/config.c \ ../other/bootstrap_daemon/src/config.h \ ../other/bootstrap_daemon/src/config_defaults.h \ ../other/bootstrap_daemon/src/global.h \ ../other/bootstrap_daemon/src/log.c \ ../other/bootstrap_daemon/src/log.h \ ../other/bootstrap_daemon/src/log_backend_stdout.c \ ../other/bootstrap_daemon/src/log_backend_stdout.h \ ../other/bootstrap_daemon/src/log_backend_syslog.c \ ../other/bootstrap_daemon/src/log_backend_syslog.h \ ../other/bootstrap_daemon/src/tox-bootstrapd.c \ ../other/bootstrap_daemon/src/global.h \ ../other/bootstrap_node_packets.c \ ../other/bootstrap_node_packets.h tox_bootstrapd_CFLAGS = \ -I$(top_srcdir)/other/bootstrap_daemon \ $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) \ $(LIBCONFIG_CFLAGS) tox_bootstrapd_LDADD = \ $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ libtoxcore.la \ $(LIBCONFIG_LIBS) \ $(LIBSODIUM_LIBS) \ $(NACL_OBJECTS) \ $(NACL_LIBS) endif EXTRA_DIST += \ $(top_srcdir)/other/bootstrap_daemon/tox-bootstrapd.conf \ $(top_srcdir)/other/bootstrap_daemon/tox-bootstrapd.service \ $(top_srcdir)/other/bootstrap_daemon/tox-bootstrapd.sh c-toxcore-0.2.13/other/bootstrap_daemon/src/command_line_arguments.c000066400000000000000000000106151415350724400256360ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Command line argument handling. */ #include "command_line_arguments.h" #include "global.h" #include "../../../toxcore/ccompat.h" #include #include #include /** * Prints --help message */ static void print_help(void) { // 2 space ident // make sure all lines fit into 80 columns // make sure options are listed in alphabetical order log_write(LOG_LEVEL_INFO, "Usage: tox-bootstrapd [OPTION]... --config=FILE_PATH\n" "\n" "Options:\n" " --config=FILE_PATH Specify path to the config file.\n" " This is a required option.\n" " Set FILE_PATH to a path to an empty file in order to\n" " use default settings.\n" " --foreground Run the daemon in foreground. The daemon won't fork\n" " (detach from the terminal) and won't use the PID file.\n" " --help Print this help message.\n" " --log-backend=BACKEND Specify which logging backend to use.\n" " Valid BACKEND values (case sensetive):\n" " syslog Writes log messages to syslog.\n" " Default option when no --log-backend is\n" " specified.\n" " stdout Writes log messages to stdout/stderr.\n" " --version Print version information.\n"); } void handle_command_line_arguments(int argc, char *argv[], char **cfg_file_path, LOG_BACKEND *log_backend, bool *run_in_foreground) { if (argc < 2) { log_write(LOG_LEVEL_ERROR, "Error: No arguments provided.\n\n"); print_help(); exit(1); } opterr = 0; static const struct option long_options[] = { {"config", required_argument, nullptr, 'c'}, // required option {"foreground", no_argument, nullptr, 'f'}, {"help", no_argument, nullptr, 'h'}, {"log-backend", required_argument, nullptr, 'l'}, // optional, defaults to syslog {"version", no_argument, nullptr, 'v'}, {nullptr, 0, nullptr, 0 } }; bool cfg_file_path_set = false; bool log_backend_set = false; *run_in_foreground = false; int opt; while ((opt = getopt_long(argc, argv, ":", long_options, nullptr)) != -1) { switch (opt) { case 'c': *cfg_file_path = optarg; cfg_file_path_set = true; break; case 'f': *run_in_foreground = true; break; case 'h': print_help(); exit(0); case 'l': if (strcmp(optarg, "syslog") == 0) { *log_backend = LOG_BACKEND_SYSLOG; log_backend_set = true; } else if (strcmp(optarg, "stdout") == 0) { *log_backend = LOG_BACKEND_STDOUT; log_backend_set = true; } else { log_write(LOG_LEVEL_ERROR, "Error: Invalid BACKEND value for --log-backend option passed: %s\n\n", optarg); print_help(); exit(1); } break; case 'v': log_write(LOG_LEVEL_INFO, "Version: %lu\n", DAEMON_VERSION_NUMBER); exit(0); case '?': log_write(LOG_LEVEL_ERROR, "Error: Unrecognized option %s\n\n", argv[optind - 1]); print_help(); exit(1); case ':': log_write(LOG_LEVEL_ERROR, "Error: No argument provided for option %s\n\n", argv[optind - 1]); print_help(); exit(1); } } if (!log_backend_set) { *log_backend = LOG_BACKEND_SYSLOG; } if (!cfg_file_path_set) { log_write(LOG_LEVEL_ERROR, "Error: The required --config option wasn't specified\n\n"); print_help(); exit(1); } } c-toxcore-0.2.13/other/bootstrap_daemon/src/command_line_arguments.h000066400000000000000000000020521415350724400256370ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Command line argument handling. */ #ifndef C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_COMMAND_LINE_ARGUMENTS_H #define C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_COMMAND_LINE_ARGUMENTS_H #include "log.h" /** * Handles command line arguments, setting cfg_file_path and log_backend. * Terminates the application if incorrect arguments are specified. * * @param argc Argc passed into main(). * @param argv Argv passed into main(). * @param cfg_file_path Sets to the provided by the user config file path. * @param log_backend Sets to the provided by the user log backend option. * @param run_in_foreground Sets to the provided by the user foreground option. */ void handle_command_line_arguments(int argc, char *argv[], char **cfg_file_path, LOG_BACKEND *log_backend, bool *run_in_foreground); #endif // C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_COMMAND_LINE_ARGUMENTS_H c-toxcore-0.2.13/other/bootstrap_daemon/src/config.c000066400000000000000000000375721415350724400224040ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Functionality related to dealing with the config file. */ #include "config.h" #include "config_defaults.h" #include "log.h" #include #include #include #include "../../bootstrap_node_packets.h" /** * Parses tcp relay ports from `cfg` and puts them into `tcp_relay_ports` array. * * Supposed to be called from get_general_config only. * * Important: iff `tcp_relay_port_count` > 0, then you are responsible for freeing `tcp_relay_ports`. */ static void parse_tcp_relay_ports_config(config_t *cfg, uint16_t **tcp_relay_ports, int *tcp_relay_port_count) { const char *NAME_TCP_RELAY_PORTS = "tcp_relay_ports"; *tcp_relay_port_count = 0; config_setting_t *ports_array = config_lookup(cfg, NAME_TCP_RELAY_PORTS); if (ports_array == nullptr) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in the configuration file.\n", NAME_TCP_RELAY_PORTS); log_write(LOG_LEVEL_WARNING, "Using default '%s':\n", NAME_TCP_RELAY_PORTS); uint16_t default_ports[DEFAULT_TCP_RELAY_PORTS_COUNT] = {DEFAULT_TCP_RELAY_PORTS}; int i; for (i = 0; i < DEFAULT_TCP_RELAY_PORTS_COUNT; i ++) { log_write(LOG_LEVEL_INFO, "Port #%d: %u\n", i, default_ports[i]); } // similar procedure to the one of reading config file below *tcp_relay_ports = (uint16_t *)malloc(DEFAULT_TCP_RELAY_PORTS_COUNT * sizeof(uint16_t)); for (i = 0; i < DEFAULT_TCP_RELAY_PORTS_COUNT; i ++) { (*tcp_relay_ports)[*tcp_relay_port_count] = default_ports[i]; if ((*tcp_relay_ports)[*tcp_relay_port_count] < MIN_ALLOWED_PORT || (*tcp_relay_ports)[*tcp_relay_port_count] > MAX_ALLOWED_PORT) { log_write(LOG_LEVEL_WARNING, "Port #%d: Invalid port: %u, should be in [%d, %d]. Skipping.\n", i, (*tcp_relay_ports)[*tcp_relay_port_count], MIN_ALLOWED_PORT, MAX_ALLOWED_PORT); continue; } (*tcp_relay_port_count) ++; } // the loop above skips invalid ports, so we adjust the allocated memory size if ((*tcp_relay_port_count) > 0) { *tcp_relay_ports = (uint16_t *)realloc(*tcp_relay_ports, (*tcp_relay_port_count) * sizeof(uint16_t)); } else { free(*tcp_relay_ports); *tcp_relay_ports = nullptr; } return; } if (config_setting_is_array(ports_array) == CONFIG_FALSE) { log_write(LOG_LEVEL_ERROR, "'%s' setting should be an array. Array syntax: 'setting = [value1, value2, ...]'.\n", NAME_TCP_RELAY_PORTS); return; } int config_port_count = config_setting_length(ports_array); if (config_port_count == 0) { log_write(LOG_LEVEL_ERROR, "'%s' is empty.\n", NAME_TCP_RELAY_PORTS); return; } *tcp_relay_ports = (uint16_t *)malloc(config_port_count * sizeof(uint16_t)); int i; for (i = 0; i < config_port_count; i ++) { config_setting_t *elem = config_setting_get_elem(ports_array, i); if (elem == nullptr) { // it's NULL if `ports_array` is not an array (we have that check earlier) or if `i` is out of range, which should not be log_write(LOG_LEVEL_WARNING, "Port #%d: Something went wrong while parsing the port. Stopping reading ports.\n", i); break; } if (config_setting_is_number(elem) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "Port #%d: Not a number. Skipping.\n", i); continue; } (*tcp_relay_ports)[*tcp_relay_port_count] = config_setting_get_int(elem); if ((*tcp_relay_ports)[*tcp_relay_port_count] < MIN_ALLOWED_PORT || (*tcp_relay_ports)[*tcp_relay_port_count] > MAX_ALLOWED_PORT) { log_write(LOG_LEVEL_WARNING, "Port #%d: Invalid port: %u, should be in [%d, %d]. Skipping.\n", i, (*tcp_relay_ports)[*tcp_relay_port_count], MIN_ALLOWED_PORT, MAX_ALLOWED_PORT); continue; } (*tcp_relay_port_count) ++; } // the loop above skips invalid ports, so we adjust the allocated memory size if ((*tcp_relay_port_count) > 0) { *tcp_relay_ports = (uint16_t *)realloc(*tcp_relay_ports, (*tcp_relay_port_count) * sizeof(uint16_t)); } else { free(*tcp_relay_ports); *tcp_relay_ports = nullptr; } } int get_general_config(const char *cfg_file_path, char **pid_file_path, char **keys_file_path, int *port, int *enable_ipv6, int *enable_ipv4_fallback, int *enable_lan_discovery, int *enable_tcp_relay, uint16_t **tcp_relay_ports, int *tcp_relay_port_count, int *enable_motd, char **motd) { config_t cfg; const char *NAME_PORT = "port"; const char *NAME_PID_FILE_PATH = "pid_file_path"; const char *NAME_KEYS_FILE_PATH = "keys_file_path"; const char *NAME_ENABLE_IPV6 = "enable_ipv6"; const char *NAME_ENABLE_IPV4_FALLBACK = "enable_ipv4_fallback"; const char *NAME_ENABLE_LAN_DISCOVERY = "enable_lan_discovery"; const char *NAME_ENABLE_TCP_RELAY = "enable_tcp_relay"; const char *NAME_ENABLE_MOTD = "enable_motd"; const char *NAME_MOTD = "motd"; config_init(&cfg); // Read the file. If there is an error, report it and exit. if (config_read_file(&cfg, cfg_file_path) == CONFIG_FALSE) { log_write(LOG_LEVEL_ERROR, "%s:%d - %s\n", config_error_file(&cfg), config_error_line(&cfg), config_error_text(&cfg)); config_destroy(&cfg); return 0; } // Get port if (config_lookup_int(&cfg, NAME_PORT, port) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_PORT); log_write(LOG_LEVEL_WARNING, "Using default '%s': %d\n", NAME_PORT, DEFAULT_PORT); *port = DEFAULT_PORT; } // Get PID file location const char *tmp_pid_file; if (config_lookup_string(&cfg, NAME_PID_FILE_PATH, &tmp_pid_file) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_PID_FILE_PATH); log_write(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_PID_FILE_PATH, DEFAULT_PID_FILE_PATH); tmp_pid_file = DEFAULT_PID_FILE_PATH; } *pid_file_path = (char *)malloc(strlen(tmp_pid_file) + 1); strcpy(*pid_file_path, tmp_pid_file); // Get keys file location const char *tmp_keys_file; if (config_lookup_string(&cfg, NAME_KEYS_FILE_PATH, &tmp_keys_file) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_KEYS_FILE_PATH); log_write(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_KEYS_FILE_PATH, DEFAULT_KEYS_FILE_PATH); tmp_keys_file = DEFAULT_KEYS_FILE_PATH; } *keys_file_path = (char *)malloc(strlen(tmp_keys_file) + 1); strcpy(*keys_file_path, tmp_keys_file); // Get IPv6 option if (config_lookup_bool(&cfg, NAME_ENABLE_IPV6, enable_ipv6) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_IPV6); log_write(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_IPV6, DEFAULT_ENABLE_IPV6 ? "true" : "false"); *enable_ipv6 = DEFAULT_ENABLE_IPV6; } // Get IPv4 fallback option if (config_lookup_bool(&cfg, NAME_ENABLE_IPV4_FALLBACK, enable_ipv4_fallback) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_IPV4_FALLBACK); log_write(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_IPV4_FALLBACK, DEFAULT_ENABLE_IPV4_FALLBACK ? "true" : "false"); *enable_ipv4_fallback = DEFAULT_ENABLE_IPV4_FALLBACK; } // Get LAN discovery option if (config_lookup_bool(&cfg, NAME_ENABLE_LAN_DISCOVERY, enable_lan_discovery) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_LAN_DISCOVERY); log_write(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_LAN_DISCOVERY, DEFAULT_ENABLE_LAN_DISCOVERY ? "true" : "false"); *enable_lan_discovery = DEFAULT_ENABLE_LAN_DISCOVERY; } // Get TCP relay option if (config_lookup_bool(&cfg, NAME_ENABLE_TCP_RELAY, enable_tcp_relay) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_TCP_RELAY); log_write(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_TCP_RELAY, DEFAULT_ENABLE_TCP_RELAY ? "true" : "false"); *enable_tcp_relay = DEFAULT_ENABLE_TCP_RELAY; } if (*enable_tcp_relay) { parse_tcp_relay_ports_config(&cfg, tcp_relay_ports, tcp_relay_port_count); } else { *tcp_relay_port_count = 0; } // Get MOTD option if (config_lookup_bool(&cfg, NAME_ENABLE_MOTD, enable_motd) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_MOTD); log_write(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_MOTD, DEFAULT_ENABLE_MOTD ? "true" : "false"); *enable_motd = DEFAULT_ENABLE_MOTD; } if (*enable_motd) { // Get MOTD const char *tmp_motd; if (config_lookup_string(&cfg, NAME_MOTD, &tmp_motd) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_MOTD); log_write(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_MOTD, DEFAULT_MOTD); tmp_motd = DEFAULT_MOTD; } size_t tmp_motd_length = strlen(tmp_motd) + 1; size_t motd_length = tmp_motd_length > MAX_MOTD_LENGTH ? MAX_MOTD_LENGTH : tmp_motd_length; *motd = (char *)malloc(motd_length); strncpy(*motd, tmp_motd, motd_length); (*motd)[motd_length - 1] = '\0'; } config_destroy(&cfg); log_write(LOG_LEVEL_INFO, "Successfully read:\n"); log_write(LOG_LEVEL_INFO, "'%s': %s\n", NAME_PID_FILE_PATH, *pid_file_path); log_write(LOG_LEVEL_INFO, "'%s': %s\n", NAME_KEYS_FILE_PATH, *keys_file_path); log_write(LOG_LEVEL_INFO, "'%s': %d\n", NAME_PORT, *port); log_write(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_IPV6, *enable_ipv6 ? "true" : "false"); log_write(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_IPV4_FALLBACK, *enable_ipv4_fallback ? "true" : "false"); log_write(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_LAN_DISCOVERY, *enable_lan_discovery ? "true" : "false"); log_write(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_TCP_RELAY, *enable_tcp_relay ? "true" : "false"); // show info about tcp ports only if tcp relay is enabled if (*enable_tcp_relay) { if (*tcp_relay_port_count == 0) { log_write(LOG_LEVEL_ERROR, "No TCP ports could be read.\n"); } else { log_write(LOG_LEVEL_INFO, "Read %d TCP ports:\n", *tcp_relay_port_count); int i; for (i = 0; i < *tcp_relay_port_count; i ++) { log_write(LOG_LEVEL_INFO, "Port #%d: %u\n", i, (*tcp_relay_ports)[i]); } } } log_write(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_MOTD, *enable_motd ? "true" : "false"); if (*enable_motd) { log_write(LOG_LEVEL_INFO, "'%s': %s\n", NAME_MOTD, *motd); } return 1; } /** * * Converts a hex string with even number of characters into binary. * * Important: You are responsible for freeing the return value. * * @return binary on success, * NULL on failure. */ static uint8_t *bootstrap_hex_string_to_bin(const char *hex_string) { if (strlen(hex_string) % 2 != 0) { return nullptr; } size_t len = strlen(hex_string) / 2; uint8_t *ret = (uint8_t *)malloc(len); const char *pos = hex_string; size_t i; for (i = 0; i < len; ++i, pos += 2) { unsigned int val; sscanf(pos, "%02x", &val); ret[i] = val; } return ret; } int bootstrap_from_config(const char *cfg_file_path, DHT *dht, int enable_ipv6) { const char *NAME_BOOTSTRAP_NODES = "bootstrap_nodes"; const char *NAME_PUBLIC_KEY = "public_key"; const char *NAME_PORT = "port"; const char *NAME_ADDRESS = "address"; config_t cfg; config_init(&cfg); if (config_read_file(&cfg, cfg_file_path) == CONFIG_FALSE) { log_write(LOG_LEVEL_ERROR, "%s:%d - %s\n", config_error_file(&cfg), config_error_line(&cfg), config_error_text(&cfg)); config_destroy(&cfg); return 0; } config_setting_t *node_list = config_lookup(&cfg, NAME_BOOTSTRAP_NODES); if (node_list == nullptr) { log_write(LOG_LEVEL_WARNING, "No '%s' setting in the configuration file. Skipping bootstrapping.\n", NAME_BOOTSTRAP_NODES); config_destroy(&cfg); return 1; } if (config_setting_length(node_list) == 0) { log_write(LOG_LEVEL_WARNING, "No bootstrap nodes found. Skipping bootstrapping.\n"); config_destroy(&cfg); return 1; } int bs_port; const char *bs_address; const char *bs_public_key; config_setting_t *node; int i = 0; while (config_setting_length(node_list)) { int address_resolved; uint8_t *bs_public_key_bin; node = config_setting_get_elem(node_list, 0); if (node == nullptr) { config_destroy(&cfg); return 0; } // Check that all settings are present if (config_setting_lookup_string(node, NAME_PUBLIC_KEY, &bs_public_key) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "Bootstrap node #%d: Couldn't find '%s' setting. Skipping the node.\n", i, NAME_PUBLIC_KEY); goto next; } if (config_setting_lookup_int(node, NAME_PORT, &bs_port) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "Bootstrap node #%d: Couldn't find '%s' setting. Skipping the node.\n", i, NAME_PORT); goto next; } if (config_setting_lookup_string(node, NAME_ADDRESS, &bs_address) == CONFIG_FALSE) { log_write(LOG_LEVEL_WARNING, "Bootstrap node #%d: Couldn't find '%s' setting. Skipping the node.\n", i, NAME_ADDRESS); goto next; } // Process settings if (strlen(bs_public_key) != CRYPTO_PUBLIC_KEY_SIZE * 2) { log_write(LOG_LEVEL_WARNING, "Bootstrap node #%d: Invalid '%s': %s. Skipping the node.\n", i, NAME_PUBLIC_KEY, bs_public_key); goto next; } if (bs_port < MIN_ALLOWED_PORT || bs_port > MAX_ALLOWED_PORT) { log_write(LOG_LEVEL_WARNING, "Bootstrap node #%d: Invalid '%s': %d, should be in [%d, %d]. Skipping the node.\n", i, NAME_PORT, bs_port, MIN_ALLOWED_PORT, MAX_ALLOWED_PORT); goto next; } bs_public_key_bin = bootstrap_hex_string_to_bin(bs_public_key); address_resolved = dht_bootstrap_from_address(dht, bs_address, enable_ipv6, net_htons(bs_port), bs_public_key_bin); free(bs_public_key_bin); if (!address_resolved) { log_write(LOG_LEVEL_WARNING, "Bootstrap node #%d: Invalid '%s': %s. Skipping the node.\n", i, NAME_ADDRESS, bs_address); goto next; } log_write(LOG_LEVEL_INFO, "Successfully added bootstrap node #%d: %s:%d %s\n", i, bs_address, bs_port, bs_public_key); next: // config_setting_lookup_string() allocates string inside and doesn't allow us to free it direcly // though it's freed when the element is removed, so we free it right away in order to keep memory // consumption minimal config_setting_remove_elem(node_list, 0); i++; } config_destroy(&cfg); return 1; } c-toxcore-0.2.13/other/bootstrap_daemon/src/config.h000066400000000000000000000026461415350724400224030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Functionality related to dealing with the config file. */ #ifndef C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_CONFIG_H #define C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_CONFIG_H #include "../../../toxcore/DHT.h" /** * Gets general config options from the config file. * * Important: You are responsible for freeing `pid_file_path` and `keys_file_path` * also, iff `tcp_relay_ports_count` > 0, then you are responsible for freeing `tcp_relay_ports` * and also `motd` iff `enable_motd` is set. * * @return 1 on success, * 0 on failure, doesn't modify any data pointed by arguments. */ int get_general_config(const char *cfg_file_path, char **pid_file_path, char **keys_file_path, int *port, int *enable_ipv6, int *enable_ipv4_fallback, int *enable_lan_discovery, int *enable_tcp_relay, uint16_t **tcp_relay_ports, int *tcp_relay_port_count, int *enable_motd, char **motd); /** * Bootstraps off nodes listed in the config file. * * @return 1 on success, some or no bootstrap nodes were added * 0 on failure, a error accured while parsing config file. */ int bootstrap_from_config(const char *cfg_file_path, DHT *dht, int enable_ipv6); #endif // C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_CONFIG_H c-toxcore-0.2.13/other/bootstrap_daemon/src/config_defaults.h000066400000000000000000000022251415350724400242630ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Default config options for when they are missing in the config file. */ #ifndef C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_CONFIG_DEFAULTS_H #define C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_CONFIG_DEFAULTS_H #include "global.h" #define DEFAULT_PID_FILE_PATH "tox-bootstrapd.pid" #define DEFAULT_KEYS_FILE_PATH "tox-bootstrapd.keys" #define DEFAULT_PORT 33445 #define DEFAULT_ENABLE_IPV6 1 // 1 - true, 0 - false #define DEFAULT_ENABLE_IPV4_FALLBACK 1 // 1 - true, 0 - false #define DEFAULT_ENABLE_LAN_DISCOVERY 1 // 1 - true, 0 - false #define DEFAULT_ENABLE_TCP_RELAY 1 // 1 - true, 0 - false #define DEFAULT_TCP_RELAY_PORTS 443, 3389, 33445 // comma-separated list of ports. make sure to adjust DEFAULT_TCP_RELAY_PORTS_COUNT accordingly #define DEFAULT_TCP_RELAY_PORTS_COUNT 3 #define DEFAULT_ENABLE_MOTD 1 // 1 - true, 0 - false #define DEFAULT_MOTD DAEMON_NAME #endif // C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_CONFIG_DEFAULTS_H c-toxcore-0.2.13/other/bootstrap_daemon/src/global.h000066400000000000000000000037341415350724400223750ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Globally used defines. */ #ifndef C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_GLOBAL_H #define C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_GLOBAL_H #include "../../../toxcore/tox.h" #define DAEMON_NAME "tox-bootstrapd" #define DAEMON_VERSION_MAJOR TOX_VERSION_MAJOR #define DAEMON_VERSION_MINOR TOX_VERSION_MINOR #define DAEMON_VERSION_PATCH TOX_VERSION_PATCH // Make sure versions are within the limit #define VERSION_IS_OK(NUM) ( NUM >= 0 && NUM <= 999 ) #if !VERSION_IS_OK(DAEMON_VERSION_MAJOR) || !VERSION_IS_OK(DAEMON_VERSION_MINOR) || !VERSION_IS_OK(DAEMON_VERSION_PATCH) #error "At least one of major, minor or patch parts of the version is out of bounds of [0, 999]. Current version: " DAEMON_VERSION_MAJOR "." DAEMON_VERSION_MINOR "." DAEMON_VERSION_PATCH #endif #undef VERSION_IS_OK // New version scheme of 1AAABBBCCC, where A B and C are major, minor and patch // versions of toxcore. The leading 1 is there just to keep the leading zeros, // so that it would be easier to read the version when printed as a number. // The version is in a visual decimal format rather than in any other format, // because the original version was using a similar format, it was using // YYYYMMDDVV date-based format for the version, with VV being an incremental // counter in case more than one version was released at that day. Due to this // some tools started showing the version to users as a plain number, rather // than some binary format that needs to be parsed before being shown to users // so we decided to keep this display format compatibility and adopted this // weird scheme with a leading 1. #define DAEMON_VERSION_NUMBER (1000000000UL + DAEMON_VERSION_MAJOR*1000000UL + DAEMON_VERSION_MINOR*1000UL + DAEMON_VERSION_PATCH*1UL) #define MIN_ALLOWED_PORT 1 #define MAX_ALLOWED_PORT 65535 #endif // C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_GLOBAL_H c-toxcore-0.2.13/other/bootstrap_daemon/src/log.c000066400000000000000000000031541415350724400217050ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Logging utility with support of multiple logging backends. */ #include "log.h" #include "log_backend_stdout.h" #include "log_backend_syslog.h" #define INVALID_BACKEND (LOG_BACKEND)-1u static LOG_BACKEND current_backend = INVALID_BACKEND; bool log_open(LOG_BACKEND backend) { if (current_backend != INVALID_BACKEND) { return false; } current_backend = backend; switch (current_backend) { case LOG_BACKEND_STDOUT: // nothing to do here break; case LOG_BACKEND_SYSLOG: log_backend_syslog_open(); break; } return true; } bool log_close(void) { if (current_backend == INVALID_BACKEND) { return false; } switch (current_backend) { case LOG_BACKEND_STDOUT: // nothing to do here break; case LOG_BACKEND_SYSLOG: log_backend_syslog_close(); break; } current_backend = INVALID_BACKEND; return true; } bool log_write(LOG_LEVEL level, const char *format, ...) { if (current_backend == INVALID_BACKEND) { return false; } va_list args; va_start(args, format); switch (current_backend) { case LOG_BACKEND_STDOUT: log_backend_stdout_write(level, format, args); break; case LOG_BACKEND_SYSLOG: log_backend_syslog_write(level, format, args); break; } va_end(args); return true; } c-toxcore-0.2.13/other/bootstrap_daemon/src/log.h000066400000000000000000000023631415350724400217130ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Logging utility with support of multiple logging backends. */ #ifndef C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_H #define C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_H #include #include "../../../toxcore/ccompat.h" typedef enum LOG_BACKEND { LOG_BACKEND_STDOUT, LOG_BACKEND_SYSLOG } LOG_BACKEND; typedef enum LOG_LEVEL { LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR } LOG_LEVEL; /** * Initializes logger. * @param backend Specifies which backend to use. * @return true on success, false if log is already opened. */ bool log_open(LOG_BACKEND backend); /** * Releases all used resources by the logger. * @return true on success, false if log is already closed. */ bool log_close(void); /** * Writes a message to the log. * @param level Log level to use. * @param format printf-like format string. * @param ... Zero or more arguments, similar to printf function. * @return true on success, false if log is closed. */ bool log_write(LOG_LEVEL level, const char *format, ...) GNU_PRINTF(2, 3); #endif // C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_H c-toxcore-0.2.13/other/bootstrap_daemon/src/log_backend_stdout.c000066400000000000000000000013171415350724400247550ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Stdout logging backend. */ #include "log_backend_stdout.h" #include static FILE *log_backend_stdout_level(LOG_LEVEL level) { switch (level) { case LOG_LEVEL_INFO: return stdout; case LOG_LEVEL_WARNING: // intentional fallthrough case LOG_LEVEL_ERROR: return stderr; } return stdout; } void log_backend_stdout_write(LOG_LEVEL level, const char *format, va_list args) { vfprintf(log_backend_stdout_level(level), format, args); fflush(log_backend_stdout_level(level)); } c-toxcore-0.2.13/other/bootstrap_daemon/src/log_backend_stdout.h000066400000000000000000000010271415350724400247600ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Stdout logging backend. */ #ifndef C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_BACKEND_STDOUT_H #define C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_BACKEND_STDOUT_H #include "log.h" #include void log_backend_stdout_write(LOG_LEVEL level, const char *format, va_list args) GNU_PRINTF(2, 0); #endif // C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_BACKEND_STDOUT_H c-toxcore-0.2.13/other/bootstrap_daemon/src/log_backend_syslog.c000066400000000000000000000023331415350724400247520ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Syslog logging backend. */ #include "log_backend_syslog.h" #include "global.h" #include "../../../toxcore/ccompat.h" #include #include #include #include void log_backend_syslog_open(void) { openlog(DAEMON_NAME, LOG_NOWAIT | LOG_PID, LOG_DAEMON); } void log_backend_syslog_close(void) { closelog(); } static int log_backend_syslog_level(LOG_LEVEL level) { switch (level) { case LOG_LEVEL_INFO: return LOG_INFO; case LOG_LEVEL_WARNING: return LOG_WARNING; case LOG_LEVEL_ERROR: return LOG_ERR; } return LOG_INFO; } void log_backend_syslog_write(LOG_LEVEL level, const char *format, va_list args) { va_list args2; va_copy(args2, args); int size = vsnprintf(nullptr, 0, format, args2); va_end(args2); assert(size >= 0); if (size < 0) { return; } char *buf = (char *)malloc(size + 1); vsnprintf(buf, size + 1, format, args); syslog(log_backend_syslog_level(level), "%s", buf); free(buf); } c-toxcore-0.2.13/other/bootstrap_daemon/src/log_backend_syslog.h000066400000000000000000000011401415350724400247520ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Syslog logging backend. */ #ifndef C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_BACKEND_SYSLOG_H #define C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_BACKEND_SYSLOG_H #include "log.h" #include void log_backend_syslog_open(void); void log_backend_syslog_close(void); void log_backend_syslog_write(LOG_LEVEL level, const char *format, va_list args) GNU_PRINTF(2, 0); #endif // C_TOXCORE_OTHER_BOOTSTRAP_DAEMON_SRC_LOG_BACKEND_SYSLOG_H c-toxcore-0.2.13/other/bootstrap_daemon/src/tox-bootstrapd.c000066400000000000000000000401461415350724400241170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014-2016 Tox project. */ /* * Tox DHT bootstrap daemon. * Main file. */ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif // system provided #include #include #include // system header, rather than C, because we need it for POSIX sigaction(2) #include // C #include #include #include #include #include // toxcore #include "../../../toxcore/tox.h" #include "../../../toxcore/LAN_discovery.h" #include "../../../toxcore/TCP_server.h" #include "../../../toxcore/logger.h" #include "../../../toxcore/mono_time.h" #include "../../../toxcore/onion_announce.h" #include "../../../toxcore/util.h" // misc #include "../../bootstrap_node_packets.h" #include "command_line_arguments.h" #include "config.h" #include "global.h" #include "log.h" #define SLEEP_MILLISECONDS(MS) usleep(1000*MS) // Uses the already existing key or creates one if it didn't exist // // returns 1 on success // 0 on failure - no keys were read or stored static int manage_keys(DHT *dht, char *keys_file_path) { enum { KEYS_SIZE = CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE }; uint8_t keys[KEYS_SIZE]; FILE *keys_file; // Check if file exits, proceed to open and load keys keys_file = fopen(keys_file_path, "r"); if (keys_file != nullptr) { const size_t read_size = fread(keys, sizeof(uint8_t), KEYS_SIZE, keys_file); if (read_size != KEYS_SIZE) { fclose(keys_file); return 0; } dht_set_self_public_key(dht, keys); dht_set_self_secret_key(dht, keys + CRYPTO_PUBLIC_KEY_SIZE); } else { // Otherwise save new keys memcpy(keys, dht_get_self_public_key(dht), CRYPTO_PUBLIC_KEY_SIZE); memcpy(keys + CRYPTO_PUBLIC_KEY_SIZE, dht_get_self_secret_key(dht), CRYPTO_SECRET_KEY_SIZE); keys_file = fopen(keys_file_path, "w"); if (!keys_file) { return 0; } const size_t write_size = fwrite(keys, sizeof(uint8_t), KEYS_SIZE, keys_file); if (write_size != KEYS_SIZE) { fclose(keys_file); return 0; } } fclose(keys_file); return 1; } // Prints public key static void print_public_key(const uint8_t *public_key) { char buffer[2 * CRYPTO_PUBLIC_KEY_SIZE + 1]; int index = 0; size_t i; for (i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; i++) { index += sprintf(buffer + index, "%02X", public_key[i]); } log_write(LOG_LEVEL_INFO, "Public Key: %s\n", buffer); } // Demonizes the process, appending PID to the PID file and closing file descriptors based on log backend // Terminates the application if the daemonization fails. static void daemonize(LOG_BACKEND log_backend, char *pid_file_path) { // Check if the PID file exists FILE *pid_file; if ((pid_file = fopen(pid_file_path, "r"))) { log_write(LOG_LEVEL_WARNING, "Another instance of the daemon is already running, PID file %s exists.\n", pid_file_path); fclose(pid_file); } // Open the PID file for writing pid_file = fopen(pid_file_path, "a+"); if (pid_file == nullptr) { log_write(LOG_LEVEL_ERROR, "Couldn't open the PID file for writing: %s. Exiting.\n", pid_file_path); exit(1); } // Fork off from the parent process const pid_t pid = fork(); if (pid > 0) { fprintf(pid_file, "%d", pid); fclose(pid_file); log_write(LOG_LEVEL_INFO, "Forked successfully: PID: %d.\n", pid); exit(0); } else { fclose(pid_file); } if (pid < 0) { log_write(LOG_LEVEL_ERROR, "Forking failed. Exiting.\n"); exit(1); } // Create a new SID for the child process if (setsid() < 0) { log_write(LOG_LEVEL_ERROR, "SID creation failure. Exiting.\n"); exit(1); } // Change the current working directory if ((chdir("/")) < 0) { log_write(LOG_LEVEL_ERROR, "Couldn't change working directory to '/'. Exiting.\n"); exit(1); } // Go quiet if (log_backend != LOG_BACKEND_STDOUT) { close(STDOUT_FILENO); close(STDIN_FILENO); close(STDERR_FILENO); } } // Logs toxcore logger message using our logger facility static void toxcore_logger_callback(void *context, Logger_Level level, const char *file, int line, const char *func, const char *message, void *userdata) { LOG_LEVEL log_level; switch (level) { case LOGGER_LEVEL_TRACE: log_level = LOG_LEVEL_INFO; break; case LOGGER_LEVEL_DEBUG: log_level = LOG_LEVEL_INFO; break; case LOGGER_LEVEL_INFO: log_level = LOG_LEVEL_INFO; break; case LOGGER_LEVEL_WARNING: log_level = LOG_LEVEL_WARNING; break; case LOGGER_LEVEL_ERROR: log_level = LOG_LEVEL_ERROR; break; default: log_level = LOG_LEVEL_INFO; break; } log_write(log_level, "%s:%d(%s) %s\n", file, line, func, message); } static volatile sig_atomic_t caught_signal = 0; static void handle_signal(int signum) { caught_signal = signum; } int main(int argc, char *argv[]) { umask(077); char *cfg_file_path; LOG_BACKEND log_backend; bool run_in_foreground; // choose backend for printing command line argument parsing output based on whether the daemon is being run from a terminal log_backend = isatty(STDOUT_FILENO) ? LOG_BACKEND_STDOUT : LOG_BACKEND_SYSLOG; log_open(log_backend); handle_command_line_arguments(argc, argv, &cfg_file_path, &log_backend, &run_in_foreground); log_close(); log_open(log_backend); log_write(LOG_LEVEL_INFO, "Running \"%s\" version %lu.\n", DAEMON_NAME, DAEMON_VERSION_NUMBER); char *pid_file_path = nullptr; char *keys_file_path = nullptr; int port; int enable_ipv6; int enable_ipv4_fallback; int enable_lan_discovery; int enable_tcp_relay; uint16_t *tcp_relay_ports = nullptr; int tcp_relay_port_count; int enable_motd; char *motd = nullptr; if (get_general_config(cfg_file_path, &pid_file_path, &keys_file_path, &port, &enable_ipv6, &enable_ipv4_fallback, &enable_lan_discovery, &enable_tcp_relay, &tcp_relay_ports, &tcp_relay_port_count, &enable_motd, &motd)) { log_write(LOG_LEVEL_INFO, "General config read successfully\n"); } else { log_write(LOG_LEVEL_ERROR, "Couldn't read config file: %s. Exiting.\n", cfg_file_path); return 1; } if (port < MIN_ALLOWED_PORT || port > MAX_ALLOWED_PORT) { log_write(LOG_LEVEL_ERROR, "Invalid port: %d, should be in [%d, %d]. Exiting.\n", port, MIN_ALLOWED_PORT, MAX_ALLOWED_PORT); free(motd); free(tcp_relay_ports); free(keys_file_path); free(pid_file_path); return 1; } if (!run_in_foreground) { daemonize(log_backend, pid_file_path); } free(pid_file_path); IP ip; ip_init(&ip, enable_ipv6); Logger *logger = logger_new(); if (MIN_LOGGER_LEVEL == LOGGER_LEVEL_TRACE || MIN_LOGGER_LEVEL == LOGGER_LEVEL_DEBUG) { logger_callback_log(logger, toxcore_logger_callback, nullptr, nullptr); } Networking_Core *net = new_networking(logger, ip, port); if (net == nullptr) { if (enable_ipv6 && enable_ipv4_fallback) { log_write(LOG_LEVEL_WARNING, "Couldn't initialize IPv6 networking. Falling back to using IPv4.\n"); enable_ipv6 = 0; ip_init(&ip, enable_ipv6); net = new_networking(logger, ip, port); if (net == nullptr) { log_write(LOG_LEVEL_ERROR, "Couldn't fallback to IPv4. Exiting.\n"); logger_kill(logger); free(motd); free(tcp_relay_ports); free(keys_file_path); return 1; } } else { log_write(LOG_LEVEL_ERROR, "Couldn't initialize networking. Exiting.\n"); logger_kill(logger); free(motd); free(tcp_relay_ports); free(keys_file_path); return 1; } } Mono_Time *const mono_time = mono_time_new(); if (mono_time == nullptr) { log_write(LOG_LEVEL_ERROR, "Couldn't initialize monotonic timer. Exiting.\n"); kill_networking(net); logger_kill(logger); free(motd); free(tcp_relay_ports); free(keys_file_path); return 1; } mono_time_update(mono_time); DHT *const dht = new_dht(logger, mono_time, net, true); if (dht == nullptr) { log_write(LOG_LEVEL_ERROR, "Couldn't initialize Tox DHT instance. Exiting.\n"); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); free(motd); free(tcp_relay_ports); free(keys_file_path); return 1; } Onion *onion = new_onion(mono_time, dht); if (!onion) { log_write(LOG_LEVEL_ERROR, "Couldn't initialize Tox Onion. Exiting.\n"); kill_dht(dht); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); free(motd); free(tcp_relay_ports); free(keys_file_path); return 1; } Onion_Announce *onion_a = new_onion_announce(mono_time, dht); if (!onion_a) { log_write(LOG_LEVEL_ERROR, "Couldn't initialize Tox Onion Announce. Exiting.\n"); kill_onion(onion); kill_dht(dht); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); free(motd); free(tcp_relay_ports); free(keys_file_path); return 1; } if (enable_motd) { if (bootstrap_set_callbacks(dht_get_net(dht), DAEMON_VERSION_NUMBER, (uint8_t *)motd, strlen(motd) + 1) == 0) { log_write(LOG_LEVEL_INFO, "Set MOTD successfully.\n"); free(motd); } else { log_write(LOG_LEVEL_ERROR, "Couldn't set MOTD: %s. Exiting.\n", motd); kill_onion_announce(onion_a); kill_onion(onion); kill_dht(dht); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); free(motd); free(tcp_relay_ports); free(keys_file_path); return 1; } } if (manage_keys(dht, keys_file_path)) { log_write(LOG_LEVEL_INFO, "Keys are managed successfully.\n"); free(keys_file_path); } else { log_write(LOG_LEVEL_ERROR, "Couldn't read/write: %s. Exiting.\n", keys_file_path); kill_onion_announce(onion_a); kill_onion(onion); kill_dht(dht); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); free(tcp_relay_ports); free(keys_file_path); return 1; } TCP_Server *tcp_server = nullptr; if (enable_tcp_relay) { if (tcp_relay_port_count == 0) { log_write(LOG_LEVEL_ERROR, "No TCP relay ports read. Exiting.\n"); kill_onion_announce(onion_a); kill_onion(onion); kill_dht(dht); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); free(tcp_relay_ports); return 1; } tcp_server = new_TCP_server(logger, enable_ipv6, tcp_relay_port_count, tcp_relay_ports, dht_get_self_secret_key(dht), onion); free(tcp_relay_ports); if (tcp_server != nullptr) { log_write(LOG_LEVEL_INFO, "Initialized Tox TCP server successfully.\n"); struct rlimit limit; const rlim_t rlim_suggested = 32768; const rlim_t rlim_min = 4096; assert(rlim_suggested >= rlim_min); if (!getrlimit(RLIMIT_NOFILE, &limit)) { if (limit.rlim_cur < limit.rlim_max) { // Some systems have a hard limit of over 1000000 open file descriptors, so let's cap it at something reasonable // so that we don't set it to an unreasonably high number. limit.rlim_cur = limit.rlim_max > rlim_suggested ? rlim_suggested : limit.rlim_max; setrlimit(RLIMIT_NOFILE, &limit); } } if (!getrlimit(RLIMIT_NOFILE, &limit) && limit.rlim_cur < rlim_min) { log_write(LOG_LEVEL_WARNING, "Current limit on the number of files this process can open (%ju) is rather low for the proper functioning of the TCP server. " "Consider raising the limit to at least %ju or the recommended %ju. " "Continuing using the current limit (%ju).\n", (uintmax_t)limit.rlim_cur, (uintmax_t)rlim_min, (uintmax_t)rlim_suggested, (uintmax_t)limit.rlim_cur); } } else { log_write(LOG_LEVEL_ERROR, "Couldn't initialize Tox TCP server. Exiting.\n"); kill_onion_announce(onion_a); kill_onion(onion); kill_dht(dht); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); return 1; } } if (bootstrap_from_config(cfg_file_path, dht, enable_ipv6)) { log_write(LOG_LEVEL_INFO, "List of bootstrap nodes read successfully.\n"); } else { log_write(LOG_LEVEL_ERROR, "Couldn't read list of bootstrap nodes in %s. Exiting.\n", cfg_file_path); kill_TCP_server(tcp_server); kill_onion_announce(onion_a); kill_onion(onion); kill_dht(dht); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); return 1; } print_public_key(dht_get_self_public_key(dht)); uint64_t last_LANdiscovery = 0; const uint16_t net_htons_port = net_htons(port); int waiting_for_dht_connection = 1; if (enable_lan_discovery) { lan_discovery_init(dht); log_write(LOG_LEVEL_INFO, "Initialized LAN discovery successfully.\n"); } struct sigaction sa; sa.sa_handler = handle_signal; // Try to restart interrupted system calls if they are restartable sa.sa_flags = SA_RESTART; // Prevent the signal handler from being called again before it returns sigfillset(&sa.sa_mask); if (sigaction(SIGINT, &sa, nullptr)) { log_write(LOG_LEVEL_WARNING, "Couldn't set signal handler for SIGINT. Continuing without the signal handler set.\n"); } if (sigaction(SIGTERM, &sa, nullptr)) { log_write(LOG_LEVEL_WARNING, "Couldn't set signal handler for SIGTERM. Continuing without the signal handler set.\n"); } while (!caught_signal) { mono_time_update(mono_time); do_dht(dht); if (enable_lan_discovery && mono_time_is_timeout(mono_time, last_LANdiscovery, LAN_DISCOVERY_INTERVAL)) { lan_discovery_send(net_htons_port, dht); last_LANdiscovery = mono_time_get(mono_time); } if (enable_tcp_relay) { do_TCP_server(tcp_server, mono_time); } networking_poll(dht_get_net(dht), nullptr); if (waiting_for_dht_connection && dht_isconnected(dht)) { log_write(LOG_LEVEL_INFO, "Connected to another bootstrap node successfully.\n"); waiting_for_dht_connection = 0; } SLEEP_MILLISECONDS(30); } switch (caught_signal) { case SIGINT: log_write(LOG_LEVEL_INFO, "Received SIGINT (%d) signal. Exiting.\n", SIGINT); break; case SIGTERM: log_write(LOG_LEVEL_INFO, "Received SIGTERM (%d) signal. Exiting.\n", SIGTERM); break; default: log_write(LOG_LEVEL_INFO, "Received (%d) signal. Exiting.\n", caught_signal); } if (enable_lan_discovery) { lan_discovery_kill(dht); } kill_TCP_server(tcp_server); kill_onion_announce(onion_a); kill_onion(onion); kill_dht(dht); mono_time_free(mono_time); kill_networking(net); logger_kill(logger); return 0; } c-toxcore-0.2.13/other/bootstrap_daemon/tox-bootstrapd.conf000066400000000000000000000042061415350724400240300ustar00rootroot00000000000000// Tox DHT bootstrap daemon configuration file. // Listening port (UDP). port = 33445 // A key file is like a password, so keep it where no one can read it. // If there is no key file, a new one will be generated. // The daemon should have permission to read/write it. keys_file_path = "/var/lib/tox-bootstrapd/keys" // The PID file written to by the daemon. // Make sure that the user that daemon runs as has permissions to write to the // PID file. pid_file_path = "/var/run/tox-bootstrapd/tox-bootstrapd.pid" // Enable IPv6. enable_ipv6 = true // Fallback to IPv4 in case IPv6 fails. enable_ipv4_fallback = true // Automatically bootstrap with nodes on local area network. enable_lan_discovery = true enable_tcp_relay = true // While Tox uses 33445 port by default, 443 (https) and 3389 (rdp) ports are very // common among nodes, so it's encouraged to keep them in place. tcp_relay_ports = [443, 3389, 33445] // Reply to MOTD (Message Of The Day) requests. enable_motd = true // Just a message that is sent when someone requests MOTD. // Put anything you want, but note that it will be trimmed to fit into 255 bytes. motd = "tox-bootstrapd" // Any number of nodes the daemon will bootstrap itself off. // // Remember to replace the provided example with your own node list. // There is a maintained list of bootstrap nodes on Tox's wiki, if you need it // (https://wiki.tox.chat/doku.php?id=users:nodes). // // You may leave the list empty or remove "bootstrap_nodes" completely, // in both cases this will be interpreted as if you don't want to bootstrap // from anyone. // // address = any IPv4 or IPv6 address and also any US-ASCII domain name. bootstrap_nodes = ( { // Example Node 1 (IPv4) address = "127.0.0.1" port = 33445 public_key = "728925473812C7AAC482BE7250BCCAD0B8CB9F737BF3D42ABD34459C1768F854" }, { // Example Node 2 (IPv6) address = "::1/128" port = 33445 public_key = "3E78BACF0F84235B30054B54898F56793E1DEF8BD46B1038B9D822E8460FAB67" }, { // Example Node 3 (US-ASCII domain name) address = "example.org" port = 33445 public_key = "8CD5A9BF0A6CE358BA36F7A653F99FA6B258FF756E490F52C1F98CC420F78858" } )c-toxcore-0.2.13/other/bootstrap_daemon/tox-bootstrapd.service000066400000000000000000000011231415350724400245360ustar00rootroot00000000000000[Unit] Description=Tox DHT Bootstrap Daemon After=network.target [Service] Type=forking RuntimeDirectory=tox-bootstrapd RuntimeDirectoryMode=750 PIDFile=/var/run/tox-bootstrapd/tox-bootstrapd.pid WorkingDirectory=/var/lib/tox-bootstrapd ExecStart=/usr/local/bin/tox-bootstrapd --config /etc/tox-bootstrapd.conf User=tox-bootstrapd Group=tox-bootstrapd # TCP Server needs to be able to have lots of TCP sockets open. LimitNOFILE=32768 # Uncomment to allow binding to ports < 1024, e.g. 443 (HTTPS) for TCP Server #CapabilityBoundingSet=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target c-toxcore-0.2.13/other/bootstrap_daemon/tox-bootstrapd.sh000066400000000000000000000074461415350724400235260ustar00rootroot00000000000000#! /bin/bash ### BEGIN INIT INFO # Provides: tox-bootstrapd # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Starts the Tox DHT bootstrapping server daemon # Description: Starts the Tox DHT bootstrapping server daemon ### END INIT INFO # PATH should only include /usr/* if it runs after the mountnfs.sh script PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="Tox DHT bootstrap daemon" NAME=tox-bootstrapd DAEMON=/usr/local/bin/$NAME CFGFILE=/etc/$NAME.conf DAEMON_ARGS="--config $CFGFILE" PIDDIR=/var/run/$NAME PIDFILE=$PIDDIR/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME USER=tox-bootstrapd GROUP=tox-bootstrapd # Set ulimit -n based on number of fds available. # This check is borrowed from Debian's tor package, with a few modifications. if [ -r /proc/sys/fs/file-max ]; then system_max=$(cat /proc/sys/fs/file-max) if [ "$system_max" -gt "80000" ] ; then MAX_FILEDESCRIPTORS=32768 elif [ "$system_max" -gt "40000" ] ; then MAX_FILEDESCRIPTORS=16384 elif [ "$system_max" -gt "20000" ] ; then MAX_FILEDESCRIPTORS=8192 elif [ "$system_max" -gt "10000" ] ; then MAX_FILEDESCRIPTORS=4096 else MAX_FILEDESCRIPTORS=1024 cat << EOF Warning: Your system has very few file descriptors available in total. Maybe you should try raising that by adding 'fs.file-max=100000' to your /etc/sysctl.conf file. Feel free to pick any number that you deem appropriate. Then run 'sysctl -p'. See /proc/sys/fs/file-max for the current value, and file-nr in the same directory for how many of those are used at the moment. EOF fi else MAX_FILEDESCRIPTORS=32768 fi # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 # Load the VERBOSE setting and other rcS variables . /lib/init/vars.sh # Define LSB log_* functions. # Depend on lsb-base (>= 3.2-14) to ensure that this file is present # and status_of_proc is working. . /lib/lsb/init-functions # # Function that starts the daemon/service # do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started if [ ! -d $PIDDIR ] then mkdir $PIDDIR fi chown $USER:$GROUP $PIDDIR start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test --chuid $USER > /dev/null || return 1 # TCP Server needs to be able to have lots of TCP sockets open. ulimit -n $MAX_FILEDESCRIPTORS start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid $USER -- $DAEMON_ARGS || return 2 } # # Function that stops the daemon/service # do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry 5 --pidfile $PIDFILE --name $NAME --chuid $USER RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE return "$RETVAL" } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc -p $PIDFILE "$DAEMON" "$NAME" && exit 0 || exit $? ;; restart) log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2 exit 3 ;; esac exit 0 c-toxcore-0.2.13/other/bootstrap_node_packets.c000066400000000000000000000032611415350724400215300ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Special bootstrap node only packets. * * Include it in your bootstrap node and use: bootstrap_set_callbacks() to enable. */ #include "bootstrap_node_packets.h" #include #define INFO_REQUEST_PACKET_LENGTH 78 static uint32_t bootstrap_version; static uint8_t bootstrap_motd[MAX_MOTD_LENGTH]; static uint16_t bootstrap_motd_length; /* To request this packet just send a packet of length INFO_REQUEST_PACKET_LENGTH * with the first byte being BOOTSTRAP_INFO_PACKET_ID */ static int handle_info_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { if (length != INFO_REQUEST_PACKET_LENGTH) { return 1; } Networking_Core *nc = (Networking_Core *)object; uint8_t data[1 + sizeof(bootstrap_version) + MAX_MOTD_LENGTH]; data[0] = BOOTSTRAP_INFO_PACKET_ID; memcpy(data + 1, &bootstrap_version, sizeof(bootstrap_version)); uint16_t len = 1 + sizeof(bootstrap_version) + bootstrap_motd_length; memcpy(data + 1 + sizeof(bootstrap_version), bootstrap_motd, bootstrap_motd_length); if (sendpacket(nc, source, data, len) == len) { return 0; } return 1; } int bootstrap_set_callbacks(Networking_Core *net, uint32_t version, uint8_t *motd, uint16_t motd_length) { if (motd_length > MAX_MOTD_LENGTH) { return -1; } bootstrap_version = net_htonl(version); memcpy(bootstrap_motd, motd, motd_length); bootstrap_motd_length = motd_length; networking_registerhandler(net, BOOTSTRAP_INFO_PACKET_ID, &handle_info_request, net); return 0; } c-toxcore-0.2.13/other/bootstrap_node_packets.h000066400000000000000000000012251415350724400215330ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015 Tox project. */ /* * Special bootstrap node only packets. * * Include it in your bootstrap node and use: bootstrap_set_callbacks() to enable. */ #ifndef C_TOXCORE_OTHER_BOOTSTRAP_NODE_PACKETS_H #define C_TOXCORE_OTHER_BOOTSTRAP_NODE_PACKETS_H #include "../toxcore/network.h" #define MAX_MOTD_LENGTH 256 /* I recommend you use a maximum of 96 bytes. The hard maximum is this though. */ int bootstrap_set_callbacks(Networking_Core *net, uint32_t version, uint8_t *motd, uint16_t motd_length); #endif // C_TOXCORE_OTHER_BOOTSTRAP_NODE_PACKETS_H c-toxcore-0.2.13/other/cpufeatures.c000066400000000000000000000001741415350724400173220ustar00rootroot00000000000000#ifdef ANDROID_CPU_FEATURES #define typeof __typeof__ #include ANDROID_CPU_FEATURES #endif typedef int unused_declaration; c-toxcore-0.2.13/other/docker/000077500000000000000000000000001415350724400160755ustar00rootroot00000000000000c-toxcore-0.2.13/other/docker/autotools/000077500000000000000000000000001415350724400201265ustar00rootroot00000000000000c-toxcore-0.2.13/other/docker/autotools/Dockerfile000066400000000000000000000014001415350724400221130ustar00rootroot00000000000000################################################ # autotools-linux FROM localbuild/travis:1.0.0 USER root RUN apt-get update && apt-get install --no-install-recommends -y \ autoconf \ automake \ libtool \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* USER travis # Copy minimal files to run "autotools-linux install", so we can avoid # rebuilding nacl and other things when only source files change. RUN mkdir -p /home/travis/build/c-toxcore /home/travis/cache WORKDIR /home/travis/build/c-toxcore COPY --chown=travis:travis c-toxcore/.travis/ /home/travis/build/c-toxcore/.travis/ RUN .travis/autotools-linux install # Now copy the rest of the sources and run the build. COPY --chown=travis:travis . /home/travis/build/ RUN .travis/autotools-linux script c-toxcore-0.2.13/other/docker/cmake/000077500000000000000000000000001415350724400171555ustar00rootroot00000000000000c-toxcore-0.2.13/other/docker/cmake/Dockerfile000066400000000000000000000011011415350724400211400ustar00rootroot00000000000000################################################ # cmake-linux FROM localbuild/travis:1.0.0 # Copy minimal files to run "cmake-linux install", so we can avoid rebuilding # astyle and other things when only source files change. RUN mkdir -p /home/travis/build/c-toxcore /home/travis/cache WORKDIR /home/travis/build/c-toxcore COPY --chown=travis:travis c-toxcore/.travis/ /home/travis/build/c-toxcore/.travis/ RUN .travis/cmake-linux install # Now copy the rest of the sources and run the build. COPY --chown=travis:travis . /home/travis/build/ RUN .travis/cmake-linux script c-toxcore-0.2.13/other/docker/run-ci000077500000000000000000000006711415350724400172240ustar00rootroot00000000000000#!/bin/bash set -eux STAGE="${1-cmake}" readarray -t FILES <<<"$(git ls-files | sed -e 's,^,c-toxcore/,')" FILES+=(c-toxcore/.git) if [ -f .git ]; then FILES+=(.git/modules/c-toxcore) fi cd .. tar -c "${FILES[@]}" | docker build -f "c-toxcore/other/docker/travis/Dockerfile" \ -t localbuild/travis:1.0.0 - tar -c "${FILES[@]}" | docker build -f "c-toxcore/other/docker/$STAGE/Dockerfile" \ -t "localbuild/$STAGE:1.0.0" - c-toxcore-0.2.13/other/docker/travis/000077500000000000000000000000001415350724400174055ustar00rootroot00000000000000c-toxcore-0.2.13/other/docker/travis/Dockerfile000066400000000000000000000037351415350724400214070ustar00rootroot00000000000000# This Docker build emulates roughly what Travis CI is doing. It is not exactly # the same (different tool versions) and success in this image may not # necessarily mean success on Travis. This image is also not automatically # tested, so it may get out of date. Send PRs if you use it and it's broken. # # For one, we use bionic, not xenial, because xenial's clang is way too old. FROM ubuntu:16.04 # Travis environment. RUN apt-get update && apt-get install --no-install-recommends -y \ apt-transport-https \ build-essential \ ca-certificates \ curl \ git \ pkg-config \ python-pip \ python-setuptools \ python3 \ software-properties-common \ wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* SHELL ["/bin/bash", "-o", "pipefail", "-c"] RUN curl https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - \ && apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main" \ && apt-get update && apt-get install --no-install-recommends -y \ clang-6.0 \ clang-format-6.0 \ llvm-6.0 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* RUN ls /usr/bin/clang-6.0 && ln -s /usr/bin/clang-6.0 /usr/bin/clang \ && ls /usr/bin/clang++-6.0 && ln -s /usr/bin/clang++-6.0 /usr/bin/clang++ \ && ls /usr/bin/clang-format-6.0 && ln -s /usr/bin/clang-format-6.0 /usr/bin/clang-format \ && ls /usr/bin/opt-6.0 && ln -s /usr/bin/opt-6.0 /usr/bin/opt # Bionic's cmake is too old. RUN pip install --upgrade pip cmake # .travis.yml RUN apt-get update && apt-get install --no-install-recommends -y \ libconfig-dev \ libgtest-dev \ libopus-dev \ libsodium-dev \ libvpx-dev \ ninja-build \ pylint3 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Set up travis user. RUN groupadd -r -g 1000 travis \ && useradd --no-log-init -r -g travis -u 1000 travis \ && mkdir -p /src/workspace /home/travis \ && chown travis:travis /home/travis USER travis # Set up environment. ENV CC=gcc CXX=g++ \ PATH=/home/travis/.local/bin:$PATH \ TRAVIS_REPO_SLUG=TokTok/c-toxcore c-toxcore-0.2.13/other/docker/windows/000077500000000000000000000000001415350724400175675ustar00rootroot00000000000000c-toxcore-0.2.13/other/docker/windows/Dockerfile000066400000000000000000000014071415350724400215630ustar00rootroot00000000000000FROM debian:buster-slim # Build-time environment variables ARG VERSION_SODIUM=1.0.18 ARG VERSION_OPUS=1.3.1 ARG VERSION_VPX=1.9.0 ARG SUPPORT_TEST=false ARG SUPPORT_ARCH_i686=true ARG SUPPORT_ARCH_x86_64=true ARG CROSS_COMPILE=true # Make those available when running the container ENV SUPPORT_TEST=${SUPPORT_TEST} ENV SUPPORT_ARCH_i686=${SUPPORT_ARCH_i686} ENV SUPPORT_ARCH_x86_64=${SUPPORT_ARCH_x86_64} ENV CROSS_COMPILE=${CROSS_COMPILE} COPY get_packages.sh . RUN sh ./get_packages.sh COPY build_dependencies.sh . RUN sh ./build_dependencies.sh COPY build_toxcore.sh . ENV ENABLE_TEST=false ENV ALLOW_TEST_FAILURE=false ENV ENABLE_ARCH_i686=true ENV ENABLE_ARCH_x86_64=true ENV EXTRA_CMAKE_FLAGS="-DTEST_TIMEOUT_SECONDS=90" ENTRYPOINT ["sh", "./build_toxcore.sh"] c-toxcore-0.2.13/other/docker/windows/build_dependencies.sh000066400000000000000000000045421415350724400237350ustar00rootroot00000000000000#!/usr/bin/env sh # disable on Cygwin otherwise some builds fail if [ "$CROSS_COMPILE" = "true" ]; then set -e -x fi #=== Cross-Compile Dependencies === build() { ARCH=${1} echo "Building for $ARCH architecture" # set some things WINDOWS_TOOLCHAIN=$ARCH-w64-mingw32 # prefix that we will copy to the user PREFIX_DIR="/root/prefix/$ARCH" rm -rf "$PREFIX_DIR" mkdir -p "$PREFIX_DIR" export MAKEFLAGS=j"$(nproc)" export CFLAGS=-O3 CURL_OPTIONS="-L --connect-timeout 10" cd /tmp rm -rf /tmp/* echo echo "=== Building Sodium $VERSION_SODIUM $ARCH ===" curl $CURL_OPTIONS -O "https://download.libsodium.org/libsodium/releases/libsodium-$VERSION_SODIUM.tar.gz" tar -xf "libsodium-$VERSION_SODIUM.tar.gz" cd "libsodium-$VERSION_SODIUM" ./configure --host="$WINDOWS_TOOLCHAIN" --prefix="$PREFIX_DIR" --disable-shared --enable-static make make install cd .. echo echo "=== Building Opus $VERSION_OPUS $ARCH ===" curl $CURL_OPTIONS -O "https://archive.mozilla.org/pub/opus/opus-$VERSION_OPUS.tar.gz" tar -xf "opus-$VERSION_OPUS.tar.gz" cd "opus-$VERSION_OPUS" ./configure --host="$WINDOWS_TOOLCHAIN" --prefix="$PREFIX_DIR" --disable-extra-programs --disable-doc --disable-shared --enable-static make make install cd .. echo echo "=== Building VPX $VERSION_VPX $ARCH ===" LIB_VPX_TARGET="" if [ "$ARCH" = "i686" ]; then LIB_VPX_TARGET=x86-win32-gcc LIB_VPX_CFLAGS="" else LIB_VPX_TARGET=x86_64-win64-gcc # There is a bug in gcc that breaks avx512 on 64-bit Windows https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54412 # VPX fails to build due to it. # This is a workaround as suggested in https://stackoverflow.com/questions/43152633 LIB_VPX_CFLAGS="-fno-asynchronous-unwind-tables" fi curl $CURL_OPTIONS "https://github.com/webmproject/libvpx/archive/v$VERSION_VPX.tar.gz" -o "libvpx-$VERSION_VPX.tar.gz" tar -xf "libvpx-$VERSION_VPX.tar.gz" cd "libvpx-$VERSION_VPX" CFLAGS="$LIB_VPX_CFLAGS" CROSS="$WINDOWS_TOOLCHAIN"- ./configure --target="$LIB_VPX_TARGET" --prefix="$PREFIX_DIR" --disable-examples --disable-unit-tests --disable-shared --enable-static make make install cd .. rm -rf /tmp/* } if [ "$SUPPORT_ARCH_i686" = "true" ]; then build i686 fi if [ "$SUPPORT_ARCH_x86_64" = "true" ]; then build x86_64 fi tree /root echo echo "Built dependencies successfully!" echo c-toxcore-0.2.13/other/docker/windows/build_toxcore.sh000066400000000000000000000121751415350724400227730ustar00rootroot00000000000000#!/bin/sh set -e -x #=== Cross-Compile Toxcore === build() { ARCH=${1} echo "Building for $ARCH architecture" # set some things WINDOWS_TOOLCHAIN=$ARCH-w64-mingw32 # toxcore dependencies that we will copy to the user for static build of toxcore (e.g. vpx, opus, sodium) DEP_PREFIX_DIR="/root/prefix/$ARCH" # where to put the result of this particular build RESULT_PREFIX_DIR="/prefix/$ARCH" rm -rf "$RESULT_PREFIX_DIR" mkdir -p "$RESULT_PREFIX_DIR" rm -rf /tmp/* # where to install static/shared toxcores before deciding whether they should be copied over to the user STATIC_TOXCORE_PREFIX_DIR="/tmp/static_prefix" SHARED_TOXCORE_PREFIX_DIR="/tmp/shared_prefix" mkdir -p "$STATIC_TOXCORE_PREFIX_DIR" "$SHARED_TOXCORE_PREFIX_DIR" export MAKEFLAGS=j"$(nproc)" export CFLAGS=-O3 echo echo "=== Building toxcore $ARCH ===" export PKG_CONFIG_PATH="$DEP_PREFIX_DIR/lib/pkgconfig" if [ "$CROSS_COMPILE" = "true" ]; then TOXCORE_DIR="/toxcore" else # get Toxcore root cd "$(cd "$(dirname -- "$0")" >/dev/null 2>&1 && pwd)" cd ../../../ TOXCORE_DIR="$PWD" fi cp -a "$TOXCORE_DIR" /tmp/toxcore cd /tmp/toxcore/build echo " SET(CMAKE_SYSTEM_NAME Windows) SET(CMAKE_C_COMPILER $WINDOWS_TOOLCHAIN-gcc) SET(CMAKE_CXX_COMPILER $WINDOWS_TOOLCHAIN-g++) SET(CMAKE_RC_COMPILER $WINDOWS_TOOLCHAIN-windres) SET(CMAKE_FIND_ROOT_PATH /usr/$WINDOWS_TOOLCHAIN $DEP_PREFIX_DIR) " >windows_toolchain.cmake if [ "$ENABLE_TEST" = "true" ]; then echo "SET(CROSSCOMPILING_EMULATOR /usr/bin/wine)" >>windows_toolchain.cmake fi cmake -DCMAKE_TOOLCHAIN_FILE=windows_toolchain.cmake \ -DCMAKE_INSTALL_PREFIX="$STATIC_TOXCORE_PREFIX_DIR" \ -DENABLE_SHARED=OFF \ -DENABLE_STATIC=ON \ -DCMAKE_C_FLAGS="$CMAKE_C_FLAGS" \ -DCMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS" \ -DCMAKE_EXE_LINKER_FLAGS="$CMAKE_EXE_LINKER_FLAGS -fstack-protector" \ -DCMAKE_SHARED_LINKER_FLAGS="$CMAKE_SHARED_LINKER_FLAGS" \ $EXTRA_CMAKE_FLAGS \ .. cmake --build . --target install -- -j"$(nproc)" if [ "$ENABLE_TEST" = "true" ]; then rm -rf /root/.wine # setup wine if [ "$ARCH" = "i686" ]; then export WINEARCH=win32 else export WINEARCH=win64 fi winecfg export CTEST_OUTPUT_ON_FAILURE=1 # add libgcc_s_sjlj-1.dll libwinpthread-1.dll into PATH env var of wine export WINEPATH="$( cd /usr/lib/gcc/"$WINDOWS_TOOLCHAIN"/*posix/ winepath -w "$PWD" )"\;"$(winepath -w /usr/"$WINDOWS_TOOLCHAIN"/lib/)" if [ "$ALLOW_TEST_FAILURE" = "true" ]; then set +e fi cmake --build . --target test -- ARGS="-j50" if [ "$ALLOW_TEST_FAILURE" = "true" ]; then set -e fi fi # move static dependencies cp -a "$STATIC_TOXCORE_PREFIX_DIR"/* "$RESULT_PREFIX_DIR" cp -a "$DEP_PREFIX_DIR"/* "$RESULT_PREFIX_DIR" # make libtox.dll cd "$SHARED_TOXCORE_PREFIX_DIR" for archive in "$STATIC_TOXCORE_PREFIX_DIR"/lib/libtox*.a; do "$WINDOWS_TOOLCHAIN"-ar xv "$archive" done if [ "$CROSS_COMPILE" = "true" ]; then LIBWINPTHREAD="/usr/$WINDOWS_TOOLCHAIN/lib/libwinpthread.a" else LIBWINPTHREAD="/usr/$WINDOWS_TOOLCHAIN/sys-root/mingw/lib/libwinpthread.a" fi "$WINDOWS_TOOLCHAIN"-gcc -Wl,--export-all-symbols \ -Wl,--out-implib=libtox.dll.a \ -shared \ -o libtox.dll \ *.obj \ "$STATIC_TOXCORE_PREFIX_DIR"/lib/*.a \ "$DEP_PREFIX_DIR"/lib/*.a \ "$LIBWINPTHREAD" \ -liphlpapi \ -lws2_32 \ -static-libgcc \ -lssp cp libtox.dll.a "$RESULT_PREFIX_DIR"/lib mkdir -p "$RESULT_PREFIX_DIR"/bin cp libtox.dll "$RESULT_PREFIX_DIR"/bin rm -rf /tmp/* # remove everything from include directory except tox headers mv "$RESULT_PREFIX_DIR"/include/tox "$RESULT_PREFIX_DIR"/tox rm -rf "$RESULT_PREFIX_DIR"/include/* mv "$RESULT_PREFIX_DIR"/tox "$RESULT_PREFIX_DIR"/include/tox sed -i "s|^prefix=.*|prefix=$RESULT_PREFIX_DIR|g" "$RESULT_PREFIX_DIR"/lib/pkgconfig/*.pc sed -i "s|^libdir=.*|libdir=$RESULT_PREFIX_DIR/lib|g" "$RESULT_PREFIX_DIR"/lib/*.la } #=== Test Supported vs. Enabled === if [ "$ENABLE_ARCH_i686" != "true" ] && [ "$ENABLE_ARCH_x86_64" != "true" ]; then echo "Error: No architecture specified. Set either ENABLE_ARCH_i686 or ENABLE_ARCH_x86_64 or both." exit 1 fi if [ "$ENABLE_ARCH_i686" = "true" ] && [ "$SUPPORT_ARCH_i686" != "true" ]; then echo "Error: Can't build for i686 architecture because the image was created without SUPPORT_ARCH_i686 set" exit 1 fi if [ "$ENABLE_ARCH_x86_64" = "true" ] && [ "$SUPPORT_ARCH_x86_64" != "true" ]; then echo "Error: Can't build for x86_64 architecture because the image was created without SUPPORT_ARCH_x86_64 set" exit 1 fi if [ "$ENABLE_TEST" = "true" ] && [ "$SUPPORT_TEST" != "true" ]; then echo "Error: Can't build with tests because the image was created without SUPPORT_TEST set" exit 1 fi #=== Build === if [ "$ENABLE_ARCH_i686" = "true" ]; then build i686 fi if [ "$ENABLE_ARCH_x86_64" = "true" ]; then build x86_64 fi tree -h /prefix echo echo "Built toxcore successfully!" echo # since we are building as root if [ "$CROSS_COMPILE" = "true" ]; then chmod 777 /prefix -R fi c-toxcore-0.2.13/other/docker/windows/get_packages.sh000066400000000000000000000017251415350724400225450ustar00rootroot00000000000000#!/usr/bin/env sh set -e -x #=== Install Packages === apt-get update # Arch-independent packages required for building toxcore's dependencies and toxcore itself apt-get install -y \ autoconf \ automake \ ca-certificates \ cmake \ curl \ libtool \ libc-dev \ make \ pkg-config \ tree \ yasm # Arch-dependent packages required for building toxcore's dependencies and toxcore itself if [ "$SUPPORT_ARCH_i686" = "true" ]; then apt-get install -y \ g++-mingw-w64-i686 \ gcc-mingw-w64-i686 fi if [ "$SUPPORT_ARCH_x86_64" = "true" ]; then apt-get install -y \ g++-mingw-w64-x86-64 \ gcc-mingw-w64-x86-64 fi # Packages needed for running toxcore tests if [ "$SUPPORT_TEST" = "true" ]; then apt-get install -y \ texinfo dpkg --add-architecture i386 apt-get update apt-get install -y \ wine \ wine32 \ wine64 fi # Clean up to reduce image size apt-get clean rm -rf \ /var/lib/apt/lists/* \ /tmp/* \ /var/tmp/* c-toxcore-0.2.13/other/fun/000077500000000000000000000000001415350724400154165ustar00rootroot00000000000000c-toxcore-0.2.13/other/fun/BUILD.bazel000066400000000000000000000014071415350724400172760ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_binary") cc_binary( name = "cracker", srcs = ["cracker.c"], deps = [ "//c-toxcore/testing:misc_tools", "@libsodium", ], ) cc_binary( name = "minimal-save-generator", srcs = ["minimal-save-generator.c"], deps = [ "@libsodium", ], ) cc_binary( name = "sign", srcs = ["sign.c"], copts = ["-w"], deps = [ "//c-toxcore/testing:misc_tools", "@libsodium", ], ) cc_binary( name = "strkey", srcs = ["strkey.c"], copts = ["-w"], deps = [ "@libsodium", ], ) cc_binary( name = "save-generator", srcs = ["save-generator.c"], deps = [ "//c-toxcore/testing:misc_tools", "//c-toxcore/toxcore", ], ) c-toxcore-0.2.13/other/fun/bootstrap_node_info.py000077500000000000000000000113571415350724400220370ustar00rootroot00000000000000#!/usr/bin/env python3 """ Copyright (c) 2014 by nurupo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import socket import sys if sys.version_info[0] == 2: print("This script requires Python 3+ in order to run.") sys.exit(1) def print_help() -> None: """Print program usage to stdout.""" print("Usage: " + sys.argv[0] + " ") print(" Example: " + sys.argv[0] + " ipv4 192.210.149.121 33445") print(" Example: " + sys.argv[0] + " ipv4 23.226.230.47 33445") print(" Example: " + sys.argv[0] + " ipv4 node.tox.biribiri.org 33445") print(" Example: " + sys.argv[0] + " ipv4 cerberus.zodiaclabs.org 33445") print(" Example: " + sys.argv[0] + " ipv6 2604:180:1::3ded:b280 33445") print("") print("Return values:") print(" 0 - received info reply from a node") print(" 1 - incorrect command line arguments") print(" 2 - didn't receive any reply from a node") print(" 3 - received a malformed/unexpected reply") # https://github.com/irungentoo/toxcore/blob/4940c4c62b6014d1f0586aa6aca7bf6e4ecfcf29/toxcore/network.h#L128 INFO_PACKET_ID = b"\xF0" # https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L28 INFO_REQUEST_PACKET_LENGTH = 78 # first byte is INFO_REQUEST_ID, other bytes don't matter as long as reqest's # length matches INFO_REQUEST_LENGTH INFO_REQUEST_PACKET = INFO_PACKET_ID + ( b"0" * (INFO_REQUEST_PACKET_LENGTH - len(INFO_PACKET_ID))) PACKET_ID_LENGTH = len(INFO_PACKET_ID) # https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L44 VERSION_LENGTH = 4 # https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L26 MAX_MOTD_LENGTH = 256 MAX_INFO_RESPONSE_PACKET_LENGTH = PACKET_ID_LENGTH + VERSION_LENGTH + MAX_MOTD_LENGTH SOCK_TIMEOUT_SECONDS = 1.0 def main(protocol: str, host: str, port: int) -> None: """Call the bootstrap node info RPC and output the response.""" if protocol == "ipv4": sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) elif protocol == "ipv6": sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) else: print("Invalid first argument") print_help() sys.exit(1) sock.sendto(INFO_REQUEST_PACKET, (host, port)) sock.settimeout(SOCK_TIMEOUT_SECONDS) try: data, _ = sock.recvfrom(MAX_INFO_RESPONSE_PACKET_LENGTH) except socket.timeout: print("The DHT bootstrap node didn't reply in " + str(SOCK_TIMEOUT_SECONDS) + " sec.") print("The likely reason for that is that the DHT bootstrap node " "is either offline or has no info set.") sys.exit(2) packet_id = data[:PACKET_ID_LENGTH] if packet_id != INFO_PACKET_ID: print("Bad response, first byte should be {info_packet_id!r}" " but got {packet_id!r}({data!r})".format( info_packet_id=INFO_PACKET_ID, packet_id=packet_id, data=data, )) print("Are you sure that you are pointing the script at a Tox " "DHT bootstrap node and that the script is up to date?") sys.exit(3) version = int.from_bytes(data[PACKET_ID_LENGTH:PACKET_ID_LENGTH + VERSION_LENGTH], byteorder="big") motd = data[PACKET_ID_LENGTH + VERSION_LENGTH:].decode("utf-8") print("Version: " + str(version)) print("MOTD: " + motd) sys.exit(0) if __name__ == "__main__": if len(sys.argv) != 4: print_help() sys.exit(1) main( protocol=sys.argv[1], host=sys.argv[2], port=int(sys.argv[3]), ) c-toxcore-0.2.13/other/fun/cracker.c000066400000000000000000000031531415350724400171760ustar00rootroot00000000000000/* Public key cracker. * * Can be used to find public keys starting with specific hex (ABCD) for example. * * NOTE: There's probably a way to make this faster. * * Usage: ./cracker ABCDEF * * Will try to find a public key starting with: ABCDEF */ #include #include #include /* Sodium includes*/ #include #include #include "../../testing/misc_tools.h" #include "../../toxcore/ccompat.h" static void print_key(uint8_t *client_id) { uint32_t j; for (j = 0; j < 32; j++) { printf("%02X", client_id[j]); } } int main(int argc, char *argv[]) { if (argc < 2) { printf("usage: ./cracker public_key(or beginning of one in hex format)\n"); return 0; } long long unsigned int num_tries = 0; uint32_t len = strlen(argv[1]) / 2; unsigned char *key = hex_string_to_bin(argv[1]); uint8_t pub_key[32], priv_key[32], c_key[32]; if (len > 32) { len = 32; } memcpy(c_key, key, len); free(key); randombytes(priv_key, 32); while (1) { crypto_scalarmult_curve25519_base(pub_key, priv_key); uint32_t i; if (memcmp(c_key, pub_key, len) == 0) { break; } for (i = 32; i != 0; --i) { priv_key[i - 1] += 1; if (priv_key[i - 1] != 0) { break; } } ++num_tries; } printf("Public key:\n"); print_key(pub_key); printf("\nPrivate key:\n"); print_key(priv_key); printf("\n %llu keys tried\n", num_tries); return 0; } c-toxcore-0.2.13/other/fun/make-funny-savefile.py000077500000000000000000000060101415350724400216360ustar00rootroot00000000000000#!/usr/bin/python """ Generate a new (and empty) save file with predefined keys. Used to play with externally generated keys. (c) 2015 Alexandre Erwin Ittner Distributed under the GNU GPL v3 or later, WITHOUT ANY WARRANTY. See the file "LICENSE.md" for license information. Usage: ./make-funny-savefile.py The new user profile will be saved to . The keys must be an hex-encoded valid key pair generated by any means (eg. strkey.c); username may be anything. A random nospam value will be generated. Once the savefile is done, load it in your favorite client to get some DHT nodes, set status and status messages, add friends, etc. Example (of course, do not try using this key for anything real): ./make-funny-savefile.py 123411DC8B1A4760B648E0C7243B65F01069E4858F45C612CE1A6F673B603830 CC39440CFC063E4A95B7F2FB2580210558BE5C073AFC1C9604D431CCA3132238 "Test user" test.tox """ PUBLIC_KEY_LENGTH = 32 PRIVATE_KEY_LENGTH = 32 # Constants taken from messenger.c MESSENGER_STATE_COOKIE_GLOBAL = 0x15ed1b1f MESSENGER_STATE_COOKIE_TYPE = 0x01ce MESSENGER_STATE_TYPE_NOSPAMKEYS = 1 MESSENGER_STATE_TYPE_DHT = 2 MESSENGER_STATE_TYPE_FRIENDS = 3 MESSENGER_STATE_TYPE_NAME = 4 MESSENGER_STATE_TYPE_STATUSMESSAGE = 5 MESSENGER_STATE_TYPE_STATUS = 6 MESSENGER_STATE_TYPE_TCP_RELAY = 10 MESSENGER_STATE_TYPE_PATH_NODE = 11 STATUS_MESSAGE = "New user".encode("utf-8") import sys import struct import os def abort(msg): print(msg) exit(1) if len(sys.argv) != 5: abort("Usage: %s " % (sys.argv[0])) try: public_key = sys.argv[1].decode("hex") except: abort("Bad public key") try: private_key = sys.argv[2].decode("hex") except: abort("Bad private key") if len(public_key) != PUBLIC_KEY_LENGTH: abort("Public key with wrong length") if len(private_key) != PRIVATE_KEY_LENGTH: abort("Private key with wrong length") user_name = sys.argv[3].encode("utf-8") if len(user_name) > 32: abort("User name too long (for this script, at least)") out_file_name = sys.argv[4] nospam = os.urandom(4) def make_subheader(h_type, h_length): return ( struct.pack(" profile.tox * * Compile: gcc minimal-save-generator.c -o minimal-save-generator -lsodium */ #include #include #include #include int main(void) { unsigned char public_key[crypto_box_PUBLICKEYBYTES]; unsigned char secret_key[crypto_box_SECRETKEYBYTES]; crypto_box_keypair(public_key, secret_key); // print new tox savedata to stderr char tox_file[] = "\x00\x00\x00\x00\x1f\x1b\xed\x15\x44\x00\x00\x00\x01\x00\xce\x01\x00\x00\x00\x00"; fwrite(tox_file, sizeof(tox_file) - 1, 1, stderr); fwrite(public_key, sizeof(public_key), 1, stderr); fwrite(secret_key, sizeof(secret_key), 1, stderr); // print info on it to stdout char public_key_str[crypto_box_PUBLICKEYBYTES * 2 + 1]; char secret_key_str[crypto_box_SECRETKEYBYTES * 2 + 1]; sodium_bin2hex(public_key_str, sizeof(public_key_str), public_key, sizeof(public_key)); sodium_bin2hex(secret_key_str, sizeof(secret_key_str), secret_key, sizeof(secret_key)); for (size_t i = 0; i < sizeof(public_key_str); i ++) { public_key_str[i] = toupper(public_key_str[i]); secret_key_str[i] = toupper(secret_key_str[i]); } fprintf(stdout, "Public key: %s\n", public_key_str); fprintf(stdout, "Secret key: %s\n", secret_key_str); // calculate checksum for tox id printing unsigned char checksum[2] = {0}; for (size_t i = 0; i < crypto_box_PUBLICKEYBYTES; i ++) { checksum[i % 2] ^= public_key[i]; } char checksum_str[sizeof(checksum) * 2 + 1]; sodium_bin2hex(checksum_str, sizeof(checksum_str), checksum, sizeof(checksum)); for (size_t i = 0; i < sizeof(checksum_str); i ++) { checksum_str[i] = toupper(checksum_str[i]); } fprintf(stdout, "Tox Id: %s00000000%s\n", public_key_str, checksum_str); return 0; } c-toxcore-0.2.13/other/fun/save-generator.c000066400000000000000000000106521415350724400205100ustar00rootroot00000000000000#include #include #include #include #include "../../testing/misc_tools.h" #include "../../toxcore/ccompat.h" #include "../../toxcore/tox.h" #define GENERATED_SAVE_FILE "save.tox" #define GENERATED_STATUS_MESSAGE "Hello World" #define GENERATED_REQUEST_MESSAGE "Add me." #define BOOTSTRAP_IP "185.14.30.213" #define BOOTSTRAP_ADDRESS "2555763C8C460495B14157D234DD56B86300A2395554BCAE4621AC345B8C1B1B" #define BOOTSTRAP_UDP_PORT 443 static bool write_save(const uint8_t *data, size_t length) { FILE *fp = fopen(GENERATED_SAVE_FILE, "w"); if (!fp) { return false; } if (fwrite(data, length, 1, fp) != 1) { fclose(fp); return false; } fclose(fp); return true; } static bool bootstrap_tox(Tox *tox) { uint8_t *key = hex_string_to_bin(BOOTSTRAP_ADDRESS); if (!key) { printf("Could not allocate memory for tox address\n"); return false; } Tox_Err_Bootstrap err; tox_bootstrap(tox, BOOTSTRAP_IP, BOOTSTRAP_UDP_PORT, key, &err); free(key); if (err != TOX_ERR_BOOTSTRAP_OK) { printf("Failed to bootstrap. Error number: %d", err); return false; } return true; } static void tox_connection_callback(Tox *tox, Tox_Connection connection, void *userdata) { if (connection == TOX_CONNECTION_UDP) { printf("Connected to the tox network.\n"); *(bool *)userdata = true; } } static void print_information(Tox *tox) { uint8_t tox_id[TOX_ADDRESS_SIZE]; char tox_id_str[TOX_ADDRESS_SIZE * 2]; tox_self_get_address(tox, tox_id); to_hex(tox_id_str, tox_id, TOX_ADDRESS_SIZE); char nospam_str[(TOX_NOSPAM_SIZE * 2) + 1]; uint32_t nospam = tox_self_get_nospam(tox); int length = snprintf(nospam_str, sizeof(nospam_str), "%08X", nospam); nospam_str[length] = '\0'; uint8_t name[TOX_MAX_NAME_LENGTH]; tox_self_get_name(tox, name); printf("INFORMATION\n"); printf("----------------------------------\n"); printf("Tox ID: %.*s.\n", (int)TOX_ADDRESS_SIZE * 2, tox_id_str); printf("Nospam: %s.\n", nospam_str); printf("Name: %s.\n", name); printf("Status message: %s.\n", GENERATED_STATUS_MESSAGE); printf("Number of friends: %zu.\n", tox_self_get_friend_list_size(tox)); printf("----------------------------------\n"); } int main(int argc, char *argv[]) { if (argc < 3) { printf("Usage: ./save-generator ...\n"); return -1; } Tox *tox = tox_new(nullptr, nullptr); if (!tox) { printf("Failed to create tox.\n"); return -1; } if (!bootstrap_tox(tox)) { tox_kill(tox); return -1; } tox_callback_self_connection_status(tox, tox_connection_callback); bool connected = false; while (!connected) { tox_iterate(tox, &connected); c_sleep(tox_iteration_interval(tox)); } Tox_Err_Set_Info err; const uint8_t *name = (uint8_t *)argv[1]; tox_self_set_name(tox, name, strlen((const char *)name), &err); if (err != TOX_ERR_SET_INFO_OK) { printf("Failed to set name. Error number %d\n", err); } tox_self_set_status_message(tox, (const uint8_t *)GENERATED_STATUS_MESSAGE, strlen(GENERATED_STATUS_MESSAGE), &err); if (err != TOX_ERR_SET_INFO_OK) { printf("Failed to set status. Error number: %d\n", err); } for (unsigned int i = 2; i < argc; i++) { //start at 2 because that is where the tox ids are uint8_t *address = hex_string_to_bin(argv[i]); Tox_Err_Friend_Add friend_err; tox_friend_add(tox, address, (const uint8_t *)GENERATED_REQUEST_MESSAGE, strlen(GENERATED_REQUEST_MESSAGE), &friend_err); free(address); if (friend_err != TOX_ERR_FRIEND_ADD_OK) { printf("Failed to add friend number %u. Error number: %d\n", i - 1, friend_err); } } const size_t length = tox_get_savedata_size(tox); uint8_t *savedata = (uint8_t *)malloc(length); if (!savedata) { printf("Could not allocate memory for savedata.\n"); tox_kill(tox); return -1; } tox_get_savedata(tox, savedata); bool ret = write_save(savedata, length); free(savedata); if (!ret) { printf("Failed to write save.\n"); tox_kill(tox); return -1; } printf("Wrote tox save to %s\n", GENERATED_SAVE_FILE); print_information(tox); tox_kill(tox); return 0; } c-toxcore-0.2.13/other/fun/sign.c000066400000000000000000000066121415350724400165270ustar00rootroot00000000000000/* Binary signer/checker using ed25519 * * Compile with: * gcc -o sign sign.c -lsodium * * Generate a keypair: * ./sign g * * Sign a file: * ./sign s PRIVATEKEY file.bin signedfile.bin * * Check a file: * * ./sign c PUBKEY signedfile.bin * * NOTE: The signature is appended to the end of the file. */ #include #include #include "../../testing/misc_tools.h" // hex_string_to_bin #include "../../toxcore/ccompat.h" static int load_file(char *filename, unsigned char **result) { int size = 0; FILE *f = fopen(filename, "rb"); if (f == nullptr) { *result = nullptr; return -1; // -1 means file opening fail } fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); *result = (unsigned char *)malloc(size + 1); if (size != fread(*result, sizeof(char), size, f)) { free(*result); fclose(f); return -2; // -2 means file reading fail } fclose(f); (*result)[size] = 0; return size; } int main(int argc, char *argv[]) { unsigned char pk[crypto_sign_ed25519_PUBLICKEYBYTES]; unsigned char sk[crypto_sign_ed25519_SECRETKEYBYTES]; if (argc == 2 && argv[1][0] == 'g') { crypto_sign_ed25519_keypair(pk, sk); printf("Public key:\n"); int i; for (i = 0; i < crypto_sign_ed25519_PUBLICKEYBYTES; i++) { printf("%02X", pk[i]); } printf("\nSecret key:\n"); for (i = 0; i < crypto_sign_ed25519_SECRETKEYBYTES; i++) { printf("%02X", sk[i]); } printf("\n"); } if (argc == 5 && argv[1][0] == 's') { unsigned char *secret_key = hex_string_to_bin(argv[2]); unsigned char *data; int size = load_file(argv[3], &data); if (size < 0) { goto fail; } unsigned long long smlen; unsigned char *sm = (unsigned char *)malloc(size + crypto_sign_ed25519_BYTES * 2); crypto_sign_ed25519(sm, &smlen, data, size, secret_key); free(secret_key); if (smlen - size != crypto_sign_ed25519_BYTES) { goto fail; } FILE *f = fopen(argv[4], "wb"); if (f == nullptr) { goto fail; } memcpy(sm + smlen, sm, crypto_sign_ed25519_BYTES); // Move signature from beginning to end of file. if (fwrite(sm + (smlen - size), 1, smlen, f) != smlen) { goto fail; } fclose(f); printf("Signed successfully.\n"); } if (argc == 4 && argv[1][0] == 'c') { unsigned char *public_key = hex_string_to_bin(argv[2]); unsigned char *data; int size = load_file(argv[3], &data); if (size < 0) { goto fail; } unsigned char *signe = (unsigned char *)malloc(size + crypto_sign_ed25519_BYTES); memcpy(signe, data + size - crypto_sign_ed25519_BYTES, crypto_sign_ed25519_BYTES); // Move signature from end to beginning of file. memcpy(signe + crypto_sign_ed25519_BYTES, data, size - crypto_sign_ed25519_BYTES); unsigned char *m = (unsigned char *)malloc(size); unsigned long long mlen; if (crypto_sign_ed25519_open(m, &mlen, signe, size, public_key) == -1) { printf("Failed checking sig.\n"); goto fail; } printf("Checked successfully.\n"); } return 0; fail: printf("FAIL\n"); return 1; } c-toxcore-0.2.13/other/fun/strkey.c000066400000000000000000000076111415350724400171100ustar00rootroot00000000000000/* strkey -- String in Public Key * * Generates Tox's key pairs, checking if a certain string is in the public key. * * Requires sodium or nacl library. * * There seem to be some problems with the code working on Windows -- it works * when built in debug mode with MinGW 4.8, but it doesn't work correctly when * built in release. * * Usage: strkey * * Offset - an integer specifying exact byte offset position of the string you * are looking for within a public key. When offset is negative, the program * just looks for the desired string being somewhere, doesn't matter where, in * the public key. * * String - a hex string that you want to have in your public key. It must have * an even number of letters, since every two hexes map to a single byte of * the public key. * * Examples: * strkey 0 0123 * Looks for a public key that begins with "0123". * * strkey 1 0123 * Looks for a public key that has "0123" starting at its second byte, i.e. "XX0123...". * * strkey 2 0123 * Looks for a public key that has "0123" starting at its third byte, i.e. "XXXX0123...". * (each two hexes represent a single byte of a public key) * * strkey -1 AF57CC * Looks for a public key that contains "AF57CC", regardless of its position. * * To compile with gcc and sodium: gcc strkey.c -o strkey -lsodium */ #include #include #include #define PRINT_TRIES_COUNT static void print_key(unsigned char *key) { size_t i; for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++) { if (key[i] < 16) { fprintf(stdout, "0"); } fprintf(stdout, "%hhX", key[i]); } } int main(int argc, char *argv[]) { unsigned char public_key[crypto_box_PUBLICKEYBYTES]; // null terminator unsigned char secret_key[crypto_box_SECRETKEYBYTES]; int offset = 0; size_t len; unsigned char desired_bin[crypto_box_PUBLICKEYBYTES]; // null terminator if (argc == 3) { offset = atoi(argv[1]); char *desired_hex = argv[2]; len = strlen(desired_hex); if (len % 2 != 0) { fprintf(stderr, "Desired key should have an even number of letters\n"); exit(1); } size_t block_length = (offset < 0 ? 0 : offset) + len / 2; if (block_length > crypto_box_PUBLICKEYBYTES) { fprintf(stderr, "The given key with the given offset exceed public key's length\n"); exit(1); } // convert hex to bin char *pos = desired_hex; size_t i; for (i = 0; i < len; pos += 2) { unsigned int value; sscanf(pos, "%02x", &value); desired_bin[i] = value; ++i; } } else { fprintf(stdout, "Usage: executable \n"); exit(1); } len /= 2; #ifdef PRINT_TRIES_COUNT long long unsigned int tries = 0; #endif if (offset < 0) { int found = 0; do { #ifdef PRINT_TRIES_COUNT tries ++; #endif crypto_box_keypair(public_key, secret_key); int i; for (i = 0; i <= crypto_box_PUBLICKEYBYTES - len; i ++) { if (memcmp(public_key + i, desired_bin, len) == 0) { found = 1; break; } } } while (!found); } else { unsigned char *p = public_key + offset; do { #ifdef PRINT_TRIES_COUNT tries ++; #endif crypto_box_keypair(public_key, secret_key); } while (memcmp(p, desired_bin, len) != 0); } fprintf(stdout, "Public key: "); print_key(public_key); fprintf(stdout, "\n"); fprintf(stdout, "Private key: "); print_key(secret_key); fprintf(stdout, "\n"); #ifdef PRINT_TRIES_COUNT fprintf(stdout, "Found the key pair on %llu try.\n", tries); #endif return 0; } c-toxcore-0.2.13/other/osx_build_script_toxcore.sh000066400000000000000000000023361415350724400223050ustar00rootroot00000000000000#!/usr/bin/env bash # written by Lubo Diakov # hard coded toxcore directory, replace with other path or variable as needed cd ~/Downloads/toxcore echo "Now working in:"`pwd` # must have working git binary, and have done git clone at least once before git pull echo "If git pull responds: Already up-to-date. you can cancel the build" echo "by typing anything except y or Y below" read -p "Continue with build? (enter y to continue): " Last_Chance # blah blah if [[ $Last_Chance = [Yy] ]]; then echo "Continuing!"; else echo "Aborted!"; exit fi sleep 3 # if libsodium is built with macports, link it from /opt/local/ to /usr/local if [ ! -L "/usr/local/lib/libsodium.dylib" ]; then # Control will enter here if $DIRECTORY doesn't exist. ln -s /opt/local/lib/libsodium.dylib /usr/local/lib/libsodium.dylib fi echo "The symlink /usr/local/lib/libsodium.dylib exists." sleep 3 # replace ppc, i386 as needed. ./configure CC="gcc -arch ppc -arch i386" CXX="g++ -arch ppc -arch i386" CPP="gcc -E" CXXCPP="g++ -E" # get rid of prior builds, start clean make clean make echo "" echo "Sudo is required for make install only, all other steps run without it." echo "Please type your sudo password below for make install:" sudo make install exit c-toxcore-0.2.13/other/pkgconfig/000077500000000000000000000000001415350724400165755ustar00rootroot00000000000000c-toxcore-0.2.13/other/pkgconfig/toxcore.pc.in000066400000000000000000000005141415350724400212110ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ Name: toxcore Description: Tox protocol library Requires.private: @toxcore_PKGCONFIG_REQUIRES@ Version: @PROJECT_VERSION@ Libs: -L${libdir} -ltoxcore Libs.private: @toxcore_PKGCONFIG_LIBS@ Cflags: -I${includedir} c-toxcore-0.2.13/other/rpm/000077500000000000000000000000001415350724400154245ustar00rootroot00000000000000c-toxcore-0.2.13/other/rpm/Makefile000066400000000000000000000020431415350724400170630ustar00rootroot00000000000000PROJECT_NAME = toxcore PROJECT_FULL_NAME = c-$(PROJECT_NAME) PROJECT_VERSION = $(shell git describe | sed -e 's/^v//' -e 's/-/_/g') PROJECT_COMMIT_ID = $(shell git rev-parse HEAD) PROJECT_COMMIT_ID_SHORT = $(shell git rev-parse --short HEAD) PROJECT_GIT_ROOT = $(shell git rev-parse --show-toplevel) all: @echo available targets: spec srpm clean spec: toxcore.spec toxcore.spec: sed -r \ -e 's#@PROJECT_NAME@#$(PROJECT_NAME)#g' \ -e 's#@PROJECT_VERSION@#$(PROJECT_VERSION)#' \ -e 's#^(%define commit )0$$#\1$(PROJECT_COMMIT_ID)#' \ toxcore.spec.in > toxcore.spec $(PROJECT_FULL_NAME)-$(PROJECT_COMMIT_ID_SHORT).tar.gz: ( \ cd $(PROJECT_GIT_ROOT); \ git archive --prefix '$(PROJECT_FULL_NAME)-$(PROJECT_COMMIT_ID_SHORT)/' -o other/rpm/$(PROJECT_FULL_NAME)-$(PROJECT_COMMIT_ID_SHORT).tar.gz $(PROJECT_COMMIT_ID) \ ) srpm: toxcore.spec $(PROJECT_FULL_NAME)-$(PROJECT_COMMIT_ID_SHORT).tar.gz rpmbuild --define "_sourcedir ." --define "_srcrpmdir ." -bs toxcore.spec clean: rm -f toxcore.spec *.tar.gz *.rpm .PHONY = clean srpm spec c-toxcore-0.2.13/other/rpm/tox-bootstrapd.service000066400000000000000000000006551415350724400220050ustar00rootroot00000000000000[Unit] Description=Tox DHT Bootstrap Daemon After=network.target [Service] Type=forking RuntimeDirectory=tox-bootstrapd RuntimeDirectoryMode=750 PIDFile=/var/run/tox-bootstrapd/tox-bootstrapd.pid WorkingDirectory=/var/lib/tox-bootstrapd ExecStart=/usr/bin/tox-bootstrapd --config /etc/tox-bootstrapd.conf User=tox-bootstrapd Group=tox-bootstrapd #CapabilityBoundingSet=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target c-toxcore-0.2.13/other/rpm/toxcore.spec.in000066400000000000000000000065351415350724400204010ustar00rootroot00000000000000%define full_name c-@PROJECT_NAME@ %define commit 0 %if "${commit}" != "0" %define shortcommit %(c=%{commit}; echo ${c:0:7}) %endif Name: @PROJECT_NAME@ Version: @PROJECT_VERSION@ Release: 1%{?dist} Summary: All-in-one secure communication platform License: GPLv3 URL: https://github.com/TokTok/%{full_name} %if "%{commit}" == "0" Source0: https://github.com/TokTok/%{full_name}/archive/v%{version}.tar.gz %else Source0: https://github.com/TokTok/%{full_name}/archive/%{commit}/%{full_name}-%{shortcommit}.tar.gz %endif BuildRequires: cmake BuildRequires: libvpx-devel BuildRequires: opus-devel BuildRequires: libsodium-devel BuildRequires: libconfig-devel BuildRequires: systemd-units %description With the rise of governmental monitoring programs, Tox, a FOSS initiative, aims to be an easy to use, all-in-one communication platform that ensures their users full privacy and secure message delivery. %package devel Summary: Development files for @PROJECT_NAME@ Requires: %{name} = %{version}-%{release} %description devel Development package for @PROJECT_NAME@ %package static Summary: @PROJECT_NAME@ static libraries Requires: %{name}-devel = %{version}-%{release} %description static @PROJECT_NAME@ static libraries %package -n tox-bootstrapd Summary: Tox DHT bootstrap daemon. Requires: %{name} = %{version}-%{release} Requires(pre): shadow-utils Requires(post): systemd-units Requires(preun): systemd-units Requires(postun): systemd-units %description -n tox-bootstrapd Tox DHT bootstrap daemon. %prep %if "%{commit}" == "0" %setup -q -n %{full_name}-%{version} %else %setup -q -n %{full_name}-%{commit} %endif %build %cmake %{__make} %{?_smp_mflags} %install %make_install mkdir -p %{buildroot}%{_unitdir} install -m 0644 other/rpm/tox-bootstrapd.service %{buildroot}%{_unitdir}/tox-bootstrapd.service install -d "%{buildroot}%{_sharedstatedir}/tox-bootstrapd" mkdir -p %{buildroot}%{_sysconfdir} install -m 0644 other/bootstrap_daemon/tox-bootstrapd.conf %{buildroot}%{_sysconfdir}/tox-bootstrapd.conf %check %{__make} %{?_smp_mflags} test %pre -n tox-bootstrapd getent group tox-bootstrapd >/dev/null || groupadd -r tox-bootstrapd getent passwd tox-bootstrapd >/dev/null || \ useradd -r -g tox-bootstrapd -d /var/lib/tox-bootstrapd -s /sbin/nologin \ -c "Account to run Tox's DHT bootstrap daemon" tox-bootstrapd %post -p /sbin/ldconfig %postun -p /sbin/ldconfig %post -n tox-bootstrapd %systemd_post tox-bootstrapd.service %postun -n tox-bootstrapd %systemd_postun_with_restart tox-bootstrapd.service %preun -n tox-bootstrapd %systemd_preun tox-bootstrapd.service %files %defattr(-, root, root) %doc LICENSE.md README.md CHANGELOG.md %{_libdir}/libtoxcore.so* %files devel %defattr(-, root, root) %{_includedir}/tox/ %{_libdir}/pkgconfig/toxcore.pc %files static %defattr(-, root, root) %{_libdir}/libtoxcore.a %files -n tox-bootstrapd %defattr(-, root, root) %{_bindir}/tox-bootstrapd %{_unitdir}/tox-bootstrapd.service %{_sharedstatedir}/tox-bootstrapd %attr(-,tox-bootstrapd,tox-bootstrapd) %{_sharedstatedir}/tox-bootstrapd/ %config(noreplace) %{_sysconfdir}/tox-bootstrapd.conf %changelog * Sat Nov 25 2017 SmokedCheese - 0.0.0-2 - Rewrite spec file * Tue Mar 3 2015 Sergey 'Jin' Bostandzhyan - 0.0.0-1 - initial package c-toxcore-0.2.13/other/tox-warning.png000066400000000000000000000123321415350724400176120ustar00rootroot00000000000000PNG  IHDR2f8gAMA a cHRMz&u0`:pQ<bKGD pHYsodIDATx{\źn * R⒊*4I}Qdd"izhc ۵Tbk'=mp( * PG*** 0UAMO U y6V]Ɇ(tL2տ js. %O|TTTvmmStL2o!mh**NcIcBuITTTv-uu9\Z߽T9;#TTTv7w!qC5XٟSTTTvGpFp#/XVyqx7td}MTTTvW$oF50rg"ʷiSQQu2; BeYhaצȟ0yp!6:U]kcW^#@V]K:&asyfTTTv凾$(Vȅ ;mw**N;Yݘb#]rG5UߥMOEEaWn#1X+OtL([,a_16=U]MV%b.س5Rv閹A>uy:(Z[[ Bô$HDŽ{$f,6{"fd[W`f.Tdƴ4]tp TV v ʄ3fdJ |UTŋ˩?R9 `^ TLLĸ8ܻB#ا" lnnNKKKe)/Oy%߿o!+WT*\vL(3===###:vtB eDZK<)|~8F0p,pu;O8!=]4~<'GTRBfߟy-:\IZYD"l/'K.LJKZJ[Z*8e _ΰah d#ϝ;.sѢEN8!]~Ϟ=,YBHy9dYb5kau3% N'ܳG%%ILďO[P p1!0jvp~Emۓ]uu5-0--H}gIvlmm YU3Q``ݻw 1Ganŋη9~x]] IDŽvկ|~o?{Vs~&w9۷žpNH0 vylm/P ~r0vGIqܟ߭[7m.iӦ=|`0숎-h`rÇ ;Z-h3uԧ׋T^~qH߭]oV\Y%7oj*!!^ĉX{{=a"%%ar2&%Νط/'K v8iY`cf$`w>Ya=>CCɤ݂ VHzG%"SSS%`p"I;"G%Vgϊ.)))999))iΝ}a'y{{O?՜qi}a/v-ڙA@cϰ9`o=|)î𿖓3n{E:l 0[cdi)%hu$9bsGvlIЭȾvc%RR83k&qR]ƍ 8_D 1WfgM"#1#Ft0.\ ^Gvs]ee;NL)In̘1lϳki?fgg[ZZ v/&-[fBf7nDĻwJL 9rD50w2L e6JG~pWynZ+b^%(6v}n'a{i 6:vDi5dv<{km͟1\6=sl .J¸QzA'f ϊfm h &M6غU>|&j߾Vvȴ.+r_yE{*wًa^Ɗ/l5vժUD] ]~cKL hDX1v~}yî qܴz/LQu-,q vqАrA:V;!_kdvs/#=C2®]9؂CiL hMC򰋍%xx0So=:u*?`WYY٥Kb]IIcHqE~*((@ vvnnna%ĉ)Bv-5lɮZH}s!coݫl<9G{8w:ZZd Z[unZ}v'EC1!3Ϙv/D="hpL9;S/PB)W 2-$4o<}vDRXX:]0]lҰC9sv%%ݫ7nltbv= VzJiw]M:̖|"CzgHfŚ251 8a2<.lݱcf" JCC ]MM ߧOmk׮t544.ri#'O̶1cϞ2T #o(*!!ݼ{tP5ė35_nىj@ ew\Y/׋dpm"3dm" 2]=('hW$`c؝>-;NN[T, `;Dܽ{땾ܝ.ǬM=z N:%{/BCC [GKVS`UQFzAKd'P7WyBS0IMڽ;'X!K`d"::CĀy.ٕat4"8OO|]~TҰ۷oX(յ `Nv%%%|#"\Ž0Z3s/g#'J]#\S`L@5eYϧ>t 4c_ clM0ìFqrTTTTzD$1⨜tƃ%lX;f d0 *i>o9Fn7d{AP&ov7X#?6ZY'zÊPOp^KdǠL0ayy`alzLH7ʇu+`Wx/20< ]@JѼXA&3網= ݝ\t7A.y?}}`WuFinD}*(5?x0,5TW`YF{2 gpرZ)i50\SC+FZdƇkZ-***a;~1YχO7܍{]JM:fM{NCݢ2v减t U| [TTTî/t+VO!vrsGt52(?t(騨WAX%tEXtdate:create2020-04-24T21:48:04+00:00@%tEXtdate:modify2020-04-24T21:48:04+00:00tEXtSoftwarepaint.net 4.0.134[zIENDB`c-toxcore-0.2.13/other/tox.png000066400000000000000000000157701415350724400161600ustar00rootroot00000000000000PNG  IHDRN ?gAMA a cHRMz&u0`:pQ<0PLTEШruv277DHI起cggTXXbKGDH pHYs  tIME0儖IDATx_hdyGֻh & `zKAY%X;+%I<9z*uЗ*uBOB#%0dZmKQI!*mPoN3s{xu;?{w9Rx@y>u>Ë}#Ov{c]Qa2g?Fp ~AOWS,o$(ML0A'6'*(FbGE1ز4bx}8 "č2ȟMD$AT~mdi'l OIF>M /Y5M/&i?ȅo%>4 x}B9,NWO "󚯦Ik(2?/FezES%4M6x&3OJP!i^#d"7QwX u9M5 J顠~ )*0 T))*NS&T}τ Ql)G&5P i(x+b_>2Q6m?v?ڟUB ̏,:rifl^MA?z|Y!זF=w2xsMQ) G]F%<}Wtw/fPZ4vp j0' Pk '% ^ ?4]? u@WnA?/+~>ħ)Av~P+' Н~cJQ@E(s)a坔?d#Q)Qܰ7י 6dfWOiHn%e@clAy/R*"}2J^丢9 ?QivePސ= ۖILc5}rIzuC#!m*diO@!DTC6_jXmIu N!vŮڣu8daEq*ADq 9ˁ0mcaeB>! Lms"֊)pJK2 tYS!O5[S{$ iM3x x ƁtCUK'a dO"𴆧x x)<-O{x x(B>\W<:ɵSTu.-OZ_ST7򁚧rKH zʩ4iM <=O? Ư)yT}<=O <=OS<=O^F6)(z '2mST<MOWSTh#5SPtIm Dӓx J_STdSPTbzwNo=(E9i1y&x z?'r%φrt[gy g,SSoS#v)iO<|q쬡MMj Gs:zg9Q=g/⚡az#1 ?HYR{Z}Կw7Ů'6J ×VMuL=ktZn:tUSRlMl,+wK؊POxL-yFr$^Ҟd}ήuy͂b3P>aܳwrNK~EQ^U tu{ݛf@K5ɾ g٦]LF.ɨ0Drxw% e;kze=mY \!_}EK쀤vcgC6'2S{:R>vMܿw]эryzSôlL R.{s?Ry:5_H&UӗvULaP 1Msb71:_P)ysw)Q'WM!jG[{*bLKMkrWiݦig;ZӶW3wJbޗDE~<g;ESO_)e2.ǚ<<=< ͲZE i}<ͪv%*OS{j:,/><5xRpL%kNOCp,(OrMa*xҡr3-i/)ʽ CO3T߾WhMI5}xv[)jj94xX~O[oo3|b><ӻ"޵'x'K)7>O_\Ә<5l>UOۏv⩼UYӅ]<5 "=m]<ӷ<UӞOtc@.EiROThRD.:ϋ_xvW[{:y'WzjAF}m_/6O-":X<OӶOZ<5'P'4OmEY%SoW4Om<}9OoOWXF qzԐnO#zZqz~Oa?=}DGӘ<: IjuO#ŋO?4O~ yz61x^<5x:4<5-x}~\ i$܆b)*OL(O]_hi^61x꼑8MiV;ciWK?xj<^TYi8E+Ob1{aыӶe{e.9Ӌ=V,f4tO{ڵ{zhHyj"q O4ۡNڟi؞ef^rA{X58i>_r7K1xUxc3O/<Ӭ܍$xO&x>>\:P=j^" NiSE(szwN.8׈i-IO- I7pOxYk>d)pO;0=5M Mێij V؞~Y< ӆ)AڗݨiOT4O=5Wl[xΛ Tm>>tL }OBez_Y*)*˦(>zxZ.OCҹ`iM O#!H=- ӈ=U EGJ XעiNjG*(C~{:}7U xUH=Wɼki<5={=w)i<>Zٳ)YJ.ixzaJxGxsO?])R{m~LMھ^ #LRZO+Y(q+0> @8/\w'J%tEXtdate:create2020-04-24T21:48:04+00:00@%tEXtdate:modify2020-04-24T21:48:04+00:00IENDB`c-toxcore-0.2.13/other/version-sync000077500000000000000000000103121415350724400172100ustar00rootroot00000000000000#!/bin/sh set -eu SOURCE_DIR=$1 MAJOR=$2 MINOR=$3 PATCH=$4 VER="$MAJOR.$MINOR.$PATCH" update() { file="$SOURCE_DIR/$1" expr="$2" sed -e "$expr" "$file" > "$file.updated-version" if diff "$file" "$file.updated-version"; then rm "$file.updated-version" else # use cat > and rm instead of move to keep file permissions cat "$file.updated-version" > "$file" rm "$file.updated-version" fi } update 'configure.ac' 's/AC_INIT(\[tox\], \[.*\])/AC_INIT([tox], ['$VER'])/' update 'toxcore/tox.api.h' 's/\(const VERSION_MAJOR *= \).*;/\1'$MAJOR';/' update 'toxcore/tox.api.h' 's/\(const VERSION_MINOR *= \).*;/\1'$MINOR';/' update 'toxcore/tox.api.h' 's/\(const VERSION_PATCH *= \).*;/\1'$PATCH';/' update 'CMakeLists.txt' 's/\(PROJECT_VERSION_MAJOR "\).*"/\1'$MAJOR'"/' update 'CMakeLists.txt' 's/\(PROJECT_VERSION_MINOR "\).*"/\1'$MINOR'"/' update 'CMakeLists.txt' 's/\(PROJECT_VERSION_PATCH "\).*"/\1'$PATCH'"/' # # calculating the SO version # # The SO version reflects changes in the ABI compatibility of the library [1]. # The general convention on this is that the SO version is a monotonically # increasing number. The major version reflects breaking changes and the minor # number reflect non-breaking updates [2]. # # The SO version for tox libraries consists of two parts: `A.B`. # - incrementing A reflects an ABI breaking change. # - incrementing B reflects a non-ABI-breaking update. B is set to 0 when A is incremented. # # As the tox versioning scheme directly reflects ABI compatibility, we can use it # to construct the SO version. # # In the `0.y.z` release cycle, breaking changes are allowed in every increment of `y`, # so we can build the SO version just by taking `y.z`. # In the `x.y.z` release cycle with `x > 0` the SO version must be calculated by taking # the major of the tox version and add a hardcoded number that is the last A of the 0.y.z # release cycle. # # References: # # [1]: https://autotools.io/libtool/version.html # [2]: http://www.ibm.com/developerworks/linux/library/l-shlibs/index.html#N1006E # # the last major version number from the 0.x release cycle # this must be constant starting from the 1.0 release LAST_SOMAJOR=2 if [ $MAJOR -eq 0 ]; then SOMAJOR=$MINOR SOMINOR=$PATCH # update lastmajor above update 'other/version-sync' 's/^\(LAST_SOMAJOR=\).*/\1'$SOMAJOR'/' else SOMAJOR=$(expr $MAJOR + $LAST_SOMAJOR) SOMINOR=$MINOR fi # # libtool has a quite cryptic implementation of the versioning system, which also # changes between systems, see https://github.com/lxde/lxqt/issues/488#issuecomment-238084222 # # .so library version, following the libtool scheme: # # current:revision:age # # current: increment if interfaces have been added, removed or changed # revision: increment if source code has changed, set to zero if current is # incremented # age: increment if interfaces have been added, set to zero if # interfaces have been removed or changed # # For a full reference see: # https://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info # # Passing such a version string to libtool will generate the following version number # on the library binary file on GNU/Linux: # # (current - age).(age).(revision) # # We do not want to use a separate version numbering for the library because the package versioning is equally good: # # Semver non-breaking: 0.y.(z+1) or x.(y+1).0: # # This would mean to increment current and age, which leave the major of SOVERSION the same. # Revision is incremented. # # Semver breaking: 0.(y+1).0 or (x+1).0.0: # # This would mean to increment current, set age and revision to zero. # # Thus to make libtool use our version, we have to pass (major + minor):patch:minor as current:revision:age to get: # # (current - age).(age).(revision) # <=> (major + minor - minor).minor.patch # <=> major.minor.patch # if [ $MAJOR -eq 0 ]; then LIBTOOL_CURRENT=$(expr $SOMAJOR + $SOMINOR) LIBTOOL_AGE=$SOMINOR LIBTOOL_REVISION=0 else LIBTOOL_CURRENT=$(expr $SOMAJOR + $SOMINOR) LIBTOOL_AGE=$SOMINOR LIBTOOL_REVISION=$PATCH fi update 'so.version' 's/^\(CURRENT=\).*/\1'$LIBTOOL_CURRENT'/' update 'so.version' 's/^\(AGE=\).*/\1'$LIBTOOL_AGE'/' update 'so.version' 's/^\(REVISION=\).*/\1'$LIBTOOL_REVISION'/' c-toxcore-0.2.13/other/windows_build_script_toxcore.sh000066400000000000000000000007351415350724400231670ustar00rootroot00000000000000#!/bin/sh export VERSION_SODIUM="1.0.18" export VERSION_OPUS="1.3.1" export VERSION_VPX="1.9.0" export SUPPORT_TEST=false export SUPPORT_ARCH_i686=true export SUPPORT_ARCH_x86_64=true export CROSS_COMPILE=false sh ./other/docker/windows/build_dependencies.sh export ENABLE_TEST=false export ALLOW_TEST_FAILURE=false export ENABLE_ARCH_i686=true export ENABLE_ARCH_x86_64=true export EXTRA_CMAKE_FLAGS="-DTEST_TIMEOUT_SECONDS=90" sh ./other/docker/windows/build_toxcore.sh c-toxcore-0.2.13/so.version000066400000000000000000000010141415350724400155310ustar00rootroot00000000000000# .so library version, following the libtool scheme: # # current:revision:age # # current: increment if interfaces have been added, removed or changed # revision: increment if source code has changed, set to zero if current is # incremented # age: increment if interfaces have been added, set to zero if # interfaces have been removed or changed # # For a full reference see: # https://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info CURRENT=15 REVISION=0 AGE=13 c-toxcore-0.2.13/super_donators/000077500000000000000000000000001415350724400165545ustar00rootroot00000000000000c-toxcore-0.2.13/super_donators/BUILD.bazel000066400000000000000000000002301415350724400204250ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_binary") cc_binary( name = "grencez_tok5", srcs = ["grencez_tok5.c"], copts = ["-Wno-unused-result"], ) c-toxcore-0.2.13/super_donators/LittleVulpix000066400000000000000000000013571415350724400211520ustar00rootroot00000000000000Thank you to all the tox-devs for the idea of a privacy-oriented chat protocol. Thank you for seeing it through so that we have tox in its current state. It has ways to go, but it's already pretty good! It is my dream that it will one day be as popular (or even more so) than the other protocols. Thank you to everyone who is actively participating in maintaining and fixing things to make tox better today than it was yesterday! And finally a big thanks to the community that stayed together even through semi-tough times and united to fund the campaign! Working filetransfers are my 'gift' to you all! Thanks to irungentoo for making it happen. "I'm commander Shepard and this is my favourite chat protocol on the Internet." @LittleVulpix, 2015. c-toxcore-0.2.13/super_donators/grencez_tok5.c000066400000000000000000000020001415350724400213070ustar00rootroot00000000000000/* Though it may look bleak at times, * this ring will stabilize to have one token, * and Tox will be the one true chat protocol! * -- Alex P. Klinkhamer (grencez) */ #include #include #include int main(int i, char** msg) { int j, fd[4], xpd, xid; if (--i<1) return 1; srand(getpid()); pipe(fd); while (xid=rand()%5, --i>0) { pipe(&fd[2]); j = (0==fork() ? 0 : 1); close(fd[j]); fd[j] = fd[j+2]; close(fd[3-j]); if (j==0) break; } #define SendSc() write(fd[1], &xid, sizeof(xid)) #define RecvPd() read(fd[0], &xpd, sizeof(xpd)) #define A(g,v) if (g) {xid=v; puts(msg[i+1]); fflush(stdout); SendSc();} SendSc(); while (RecvPd(), 1) { sleep(1); if (i==0) { A( xpd==0 && xid==0 , 1 ); A( xpd==1 && xid<=1 , 2 ); A( xpd> 1 && xid> 1 , 0 ); continue; } A( xpd==0 && xid> 1 , xid/4 ); A( xpd==1 && xid!=1 , 1 ); A( xpd==2 && xid<=1 , 2+xid ); A( xpd>=3 && xid<=1 , 4 ); } return 0; } c-toxcore-0.2.13/super_donators/sir@cmpwn.com000077500000000000000000000017771415350724400212350ustar00rootroot00000000000000#!/bin/bash # Run ./sir@cmpwn.com # Arrow keys or wasd to move c=`tput cols`;L=`tput lines` let x=$c/2;let y=$L/2;d=0;le=3;t="$y;$x";i=0;j=0;S=0 A(){ let i=($RANDOM%$c);let j=($RANDOM%$L);};A B(){ printf $*;};C(){ B "\x1B[$1";};D(){ C "$1H";} F(){ D "0;0";C 2J;C "?25h";printf "GAME OVER\nSCORE: $S\n";exit;};trap F INT C ?25l;C 2J;da(){ D "$j;$i";echo "$1";} G() { for n in $t; do D "$n";echo "$1";done;} mt(){ t=`echo "$t"|cut -d' ' -f2-`;} sc(){ D "0;0";echo "Score: $S"; } gt() { t+=" $y;$x";};ct() { for n in $t; do [ "$y;$x" == "$n" ]&&F;done;} M() { case $d in 0)let y--;;1)let x--;;2)let y++;;3)let x++;;esac let x%=$c;let y%=$L;ct;[ "$y$x" == "$j$i" ]&&{ let le++;A;let S++;} l=`tr -dc ' '<<<"$t"|wc -c`;gt;[ $l -gt $le ]&&mt;} ky() { k=$1;read -sN1 -t 0.01 k1;read -sN1 -t 0.01 k2;read -sN1 -t 0.01 k3 k+=${k1}${k2}${k3};case $k in w|$'\e[A'|$'\e0A')d=0;;a|$'\e[D'|$'\e0D')d=1;; s|$'\e[B'|$'\e0B')d=2;;d|$'\e[C'|$'\e0C')d=3;;esac;} while :;do da ' ';G ' ';M;da "@";G "#";sc;read -s -n 1 -t 0.1 k && ky "$k";done c-toxcore-0.2.13/testing/000077500000000000000000000000001415350724400151625ustar00rootroot00000000000000c-toxcore-0.2.13/testing/BUILD.bazel000066400000000000000000000016471415350724400170500ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") cc_library( name = "misc_tools", srcs = ["misc_tools.c"], hdrs = ["misc_tools.h"], visibility = ["//c-toxcore:__subpackages__"], deps = ["//c-toxcore/toxcore"], ) cc_library( name = "trace", srcs = ["trace.cc"], visibility = ["//c-toxcore:__subpackages__"], alwayslink = True, ) cc_binary( name = "DHT_test", srcs = ["DHT_test.c"], deps = [ ":misc_tools", "//c-toxcore/toxcore", ], ) cc_binary( name = "Messenger_test", srcs = ["Messenger_test.c"], deps = [ ":misc_tools", "//c-toxcore/toxcore", ], ) cc_binary( name = "random_testing", srcs = ["random_testing.cc"], deps = [ ":misc_tools", "//c-toxcore/toxcore", ], ) cc_binary( name = "afl_toxsave", srcs = ["afl_toxsave.c"], deps = [ "//c-toxcore/toxcore", ], ) c-toxcore-0.2.13/testing/DHT_test.c000066400000000000000000000166251415350724400170160ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* DHT test * A file with a main that runs our DHT for testing. * * Compile with: gcc -O2 -Wall -D VANILLA_NACL -o test ../core/Lossless_UDP.c ../core/network.c ../core/net_crypto.c ../core/Messenger.c ../nacl/build/${HOSTNAME%.*}/lib/amd64/{cpucycles.o,libnacl.a,randombytes.o} DHT_test.c * * Command line arguments are the ip, port and public key of a node. * EX: ./test 127.0.0.1 33445 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * * The test will then ask you for the id (in hex format) of the friend you wish to add */ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #include #include #include #include #if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) #include #endif #include "../toxcore/DHT.h" #include "../toxcore/friend_requests.h" #include "../toxcore/mono_time.h" #include "misc_tools.h" #define PORT 33445 static const uint8_t zeroes_cid[CRYPTO_PUBLIC_KEY_SIZE] = {0}; static void print_client_id(const uint8_t *public_key) { uint32_t j; for (j = 0; j < CRYPTO_PUBLIC_KEY_SIZE; j++) { printf("%02X", public_key[j]); } } static void print_hardening(const Hardening *h) { printf("Hardening:\n"); printf("routes_requests_ok: %u\n", h->routes_requests_ok); printf("routes_requests_timestamp: %llu\n", (long long unsigned int)h->routes_requests_timestamp); printf("routes_requests_pingedid: "); print_client_id(h->routes_requests_pingedid); printf("\nsend_nodes_ok: %u\n", h->send_nodes_ok); printf("send_nodes_timestamp: %llu\n", (long long unsigned int)h->send_nodes_timestamp); printf("send_nodes_pingedid: "); print_client_id(h->send_nodes_pingedid); printf("\ntesting_requests: %u\n", h->testing_requests); printf("testing_timestamp: %llu\n", (long long unsigned int)h->testing_timestamp); printf("testing_pingedid: "); print_client_id(h->testing_pingedid); printf("\n\n"); } static void print_assoc(const IPPTsPng *assoc, uint8_t ours) { const IP_Port *ipp = &assoc->ip_port; char ip_str[IP_NTOA_LEN]; printf("\nIP: %s Port: %u", ip_ntoa(&ipp->ip, ip_str, sizeof(ip_str)), net_ntohs(ipp->port)); printf("\nTimestamp: %llu", (long long unsigned int) assoc->timestamp); printf("\nLast pinged: %llu\n", (long long unsigned int) assoc->last_pinged); ipp = &assoc->ret_ip_port; if (ours) { printf("OUR IP: %s Port: %u\n", ip_ntoa(&ipp->ip, ip_str, sizeof(ip_str)), net_ntohs(ipp->port)); } else { printf("RET IP: %s Port: %u\n", ip_ntoa(&ipp->ip, ip_str, sizeof(ip_str)), net_ntohs(ipp->port)); } printf("Timestamp: %llu\n", (long long unsigned int) assoc->ret_timestamp); print_hardening(&assoc->hardening); } static void print_clientlist(DHT *dht) { uint32_t i; printf("___________________CLOSE________________________________\n"); for (i = 0; i < LCLIENT_LIST; i++) { const Client_data *client = dht_get_close_client(dht, i); if (public_key_cmp(client->public_key, zeroes_cid) == 0) { continue; } printf("ClientID: "); print_client_id(client->public_key); print_assoc(&client->assoc4, 1); print_assoc(&client->assoc6, 1); } } static void print_friendlist(DHT *dht) { uint32_t i, k; IP_Port p_ip; printf("_________________FRIENDS__________________________________\n"); for (k = 0; k < dht_get_num_friends(dht); k++) { printf("FRIEND %u\n", k); printf("ID: "); print_client_id(dht_get_friend_public_key(dht, k)); int friendok = dht_getfriendip(dht, dht_get_friend_public_key(dht, k), &p_ip); char ip_str[IP_NTOA_LEN]; printf("\nIP: %s:%u (%d)", ip_ntoa(&p_ip.ip, ip_str, sizeof(ip_str)), net_ntohs(p_ip.port), friendok); printf("\nCLIENTS IN LIST:\n\n"); for (i = 0; i < MAX_FRIEND_CLIENTS; i++) { const Client_data *client = dht_friend_client(dht_get_friend(dht, k), i); if (public_key_cmp(client->public_key, zeroes_cid) == 0) { continue; } printf("ClientID: "); print_client_id(client->public_key); print_assoc(&client->assoc4, 0); print_assoc(&client->assoc6, 0); } } } #if 0 /* TODO(slvr): */ static void printpacket(uint8_t *data, uint32_t length, IP_Port ip_port) { uint32_t i; printf("UNHANDLED PACKET RECEIVED\nLENGTH:%u\nCONTENTS:\n", length); printf("--------------------BEGIN-----------------------------\n"); for (i = 0; i < length; i++) { if (data[i] < 16) { printf("0"); } printf("%X", data[i]); } printf("\n--------------------END-----------------------------\n\n\n"); } #endif int main(int argc, char *argv[]) { if (argc < 4) { printf("Usage: %s [--ipv4|--ipv6] ip port public_key\n", argv[0]); exit(0); } /* let user override default by cmdline */ bool ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; /* x */ int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled); if (argvoffset < 0) { exit(1); } //memcpy(self_client_id, "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", 32); /* initialize networking */ /* bind to ip 0.0.0.0:PORT */ IP ip; ip_init(&ip, ipv6enabled); Mono_Time *const mono_time = mono_time_new(); Logger *const logger = logger_new(); DHT *dht = new_dht(logger, mono_time, new_networking(logger, ip, PORT), true); printf("OUR ID: "); for (uint32_t i = 0; i < 32; i++) { const uint8_t *const self_public_key = dht_get_self_public_key(dht); if (self_public_key[i] < 16) { printf("0"); } printf("%X", self_public_key[i]); } char temp_id[128]; printf("\nEnter the public_key of the friend you wish to add (32 bytes HEX format):\n"); if (!fgets(temp_id, sizeof(temp_id), stdin)) { exit(0); } if ((strlen(temp_id) > 0) && (temp_id[strlen(temp_id) - 1] == '\n')) { temp_id[strlen(temp_id) - 1] = '\0'; } uint8_t *bin_id = hex_string_to_bin(temp_id); dht_addfriend(dht, bin_id, nullptr, nullptr, 0, nullptr); free(bin_id); perror("Initialization"); uint16_t port = net_htons(atoi(argv[argvoffset + 2])); unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]); int res = dht_bootstrap_from_address(dht, argv[argvoffset + 1], ipv6enabled, port, binary_string); free(binary_string); if (!res) { printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]); return 1; } #if 0 /* TODO(slvr): */ IP_Port ip_port; uint8_t data[MAX_UDP_PACKET_SIZE]; uint32_t length; #endif while (1) { mono_time_update(mono_time); do_dht(dht); #if 0 /* TODO(slvr): */ while (receivepacket(&ip_port, data, &length) != -1) { if (dht_handlepacket(data, length, ip_port) && friendreq_handlepacket(data, length, ip_port)) { //unhandled packet printpacket(data, length, ip_port); } else { printf("Received handled packet with length: %u\n", length); } } #endif networking_poll(dht_get_net(dht), nullptr); print_clientlist(dht); print_friendlist(dht); c_sleep(300); } } c-toxcore-0.2.13/testing/Makefile.inc000066400000000000000000000025421415350724400173750ustar00rootroot00000000000000if BUILD_TESTS TEST_LIB = misc_tools endif if BUILD_TESTING TEST_LIB = misc_tools endif noinst_LTLIBRARIES += libmisc_tools.la libmisc_tools_la_SOURCES = ../testing/misc_tools.c ../testing/misc_tools.h libmisc_tools_la_CFLAGS = $(LIBSODIUM_CFLAGS) libmisc_tools_la_LIBADD = $(LIBSODIUM_LDFLAGS) if BUILD_TESTING noinst_PROGRAMS += DHT_test \ Messenger_test DHT_test_SOURCES = ../testing/DHT_test.c DHT_test_CFLAGS = $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) DHT_test_LDADD = $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ libmisc_tools.la \ libtoxcore.la \ $(LIBSODIUM_LIBS) \ $(NACL_OBJECTS) \ $(NACL_LIBS) \ $(WINSOCK2_LIBS) Messenger_test_SOURCES = \ ../testing/Messenger_test.c Messenger_test_CFLAGS = $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) Messenger_test_LDADD = $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ libmisc_tools.la \ libtoxcore.la \ $(LIBSODIUM_LIBS) \ $(NACL_OBJECTS) \ $(NACL_LIBS) \ $(WINSOCK2_LIBS) endif c-toxcore-0.2.13/testing/Messenger_test.c000066400000000000000000000135131415350724400203200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* Messenger test * * This program adds a friend and accepts all friend requests with the proper message. * * It tries sending a message to the added friend. * * If it receives a message from a friend it replies back. * * * This is how I compile it: gcc -O2 -Wall -D VANILLA_NACL -o test ../core/Lossless_UDP.c ../core/network.c ../core/net_crypto.c ../core/Messenger.c ../core/DHT.c ../nacl/build/${HOSTNAME%.*}/lib/amd64/{cpucycles.o,libnacl.a,randombytes.o} Messenger_test.c * * * Command line arguments are the ip, port and public_key of a node (for bootstrapping). * * EX: ./test 127.0.0.1 33445 CDCFD319CE3460824B33BE58FD86B8941C9585181D8FBD7C79C5721D7C2E9F7C * * Or the argument can be the path to the save file. * * EX: ./test Save.bak */ #include #include #include #if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) #include #endif #include "../toxcore/Messenger.h" #include "../toxcore/mono_time.h" #include "misc_tools.h" static void print_message(Messenger *m, uint32_t friendnumber, unsigned int type, const uint8_t *string, size_t length, void *userdata) { printf("Message with length %u received from %u: %s \n", (unsigned)length, friendnumber, string); m_send_message_generic(m, friendnumber, type, (const uint8_t *)"Test1", 6, nullptr); } /* TODO(irungentoo): needed as print_request has to match the interface expected by * networking_requesthandler and so cannot take a Messenger * */ static Messenger *m; static void print_request(Messenger *m2, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata) { printf("Friend request received from: \n"); printf("ClientID: "); uint32_t j; for (j = 0; j < 32; j++) { if (public_key[j] < 16) { printf("0"); } printf("%hhX", public_key[j]); } printf("\nOf length: %u with data: %s \n", (unsigned)length, data); if (length != sizeof("Install Gentoo")) { return; } if (memcmp(data, "Install Gentoo", sizeof("Install Gentoo")) == 0) //if the request contained the message of peace the person is obviously a friend so we add him. { printf("Friend request accepted.\n"); m_addfriend_norequest(m2, public_key); } } int main(int argc, char *argv[]) { /* let user override default by cmdline */ bool ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; /* x */ int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled); if (argvoffset < 0) { exit(1); } /* with optional --ipvx, now it can be 1-4 arguments... */ if (argc != argvoffset + 4) { printf("Usage: %s [--ipv4|--ipv6] ip port public_key (of the DHT bootstrap node)\n", argv[0]); exit(0); } Mono_Time *const mono_time = mono_time_new(); if (mono_time == nullptr) { fputs("Failed to allocate monotonic timer datastructure\n", stderr); exit(0); } Messenger_Options options = {0}; options.ipv6enabled = ipv6enabled; m = new_messenger(mono_time, &options, nullptr); if (!m) { fputs("Failed to allocate messenger datastructure\n", stderr); exit(0); } if (argc == argvoffset + 4) { uint16_t port = net_htons(atoi(argv[argvoffset + 2])); uint8_t *bootstrap_key = hex_string_to_bin(argv[argvoffset + 3]); int res = dht_bootstrap_from_address(m->dht, argv[argvoffset + 1], ipv6enabled, port, bootstrap_key); free(bootstrap_key); if (!res) { printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]); exit(1); } } m_callback_friendrequest(m, print_request); m_callback_friendmessage(m, print_message); printf("OUR ID: "); uint32_t i; uint8_t address[FRIEND_ADDRESS_SIZE]; getaddress(m, address); for (i = 0; i < FRIEND_ADDRESS_SIZE; i++) { if (address[i] < 16) { printf("0"); } printf("%hhX", address[i]); } setname(m, (const uint8_t *)"Anon", 5); char temp_hex_id[128]; printf("\nEnter the address of the friend you wish to add (38 bytes HEX format):\n"); if (!fgets(temp_hex_id, sizeof(temp_hex_id), stdin)) { exit(0); } if ((strlen(temp_hex_id) > 0) && (temp_hex_id[strlen(temp_hex_id) - 1] == '\n')) { temp_hex_id[strlen(temp_hex_id) - 1] = '\0'; } uint8_t *bin_id = hex_string_to_bin(temp_hex_id); const int num = m_addfriend(m, bin_id, (const uint8_t *)"Install Gentoo", sizeof("Install Gentoo")); free(bin_id); perror("Initialization"); while (1) { mono_time_update(mono_time); uint8_t name[128]; const char *const filename = "Save.bak"; getname(m, num, name); printf("%s\n", name); m_send_message_generic(m, num, MESSAGE_NORMAL, (const uint8_t *)"Test", 5, nullptr); do_messenger(m, nullptr); c_sleep(30); FILE *file = fopen(filename, "wb"); if (file == nullptr) { printf("Failed to open file %s\n", filename); kill_messenger(m); return 1; } uint8_t *buffer = (uint8_t *)malloc(messenger_size(m)); if (buffer == nullptr) { fputs("Failed to allocate memory\n", stderr); fclose(file); kill_messenger(m); return 1; } messenger_save(m, buffer); const size_t write_result = fwrite(buffer, 1, messenger_size(m), file); if (write_result < messenger_size(m)) { free(buffer); fclose(file); kill_messenger(m); return 1; } free(buffer); fclose(file); } } c-toxcore-0.2.13/testing/afl_testdata/000077500000000000000000000000001415350724400176155ustar00rootroot00000000000000c-toxcore-0.2.13/testing/afl_testdata/tox_saves/000077500000000000000000000000001415350724400216305ustar00rootroot00000000000000c-toxcore-0.2.13/testing/afl_testdata/tox_saves/david.tox000066400000000000000000000036731415350724400234640ustar00rootroot00000000000000DLgcP\bv8O`7 o,`-KY>0)H 9S$Jj4׸]+ YUu} "Va̵O`:XZ%i2*ƫ|HŻ_bi {Y#(!*%SkzTۢIpw_@*'ўrFN#?X L-ռ*mV#rmisrO.`c1m`k;*PR< ق#OpzF~.,"E0߮P6D5fK_0T@Y^g5tsbsnFwne_G}ֱ$<;dnޟUy8#L|@`mQh9R< ق#OpzF~.,"E0߮P6D5fK_0T@Y^g5tsbsnFwne_G}ֱ$<;d*'ўrFN#?X L-ռ*mV#rmisrO.`c1m`k;*P*ƫ|HŻ_bi {Y#(!*%SkzTۢIpw_@Uu} "Va̵O`:XZ%i2R< ق#OpzF~.,"E0߮P6D5fK_0#(!*%SkzTۢIpw_@*ƫ|HŻ_bi {YT@Y^g5tsbsnFwne_G}ֱ$<;dnޟUy8#L|@`mQh9*'ўrFN#?X L-ռ*mV#rmisrO.`c1m`k;*PdavidToxing on qTox' Uu} "Va̵O`:XZ%i28 7cMdEcY[aC #include #include "../toxcore/tox.h" int main(int argc, char **argv) { if (argc != 2) { return -1; } // determine file size FILE *fileptr = fopen(argv[1], "rb"); fseek(fileptr, 0, SEEK_END); long filelen = ftell(fileptr); rewind(fileptr); // read file into buffer uint8_t *buffer = (uint8_t *)malloc(filelen * sizeof(uint8_t)); size_t bytes_read = fread(buffer, filelen, 1, fileptr); if (bytes_read != filelen) { free(buffer); return -1; } fclose(fileptr); Tox_Err_Options_New error_options; struct Tox_Options *tox_options = tox_options_new(&error_options); if (error_options != TOX_ERR_OPTIONS_NEW_OK) { free(buffer); return -1; } // pass test data to Tox tox_options_set_savedata_data(tox_options, buffer, filelen); tox_options_set_savedata_type(tox_options, TOX_SAVEDATA_TYPE_TOX_SAVE); Tox_Err_New error_new; Tox *tox = tox_new(tox_options, &error_new); tox_options_free(tox_options); if (!tox || error_new != TOX_ERR_NEW_OK) { free(buffer); return -1; } tox_kill(tox); free(buffer); return 0; } c-toxcore-0.2.13/testing/misc_tools.c000066400000000000000000000144221415350724400175040ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Miscellaneous functions and data structures for doing random things. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifndef _POSIX_C_SOURCE // For nanosleep(). #define _POSIX_C_SOURCE 200112L #endif #include "misc_tools.h" #include #include #include #include #include #include #ifndef VANILLA_NACL #include #endif #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) #include #else #include #endif #include "../toxcore/ccompat.h" #include "../toxcore/tox.h" void c_sleep(uint32_t x) { #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) Sleep(x); #else struct timespec req; req.tv_sec = x / 1000; req.tv_nsec = (long)x % 1000 * 1000 * 1000; nanosleep(&req, nullptr); #endif } // You are responsible for freeing the return value! uint8_t *hex_string_to_bin(const char *hex_string) { // byte is represented by exactly 2 hex digits, so length of binary string // is half of that of the hex one. only hex string with even length // valid. the more proper implementation would be to check if strlen(hex_string) // is odd and return error code if it is. we assume strlen is even. if it's not // then the last byte just won't be written in 'ret'. size_t i, len = strlen(hex_string) / 2; uint8_t *ret = (uint8_t *)malloc(len); const char *pos = hex_string; for (i = 0; i < len; ++i, pos += 2) { unsigned int val; sscanf(pos, "%02x", &val); ret[i] = val; } return ret; } void to_hex(char *out, uint8_t *in, int size) { while (size--) { if (*in >> 4 < 0xA) { *out++ = '0' + (*in >> 4); } else { *out++ = 'A' + (*in >> 4) - 0xA; } if ((*in & 0xf) < 0xA) { *out++ = '0' + (*in & 0xF); } else { *out++ = 'A' + (*in & 0xF) - 0xA; } in++; } } /* Reimplementation of strncasecmp() function from strings.h, as strings.h is * POSIX and not portable. Specifically it doesn't exist on MSVC. */ int tox_strncasecmp(const char *s1, const char *s2, size_t n) { while (n--) { int c1 = tolower(*(s1++)); int c2 = tolower(*(s2++)); if (c1 == '\0' || c2 == '\0' || c1 != c2) { return c1 - c2; } } return 0; } int cmdline_parsefor_ipv46(int argc, char **argv, bool *ipv6enabled) { int argvoffset = 0, argi; for (argi = 1; argi < argc; argi++) { if (!tox_strncasecmp(argv[argi], "--ipv", 5)) { if (argv[argi][5] && !argv[argi][6]) { char c = argv[argi][5]; if (c == '4') { *ipv6enabled = false; } else if (c == '6') { *ipv6enabled = true; } else { printf("Invalid argument: %s. Try --ipv4 or --ipv6!\n", argv[argi]); return -1; } } else { printf("Invalid argument: %s. Try --ipv4 or --ipv6!\n", argv[argi]); return -1; } if (argvoffset != argi - 1) { printf("Argument must come first: %s.\n", argv[argi]); return -1; } argvoffset++; } } return argvoffset; } 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; } 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); } 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); tox_options_set_start_port(log_options, 33445); tox_options_set_end_port(log_options, 33445 + 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); } #ifndef VANILLA_NACL static const char *test_rng_name(void) { return "test_rng"; } static uint32_t rng_state; static uint32_t test_rng_random(void) { rng_state = 2624534371 * rng_state + 1; return rng_state; } static void test_rng_buf(void *const buf, const size_t size) { uint8_t *p = (uint8_t *)buf; uint32_t r = 0; for (size_t i = 0; i < size; i++) { if ((i % 4) == 0) { r = test_rng_random(); } *p = (r >> ((i % 4) * 8)) & 0xff; ++p; } } static uint32_t test_rng_uniform(const uint32_t upper_bound) { // XXX: Not uniform! But that's ok for testing purposes. return test_rng_random() % upper_bound; } static void test_rng_stir(void) { } static int test_rng_close(void) { return 0; } static randombytes_implementation test_rng = { test_rng_name, test_rng_random, test_rng_stir, test_rng_uniform, test_rng_buf, test_rng_close }; /* Simple insecure PRNG for testing purposes */ int use_test_rng(uint32_t seed) { rng_state = seed; return randombytes_set_implementation(&test_rng); } #else int use_test_rng(uint32_t seed) { assert(!"libsodium required for use_test_rng"); } #endif c-toxcore-0.2.13/testing/misc_tools.h000066400000000000000000000016521415350724400175120ustar00rootroot00000000000000#ifndef C_TOXCORE_TESTING_MISC_TOOLS_H #define C_TOXCORE_TESTING_MISC_TOOLS_H #include "../toxcore/tox.h" #ifdef __cplusplus extern "C" { #endif // Amount of time in milliseconds to wait between tox_iterate calls. #define ITERATION_INTERVAL 200 void c_sleep(uint32_t x); uint8_t *hex_string_to_bin(const char *hex_string); void to_hex(char *out, uint8_t *in, int size); int tox_strncasecmp(const char *s1, const char *s2, size_t n); int cmdline_parsefor_ipv46(int argc, char **argv, bool *ipv6enabled); 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); 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); int use_test_rng(uint32_t seed); #ifdef __cplusplus } #endif #endif c-toxcore-0.2.13/testing/random_testing.cc000066400000000000000000000325311415350724400205120ustar00rootroot00000000000000// Program to perform random actions in a network of toxes. // // Useful to find reproducing test cases for seemingly random bugs. #include #include #include #include #include #include #include #include "../toxcore/tox.h" #include "misc_tools.h" namespace { // Whether to write log messages when handling callbacks. constexpr bool LOG_CALLBACKS = false; // Number of participants in the test run. constexpr uint32_t NUM_TOXES = 10; // Maximum amount of time in iterations to wait for bootstrapping and friend // connections to succeed. constexpr uint32_t MAX_BOOTSTRAP_ITERATIONS = 1000; // Number of conferences up to which users may create their own new conferences. // They may still be invited and join. constexpr uint32_t MAX_CONFERENCES_PER_USER = 3; // Maximum number of actions per program execution. constexpr uint32_t MAX_ACTIONS = 10000; // Maximum number of attempts at executing a random action. constexpr uint32_t MAX_ACTION_ATTEMPTS = 100; // Number of tox_iterate calls between each action. constexpr uint32_t ITERATIONS_PER_ACTION = 1; struct Tox_Options_Deleter { void operator()(Tox_Options *options) const { tox_options_free(options); } }; using Tox_Options_Ptr = std::unique_ptr; struct Tox_Deleter { void operator()(Tox *tox) const { tox_kill(tox); } }; using Tox_Ptr = std::unique_ptr; struct Local_State { uint32_t friends_online = 0; uint32_t next_invite = 0; explicit Local_State(Tox_Ptr tox, uint32_t id) : tox_(std::move(tox)), id_(id) {} Tox *tox() const { return tox_.get(); } uint32_t id() const { return id_; } private: Tox_Ptr tox_; uint32_t id_; }; struct Action; struct Random { std::uniform_int_distribution<> tox_selector; std::uniform_int_distribution<> friend_selector; std::uniform_int_distribution<> name_length_selector; std::uniform_int_distribution<> message_length_selector; std::uniform_int_distribution<> byte_selector; std::vector action_weights; std::discrete_distribution action_selector; explicit Random(std::vector const &actions); }; struct Action { uint32_t weight; char const *title; bool (*can)(Local_State const &state); void (*run)(Local_State *state, Random *rnd, std::mt19937 *rng); }; std::vector get_action_weights(std::vector const &actions) { std::vector weights; for (Action const &action : actions) { weights.push_back(action.weight); } return weights; } Random::Random(std::vector const &actions) : tox_selector(0, NUM_TOXES - 1), friend_selector(0, NUM_TOXES - 2), name_length_selector(0, TOX_MAX_NAME_LENGTH - 1), message_length_selector(0, TOX_MAX_MESSAGE_LENGTH - 1), byte_selector(0, 255), action_weights(get_action_weights(actions)), action_selector(action_weights.begin(), action_weights.end()) {} struct Global_State : std::vector { // Non-copyable; Global_State(Global_State const &) = delete; Global_State(Global_State &&) = default; ~Global_State(); explicit Global_State(std::vector const &actions) : actions_(actions), rnd_(actions), action_counter_(actions.size()) {} Action const &action(size_t id) const { return actions_.at(id); } Random *rnd() { return &rnd_; } std::vector &action_counter() { return action_counter_; } private: std::vector const &actions_; Random rnd_; std::vector action_counter_; }; Global_State::~Global_State() {} void handle_friend_connection_status(Tox *tox, uint32_t friend_number, Tox_Connection connection_status, void *user_data) { Local_State *state = static_cast(user_data); if (connection_status == TOX_CONNECTION_NONE) { std::printf("Tox #%u lost friend %u!\n", state->id(), friend_number); state->friends_online--; } else { state->friends_online++; } } void handle_conference_invite(Tox *tox, uint32_t friend_number, Tox_Conference_Type type, const uint8_t *cookie, size_t length, void *user_data) { Local_State *state = static_cast(user_data); if (LOG_CALLBACKS) { std::printf("Tox #%u joins the conference it was invited to\n", state->id()); } Tox_Err_Conference_Join err; tox_conference_join(tox, friend_number, cookie, length, &err); assert(err == TOX_ERR_CONFERENCE_JOIN_OK); } 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) { Local_State *state = static_cast(user_data); if (LOG_CALLBACKS) { std::printf("Tox #%u received a message of length %u\n", state->id(), static_cast(length)); } } void handle_conference_peer_list_changed(Tox *tox, uint32_t conference_number, void *user_data) { Local_State *state = static_cast(user_data); if (LOG_CALLBACKS) { std::printf("Tox #%u rebuilds peer list for conference %u\n", state->id(), conference_number); } Tox_Err_Conference_Peer_Query err; uint32_t const count = tox_conference_peer_count(tox, conference_number, &err); assert(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK); 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); assert(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK); std::vector name(size); tox_conference_peer_get_name(tox, conference_number, peer_number, &name[0], &err); assert(err == TOX_ERR_CONFERENCE_PEER_QUERY_OK); } } Global_State make_toxes(std::vector const &actions) { Global_State toxes(actions); Tox_Options_Ptr options(tox_options_new(nullptr)); tox_options_set_local_discovery_enabled(options.get(), false); for (uint32_t i = 0; i < NUM_TOXES; i++) { Tox_Err_New err; toxes.emplace_back(Tox_Ptr(tox_new(options.get(), &err)), i); assert(err == TOX_ERR_NEW_OK); assert(toxes.back().tox() != nullptr); tox_callback_friend_connection_status(toxes.back().tox(), handle_friend_connection_status); tox_callback_conference_invite(toxes.back().tox(), handle_conference_invite); tox_callback_conference_message(toxes.back().tox(), handle_conference_message); tox_callback_conference_peer_list_changed(toxes.back().tox(), handle_conference_peer_list_changed); } std::printf("Bootstrapping %u toxes\n", NUM_TOXES); uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; tox_self_get_dht_id(toxes.front().tox(), dht_key); const uint16_t dht_port = tox_self_get_udp_port(toxes.front().tox(), nullptr); for (Local_State const &state : toxes) { Tox_Err_Bootstrap err; tox_bootstrap(state.tox(), "localhost", dht_port, dht_key, &err); assert(err == TOX_ERR_BOOTSTRAP_OK); } std::printf("Creating full mesh of friendships\n"); for (Local_State const &state1 : toxes) { for (Local_State const &state2 : toxes) { if (state1.tox() != state2.tox()) { Tox_Err_Friend_Add err; uint8_t key[TOX_PUBLIC_KEY_SIZE]; tox_self_get_public_key(state1.tox(), key); tox_friend_add_norequest(state2.tox(), key, &err); assert(err == TOX_ERR_FRIEND_ADD_OK); } } } return toxes; } bool all_connected(Global_State const &toxes) { return std::all_of(toxes.begin(), toxes.end(), [](Local_State const &state) { return state.friends_online == NUM_TOXES - 1; }); } bool bootstrap_toxes(Global_State *toxes) { std::printf("Waiting for %u iterations for all friends to come online\n", MAX_BOOTSTRAP_ITERATIONS); for (uint32_t i = 0; i < MAX_BOOTSTRAP_ITERATIONS; i++) { c_sleep(tox_iteration_interval(toxes->front().tox())); for (Local_State &state : *toxes) { tox_iterate(state.tox(), &state); } if (all_connected(*toxes)) { std::printf("Took %u iterations\n", i); return true; } } return false; } bool execute_random_action(Global_State *toxes, std::mt19937 *rng) { // First, choose a random actor. Local_State &actor = toxes->at(toxes->rnd()->tox_selector(*rng)); size_t const action_id = toxes->rnd()->action_selector(*rng); Action const &action = toxes->action(action_id); if (!action.can(actor)) { return false; } std::printf("Tox #%u %s", actor.id(), action.title); action.run(&actor, toxes->rnd(), rng); std::printf("\n"); toxes->action_counter().at(action_id)++; return true; } bool attempt_action(Global_State *toxes, std::mt19937 *rng) { for (uint32_t i = 0; i < MAX_ACTION_ATTEMPTS; i++) { if (execute_random_action(toxes, rng)) { return true; } } return false; } } // namespace int main() { std::vector const actions = { { 10, "creates a new conference", [](Local_State const &state) { return tox_conference_get_chatlist_size(state.tox()) < MAX_CONFERENCES_PER_USER; }, [](Local_State *state, Random *rnd, std::mt19937 *rng) { Tox_Err_Conference_New err; tox_conference_new(state->tox(), &err); assert(err == TOX_ERR_CONFERENCE_NEW_OK); }, }, { 10, "invites a random friend to a conference", [](Local_State const &state) { return tox_conference_get_chatlist_size(state.tox()) != 0; }, [](Local_State *state, Random *rnd, std::mt19937 *rng) { size_t chat_count = tox_conference_get_chatlist_size(state->tox()); assert(chat_count != 0); // Condition above. Tox_Err_Conference_Invite err; tox_conference_invite(state->tox(), rnd->friend_selector(*rng), state->next_invite % chat_count, &err); state->next_invite++; assert(err == TOX_ERR_CONFERENCE_INVITE_OK); }, }, { 10, "deletes the last conference", [](Local_State const &state) { return tox_conference_get_chatlist_size(state.tox()) != 0; }, [](Local_State *state, Random *rnd, std::mt19937 *rng) { Tox_Err_Conference_Delete err; tox_conference_delete(state->tox(), tox_conference_get_chatlist_size(state->tox()) - 1, &err); assert(err == TOX_ERR_CONFERENCE_DELETE_OK); }, }, { 10, "sends a message to the last conference", [](Local_State const &state) { return tox_conference_get_chatlist_size(state.tox()) != 0; }, [](Local_State *state, Random *rnd, std::mt19937 *rng) { std::vector message(rnd->message_length_selector(*rng)); for (uint8_t &byte : message) { byte = rnd->byte_selector(*rng); } Tox_Err_Conference_Send_Message err; tox_conference_send_message( state->tox(), tox_conference_get_chatlist_size(state->tox()) - 1, TOX_MESSAGE_TYPE_NORMAL, message.data(), message.size(), &err); if (err == TOX_ERR_CONFERENCE_SEND_MESSAGE_OK) { printf(" (OK, length = %u)", static_cast(message.size())); } else { printf(" (FAILED: %u)", err); } }, }, { 10, "changes their name", [](Local_State const &state) { return true; }, [](Local_State *state, Random *rnd, std::mt19937 *rng) { std::vector name(rnd->name_length_selector(*rng)); for (uint8_t &byte : name) { byte = rnd->byte_selector(*rng); } Tox_Err_Set_Info err; tox_self_set_name(state->tox(), name.data(), name.size(), &err); assert(err == TOX_ERR_SET_INFO_OK); printf(" (length = %u)", static_cast(name.size())); }, }, { 10, "sets their name to empty", [](Local_State const &state) { return true; }, [](Local_State *state, Random *rnd, std::mt19937 *rng) { Tox_Err_Set_Info err; tox_self_set_name(state->tox(), nullptr, 0, &err); assert(err == TOX_ERR_SET_INFO_OK); }, }, }; Global_State toxes = make_toxes(actions); std::mt19937 rng; uint32_t action_number; for (action_number = 0; action_number < MAX_ACTIONS; action_number++) { if (!all_connected(toxes) && !bootstrap_toxes(&toxes)) { std::printf("Bootstrapping took too long; %u actions performed\n", action_number); return EXIT_FAILURE; } if (!attempt_action(&toxes, &rng)) { std::printf( "System is stuck after %u actions: none of the toxes can perform an action anymore\n", action_number); return EXIT_FAILURE; } for (uint32_t i = 0; i < ITERATIONS_PER_ACTION; i++) { c_sleep(ITERATION_INTERVAL); for (Local_State &state : toxes) { tox_iterate(state.tox(), &state); } } } std::printf("Test execution success: %u actions performed\n", action_number); std::printf("Per-action statistics:\n"); for (uint32_t i = 0; i < toxes.action_counter().size(); i++) { std::printf("%u x '%s'\n", toxes.action_counter().at(i), actions[i].title); } return 0; } c-toxcore-0.2.13/testing/run_afl.sh000077500000000000000000000004421415350724400171470ustar00rootroot00000000000000#! /bin/sh # move to repo root cd ../ rm -R _afl_build mkdir _afl_build cd _afl_build # build c-toxcore using afl instrumentation cmake -DCMAKE_C_COMPILER=afl-clang -DBUILD_MISC_TESTS=ON .. make # start fuzzing afl-fuzz -i ../testing/afl_testdata/tox_saves/ -o afl_out/ ./afl_toxsave @@ c-toxcore-0.2.13/testing/trace.cc000066400000000000000000000116531415350724400165750ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2020 The TokTok team. */ #include #include #include #include #include #include #include #include #include #include #define LOG_TO_STDOUT 0 /** * This file contains the support code for using -finstrument-functions. * * If you link this into a program, e.g. a test program, that was compiled with * -finstrument-functions, it will print an indented execution trace. E.g.: * * @code * int g() { return 0; } * int f() { return g(); } * int main() { return f(); } * @endcode * * Compiling the above with instrumentation and linking it with this file will * result in the following output: * * @verbatim * -> main * -> f * -> g * <- g * <- f * <- main * @endverbatim * * There are a few filter options you can apply below. Note that you'll need * addr2line in your PATH for this to work. */ namespace { class CString { char const *str_; public: // cppcheck-suppress noExplicitConstructor constexpr CString(char const *str) : str_(str) {} friend bool operator<(CString lhs, CString rhs) { return strcmp(lhs.str_, rhs.str_) < 0; } }; template bool contains(CString const (&array)[N], std::string const &str) { return std::binary_search(array, array + N, str.c_str()); } struct Symbol { std::string const name; std::string const file; }; /** * This array lists functions that should *not* appear in the trace. */ constexpr CString exclusions[] = { "__bswap_32", "logger_callback_log", "make_family", "make_proto", "make_socktype", }; /** * This array lists source files that *should* appear in the trace. * * If this array is empty, all files are traced. */ constexpr CString filter_sources[] = { "onion.c", "onion_announce.c", "onion_client.c", }; class ExecutionTrace { using Process = std::unique_ptr; using File = std::unique_ptr; std::string const exe = []() { std::array result; ssize_t const count = readlink("/proc/self/exe", result.data(), result.size()); assert(count > 0); return count > 0 ? std::string(result.data(), count) : "/proc/self/exe"; }(); #if LOG_TO_STDOUT File const log_file{stdout, std::fclose}; #else File const log_file{std::fopen("trace.log", "w"), std::fclose}; #endif unsigned nesting = 0; std::map symbols; static std::string sh(std::string cmd) { std::string result; Process pipe(popen(cmd.c_str(), "r"), pclose); if (!pipe) { return ""; } std::array buffer; while (std::fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { result += buffer.data(); } return result; } Symbol const &resolve(void *fn) { // Already in the cache. auto const found = symbols.find(fn); if (found != symbols.end()) { return found->second; } // 0x<64 bit number>\0 std::array addr; std::snprintf(addr.data(), addr.size(), "0x%lx", static_cast(reinterpret_cast(fn))); std::string const output = sh("addr2line -fs -e " + exe + " " + addr.data()); std::size_t const newline = output.find_first_of('\n'); std::size_t const colon = output.find_first_of(':'); std::string const sym = output.substr(0, newline); std::string const file = output.substr(newline + 1, colon - (newline + 1)); auto const inserted = symbols.insert({fn, {sym, file}}); return inserted.first->second; } void indent() const { for (unsigned i = 0; i < nesting; i++) { std::fputc(' ', log_file.get()); } } void print(char const *prefix, Symbol const &sym) { indent(); std::fprintf(log_file.get(), "%s %s (%s)\n", prefix, sym.name.c_str(), sym.file.c_str()); } static bool excluded(Symbol const &sym) { if (sizeof(filter_sources) != 0) { if (!contains(filter_sources, sym.file)) { return true; } } return contains(exclusions, sym.name); } public: void enter(void *fn) { Symbol const &sym = resolve(fn); if (!excluded(sym)) { print("->", sym); ++nesting; } } void exit(void *fn) { Symbol const &sym = resolve(fn); if (!excluded(sym)) { --nesting; print("<-", sym); } } }; // We leak this one. static ExecutionTrace *trace; } // namespace extern "C" void __cyg_profile_func_enter(void *this_fn, void *call_site) __attribute__((__no_instrument_function__)); void __cyg_profile_func_enter(void *this_fn, void *call_site) { if (trace == nullptr) { trace = new ExecutionTrace(); } trace->enter(this_fn); } extern "C" void __cyg_profile_func_exit(void *this_fn, void *call_site) __attribute__((__no_instrument_function__)); void __cyg_profile_func_exit(void *this_fn, void *call_site) { assert(trace != nullptr); trace->exit(this_fn); } c-toxcore-0.2.13/third_party/000077500000000000000000000000001415350724400160365ustar00rootroot00000000000000c-toxcore-0.2.13/third_party/.gitignore000066400000000000000000000000031415350724400200170ustar00rootroot00000000000000*/ c-toxcore-0.2.13/third_party/README.md000066400000000000000000000002551415350724400173170ustar00rootroot00000000000000# Third party dependencies All toxcore dependencies you want to build yourself should end up here, not in the source root. This directory is exempt from code style checks. c-toxcore-0.2.13/tox.spec.in000066400000000000000000000027171415350724400156070ustar00rootroot00000000000000Name: @PACKAGE_NAME@ Version: @VERSION@ Release: 1%{?dist} Summary: All-in-one secure communication platform License: GPLv3 URL: https://github.com/irungentoo/toxcore Source0: https://github.com/irungentoo/toxcore/releases/tox-%{version}.tar.gz BuildRequires: autoconf automake libtool libvpx-devel opus-devel BuildRequires: libsodium-devel libconfig-devel %description With the rise of governmental monitoring programs, Tox, a FOSS initiative, aims to be an easy to use, all-in-one communication platform that ensures their users full privacy and secure message delivery. %package devel Summary: Development files for @PACKAGE_NAME@ Requires: %{name} = %{version}-%{release} %description devel Development package for @PACKAGE_NAME@ %prep %setup -q %build %configure \ --enable-shared \ --disable-static \ --enable-av \ --disable-daemon \ --disable-testing make %{?_smp_mflags} %install %make_install # remove la files find %{buildroot} -name '*.la' -delete -print # not handling DHT_bootstrap yet rm -f %{buildroot}%{_bindir}/DHT_bootstrap %post /sbin/ldconfig %postun /sbin/ldconfig %files %defattr(-,root,root) %doc LICENSE.md README.md %{_libdir}/libtox*.so.* %files devel %defattr(-, root, root) %{_includedir}/tox/ %{_libdir}/libtox*.so %{_libdir}/pkgconfig/libtox*.pc %changelog * Tue Mar 3 2015 Sergey 'Jin' Bostandzhyan - 0.0.0-1 - initial package c-toxcore-0.2.13/toxav/000077500000000000000000000000001415350724400146465ustar00rootroot00000000000000c-toxcore-0.2.13/toxav/BUILD.bazel000066400000000000000000000047431415350724400165340ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_test") load("//tools:no_undefined.bzl", "cc_library") filegroup( name = "public_headers", srcs = ["toxav.h"], visibility = ["//c-toxcore:__pkg__"], ) cc_library( name = "public", hdrs = [":public_headers"], ) cc_library( name = "ring_buffer", srcs = ["ring_buffer.c"], hdrs = ["ring_buffer.h"], deps = ["//c-toxcore/toxcore:ccompat"], ) cc_test( name = "ring_buffer_test", size = "small", srcs = ["ring_buffer_test.cc"], deps = [ ":ring_buffer", "@com_google_googletest//:gtest_main", ], ) cc_library( name = "ring_buffer_srcs", hdrs = [ "ring_buffer.c", "ring_buffer.h", ], visibility = ["//c-toxcore/testing:__pkg__"], deps = ["//c-toxcore/toxcore:ccompat"], ) cc_library( name = "bwcontroller", srcs = ["bwcontroller.c"], hdrs = ["bwcontroller.h"], deps = [ ":ring_buffer", "//c-toxcore/toxcore", "//c-toxcore/toxcore:Messenger", ], ) cc_library( name = "rtp", srcs = ["rtp.c"], hdrs = ["rtp.h"], deps = [":bwcontroller"], ) cc_test( name = "rtp_test", size = "small", srcs = ["rtp_test.cc"], deps = [ ":rtp", "//c-toxcore/toxcore:crypto_core", "@com_google_googletest//:gtest_main", ], ) cc_library( name = "audio", srcs = ["audio.c"], hdrs = ["audio.h"], deps = [ ":public", ":rtp", "//c-toxcore/toxcore:network", "@opus", ], ) cc_library( name = "video", srcs = [ "msi.c", "video.c", ], hdrs = [ "msi.h", "video.h", ], deps = [ ":audio", ":public", "//c-toxcore/toxcore:network", "@libvpx", ], ) cc_library( name = "groupav", srcs = ["groupav.c"], hdrs = ["groupav.h"], deps = [ "//c-toxcore/toxcore", "@opus", ], ) cc_library( name = "toxav", srcs = [ "toxav.c", "toxav_old.c", ], hdrs = [ "toxav.api.h", "toxav.h", ], visibility = ["//c-toxcore:__subpackages__"], deps = [ ":groupav", ":video", ], ) CIMPLE_SRCS = glob( [ "*.c", "*.h", ], exclude = ["*.api.h"], ) sh_test( name = "cimple_test", size = "small", srcs = ["//hs-tokstyle/tools:check-cimple"], args = ["$(location %s)" % f for f in CIMPLE_SRCS], data = CIMPLE_SRCS, tags = ["haskell"], ) c-toxcore-0.2.13/toxav/Makefile.inc000066400000000000000000000030161415350724400170560ustar00rootroot00000000000000if BUILD_AV lib_LTLIBRARIES += libtoxav.la libtoxav_la_include_HEADERS = ../toxav/toxav.h libtoxav_la_includedir = $(includedir)/tox libtoxav_la_SOURCES = ../toxav/rtp.h \ ../toxav/rtp.c \ ../toxav/msi.h \ ../toxav/msi.c \ ../toxav/groupav.h \ ../toxav/groupav.c \ ../toxav/audio.h \ ../toxav/audio.c \ ../toxav/video.h \ ../toxav/video.c \ ../toxav/bwcontroller.h \ ../toxav/bwcontroller.c \ ../toxav/ring_buffer.h \ ../toxav/ring_buffer.c \ ../toxav/toxav.h \ ../toxav/toxav.c \ ../toxav/toxav_old.c libtoxav_la_CFLAGS = -I../toxcore \ -I../toxav \ $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) \ $(AV_CFLAGS) \ $(PTHREAD_CFLAGS) libtoxav_la_LDFLAGS = $(LT_LDFLAGS) \ $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ $(EXTRA_LT_LDFLAGS) \ $(WINSOCK2_LIBS) libtoxav_la_LIBADD = libtoxcore.la \ $(LIBSODIUM_LIBS) \ $(NACL_LIBS) \ $(PTHREAD_LIBS) \ $(AV_LIBS) if SET_SO_VERSION EXTRA_libtoxav_la_DEPENDENCIES = ../so.version endif endif c-toxcore-0.2.13/toxav/audio.c000066400000000000000000000366271415350724400161310ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "audio.h" #include #include #include "rtp.h" #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" static struct JitterBuffer *jbuf_new(uint32_t capacity); static void jbuf_clear(struct JitterBuffer *q); static void jbuf_free(struct JitterBuffer *q); static int jbuf_write(const Logger *log, struct JitterBuffer *q, struct RTPMessage *m); static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success); static OpusEncoder *create_audio_encoder(const Logger *log, int32_t bit_rate, int32_t sampling_rate, int32_t channel_count); static bool reconfigure_audio_encoder(const Logger *log, OpusEncoder **e, int32_t new_br, int32_t new_sr, uint8_t new_ch, int32_t *old_br, int32_t *old_sr, int32_t *old_ch); static bool reconfigure_audio_decoder(ACSession *ac, int32_t sampling_rate, int8_t channels); ACSession *ac_new(Mono_Time *mono_time, const Logger *log, ToxAV *av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data) { ACSession *ac = (ACSession *)calloc(sizeof(ACSession), 1); if (!ac) { LOGGER_WARNING(log, "Allocation failed! Application might misbehave!"); return nullptr; } if (create_recursive_mutex(ac->queue_mutex) != 0) { LOGGER_WARNING(log, "Failed to create recursive mutex!"); free(ac); return nullptr; } int status; ac->decoder = opus_decoder_create(AUDIO_DECODER_START_SAMPLE_RATE, AUDIO_DECODER_START_CHANNEL_COUNT, &status); if (status != OPUS_OK) { LOGGER_ERROR(log, "Error while starting audio decoder: %s", opus_strerror(status)); goto BASE_CLEANUP; } ac->j_buf = jbuf_new(AUDIO_JITTERBUFFER_COUNT); if (ac->j_buf == nullptr) { LOGGER_WARNING(log, "Jitter buffer creaton failed!"); opus_decoder_destroy(ac->decoder); goto BASE_CLEANUP; } ac->mono_time = mono_time; ac->log = log; /* Initialize encoders with default values */ ac->encoder = create_audio_encoder(log, AUDIO_START_BITRATE, AUDIO_START_SAMPLE_RATE, AUDIO_START_CHANNEL_COUNT); if (ac->encoder == nullptr) { goto DECODER_CLEANUP; } ac->le_bit_rate = AUDIO_START_BITRATE; ac->le_sample_rate = AUDIO_START_SAMPLE_RATE; ac->le_channel_count = AUDIO_START_CHANNEL_COUNT; ac->ld_channel_count = AUDIO_DECODER_START_CHANNEL_COUNT; ac->ld_sample_rate = AUDIO_DECODER_START_SAMPLE_RATE; ac->ldrts = 0; /* Make it possible to reconfigure straight away */ /* These need to be set in order to properly * do error correction with opus */ ac->lp_frame_duration = AUDIO_MAX_FRAME_DURATION_MS; ac->lp_sampling_rate = AUDIO_DECODER_START_SAMPLE_RATE; ac->lp_channel_count = AUDIO_DECODER_START_CHANNEL_COUNT; ac->av = av; ac->friend_number = friend_number; ac->acb = cb; ac->acb_user_data = cb_data; return ac; DECODER_CLEANUP: opus_decoder_destroy(ac->decoder); jbuf_free((struct JitterBuffer *)ac->j_buf); BASE_CLEANUP: pthread_mutex_destroy(ac->queue_mutex); free(ac); return nullptr; } void ac_kill(ACSession *ac) { if (!ac) { return; } opus_encoder_destroy(ac->encoder); opus_decoder_destroy(ac->decoder); jbuf_free((struct JitterBuffer *)ac->j_buf); pthread_mutex_destroy(ac->queue_mutex); LOGGER_DEBUG(ac->log, "Terminated audio handler: %p", (void *)ac); free(ac); } void ac_iterate(ACSession *ac) { if (!ac) { return; } /* TODO: fix this and jitter buffering */ /* Enough space for the maximum frame size (120 ms 48 KHz stereo audio) */ int16_t *temp_audio_buffer = (int16_t *)malloc(AUDIO_MAX_BUFFER_SIZE_PCM16 * AUDIO_MAX_CHANNEL_COUNT * sizeof(int16_t)); if (temp_audio_buffer == nullptr) { LOGGER_ERROR(ac->log, "Failed to allocate memory for audio buffer"); return; } pthread_mutex_lock(ac->queue_mutex); struct JitterBuffer *const j_buf = (struct JitterBuffer *)ac->j_buf; int rc = 0; struct RTPMessage *msg = jbuf_read(j_buf, &rc); for (; msg != nullptr || rc == 2; msg = jbuf_read(j_buf, &rc)) { pthread_mutex_unlock(ac->queue_mutex); if (rc == 2) { LOGGER_DEBUG(ac->log, "OPUS correction"); int fs = (ac->lp_sampling_rate * ac->lp_frame_duration) / 1000; rc = opus_decode(ac->decoder, nullptr, 0, temp_audio_buffer, fs, 1); } else { /* Get values from packet and decode. */ /* NOTE: This didn't work very well */ #if 0 rc = convert_bw_to_sampling_rate(opus_packet_get_bandwidth(msg->data)); if (rc != -1) { cs->last_packet_sampling_rate = rc; } else { LOGGER_WARNING(ac->log, "Failed to load packet values!"); rtp_free_msg(msg); continue; } #endif /* Pick up sampling rate from packet */ memcpy(&ac->lp_sampling_rate, msg->data, 4); ac->lp_sampling_rate = net_ntohl(ac->lp_sampling_rate); ac->lp_channel_count = opus_packet_get_nb_channels(msg->data + 4); /** NOTE: even though OPUS supports decoding mono frames with stereo decoder and vice versa, * it didn't work quite well. */ if (!reconfigure_audio_decoder(ac, ac->lp_sampling_rate, ac->lp_channel_count)) { LOGGER_WARNING(ac->log, "Failed to reconfigure decoder!"); free(msg); continue; } /* * frame_size = opus_decode(dec, packet, len, decoded, max_size, 0); * where * packet is the byte array containing the compressed data * len is the exact number of bytes contained in the packet * decoded is the decoded audio data in opus_int16 (or float for opus_decode_float()) * max_size is the max duration of the frame in samples (per channel) that can fit * into the decoded_frame array */ rc = opus_decode(ac->decoder, msg->data + 4, msg->len - 4, temp_audio_buffer, 5760, 0); free(msg); } if (rc < 0) { LOGGER_WARNING(ac->log, "Decoding error: %s", opus_strerror(rc)); } else if (ac->acb) { ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate; ac->acb(ac->av, ac->friend_number, temp_audio_buffer, rc, ac->lp_channel_count, ac->lp_sampling_rate, ac->acb_user_data); } free(temp_audio_buffer); return; } pthread_mutex_unlock(ac->queue_mutex); free(temp_audio_buffer); } int ac_queue_message(Mono_Time *mono_time, void *acp, struct RTPMessage *msg) { if (!acp || !msg) { if (msg) { free(msg); } return -1; } ACSession *ac = (ACSession *)acp; if ((msg->header.pt & 0x7f) == (RTP_TYPE_AUDIO + 2) % 128) { LOGGER_WARNING(ac->log, "Got dummy!"); free(msg); return 0; } if ((msg->header.pt & 0x7f) != RTP_TYPE_AUDIO % 128) { LOGGER_WARNING(ac->log, "Invalid payload type!"); free(msg); return -1; } pthread_mutex_lock(ac->queue_mutex); int rc = jbuf_write(ac->log, (struct JitterBuffer *)ac->j_buf, msg); pthread_mutex_unlock(ac->queue_mutex); if (rc == -1) { LOGGER_WARNING(ac->log, "Could not queue the message!"); free(msg); return -1; } return 0; } int ac_reconfigure_encoder(ACSession *ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels) { if (!ac || !reconfigure_audio_encoder(ac->log, &ac->encoder, bit_rate, sampling_rate, channels, &ac->le_bit_rate, &ac->le_sample_rate, &ac->le_channel_count)) { return -1; } return 0; } struct JitterBuffer { struct RTPMessage **queue; uint32_t size; uint32_t capacity; uint16_t bottom; uint16_t top; }; static struct JitterBuffer *jbuf_new(uint32_t capacity) { unsigned int size = 1; while (size <= (capacity * 4)) { size *= 2; } struct JitterBuffer *q = (struct JitterBuffer *)calloc(sizeof(struct JitterBuffer), 1); if (!q) { return nullptr; } q->queue = (struct RTPMessage **)calloc(sizeof(struct RTPMessage *), size); if (!q->queue) { free(q); return nullptr; } q->size = size; q->capacity = capacity; return q; } static void jbuf_clear(struct JitterBuffer *q) { for (; q->bottom != q->top; ++q->bottom) { if (q->queue[q->bottom % q->size]) { free(q->queue[q->bottom % q->size]); q->queue[q->bottom % q->size] = nullptr; } } } static void jbuf_free(struct JitterBuffer *q) { if (!q) { return; } jbuf_clear(q); free(q->queue); free(q); } static int jbuf_write(const Logger *log, struct JitterBuffer *q, struct RTPMessage *m) { uint16_t sequnum = m->header.sequnum; unsigned int num = sequnum % q->size; if ((uint32_t)(sequnum - q->bottom) > q->size) { LOGGER_DEBUG(log, "Clearing filled jitter buffer: %p", (void *)q); jbuf_clear(q); q->bottom = sequnum - q->capacity; q->queue[num] = m; q->top = sequnum + 1; return 0; } if (q->queue[num]) { return -1; } q->queue[num] = m; if ((sequnum - q->bottom) >= (q->top - q->bottom)) { q->top = sequnum + 1; } return 0; } static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success) { if (q->top == q->bottom) { *success = 0; return nullptr; } unsigned int num = q->bottom % q->size; if (q->queue[num]) { struct RTPMessage *ret = q->queue[num]; q->queue[num] = nullptr; ++q->bottom; *success = 1; return ret; } if ((uint32_t)(q->top - q->bottom) > q->capacity) { ++q->bottom; *success = 2; return nullptr; } *success = 0; return nullptr; } static OpusEncoder *create_audio_encoder(const Logger *log, int32_t bit_rate, int32_t sampling_rate, int32_t channel_count) { int status = OPUS_OK; /* * OPUS_APPLICATION_VOIP Process signal for improved speech intelligibility * OPUS_APPLICATION_AUDIO Favor faithfulness to the original input * OPUS_APPLICATION_RESTRICTED_LOWDELAY Configure the minimum possible coding delay */ OpusEncoder *rc = opus_encoder_create(sampling_rate, channel_count, OPUS_APPLICATION_VOIP, &status); if (status != OPUS_OK) { LOGGER_ERROR(log, "Error while starting audio encoder: %s", opus_strerror(status)); return nullptr; } /* * Rates from 500 to 512000 bits per second are meaningful as well as the special * values OPUS_BITRATE_AUTO and OPUS_BITRATE_MAX. The value OPUS_BITRATE_MAX can * be used to cause the codec to use as much rate as it can, which is useful for * controlling the rate by adjusting the output buffer size. * * Parameters: * `[in]` `x` `opus_int32`: bitrate in bits per second. */ status = opus_encoder_ctl(rc, OPUS_SET_BITRATE(bit_rate)); if (status != OPUS_OK) { LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); goto FAILURE; } /* * Configures the encoder's use of inband forward error correction. * Note: * This is only applicable to the LPC layer * Parameters: * `[in]` `x` `int`: FEC flag, 0 (disabled) is default */ /* Enable in-band forward error correction in codec */ status = opus_encoder_ctl(rc, OPUS_SET_INBAND_FEC(1)); if (status != OPUS_OK) { LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); goto FAILURE; } /* * Configures the encoder's expected packet loss percentage. * Higher values with trigger progressively more loss resistant behavior in * the encoder at the expense of quality at a given bitrate in the lossless case, * but greater quality under loss. * Parameters: * `[in]` `x` `int`: Loss percentage in the range 0-100, inclusive. */ /* Make codec resistant to up to 10% packet loss * NOTE This could also be adjusted on the fly, rather than hard-coded, * with feedback from the receiving client. */ status = opus_encoder_ctl(rc, OPUS_SET_PACKET_LOSS_PERC(AUDIO_OPUS_PACKET_LOSS_PERC)); if (status != OPUS_OK) { LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); goto FAILURE; } /* * Configures the encoder's computational complexity. * * The supported range is 0-10 inclusive with 10 representing the highest complexity. * The default value is 10. * * Parameters: * `[in]` `x` `int`: 0-10, inclusive */ /* Set algorithm to the highest complexity, maximizing compression */ status = opus_encoder_ctl(rc, OPUS_SET_COMPLEXITY(AUDIO_OPUS_COMPLEXITY)); if (status != OPUS_OK) { LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); goto FAILURE; } return rc; FAILURE: opus_encoder_destroy(rc); return nullptr; } static bool reconfigure_audio_encoder(const Logger *log, OpusEncoder **e, int32_t new_br, int32_t new_sr, uint8_t new_ch, int32_t *old_br, int32_t *old_sr, int32_t *old_ch) { /* Values are checked in toxav.c */ if (*old_sr != new_sr || *old_ch != new_ch) { OpusEncoder *new_encoder = create_audio_encoder(log, new_br, new_sr, new_ch); if (new_encoder == nullptr) { return false; } opus_encoder_destroy(*e); *e = new_encoder; } else if (*old_br == new_br) { return true; /* Nothing changed */ } int status = opus_encoder_ctl(*e, OPUS_SET_BITRATE(new_br)); if (status != OPUS_OK) { LOGGER_ERROR(log, "Error while setting encoder ctl: %s", opus_strerror(status)); return false; } *old_br = new_br; *old_sr = new_sr; *old_ch = new_ch; LOGGER_DEBUG(log, "Reconfigured audio encoder br: %d sr: %d cc:%d", new_br, new_sr, new_ch); return true; } static bool reconfigure_audio_decoder(ACSession *ac, int32_t sampling_rate, int8_t channels) { if (sampling_rate != ac->ld_sample_rate || channels != ac->ld_channel_count) { if (current_time_monotonic(ac->mono_time) - ac->ldrts < 500) { return false; } int status; OpusDecoder *new_dec = opus_decoder_create(sampling_rate, channels, &status); if (status != OPUS_OK) { LOGGER_ERROR(ac->log, "Error while starting audio decoder(%d %d): %s", sampling_rate, channels, opus_strerror(status)); return false; } ac->ld_sample_rate = sampling_rate; ac->ld_channel_count = channels; ac->ldrts = current_time_monotonic(ac->mono_time); opus_decoder_destroy(ac->decoder); ac->decoder = new_dec; LOGGER_DEBUG(ac->log, "Reconfigured audio decoder sr: %d cc: %d", sampling_rate, channels); } return true; } c-toxcore-0.2.13/toxav/audio.h000066400000000000000000000046111415350724400161220ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifndef C_TOXCORE_TOXAV_AUDIO_H #define C_TOXCORE_TOXAV_AUDIO_H #include "toxav.h" #include "../toxcore/logger.h" #include "../toxcore/util.h" #include "rtp.h" #include #include #define AUDIO_JITTERBUFFER_COUNT 3 #define AUDIO_MAX_SAMPLE_RATE 48000 #define AUDIO_MAX_CHANNEL_COUNT 2 #define AUDIO_START_SAMPLE_RATE 48000 #define AUDIO_START_BITRATE 48000 #define AUDIO_START_CHANNEL_COUNT 2 #define AUDIO_OPUS_PACKET_LOSS_PERC 10 #define AUDIO_OPUS_COMPLEXITY 10 #define AUDIO_DECODER_START_SAMPLE_RATE 48000 #define AUDIO_DECODER_START_CHANNEL_COUNT 1 #define AUDIO_MAX_FRAME_DURATION_MS 120 // ((sampling_rate_in_hz * frame_duration_in_ms) / 1000) * 2 // because PCM16 needs 2 bytes for 1 sample // These are per frame and per channel. #define AUDIO_MAX_BUFFER_SIZE_PCM16 ((AUDIO_MAX_SAMPLE_RATE * AUDIO_MAX_FRAME_DURATION_MS) / 1000) #define AUDIO_MAX_BUFFER_SIZE_BYTES (AUDIO_MAX_BUFFER_SIZE_PCM16 * 2) typedef struct ACSession_s { Mono_Time *mono_time; const Logger *log; /* encoding */ OpusEncoder *encoder; int32_t le_sample_rate; /* Last encoder sample rate */ int32_t le_channel_count; /* Last encoder channel count */ int32_t le_bit_rate; /* Last encoder bit rate */ /* decoding */ OpusDecoder *decoder; int32_t lp_channel_count; /* Last packet channel count */ int32_t lp_sampling_rate; /* Last packet sample rate */ int32_t lp_frame_duration; /* Last packet frame duration */ int32_t ld_sample_rate; /* Last decoder sample rate */ int32_t ld_channel_count; /* Last decoder channel count */ uint64_t ldrts; /* Last decoder reconfiguration time stamp */ void *j_buf; pthread_mutex_t queue_mutex[1]; ToxAV *av; uint32_t friend_number; /* Audio frame receive callback */ toxav_audio_receive_frame_cb *acb; void *acb_user_data; } ACSession; ACSession *ac_new(Mono_Time *mono_time, const Logger *log, ToxAV *av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data); void ac_kill(ACSession *ac); void ac_iterate(ACSession *ac); int ac_queue_message(Mono_Time *mono_time, void *acp, struct RTPMessage *msg); int ac_reconfigure_encoder(ACSession *ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels); #endif // C_TOXCORE_TOXAV_AUDIO_H c-toxcore-0.2.13/toxav/bwcontroller.c000066400000000000000000000150561415350724400175350ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "bwcontroller.h" #include #include #include #include #include "ring_buffer.h" #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/util.h" #define BWC_PACKET_ID 196 #define BWC_SEND_INTERVAL_MS 950 // 0.95s #define BWC_AVG_PKT_COUNT 20 #define BWC_AVG_LOSS_OVER_CYCLES_COUNT 30 typedef struct BWCCycle { uint32_t last_recv_timestamp; /* Last recv update time stamp */ uint32_t last_sent_timestamp; /* Last sent update time stamp */ uint32_t last_refresh_timestamp; /* Last refresh time stamp */ uint32_t lost; uint32_t recv; } BWCCycle; typedef struct BWCRcvPkt { uint32_t packet_length_array[BWC_AVG_PKT_COUNT]; RingBuffer *rb; } BWCRcvPkt; struct BWController_s { m_cb *mcb; void *mcb_user_data; Messenger *m; Tox *tox; uint32_t friend_number; BWCCycle cycle; BWCRcvPkt rcvpkt; /* To calculate average received packet (this means split parts, not the full message!) */ uint32_t packet_loss_counted_cycles; Mono_Time *bwc_mono_time; }; struct BWCMessage { uint32_t lost; uint32_t recv; }; static int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object); static int bwc_send_custom_lossy_packet(Tox *tox, int32_t friendnumber, const uint8_t *data, uint32_t length); static void send_update(BWController *bwc); BWController *bwc_new(Messenger *m, Tox *tox, uint32_t friendnumber, m_cb *mcb, void *mcb_user_data, Mono_Time *bwc_mono_time) { BWController *retu = (BWController *)calloc(sizeof(struct BWController_s), 1); if (retu == nullptr) { return nullptr; } LOGGER_DEBUG(m->log, "Creating bandwidth controller"); retu->mcb = mcb; retu->mcb_user_data = mcb_user_data; retu->m = m; retu->friend_number = friendnumber; retu->bwc_mono_time = bwc_mono_time; uint64_t now = current_time_monotonic(bwc_mono_time); retu->cycle.last_sent_timestamp = now; retu->cycle.last_refresh_timestamp = now; retu->tox = tox; retu->rcvpkt.rb = rb_new(BWC_AVG_PKT_COUNT); retu->cycle.lost = 0; retu->cycle.recv = 0; retu->packet_loss_counted_cycles = 0; /* Fill with zeros */ for (int i = 0; i < BWC_AVG_PKT_COUNT; ++i) { rb_write(retu->rcvpkt.rb, &retu->rcvpkt.packet_length_array[i]); } m_callback_rtp_packet(m, friendnumber, BWC_PACKET_ID, bwc_handle_data, retu); return retu; } void bwc_kill(BWController *bwc) { if (!bwc) { return; } m_callback_rtp_packet(bwc->m, bwc->friend_number, BWC_PACKET_ID, nullptr, nullptr); rb_kill(bwc->rcvpkt.rb); free(bwc); } void bwc_add_lost(BWController *bwc, uint32_t bytes_lost) { if (!bwc) { return; } if (bytes_lost > 0) { LOGGER_DEBUG(bwc->m->log, "BWC lost(1): %d", (int)bytes_lost); bwc->cycle.lost += bytes_lost; send_update(bwc); } } void bwc_add_recv(BWController *bwc, uint32_t recv_bytes) { if (!bwc || !recv_bytes) { return; } ++bwc->packet_loss_counted_cycles; bwc->cycle.recv += recv_bytes; send_update(bwc); } static void send_update(BWController *bwc) { if (bwc->packet_loss_counted_cycles > BWC_AVG_LOSS_OVER_CYCLES_COUNT && current_time_monotonic(bwc->bwc_mono_time) - bwc->cycle.last_sent_timestamp > BWC_SEND_INTERVAL_MS) { bwc->packet_loss_counted_cycles = 0; if (bwc->cycle.lost) { LOGGER_DEBUG(bwc->m->log, "%p Sent update rcv: %u lost: %u percent: %f %%", (void *)bwc, bwc->cycle.recv, bwc->cycle.lost, (((double) bwc->cycle.lost / (bwc->cycle.recv + bwc->cycle.lost)) * 100.0)); uint8_t bwc_packet[sizeof(struct BWCMessage) + 1]; size_t offset = 0; bwc_packet[offset] = BWC_PACKET_ID; // set packet ID ++offset; offset += net_pack_u32(bwc_packet + offset, bwc->cycle.lost); offset += net_pack_u32(bwc_packet + offset, bwc->cycle.recv); assert(offset == sizeof(bwc_packet)); if (bwc_send_custom_lossy_packet(bwc->tox, bwc->friend_number, bwc_packet, sizeof(bwc_packet)) == -1) { const char *netstrerror = net_new_strerror(net_error()); LOGGER_WARNING(bwc->m->log, "BWC send failed (len: %u)! std error: %s, net error %s", (unsigned)sizeof(bwc_packet), strerror(errno), netstrerror); net_kill_strerror(netstrerror); } } bwc->cycle.last_sent_timestamp = current_time_monotonic(bwc->bwc_mono_time); bwc->cycle.lost = 0; bwc->cycle.recv = 0; } } static int on_update(BWController *bwc, const struct BWCMessage *msg) { LOGGER_DEBUG(bwc->m->log, "%p Got update from peer", (void *)bwc); /* Peers sent update too soon */ if (bwc->cycle.last_recv_timestamp + BWC_SEND_INTERVAL_MS > current_time_monotonic(bwc->bwc_mono_time)) { LOGGER_INFO(bwc->m->log, "%p Rejecting extra update", (void *)bwc); return -1; } bwc->cycle.last_recv_timestamp = current_time_monotonic(bwc->bwc_mono_time); const uint32_t recv = msg->recv; const uint32_t lost = msg->lost; if (lost && bwc->mcb) { LOGGER_DEBUG(bwc->m->log, "recved: %u lost: %u percentage: %f %%", recv, lost, (((double) lost / (recv + lost)) * 100.0)); bwc->mcb(bwc, bwc->friend_number, ((float) lost / (recv + lost)), bwc->mcb_user_data); } return 0; } /* * return -1 on failure, 0 on success * */ static int bwc_send_custom_lossy_packet(Tox *tox, int32_t friendnumber, const uint8_t *data, uint32_t length) { Tox_Err_Friend_Custom_Packet error; tox_friend_send_lossy_packet(tox, friendnumber, data, (size_t)length, &error); if (error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK) { return 0; } return -1; } static int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) { if (length - 1 != sizeof(struct BWCMessage)) { return -1; } size_t offset = 1; // Ignore packet id. struct BWCMessage msg; offset += net_unpack_u32(data + offset, &msg.lost); offset += net_unpack_u32(data + offset, &msg.recv); assert(offset == length); return on_update((BWController *)object, &msg); } c-toxcore-0.2.13/toxav/bwcontroller.h000066400000000000000000000015161415350724400175360ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifndef C_TOXCORE_TOXAV_BWCONTROLLER_H #define C_TOXCORE_TOXAV_BWCONTROLLER_H #include "../toxcore/Messenger.h" #include "../toxcore/tox.h" #ifndef TOX_DEFINED #define TOX_DEFINED typedef struct Tox Tox; #endif /* TOX_DEFINED */ typedef struct BWController_s BWController; typedef void m_cb(BWController *bwc, uint32_t friend_number, float todo, void *user_data); BWController *bwc_new(Messenger *m, Tox *tox, uint32_t friendnumber, m_cb *mcb, void *mcb_user_data, Mono_Time *bwc_mono_time); void bwc_kill(BWController *bwc); void bwc_add_lost(BWController *bwc, uint32_t bytes_lost); void bwc_add_recv(BWController *bwc, uint32_t recv_bytes); #endif // C_TOXCORE_TOXAV_BWCONTROLLER_H c-toxcore-0.2.13/toxav/groupav.c000066400000000000000000000376551415350724400165150ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "groupav.h" #include #include #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/util.h" #define GROUP_JBUF_SIZE 6 #define GROUP_JBUF_DEAD_SECONDS 4 typedef struct Group_Audio_Packet { uint16_t sequnum; uint16_t length; uint8_t data[]; } Group_Audio_Packet; typedef struct Group_JitterBuffer { Group_Audio_Packet **queue; uint32_t size; uint32_t capacity; uint16_t bottom; uint16_t top; uint64_t last_queued_time; } Group_JitterBuffer; static Group_JitterBuffer *create_queue(unsigned int capacity) { unsigned int size = 1; while (size <= capacity) { size *= 2; } Group_JitterBuffer *q = (Group_JitterBuffer *)calloc(sizeof(Group_JitterBuffer), 1); if (!q) { return nullptr; } q->queue = (Group_Audio_Packet **)calloc(sizeof(Group_Audio_Packet *), size); if (!q->queue) { free(q); return nullptr; } q->size = size; q->capacity = capacity; return q; } static void clear_queue(Group_JitterBuffer *q) { for (; q->bottom != q->top; ++q->bottom) { if (q->queue[q->bottom % q->size]) { free(q->queue[q->bottom % q->size]); q->queue[q->bottom % q->size] = nullptr; } } } static void terminate_queue(Group_JitterBuffer *q) { if (!q) { return; } clear_queue(q); free(q->queue); free(q); } /* Return 0 if packet was queued, -1 if it wasn't. */ static int queue(Group_JitterBuffer *q, const Mono_Time *mono_time, Group_Audio_Packet *pk) { uint16_t sequnum = pk->sequnum; unsigned int num = sequnum % q->size; if (!mono_time_is_timeout(mono_time, q->last_queued_time, GROUP_JBUF_DEAD_SECONDS)) { if ((uint32_t)(sequnum - q->bottom) > (1 << 15)) { /* Drop old packet. */ return -1; } } if ((uint32_t)(sequnum - q->bottom) > q->size) { clear_queue(q); q->bottom = sequnum - q->capacity; q->queue[num] = pk; q->top = sequnum + 1; q->last_queued_time = mono_time_get(mono_time); return 0; } if (q->queue[num]) { return -1; } q->queue[num] = pk; if ((sequnum - q->bottom) >= (q->top - q->bottom)) { q->top = sequnum + 1; } q->last_queued_time = mono_time_get(mono_time); return 0; } /* success is 0 when there is nothing to dequeue, 1 when there's a good packet, 2 when there's a lost packet */ static Group_Audio_Packet *dequeue(Group_JitterBuffer *q, int *success) { if (q->top == q->bottom) { *success = 0; return nullptr; } unsigned int num = q->bottom % q->size; if (q->queue[num]) { Group_Audio_Packet *ret = q->queue[num]; q->queue[num] = nullptr; ++q->bottom; *success = 1; return ret; } if ((uint32_t)(q->top - q->bottom) > q->capacity) { ++q->bottom; *success = 2; return nullptr; } *success = 0; return nullptr; } typedef struct Group_AV { const Logger *log; Tox *tox; Group_Chats *g_c; OpusEncoder *audio_encoder; unsigned int audio_channels; unsigned int audio_sample_rate; unsigned int audio_bitrate; uint16_t audio_sequnum; audio_data_cb *audio_data; void *userdata; } Group_AV; typedef struct Group_Peer_AV { const Mono_Time *mono_time; Group_JitterBuffer *buffer; OpusDecoder *audio_decoder; int decoder_channels; unsigned int last_packet_samples; } Group_Peer_AV; static void kill_group_av(Group_AV *group_av) { if (group_av->audio_encoder) { opus_encoder_destroy(group_av->audio_encoder); } free(group_av); } static int recreate_encoder(Group_AV *group_av) { if (group_av->audio_encoder) { opus_encoder_destroy(group_av->audio_encoder); group_av->audio_encoder = nullptr; } int rc = OPUS_OK; group_av->audio_encoder = opus_encoder_create(group_av->audio_sample_rate, group_av->audio_channels, OPUS_APPLICATION_AUDIO, &rc); if (rc != OPUS_OK) { LOGGER_ERROR(group_av->log, "Error while starting audio encoder: %s", opus_strerror(rc)); group_av->audio_encoder = nullptr; return -1; } rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_BITRATE(group_av->audio_bitrate)); if (rc != OPUS_OK) { LOGGER_ERROR(group_av->log, "Error while setting encoder ctl: %s", opus_strerror(rc)); opus_encoder_destroy(group_av->audio_encoder); group_av->audio_encoder = nullptr; return -1; } rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_COMPLEXITY(10)); if (rc != OPUS_OK) { LOGGER_ERROR(group_av->log, "Error while setting encoder ctl: %s", opus_strerror(rc)); opus_encoder_destroy(group_av->audio_encoder); group_av->audio_encoder = nullptr; return -1; } return 0; } static Group_AV *new_group_av(const Logger *log, Tox *tox, Group_Chats *g_c, audio_data_cb *audio_callback, void *userdata) { if (!g_c) { return nullptr; } Group_AV *group_av = (Group_AV *)calloc(1, sizeof(Group_AV)); if (!group_av) { return nullptr; } group_av->log = log; group_av->tox = tox; group_av->g_c = g_c; group_av->audio_data = audio_callback; group_av->userdata = userdata; return group_av; } static void group_av_peer_new(void *object, uint32_t groupnumber, uint32_t friendgroupnumber) { Group_AV *group_av = (Group_AV *)object; Group_Peer_AV *peer_av = (Group_Peer_AV *)calloc(1, sizeof(Group_Peer_AV)); if (!peer_av) { return; } peer_av->mono_time = group_av->g_c->mono_time; peer_av->buffer = create_queue(GROUP_JBUF_SIZE); if (group_peer_set_object(group_av->g_c, groupnumber, friendgroupnumber, peer_av) == -1) { free(peer_av); } } static void group_av_peer_delete(void *object, uint32_t groupnumber, void *peer_object) { Group_Peer_AV *peer_av = (Group_Peer_AV *)peer_object; if (!peer_av) { return; } if (peer_av->audio_decoder) { opus_decoder_destroy(peer_av->audio_decoder); } terminate_queue(peer_av->buffer); free(peer_object); } static void group_av_groupchat_delete(void *object, uint32_t groupnumber) { if (object) { kill_group_av((Group_AV *)object); } } static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, uint32_t groupnumber, uint32_t friendgroupnumber) { if (!group_av || !peer_av) { return -1; } int success; Group_Audio_Packet *pk = dequeue(peer_av->buffer, &success); if (success == 0) { return -1; } int16_t *out_audio = nullptr; int out_audio_samples = 0; unsigned int sample_rate = 48000; if (success == 1) { int channels = opus_packet_get_nb_channels(pk->data); if (channels == OPUS_INVALID_PACKET) { free(pk); return -1; } if (channels != 1 && channels != 2) { free(pk); return -1; } if (channels != peer_av->decoder_channels) { if (peer_av->audio_decoder) { opus_decoder_destroy(peer_av->audio_decoder); peer_av->audio_decoder = nullptr; } int rc; peer_av->audio_decoder = opus_decoder_create(sample_rate, channels, &rc); if (rc != OPUS_OK) { LOGGER_ERROR(group_av->log, "Error while starting audio decoder: %s", opus_strerror(rc)); free(pk); return -1; } peer_av->decoder_channels = channels; } int num_samples = opus_decoder_get_nb_samples(peer_av->audio_decoder, pk->data, pk->length); out_audio = (int16_t *)malloc(num_samples * peer_av->decoder_channels * sizeof(int16_t)); if (!out_audio) { free(pk); return -1; } out_audio_samples = opus_decode(peer_av->audio_decoder, pk->data, pk->length, out_audio, num_samples, 0); free(pk); if (out_audio_samples <= 0) { free(out_audio); return -1; } peer_av->last_packet_samples = out_audio_samples; } else { if (!peer_av->audio_decoder) { return -1; } if (!peer_av->last_packet_samples) { return -1; } out_audio = (int16_t *)malloc(peer_av->last_packet_samples * peer_av->decoder_channels * sizeof(int16_t)); if (!out_audio) { free(pk); return -1; } out_audio_samples = opus_decode(peer_av->audio_decoder, nullptr, 0, out_audio, peer_av->last_packet_samples, 1); if (out_audio_samples <= 0) { free(out_audio); return -1; } } if (out_audio) { if (group_av->audio_data) { group_av->audio_data(group_av->tox, groupnumber, friendgroupnumber, out_audio, out_audio_samples, peer_av->decoder_channels, sample_rate, group_av->userdata); } free(out_audio); return 0; } return -1; } static int handle_group_audio_packet(void *object, uint32_t groupnumber, uint32_t friendgroupnumber, void *peer_object, const uint8_t *packet, uint16_t length) { if (!peer_object || !object || length <= sizeof(uint16_t)) { return -1; } Group_Peer_AV *peer_av = (Group_Peer_AV *)peer_object; Group_Audio_Packet *pk = (Group_Audio_Packet *)calloc(1, sizeof(Group_Audio_Packet) + (length - sizeof(uint16_t))); if (!pk) { return -1; } net_unpack_u16(packet, &pk->sequnum); pk->length = length - sizeof(uint16_t); memcpy(pk->data, packet + sizeof(uint16_t), pk->length); if (queue(peer_av->buffer, peer_av->mono_time, pk) == -1) { free(pk); return -1; } while (decode_audio_packet((Group_AV *)object, peer_av, groupnumber, friendgroupnumber) == 0) { continue; } return 0; } /* Enable A/V in a groupchat. * * return 0 on success. * return -1 on failure. */ int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t groupnumber, audio_data_cb *audio_callback, void *userdata) { if (group_get_type(g_c, groupnumber) != GROUPCHAT_TYPE_AV || group_get_object(g_c, groupnumber) != nullptr) { return -1; } Group_AV *group_av = new_group_av(log, tox, g_c, audio_callback, userdata); if (group_av == nullptr) { return -1; } if (group_set_object(g_c, groupnumber, group_av) == -1 || callback_groupchat_peer_new(g_c, groupnumber, group_av_peer_new) == -1 || callback_groupchat_peer_delete(g_c, groupnumber, group_av_peer_delete) == -1 || callback_groupchat_delete(g_c, groupnumber, group_av_groupchat_delete) == -1) { kill_group_av(group_av); return -1; } int numpeers = group_number_peers(g_c, groupnumber, false); for (uint32_t i = 0; i < numpeers; ++i) { group_av_peer_new(group_av, groupnumber, i); } group_lossy_packet_registerhandler(g_c, GROUP_AUDIO_PACKET_ID, &handle_group_audio_packet); return 0; } /* Disable A/V in a groupchat. * * return 0 on success. * return -1 on failure. */ int groupchat_disable_av(Group_Chats *g_c, uint32_t groupnumber) { if (group_get_type(g_c, groupnumber) != GROUPCHAT_TYPE_AV) { return -1; } Group_AV *group_av = (Group_AV *)group_get_object(g_c, groupnumber); if (group_av == nullptr) { return -1; } int numpeers = group_number_peers(g_c, groupnumber, false); for (uint32_t i = 0; i < numpeers; ++i) { group_av_peer_delete(group_av, groupnumber, group_peer_get_object(g_c, groupnumber, i)); group_peer_set_object(g_c, groupnumber, i, nullptr); } kill_group_av(group_av); if (group_set_object(g_c, groupnumber, nullptr) == -1 || callback_groupchat_peer_new(g_c, groupnumber, nullptr) == -1 || callback_groupchat_peer_delete(g_c, groupnumber, nullptr) == -1 || callback_groupchat_delete(g_c, groupnumber, nullptr) == -1) { return -1; } return 0; } /* Return whether A/V is enabled in the groupchat. */ bool groupchat_av_enabled(Group_Chats *g_c, uint32_t groupnumber) { return group_get_object(g_c, groupnumber) != nullptr; } /* Create a new toxav group. * * return group number on success. * return -1 on failure. */ int add_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, audio_data_cb *audio_callback, void *userdata) { int groupnumber = add_groupchat(g_c, GROUPCHAT_TYPE_AV); if (groupnumber == -1) { return -1; } if (groupchat_enable_av(log, tox, g_c, groupnumber, audio_callback, userdata) == -1) { del_groupchat(g_c, groupnumber, true); return -1; } return groupnumber; } /* Join a AV group (you need to have been invited first.) * * returns group number on success * returns -1 on failure. */ int join_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t friendnumber, const uint8_t *data, uint16_t length, audio_data_cb *audio_callback, void *userdata) { int groupnumber = join_groupchat(g_c, friendnumber, GROUPCHAT_TYPE_AV, data, length); if (groupnumber == -1) { return -1; } if (groupchat_enable_av(log, tox, g_c, groupnumber, audio_callback, userdata) == -1) { del_groupchat(g_c, groupnumber, true); return -1; } return groupnumber; } /* Send an encoded audio packet to the group chat. * * return 0 on success. * return -1 on failure. */ static int send_audio_packet(Group_Chats *g_c, uint32_t groupnumber, uint8_t *packet, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_DATA_SIZE - 1 - sizeof(uint16_t)) { return -1; } const uint16_t plen = 1 + sizeof(uint16_t) + length; Group_AV *const group_av = (Group_AV *)group_get_object(g_c, groupnumber); if (!group_av) { return -1; } uint8_t data[MAX_CRYPTO_DATA_SIZE]; uint8_t *ptr = data; *ptr = GROUP_AUDIO_PACKET_ID; ++ptr; ptr += net_pack_u16(ptr, group_av->audio_sequnum); memcpy(ptr, packet, length); if (send_group_lossy_packet(g_c, groupnumber, data, plen) == -1) { return -1; } ++group_av->audio_sequnum; return 0; } /* Send audio to the group chat. * * return 0 on success. * return -1 on failure. */ int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate) { Group_AV *group_av = (Group_AV *)group_get_object(g_c, groupnumber); if (!group_av) { return -1; } if (channels != 1 && channels != 2) { return -1; } if (sample_rate != 8000 && sample_rate != 12000 && sample_rate != 16000 && sample_rate != 24000 && sample_rate != 48000) { return -1; } if (!group_av->audio_encoder || group_av->audio_channels != channels || group_av->audio_sample_rate != sample_rate) { group_av->audio_channels = channels; group_av->audio_sample_rate = sample_rate; if (channels == 1) { group_av->audio_bitrate = 32000; // TODO(mannol): add way of adjusting bitrate } else { group_av->audio_bitrate = 64000; // TODO(mannol): add way of adjusting bitrate } if (recreate_encoder(group_av) == -1) { return -1; } } uint8_t encoded[1024]; int32_t size = opus_encode(group_av->audio_encoder, pcm, samples, encoded, sizeof(encoded)); if (size <= 0) { return -1; } return send_audio_packet(g_c, groupnumber, encoded, size); } c-toxcore-0.2.13/toxav/groupav.h000066400000000000000000000042661415350724400165120ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ #ifndef C_TOXCORE_TOXAV_GROUPAV_H #define C_TOXCORE_TOXAV_GROUPAV_H #include "../toxcore/group.h" #include "../toxcore/tox.h" /* Audio encoding/decoding */ #include #define GROUP_AUDIO_PACKET_ID 192 // TODO(iphydf): Use this better typed one instead of the void-pointer one below. // typedef void audio_data_cb(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, // uint32_t samples, uint8_t channels, uint32_t sample_rate, void *userdata); typedef void audio_data_cb(void *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, uint32_t samples, uint8_t channels, uint32_t sample_rate, void *userdata); /* Create and connect to a new toxav group. * * return group number on success. * return -1 on failure. */ int add_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, audio_data_cb *audio_callback, void *userdata); /* Join a AV group (you need to have been invited first.) * * returns group number on success * returns -1 on failure. */ int join_av_groupchat(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t friendnumber, const uint8_t *data, uint16_t length, audio_data_cb *audio_callback, void *userdata); /* Send audio to the group chat. * * return 0 on success. * return -1 on failure. */ int group_send_audio(Group_Chats *g_c, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate); /* Enable A/V in a groupchat. * * return 0 on success. * return -1 on failure. */ int groupchat_enable_av(const Logger *log, Tox *tox, Group_Chats *g_c, uint32_t groupnumber, audio_data_cb *audio_callback, void *userdata); /* Disable A/V in a groupchat. * * return 0 on success. * return -1 on failure. */ int groupchat_disable_av(Group_Chats *g_c, uint32_t groupnumber); /* Return whether A/V is enabled in the groupchat. */ bool groupchat_av_enabled(Group_Chats *g_c, uint32_t groupnumber); #endif // C_TOXCORE_TOXAV_GROUPAV_H c-toxcore-0.2.13/toxav/msi.c000066400000000000000000000571621415350724400156150ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "msi.h" #include "../toxcore/logger.h" #include "../toxcore/util.h" #include #include #include #include #define MSI_MAXMSG_SIZE 256 /** * Protocol: * * `|id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}|` */ typedef enum MSIHeaderID { ID_REQUEST = 1, ID_ERROR, ID_CAPABILITIES, } MSIHeaderID; typedef enum MSIRequest { REQU_INIT, REQU_PUSH, REQU_POP, } MSIRequest; typedef struct MSIHeaderRequest { MSIRequest value; bool exists; } MSIHeaderRequest; typedef struct MSIHeaderError { MSIError value; bool exists; } MSIHeaderError; typedef struct MSIHeaderCapabilities { uint8_t value; bool exists; } MSIHeaderCapabilities; typedef struct MSIMessage { MSIHeaderRequest request; MSIHeaderError error; MSIHeaderCapabilities capabilities; } MSIMessage; static void msg_init(MSIMessage *dest, MSIRequest request); static int msg_parse_in(const Logger *log, MSIMessage *dest, const uint8_t *data, uint16_t length); static uint8_t *msg_parse_header_out(MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length); static int send_message(Messenger *m, uint32_t friend_number, const MSIMessage *msg); static int send_error(Messenger *m, uint32_t friend_number, MSIError error); static int invoke_callback(MSICall *call, MSICallbackID cb); static MSICall *get_call(MSISession *session, uint32_t friend_number); static MSICall *new_call(MSISession *session, uint32_t friend_number); static void kill_call(MSICall *call); static void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data); static void handle_init(MSICall *call, const MSIMessage *msg); static void handle_push(MSICall *call, const MSIMessage *msg); static void handle_pop(MSICall *call, const MSIMessage *msg); static void handle_msi_packet(Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object); /** * Public functions */ void msi_register_callback(MSISession *session, msi_action_cb *callback, MSICallbackID id) { if (!session) { return; } pthread_mutex_lock(session->mutex); session->callbacks[id] = callback; pthread_mutex_unlock(session->mutex); } MSISession *msi_new(Messenger *m) { if (m == nullptr) { return nullptr; } MSISession *retu = (MSISession *)calloc(sizeof(MSISession), 1); if (retu == nullptr) { LOGGER_ERROR(m->log, "Allocation failed! Program might misbehave!"); return nullptr; } if (create_recursive_mutex(retu->mutex) != 0) { LOGGER_ERROR(m->log, "Failed to init mutex! Program might misbehave"); free(retu); return nullptr; } retu->messenger = m; m_callback_msi_packet(m, handle_msi_packet, retu); /* This is called when remote terminates session */ m_callback_connectionstatus_internal_av(m, on_peer_status, retu); LOGGER_DEBUG(m->log, "New msi session: %p ", (void *)retu); return retu; } int msi_kill(MSISession *session, const Logger *log) { if (session == nullptr) { LOGGER_ERROR(log, "Tried to terminate non-existing session"); return -1; } m_callback_msi_packet(session->messenger, nullptr, nullptr); if (pthread_mutex_trylock(session->mutex) != 0) { LOGGER_ERROR(log, "Failed to acquire lock on msi mutex"); return -1; } if (session->calls) { MSIMessage msg; msg_init(&msg, REQU_POP); MSICall *it = get_call(session, session->calls_head); while (it) { send_message(session->messenger, it->friend_number, &msg); MSICall *temp_it = it; it = it->next; kill_call(temp_it); /* This will eventually free session->calls */ } } pthread_mutex_unlock(session->mutex); pthread_mutex_destroy(session->mutex); LOGGER_DEBUG(log, "Terminated session: %p", (void *)session); free(session); return 0; } int msi_invite(MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities) { if (!session) { return -1; } LOGGER_DEBUG(session->messenger->log, "Session: %p Inviting friend: %u", (void *)session, friend_number); if (pthread_mutex_trylock(session->mutex) != 0) { LOGGER_ERROR(session->messenger->log, "Failed to acquire lock on msi mutex"); return -1; } if (get_call(session, friend_number) != nullptr) { LOGGER_ERROR(session->messenger->log, "Already in a call"); pthread_mutex_unlock(session->mutex); return -1; } MSICall *temp = new_call(session, friend_number); if (temp == nullptr) { pthread_mutex_unlock(session->mutex); return -1; } temp->self_capabilities = capabilities; MSIMessage msg; msg_init(&msg, REQU_INIT); msg.capabilities.exists = true; msg.capabilities.value = capabilities; send_message(temp->session->messenger, temp->friend_number, &msg); temp->state = MSI_CALL_REQUESTING; *call = temp; LOGGER_DEBUG(session->messenger->log, "Invite sent"); pthread_mutex_unlock(session->mutex); return 0; } int msi_hangup(MSICall *call) { if (!call || !call->session) { return -1; } MSISession *session = call->session; LOGGER_DEBUG(session->messenger->log, "Session: %p Hanging up call with friend: %u", (void *)call->session, call->friend_number); if (pthread_mutex_trylock(session->mutex) != 0) { LOGGER_ERROR(session->messenger->log, "Failed to acquire lock on msi mutex"); return -1; } if (call->state == MSI_CALL_INACTIVE) { LOGGER_ERROR(session->messenger->log, "Call is in invalid state!"); pthread_mutex_unlock(session->mutex); return -1; } MSIMessage msg; msg_init(&msg, REQU_POP); send_message(session->messenger, call->friend_number, &msg); kill_call(call); pthread_mutex_unlock(session->mutex); return 0; } int msi_answer(MSICall *call, uint8_t capabilities) { if (!call || !call->session) { return -1; } MSISession *session = call->session; LOGGER_DEBUG(session->messenger->log, "Session: %p Answering call from: %u", (void *)call->session, call->friend_number); if (pthread_mutex_trylock(session->mutex) != 0) { LOGGER_ERROR(session->messenger->log, "Failed to acquire lock on msi mutex"); return -1; } if (call->state != MSI_CALL_REQUESTED) { /* Though sending in invalid state will not cause anything weird * Its better to not do it like a maniac */ LOGGER_ERROR(session->messenger->log, "Call is in invalid state!"); pthread_mutex_unlock(session->mutex); return -1; } call->self_capabilities = capabilities; MSIMessage msg; msg_init(&msg, REQU_PUSH); msg.capabilities.exists = true; msg.capabilities.value = capabilities; send_message(session->messenger, call->friend_number, &msg); call->state = MSI_CALL_ACTIVE; pthread_mutex_unlock(session->mutex); return 0; } int msi_change_capabilities(MSICall *call, uint8_t capabilities) { if (!call || !call->session) { return -1; } MSISession *session = call->session; LOGGER_DEBUG(session->messenger->log, "Session: %p Trying to change capabilities to friend %u", (void *)call->session, call->friend_number); if (pthread_mutex_trylock(session->mutex) != 0) { LOGGER_ERROR(session->messenger->log, "Failed to acquire lock on msi mutex"); return -1; } if (call->state != MSI_CALL_ACTIVE) { LOGGER_ERROR(session->messenger->log, "Call is in invalid state!"); pthread_mutex_unlock(session->mutex); return -1; } call->self_capabilities = capabilities; MSIMessage msg; msg_init(&msg, REQU_PUSH); msg.capabilities.exists = true; msg.capabilities.value = capabilities; send_message(call->session->messenger, call->friend_number, &msg); pthread_mutex_unlock(session->mutex); return 0; } /** * Private functions */ static void msg_init(MSIMessage *dest, MSIRequest request) { memset(dest, 0, sizeof(*dest)); dest->request.exists = true; dest->request.value = request; } static int msg_parse_in(const Logger *log, MSIMessage *dest, const uint8_t *data, uint16_t length) { /* Parse raw data received from socket into MSIMessage struct */ #define CHECK_SIZE(bytes, constraint, size) \ do { \ constraint -= 2 + size; \ if (constraint < 1) { \ LOGGER_ERROR(log, "Read over length!"); \ return -1; \ } \ if (bytes[1] != size) { \ LOGGER_ERROR(log, "Invalid data size!"); \ return -1; \ } \ } while (0) /* Assumes size == 1 */ #define CHECK_ENUM_HIGH(bytes, enum_high) \ do { \ if (bytes[2] > enum_high) { \ LOGGER_ERROR(log, "Failed enum high limit!"); \ return -1; \ } \ } while (0) assert(dest); if (length == 0 || data[length - 1]) { /* End byte must have value 0 */ LOGGER_ERROR(log, "Invalid end byte"); return -1; } memset(dest, 0, sizeof(*dest)); const uint8_t *it = data; int size_constraint = length; while (*it) {/* until end byte is hit */ switch (*it) { case ID_REQUEST: CHECK_SIZE(it, size_constraint, 1); CHECK_ENUM_HIGH(it, REQU_POP); dest->request.value = (MSIRequest)it[2]; dest->request.exists = true; it += 3; break; case ID_ERROR: CHECK_SIZE(it, size_constraint, 1); CHECK_ENUM_HIGH(it, MSI_E_UNDISCLOSED); dest->error.value = (MSIError)it[2]; dest->error.exists = true; it += 3; break; case ID_CAPABILITIES: CHECK_SIZE(it, size_constraint, 1); dest->capabilities.value = it[2]; dest->capabilities.exists = true; it += 3; break; default: LOGGER_ERROR(log, "Invalid id byte"); return -1; } } if (dest->request.exists == false) { LOGGER_ERROR(log, "Invalid request field!"); return -1; } #undef CHECK_ENUM_HIGH #undef CHECK_SIZE return 0; } static uint8_t *msg_parse_header_out(MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length) { /* Parse a single header for sending */ assert(dest); assert(value); assert(value_len); *dest = id; ++dest; *dest = value_len; ++dest; memcpy(dest, value, value_len); *length += (2 + value_len); return dest + value_len; /* Set to next position ready to be written */ } static int send_message(Messenger *m, uint32_t friend_number, const MSIMessage *msg) { /* Parse and send message */ assert(m); uint8_t parsed [MSI_MAXMSG_SIZE]; uint8_t *it = parsed; uint16_t size = 0; if (msg->request.exists) { uint8_t cast = msg->request.value; it = msg_parse_header_out(ID_REQUEST, it, &cast, sizeof(cast), &size); } else { LOGGER_DEBUG(m->log, "Must have request field"); return -1; } if (msg->error.exists) { uint8_t cast = msg->error.value; it = msg_parse_header_out(ID_ERROR, it, &cast, sizeof(cast), &size); } if (msg->capabilities.exists) { it = msg_parse_header_out(ID_CAPABILITIES, it, &msg->capabilities.value, sizeof(msg->capabilities.value), &size); } if (it == parsed) { LOGGER_WARNING(m->log, "Parsing message failed; empty message"); return -1; } *it = 0; ++size; if (m_msi_packet(m, friend_number, parsed, size)) { LOGGER_DEBUG(m->log, "Sent message"); return 0; } return -1; } static int send_error(Messenger *m, uint32_t friend_number, MSIError error) { /* Send error message */ assert(m); LOGGER_DEBUG(m->log, "Sending error: %d to friend: %d", error, friend_number); MSIMessage msg; msg_init(&msg, REQU_POP); msg.error.exists = true; msg.error.value = error; send_message(m, friend_number, &msg); return 0; } static int invoke_callback(MSICall *call, MSICallbackID cb) { assert(call); if (call->session->callbacks[cb]) { LOGGER_DEBUG(call->session->messenger->log, "Invoking callback function: %d", cb); if (call->session->callbacks[cb](call->session->av, call) != 0) { LOGGER_WARNING(call->session->messenger->log, "Callback state handling failed, sending error"); goto FAILURE; } return 0; } FAILURE: /* If no callback present or error happened while handling, * an error message will be sent to friend */ if (call->error == MSI_E_NONE) { call->error = MSI_E_HANDLE; } return -1; } static MSICall *get_call(MSISession *session, uint32_t friend_number) { assert(session); if (session->calls == nullptr || session->calls_tail < friend_number) { return nullptr; } return session->calls[friend_number]; } static MSICall *new_call(MSISession *session, uint32_t friend_number) { assert(session); MSICall *rc = (MSICall *)calloc(sizeof(MSICall), 1); if (rc == nullptr) { return nullptr; } rc->session = session; rc->friend_number = friend_number; if (session->calls == nullptr) { /* Creating */ session->calls = (MSICall **)calloc(sizeof(MSICall *), friend_number + 1); if (session->calls == nullptr) { free(rc); return nullptr; } session->calls_tail = friend_number; session->calls_head = friend_number; } else if (session->calls_tail < friend_number) { /* Appending */ MSICall **tmp = (MSICall **)realloc(session->calls, sizeof(MSICall *) * (friend_number + 1)); if (tmp == nullptr) { free(rc); return nullptr; } session->calls = tmp; /* Set fields in between to null */ uint32_t i = session->calls_tail + 1; for (; i < friend_number; ++i) { session->calls[i] = nullptr; } rc->prev = session->calls[session->calls_tail]; session->calls[session->calls_tail]->next = rc; session->calls_tail = friend_number; } else if (session->calls_head > friend_number) { /* Inserting at front */ rc->next = session->calls[session->calls_head]; session->calls[session->calls_head]->prev = rc; session->calls_head = friend_number; } session->calls[friend_number] = rc; return rc; } static void kill_call(MSICall *call) { /* Assume that session mutex is locked */ if (call == nullptr) { return; } MSISession *session = call->session; LOGGER_DEBUG(session->messenger->log, "Killing call: %p", (void *)call); MSICall *prev = call->prev; MSICall *next = call->next; if (prev) { prev->next = next; } else if (next) { session->calls_head = next->friend_number; } else { goto CLEAR_CONTAINER; } if (next) { next->prev = prev; } else if (prev) { session->calls_tail = prev->friend_number; } else { goto CLEAR_CONTAINER; } session->calls[call->friend_number] = nullptr; free(call); return; CLEAR_CONTAINER: session->calls_head = 0; session->calls_tail = 0; free(session->calls); free(call); session->calls = nullptr; } static void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data) { MSISession *session = (MSISession *)data; switch (status) { case 0: { /* Friend is now offline */ LOGGER_DEBUG(m->log, "Friend %d is now offline", friend_number); pthread_mutex_lock(session->mutex); MSICall *call = get_call(session, friend_number); if (call == nullptr) { pthread_mutex_unlock(session->mutex); return; } invoke_callback(call, MSI_ON_PEERTIMEOUT); /* Failure is ignored */ kill_call(call); pthread_mutex_unlock(session->mutex); } break; default: break; } } static void handle_init(MSICall *call, const MSIMessage *msg) { assert(call); LOGGER_DEBUG(call->session->messenger->log, "Session: %p Handling 'init' friend: %d", (void *)call->session, call->friend_number); if (!msg->capabilities.exists) { LOGGER_WARNING(call->session->messenger->log, "Session: %p Invalid capabilities on 'init'", (void *)call->session); call->error = MSI_E_INVALID_MESSAGE; goto FAILURE; } switch (call->state) { case MSI_CALL_INACTIVE: { /* Call requested */ call->peer_capabilities = msg->capabilities.value; call->state = MSI_CALL_REQUESTED; if (invoke_callback(call, MSI_ON_INVITE) == -1) { goto FAILURE; } } break; case MSI_CALL_ACTIVE: { /* If peer sent init while the call is already * active it's probable that he is trying to * re-call us while the call is not terminated * on our side. We can assume that in this case * we can automatically answer the re-call. */ LOGGER_INFO(call->session->messenger->log, "Friend is recalling us"); MSIMessage out_msg; msg_init(&out_msg, REQU_PUSH); out_msg.capabilities.exists = true; out_msg.capabilities.value = call->self_capabilities; send_message(call->session->messenger, call->friend_number, &out_msg); /* If peer changed capabilities during re-call they will * be handled accordingly during the next step */ } break; case MSI_CALL_REQUESTED: // fall-through case MSI_CALL_REQUESTING: { LOGGER_WARNING(call->session->messenger->log, "Session: %p Invalid state on 'init'", (void *)call->session); call->error = MSI_E_INVALID_STATE; goto FAILURE; } } return; FAILURE: send_error(call->session->messenger, call->friend_number, call->error); kill_call(call); } static void handle_push(MSICall *call, const MSIMessage *msg) { assert(call); LOGGER_DEBUG(call->session->messenger->log, "Session: %p Handling 'push' friend: %d", (void *)call->session, call->friend_number); if (!msg->capabilities.exists) { LOGGER_WARNING(call->session->messenger->log, "Session: %p Invalid capabilities on 'push'", (void *)call->session); call->error = MSI_E_INVALID_MESSAGE; goto FAILURE; } switch (call->state) { case MSI_CALL_ACTIVE: { /* Only act if capabilities changed */ if (call->peer_capabilities != msg->capabilities.value) { LOGGER_INFO(call->session->messenger->log, "Friend is changing capabilities to: %u", msg->capabilities.value); call->peer_capabilities = msg->capabilities.value; if (invoke_callback(call, MSI_ON_CAPABILITIES) == -1) { goto FAILURE; } } } break; case MSI_CALL_REQUESTING: { LOGGER_INFO(call->session->messenger->log, "Friend answered our call"); /* Call started */ call->peer_capabilities = msg->capabilities.value; call->state = MSI_CALL_ACTIVE; if (invoke_callback(call, MSI_ON_START) == -1) { goto FAILURE; } } break; /* Pushes during initialization state are ignored */ case MSI_CALL_INACTIVE: // fall-through case MSI_CALL_REQUESTED: { LOGGER_WARNING(call->session->messenger->log, "Ignoring invalid push"); } break; } return; FAILURE: send_error(call->session->messenger, call->friend_number, call->error); kill_call(call); } static void handle_pop(MSICall *call, const MSIMessage *msg) { assert(call); LOGGER_DEBUG(call->session->messenger->log, "Session: %p Handling 'pop', friend id: %d", (void *)call->session, call->friend_number); /* callback errors are ignored */ if (msg->error.exists) { LOGGER_WARNING(call->session->messenger->log, "Friend detected an error: %d", msg->error.value); call->error = msg->error.value; invoke_callback(call, MSI_ON_ERROR); } else { switch (call->state) { case MSI_CALL_INACTIVE: { LOGGER_ERROR(call->session->messenger->log, "Handling what should be impossible case"); abort(); } case MSI_CALL_ACTIVE: { /* Hangup */ LOGGER_INFO(call->session->messenger->log, "Friend hung up on us"); invoke_callback(call, MSI_ON_END); } break; case MSI_CALL_REQUESTING: { /* Reject */ LOGGER_INFO(call->session->messenger->log, "Friend rejected our call"); invoke_callback(call, MSI_ON_END); } break; case MSI_CALL_REQUESTED: { /* Cancel */ LOGGER_INFO(call->session->messenger->log, "Friend canceled call invite"); invoke_callback(call, MSI_ON_END); } break; } } kill_call(call); } static void handle_msi_packet(Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object) { LOGGER_DEBUG(m->log, "Got msi message"); MSISession *session = (MSISession *)object; MSIMessage msg; if (msg_parse_in(m->log, &msg, data, length) == -1) { LOGGER_WARNING(m->log, "Error parsing message"); send_error(m, friend_number, MSI_E_INVALID_MESSAGE); return; } LOGGER_DEBUG(m->log, "Successfully parsed message"); pthread_mutex_lock(session->mutex); MSICall *call = get_call(session, friend_number); if (call == nullptr) { if (msg.request.value != REQU_INIT) { send_error(m, friend_number, MSI_E_STRAY_MESSAGE); pthread_mutex_unlock(session->mutex); return; } call = new_call(session, friend_number); if (call == nullptr) { send_error(m, friend_number, MSI_E_SYSTEM); pthread_mutex_unlock(session->mutex); return; } } switch (msg.request.value) { case REQU_INIT: handle_init(call, &msg); break; case REQU_PUSH: handle_push(call, &msg); break; case REQU_POP: handle_pop(call, &msg); /* always kills the call */ break; } pthread_mutex_unlock(session->mutex); } c-toxcore-0.2.13/toxav/msi.h000066400000000000000000000070661415350724400156200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifndef C_TOXCORE_TOXAV_MSI_H #define C_TOXCORE_TOXAV_MSI_H #include "audio.h" #include "video.h" #include "../toxcore/Messenger.h" #include "../toxcore/logger.h" #include #include /** * Error codes. */ typedef enum MSIError { MSI_E_NONE, MSI_E_INVALID_MESSAGE, MSI_E_INVALID_PARAM, MSI_E_INVALID_STATE, MSI_E_STRAY_MESSAGE, MSI_E_SYSTEM, MSI_E_HANDLE, MSI_E_UNDISCLOSED, /* NOTE: must be last enum otherwise parsing will not work */ } MSIError; /** * Supported capabilities */ typedef enum MSICapabilities { MSI_CAP_S_AUDIO = 4, /* sending audio */ MSI_CAP_S_VIDEO = 8, /* sending video */ MSI_CAP_R_AUDIO = 16, /* receiving audio */ MSI_CAP_R_VIDEO = 32, /* receiving video */ } MSICapabilities; /** * Call state identifiers. */ typedef enum MSICallState { MSI_CALL_INACTIVE, /* Default */ MSI_CALL_ACTIVE, MSI_CALL_REQUESTING, /* when sending call invite */ MSI_CALL_REQUESTED, /* when getting call invite */ } MSICallState; /** * Callbacks ids that handle the states */ typedef enum MSICallbackID { MSI_ON_INVITE, /* Incoming call */ MSI_ON_START, /* Call (RTP transmission) started */ MSI_ON_END, /* Call that was active ended */ MSI_ON_ERROR, /* On protocol error */ MSI_ON_PEERTIMEOUT, /* Peer timed out; stop the call */ MSI_ON_CAPABILITIES, /* Peer requested capabilities change */ } MSICallbackID; /** * The call struct. Please do not modify outside msi.c */ typedef struct MSICall_s { struct MSISession_s *session; /* Session pointer */ MSICallState state; uint8_t peer_capabilities; /* Peer capabilities */ uint8_t self_capabilities; /* Self capabilities */ uint16_t peer_vfpsz; /* Video frame piece size */ uint32_t friend_number; /* Index of this call in MSISession */ MSIError error; /* Last error */ struct ToxAVCall_s *av_call; /* Pointer to av call handler */ struct MSICall_s *next; struct MSICall_s *prev; } MSICall; /** * Expected return on success is 0, if any other number is * returned the call is considered errored and will be handled * as such which means it will be terminated without any notice. */ typedef int msi_action_cb(void *av, MSICall *call); /** * Control session struct. Please do not modify outside msi.c */ typedef struct MSISession_s { /* Call handlers */ MSICall **calls; uint32_t calls_tail; uint32_t calls_head; void *av; Messenger *messenger; pthread_mutex_t mutex[1]; msi_action_cb *callbacks[7]; } MSISession; /** * Start the control session. */ MSISession *msi_new(Messenger *m); /** * Terminate control session. NOTE: all calls will be freed */ int msi_kill(MSISession *session, const Logger *log); /** * Callback setter. */ void msi_register_callback(MSISession *session, msi_action_cb *callback, MSICallbackID id); /** * Send invite request to friend_number. */ int msi_invite(MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities); /** * Hangup call. NOTE: `call` will be freed */ int msi_hangup(MSICall *call); /** * Answer call request. */ int msi_answer(MSICall *call, uint8_t capabilities); /** * Change capabilities of the call. */ int msi_change_capabilities(MSICall *call, uint8_t capabilities); #endif // C_TOXCORE_TOXAV_MSI_H c-toxcore-0.2.13/toxav/ring_buffer.c000066400000000000000000000037651415350724400173150ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. * Copyright © 2013 plutooo */ #include "ring_buffer.h" #include "../toxcore/ccompat.h" #include struct RingBuffer { uint16_t size; /* Max size */ uint16_t start; uint16_t end; void **data; }; bool rb_full(const RingBuffer *b) { return (b->end + 1) % b->size == b->start; } bool rb_empty(const RingBuffer *b) { return b->end == b->start; } /* * returns: NULL on success * input value "p" on FAILURE -> caller can free on failed rb_write */ void *rb_write(RingBuffer *b, void *p) { if (b == nullptr) { return p; } void *rc = nullptr; if ((b->end + 1) % b->size == b->start) { /* full */ rc = b->data[b->start]; } b->data[b->end] = p; b->end = (b->end + 1) % b->size; if (b->end == b->start) { b->start = (b->start + 1) % b->size; } return rc; } bool rb_read(RingBuffer *b, void **p) { if (b->end == b->start) { /* Empty */ *p = nullptr; return false; } *p = b->data[b->start]; b->start = (b->start + 1) % b->size; return true; } RingBuffer *rb_new(int size) { RingBuffer *buf = (RingBuffer *)calloc(sizeof(RingBuffer), 1); if (!buf) { return nullptr; } buf->size = size + 1; /* include empty elem */ buf->data = (void **)calloc(buf->size, sizeof(void *)); if (!buf->data) { free(buf); return nullptr; } return buf; } void rb_kill(RingBuffer *b) { if (b) { free(b->data); free(b); } } uint16_t rb_size(const RingBuffer *b) { if (rb_empty(b)) { return 0; } return b->end > b->start ? b->end - b->start : (b->size - b->start) + b->end; } uint16_t rb_data(const RingBuffer *b, void **dest) { uint16_t i = 0; for (; i < rb_size(b); ++i) { dest[i] = b->data[(b->start + i) % b->size]; } return i; } c-toxcore-0.2.13/toxav/ring_buffer.h000066400000000000000000000013441415350724400173110ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. * Copyright © 2013 plutooo */ #ifndef C_TOXCORE_TOXAV_RING_BUFFER_H #define C_TOXCORE_TOXAV_RING_BUFFER_H #include #include #ifdef __cplusplus extern "C" { #endif /* Ring buffer */ typedef struct RingBuffer RingBuffer; bool rb_full(const RingBuffer *b); bool rb_empty(const RingBuffer *b); void *rb_write(RingBuffer *b, void *p); bool rb_read(RingBuffer *b, void **p); RingBuffer *rb_new(int size); void rb_kill(RingBuffer *b); uint16_t rb_size(const RingBuffer *b); uint16_t rb_data(const RingBuffer *b, void **dest); #ifdef __cplusplus } #endif #endif // C_TOXCORE_TOXAV_RING_BUFFER_H c-toxcore-0.2.13/toxav/ring_buffer_test.cc000066400000000000000000000111461415350724400205070ustar00rootroot00000000000000#include "ring_buffer.h" #include #include #include #include namespace { template class TypedRingBuffer; template class TypedRingBuffer { public: explicit TypedRingBuffer(int size) : rb_(rb_new(size)) {} ~TypedRingBuffer() { rb_kill(rb_); } TypedRingBuffer(TypedRingBuffer const &) = delete; bool full() const { return rb_full(rb_); } bool empty() const { return rb_empty(rb_); } T *write(T *p) { return static_cast(rb_write(rb_, p)); } bool read(T **p) { void *vp; bool res = rb_read(rb_, &vp); *p = static_cast(vp); return res; } uint16_t size() const { return rb_size(rb_); } uint16_t data(T **dest) const { std::vector vdest(size()); uint16_t res = rb_data(rb_, vdest.data()); for (uint16_t i = 0; i < size(); i++) { dest[i] = static_cast(vdest.at(i)); } return res; } bool contains(T *p) const { std::vector elts(size()); data(elts.data()); return std::find(elts.begin(), elts.end(), p) != elts.end(); } bool ok() const { return rb_ != nullptr; } private: RingBuffer *rb_; }; TEST(RingBuffer, EmptyBufferReportsEmpty) { TypedRingBuffer rb(10); ASSERT_TRUE(rb.ok()); EXPECT_TRUE(rb.empty()); } TEST(RingBuffer, EmptyBufferReportsNotFull) { TypedRingBuffer rb(10); ASSERT_TRUE(rb.ok()); EXPECT_FALSE(rb.full()); } TEST(RingBuffer, ZeroSizedRingBufferIsBothEmptyAndFull) { TypedRingBuffer rb(0); ASSERT_TRUE(rb.ok()); EXPECT_TRUE(rb.empty()); EXPECT_TRUE(rb.full()); } TEST(RingBuffer, WritingMakesBufferNotEmpty) { TypedRingBuffer rb(2); ASSERT_TRUE(rb.ok()); int value0 = 123; rb.write(&value0); EXPECT_FALSE(rb.empty()); } TEST(RingBuffer, WritingOneElementMakesBufferNotFull) { TypedRingBuffer rb(2); ASSERT_TRUE(rb.ok()); int value0 = 123; rb.write(&value0); EXPECT_FALSE(rb.full()); } TEST(RingBuffer, WritingAllElementsMakesBufferFull) { TypedRingBuffer rb(2); ASSERT_TRUE(rb.ok()); int value0 = 123; int value1 = 231; rb.write(&value0); rb.write(&value1); EXPECT_TRUE(rb.full()); } TEST(RingBuffer, ReadingElementFromFullBufferMakesItNotFull) { TypedRingBuffer rb(2); ASSERT_TRUE(rb.ok()); int value0 = 123; int value1 = 231; rb.write(&value0); rb.write(&value1); EXPECT_TRUE(rb.full()); int *retrieved; // Reading deletes the element. EXPECT_TRUE(rb.read(&retrieved)); EXPECT_FALSE(rb.full()); } TEST(RingBuffer, ZeroSizeBufferCanBeWrittenToOnce) { TypedRingBuffer rb(0); ASSERT_TRUE(rb.ok()); int value0 = 123; // Strange behaviour: we can write one element to a 0-size buffer. EXPECT_EQ(nullptr, rb.write(&value0)); EXPECT_EQ(&value0, rb.write(&value0)); int *retrieved = nullptr; // But then we can't read it. EXPECT_FALSE(rb.read(&retrieved)); EXPECT_EQ(nullptr, retrieved); } TEST(RingBuffer, ReadingFromEmptyBufferFails) { TypedRingBuffer rb(2); ASSERT_TRUE(rb.ok()); int *retrieved; EXPECT_FALSE(rb.read(&retrieved)); } TEST(RingBuffer, WritingToBufferWhenFullOverwritesBeginning) { TypedRingBuffer rb(2); ASSERT_TRUE(rb.ok()); int value0 = 123; int value1 = 231; int value2 = 312; int value3 = 432; EXPECT_EQ(nullptr, rb.write(&value0)); EXPECT_EQ(nullptr, rb.write(&value1)); EXPECT_TRUE(rb.contains(&value0)); EXPECT_TRUE(rb.contains(&value1)); // Adding another element evicts the first element. EXPECT_EQ(&value0, rb.write(&value2)); EXPECT_FALSE(rb.contains(&value0)); EXPECT_TRUE(rb.contains(&value2)); // Adding another evicts the second. EXPECT_EQ(&value1, rb.write(&value3)); EXPECT_FALSE(rb.contains(&value1)); EXPECT_TRUE(rb.contains(&value3)); } TEST(RingBuffer, SizeIsNumberOfElementsInBuffer) { TypedRingBuffer rb(10); ASSERT_TRUE(rb.ok()); int value0 = 123; EXPECT_EQ(rb.size(), 0); rb.write(&value0); EXPECT_EQ(rb.size(), 1); rb.write(&value0); EXPECT_EQ(rb.size(), 2); rb.write(&value0); EXPECT_EQ(rb.size(), 3); rb.write(&value0); EXPECT_EQ(rb.size(), 4); int *retrieved; rb.read(&retrieved); EXPECT_EQ(rb.size(), 3); rb.read(&retrieved); EXPECT_EQ(rb.size(), 2); rb.read(&retrieved); EXPECT_EQ(rb.size(), 1); rb.read(&retrieved); EXPECT_EQ(rb.size(), 0); } TEST(RingBuffer, SizeIsLimitedByMaxSize) { TypedRingBuffer rb(4); ASSERT_TRUE(rb.ok()); int value0 = 123; rb.write(&value0); rb.write(&value0); rb.write(&value0); rb.write(&value0); EXPECT_EQ(rb.size(), 4); // Add one more. rb.write(&value0); // Still size is 4. EXPECT_EQ(rb.size(), 4); } } // namespace c-toxcore-0.2.13/toxav/rtp.c000066400000000000000000000745331415350724400156330ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "rtp.h" #include #include #include #include #include "bwcontroller.h" #include "../toxcore/Messenger.h" #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/util.h" /** * The number of milliseconds we want to keep a keyframe in the buffer for, * even though there are no free slots for incoming frames. */ #define VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS 15 /* * return -1 on failure, 0 on success * */ static int rtp_send_custom_lossy_packet(Tox *tox, int32_t friendnumber, const uint8_t *data, uint32_t length) { Tox_Err_Friend_Custom_Packet error; tox_friend_send_lossy_packet(tox, friendnumber, data, (size_t)length, &error); if (error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK) { return 0; } return -1; } // allocate_len is NOT including header! static struct RTPMessage *new_message(const struct RTPHeader *header, size_t allocate_len, const uint8_t *data, uint16_t data_length) { assert(allocate_len >= data_length); struct RTPMessage *msg = (struct RTPMessage *)calloc(1, sizeof(struct RTPMessage) + allocate_len); if (msg == nullptr) { return nullptr; } msg->len = data_length; // result without header msg->header = *header; memcpy(msg->data, data, msg->len); return msg; } /** * Instruct the caller to clear slot 0. */ #define GET_SLOT_RESULT_DROP_OLDEST_SLOT (-1) /** * Instruct the caller to drop the incoming packet. */ #define GET_SLOT_RESULT_DROP_INCOMING (-2) /** * Find the next free slot in work_buffer for the incoming data packet. * * - If the data packet belongs to a frame that's already in the work_buffer then * use that slot. * - If there is no free slot return GET_SLOT_RESULT_DROP_OLDEST_SLOT. * - If the data packet is too old return GET_SLOT_RESULT_DROP_INCOMING. * * If there is a keyframe being assembled in slot 0, keep it a bit longer and * do not kick it out right away if all slots are full instead kick out the new * incoming interframe. */ static int8_t get_slot(const Logger *log, struct RTPWorkBufferList *wkbl, bool is_keyframe, const struct RTPHeader *header, bool is_multipart) { if (is_multipart) { // This RTP message is part of a multipart frame, so we try to find an // existing slot with the previous parts of the frame in it. for (uint8_t i = 0; i < wkbl->next_free_entry; ++i) { const struct RTPWorkBuffer *slot = &wkbl->work_buffer[i]; if ((slot->buf->header.sequnum == header->sequnum) && (slot->buf->header.timestamp == header->timestamp)) { // Sequence number and timestamp match, so this slot belongs to // the same frame. // // In reality, these will almost certainly either both match or // both not match. Only if somehow there were 65535 frames // between, the timestamp will matter. return i; } } } // The message may or may not be part of a multipart frame. // // If it is part of a multipart frame, then this is an entirely new frame // for which we did not have a slot *or* the frame is so old that its slot // has been evicted by now. // // |----------- time -----------> // _________________ // slot 0 | | // ----------------- // _________________ // slot 1 | | // ----------------- // ____________ // slot 2 | | -> frame too old, drop // ------------ // // // // |----------- time -----------> // _________________ // slot 0 | | // ----------------- // _________________ // slot 1 | | // ----------------- // ____________ // slot 2 | | -> ok, start filling in a new slot // ------------ // If there is a free slot: if (wkbl->next_free_entry < USED_RTP_WORKBUFFER_COUNT) { // If there is at least one filled slot: if (wkbl->next_free_entry > 0) { // Get the most recently filled slot. const struct RTPWorkBuffer *slot = &wkbl->work_buffer[wkbl->next_free_entry - 1]; // If the incoming packet is older than our newest slot, drop it. // This is the first situation in the above diagram. if (slot->buf->header.timestamp > header->timestamp) { LOGGER_DEBUG(log, "workbuffer:2:timestamp too old"); return GET_SLOT_RESULT_DROP_INCOMING; } } // Not all slots are filled, and the packet is newer than our most // recent slot, so it's a new frame we want to start assembling. This is // the second situation in the above diagram. return wkbl->next_free_entry; } // If the incoming frame is a key frame, then stop assembling the oldest // slot, regardless of whether there was a keyframe in that or not. if (is_keyframe) { return GET_SLOT_RESULT_DROP_OLDEST_SLOT; } // The incoming slot is not a key frame, so we look at slot 0 to see what to // do next. const struct RTPWorkBuffer *slot = &wkbl->work_buffer[0]; // The incoming frame is not a key frame, but the existing slot 0 is also // not a keyframe, so we stop assembling the existing frame and make space // for the new one. if (!slot->is_keyframe) { return GET_SLOT_RESULT_DROP_OLDEST_SLOT; } // If this key frame is fully received, we also stop assembling and clear // slot 0. This also means sending the frame to the decoder. if (slot->received_len == slot->buf->header.data_length_full) { return GET_SLOT_RESULT_DROP_OLDEST_SLOT; } // This is a key frame, not fully received yet, but it's already much older // than the incoming frame, so we stop assembling it and send whatever part // we did receive to the decoder. if (slot->buf->header.timestamp + VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS <= header->timestamp) { return GET_SLOT_RESULT_DROP_OLDEST_SLOT; } // This is a key frame, it's not too old yet, so we keep it in its slot for // a little longer. LOGGER_INFO(log, "keep KEYFRAME in workbuffer"); return GET_SLOT_RESULT_DROP_INCOMING; } /** * Returns an assembled frame (as much data as we currently have for this frame, * some pieces may be missing) * * If there are no frames ready, we return NULL. If this function returns * non-NULL, it transfers ownership of the message to the caller, i.e. the * caller is responsible for storing it elsewhere or calling free(). */ static struct RTPMessage *process_frame(const Logger *log, struct RTPWorkBufferList *wkbl, uint8_t slot_id) { assert(wkbl->next_free_entry >= 0); if (wkbl->next_free_entry == 0) { // There are no frames in any slot. return nullptr; } // Slot 0 contains a key frame, slot_id points at an interframe that is // relative to that key frame, so we don't use it yet. if (wkbl->work_buffer[0].is_keyframe && slot_id != 0) { LOGGER_DEBUG(log, "process_frame:KEYFRAME waiting in slot 0"); return nullptr; } // Either slot_id is 0 and slot 0 is a key frame, or there is no key frame // in slot 0 (and slot_id is anything). struct RTPWorkBuffer *const slot = &wkbl->work_buffer[slot_id]; // Move ownership of the frame out of the slot into m_new. struct RTPMessage *const m_new = slot->buf; slot->buf = nullptr; assert(wkbl->next_free_entry >= 1); if (slot_id != wkbl->next_free_entry - 1) { // The slot is not the last slot, so we created a gap. We move all the // entries after it one step up. for (uint8_t i = slot_id; i < wkbl->next_free_entry - 1; ++i) { // Move entry (i+1) into entry (i). wkbl->work_buffer[i] = wkbl->work_buffer[i + 1]; } } // We now have a free entry at the end of the array. --wkbl->next_free_entry; // Clear the newly freed entry. const struct RTPWorkBuffer empty = {0}; wkbl->work_buffer[wkbl->next_free_entry] = empty; // Move ownership of the frame to the caller. return m_new; } /** * @param log A logger. * @param wkbl The list of in-progress frames, i.e. all the slots. * @param slot_id The slot we want to fill the data into. * @param is_keyframe Whether the data is part of a key frame. * @param header The RTP header from the incoming packet. * @param incoming_data The pure payload without header. * @param incoming_data_length The length in bytes of the incoming data payload. */ static bool fill_data_into_slot(const Logger *log, struct RTPWorkBufferList *wkbl, const uint8_t slot_id, bool is_keyframe, const struct RTPHeader *header, const uint8_t *incoming_data, uint16_t incoming_data_length) { // We're either filling the data into an existing slot, or in a new one that // is the next free entry. assert(slot_id <= wkbl->next_free_entry); struct RTPWorkBuffer *const slot = &wkbl->work_buffer[slot_id]; assert(header != nullptr); assert(is_keyframe == (bool)(header->flags & RTP_KEY_FRAME)); if (slot->received_len == 0) { assert(slot->buf == nullptr); // No data for this slot has been received, yet, so we create a new // message for it with enough memory for the entire frame. struct RTPMessage *msg = (struct RTPMessage *)calloc(1, sizeof(struct RTPMessage) + header->data_length_full); if (msg == nullptr) { LOGGER_ERROR(log, "Out of memory while trying to allocate for frame of size %u", (unsigned)header->data_length_full); // Out of memory: throw away the incoming data. return false; } // Unused in the new video receiving code, as it's 16 bit and can't hold // the full length of large frames. Instead, we use slot->received_len. msg->len = 0; msg->header = *header; slot->buf = msg; slot->is_keyframe = is_keyframe; slot->received_len = 0; assert(wkbl->next_free_entry < USED_RTP_WORKBUFFER_COUNT); ++wkbl->next_free_entry; } // We already checked this when we received the packet, but we rely on it // here, so assert again. assert(header->offset_full < header->data_length_full); // Copy the incoming chunk of data into the correct position in the full // frame data array. memcpy( slot->buf->data + header->offset_full, incoming_data, incoming_data_length ); // Update the total received length of this slot. slot->received_len += incoming_data_length; // Update received length also in the header of the message, for later use. slot->buf->header.received_length_full = slot->received_len; return slot->received_len == header->data_length_full; } static void update_bwc_values(const Logger *log, RTPSession *session, const struct RTPMessage *msg) { if (session->first_packets_counter < DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT) { ++session->first_packets_counter; } else { uint32_t data_length_full = msg->header.data_length_full; // without header uint32_t received_length_full = msg->header.received_length_full; // without header bwc_add_recv(session->bwc, data_length_full); if (received_length_full < data_length_full) { LOGGER_DEBUG(log, "BWC: full length=%u received length=%d", data_length_full, received_length_full); bwc_add_lost(session->bwc, (data_length_full - received_length_full)); } } } /** * Handle a single RTP video packet. * * The packet may or may not be part of a multipart frame. This function will * find out and handle it appropriately. * * @param session The current RTP session with: * * session->mcb == vc_queue_message() // this function is called from here * session->mp == struct RTPMessage * * session->cs == call->video.second // == VCSession created by vc_new() call * * @param header The RTP header deserialised from the packet. * @param incoming_data The packet data *not* header, i.e. this is the actual * payload. * @param incoming_data_length The packet length *not* including header, i.e. * this is the actual payload length. * @param log A logger. * * @return -1 on error, 0 on success. */ static int handle_video_packet(RTPSession *session, const struct RTPHeader *header, const uint8_t *incoming_data, uint16_t incoming_data_length, const Logger *log) { // Full frame length in bytes. The frame may be split into multiple packets, // but this value is the complete assembled frame size. const uint32_t full_frame_length = header->data_length_full; // Current offset in the frame. If this is the first packet of a multipart // frame or it's not a multipart frame, then this value is 0. const uint32_t offset = header->offset_full; // without header // The sender tells us whether this is a key frame. const bool is_keyframe = (header->flags & RTP_KEY_FRAME) != 0; LOGGER_DEBUG(log, "-- handle_video_packet -- full lens=%u len=%u offset=%u is_keyframe=%s", (unsigned)incoming_data_length, (unsigned)full_frame_length, (unsigned)offset, is_keyframe ? "K" : "."); LOGGER_DEBUG(log, "wkbl->next_free_entry:003=%d", session->work_buffer_list->next_free_entry); const bool is_multipart = full_frame_length != incoming_data_length; /* The message was sent in single part */ int8_t slot_id = get_slot(log, session->work_buffer_list, is_keyframe, header, is_multipart); LOGGER_DEBUG(log, "slot num=%d", slot_id); // get_slot told us to drop the packet, so we ignore it. if (slot_id == GET_SLOT_RESULT_DROP_INCOMING) { return -1; } // get_slot said there is no free slot. if (slot_id == GET_SLOT_RESULT_DROP_OLDEST_SLOT) { LOGGER_DEBUG(log, "there was no free slot, so we process the oldest frame"); // We now own the frame. struct RTPMessage *m_new = process_frame(log, session->work_buffer_list, 0); // The process_frame function returns NULL if there is no slot 0, i.e. // the work buffer list is completely empty. It can't be empty, because // get_slot just told us it's full, so process_frame must return non-null. assert(m_new != nullptr); LOGGER_DEBUG(log, "-- handle_video_packet -- CALLBACK-001a b0=%d b1=%d", (int)m_new->data[0], (int)m_new->data[1]); update_bwc_values(log, session, m_new); // Pass ownership of m_new to the callback. session->mcb(session->m->mono_time, session->cs, m_new); // Now we no longer own m_new. m_new = nullptr; // Now we must have a free slot, so we either get that slot, i.e. >= 0, // or get told to drop the incoming packet if it's too old. slot_id = get_slot(log, session->work_buffer_list, is_keyframe, header, /* is_multipart */false); if (slot_id == GET_SLOT_RESULT_DROP_INCOMING) { // The incoming frame is too old, so we drop it. return -1; } } // We must have a valid slot here. assert(slot_id >= 0); LOGGER_DEBUG(log, "fill_data_into_slot.1"); // fill in this part into the slot buffer at the correct offset if (!fill_data_into_slot( log, session->work_buffer_list, slot_id, is_keyframe, header, incoming_data, incoming_data_length)) { // Memory allocation failed. Return error. return -1; } struct RTPMessage *m_new = process_frame(log, session->work_buffer_list, slot_id); if (m_new) { LOGGER_DEBUG(log, "-- handle_video_packet -- CALLBACK-003a b0=%d b1=%d", (int)m_new->data[0], (int)m_new->data[1]); update_bwc_values(log, session, m_new); session->mcb(session->m->mono_time, session->cs, m_new); m_new = nullptr; } return 0; } /** * @return -1 on error, 0 on success. */ static int handle_rtp_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) { RTPSession *session = (RTPSession *)object; if (!session || length < RTP_HEADER_SIZE + 1) { LOGGER_WARNING(m->log, "No session or invalid length of received buffer!"); return -1; } // Get the packet type. const uint8_t packet_type = data[0]; ++data; --length; // Unpack the header. struct RTPHeader header; rtp_header_unpack(data, &header); if (header.pt != packet_type % 128) { LOGGER_WARNING(m->log, "RTPHeader packet type and Tox protocol packet type did not agree: %d != %d", header.pt, packet_type % 128); return -1; } if (header.pt != session->payload_type % 128) { LOGGER_WARNING(m->log, "RTPHeader packet type does not match this session's payload type: %d != %d", header.pt, session->payload_type % 128); return -1; } if (header.flags & RTP_LARGE_FRAME && header.offset_full >= header.data_length_full) { LOGGER_ERROR(m->log, "Invalid video packet: frame offset (%u) >= full frame length (%u)", (unsigned)header.offset_full, (unsigned)header.data_length_full); return -1; } if (header.offset_lower >= header.data_length_lower) { LOGGER_ERROR(m->log, "Invalid old protocol video packet: frame offset (%u) >= full frame length (%u)", (unsigned)header.offset_lower, (unsigned)header.data_length_lower); return -1; } LOGGER_DEBUG(m->log, "header.pt %d, video %d", (uint8_t)header.pt, (RTP_TYPE_VIDEO % 128)); // The sender uses the new large-frame capable protocol and is sending a // video packet. if ((header.flags & RTP_LARGE_FRAME) && header.pt == (RTP_TYPE_VIDEO % 128)) { return handle_video_packet(session, &header, data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE, m->log); } // everything below here is for the old 16 bit protocol ------------------ if (header.data_length_lower == length - RTP_HEADER_SIZE) { /* The message is sent in single part */ /* Message is not late; pick up the latest parameters */ session->rsequnum = header.sequnum; session->rtimestamp = header.timestamp; bwc_add_recv(session->bwc, length); /* Invoke processing of active multiparted message */ if (session->mp) { session->mcb(session->m->mono_time, session->cs, session->mp); session->mp = nullptr; } /* The message came in the allowed time; */ return session->mcb(session->m->mono_time, session->cs, new_message(&header, length - RTP_HEADER_SIZE, data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE)); } /* The message is sent in multiple parts */ if (session->mp) { /* There are 2 possible situations in this case: * 1) being that we got the part of already processing message. * 2) being that we got the part of a new/old message. * * We handle them differently as we only allow a single multiparted * processing message */ if (session->mp->header.sequnum == header.sequnum && session->mp->header.timestamp == header.timestamp) { /* First case */ /* Make sure we have enough allocated memory */ if (session->mp->header.data_length_lower - session->mp->len < length - RTP_HEADER_SIZE || session->mp->header.data_length_lower <= header.offset_lower) { /* There happened to be some corruption on the stream; * continue wihtout this part */ return 0; } memcpy(session->mp->data + header.offset_lower, data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE); session->mp->len += length - RTP_HEADER_SIZE; bwc_add_recv(session->bwc, length); if (session->mp->len == session->mp->header.data_length_lower) { /* Received a full message; now push it for the further * processing. */ session->mcb(session->m->mono_time, session->cs, session->mp); session->mp = nullptr; } } else { /* Second case */ if (session->mp->header.timestamp > header.timestamp) { /* The received message part is from the old message; * discard it. */ return 0; } /* Push the previous message for processing */ session->mcb(session->m->mono_time, session->cs, session->mp); session->mp = nullptr; goto NEW_MULTIPARTED; } } else { /* In this case threat the message as if it was received in order */ /* This is also a point for new multiparted messages */ NEW_MULTIPARTED: /* Message is not late; pick up the latest parameters */ session->rsequnum = header.sequnum; session->rtimestamp = header.timestamp; bwc_add_recv(session->bwc, length); /* Store message. */ session->mp = new_message(&header, header.data_length_lower, data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE); if (session->mp != nullptr) { memmove(session->mp->data + header.offset_lower, session->mp->data, session->mp->len); } else { LOGGER_WARNING(m->log, "new_message() returned a null pointer"); return -1; } } return 0; } size_t rtp_header_pack(uint8_t *const rdata, const struct RTPHeader *header) { uint8_t *p = rdata; *p = (header->ve & 3) << 6 | (header->pe & 1) << 5 | (header->xe & 1) << 4 | (header->cc & 0xf); ++p; *p = (header->ma & 1) << 7 | (header->pt & 0x7f); ++p; p += net_pack_u16(p, header->sequnum); p += net_pack_u32(p, header->timestamp); p += net_pack_u32(p, header->ssrc); p += net_pack_u64(p, header->flags); p += net_pack_u32(p, header->offset_full); p += net_pack_u32(p, header->data_length_full); p += net_pack_u32(p, header->received_length_full); for (size_t i = 0; i < RTP_PADDING_FIELDS; ++i) { p += net_pack_u32(p, 0); } p += net_pack_u16(p, header->offset_lower); p += net_pack_u16(p, header->data_length_lower); assert(p == rdata + RTP_HEADER_SIZE); return p - rdata; } size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header) { const uint8_t *p = data; header->ve = (*p >> 6) & 3; header->pe = (*p >> 5) & 1; header->xe = (*p >> 4) & 1; header->cc = *p & 0xf; ++p; header->ma = (*p >> 7) & 1; header->pt = *p & 0x7f; ++p; p += net_unpack_u16(p, &header->sequnum); p += net_unpack_u32(p, &header->timestamp); p += net_unpack_u32(p, &header->ssrc); p += net_unpack_u64(p, &header->flags); p += net_unpack_u32(p, &header->offset_full); p += net_unpack_u32(p, &header->data_length_full); p += net_unpack_u32(p, &header->received_length_full); p += sizeof(uint32_t) * RTP_PADDING_FIELDS; p += net_unpack_u16(p, &header->offset_lower); p += net_unpack_u16(p, &header->data_length_lower); assert(p == data + RTP_HEADER_SIZE); return p - data; } RTPSession *rtp_new(int payload_type, Messenger *m, Tox *tox, uint32_t friendnumber, BWController *bwc, void *cs, rtp_m_cb *mcb) { assert(mcb != nullptr); assert(cs != nullptr); assert(m != nullptr); RTPSession *session = (RTPSession *)calloc(1, sizeof(RTPSession)); if (!session) { LOGGER_WARNING(m->log, "Alloc failed! Program might misbehave!"); return nullptr; } session->work_buffer_list = (struct RTPWorkBufferList *)calloc(1, sizeof(struct RTPWorkBufferList)); if (session->work_buffer_list == nullptr) { LOGGER_ERROR(m->log, "out of memory while allocating work buffer list"); free(session); return nullptr; } // First entry is free. session->work_buffer_list->next_free_entry = 0; session->ssrc = payload_type == RTP_TYPE_VIDEO ? 0 : random_u32(); session->payload_type = payload_type; session->m = m; session->tox = tox; session->friend_number = friendnumber; // set NULL just in case session->mp = nullptr; session->first_packets_counter = 1; /* Also set payload type as prefix */ session->bwc = bwc; session->cs = cs; session->mcb = mcb; if (-1 == rtp_allow_receiving(session)) { LOGGER_WARNING(m->log, "Failed to start rtp receiving mode"); free(session->work_buffer_list); free(session); return nullptr; } return session; } void rtp_kill(RTPSession *session) { if (!session) { return; } LOGGER_DEBUG(session->m->log, "Terminated RTP session: %p", (void *)session); rtp_stop_receiving(session); LOGGER_DEBUG(session->m->log, "Terminated RTP session V3 work_buffer_list->next_free_entry: %d", (int)session->work_buffer_list->next_free_entry); free(session->work_buffer_list); free(session); } int rtp_allow_receiving(RTPSession *session) { if (session == nullptr) { return -1; } if (m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, handle_rtp_packet, session) == -1) { LOGGER_WARNING(session->m->log, "Failed to register rtp receive handler"); return -1; } LOGGER_DEBUG(session->m->log, "Started receiving on session: %p", (void *)session); return 0; } int rtp_stop_receiving(RTPSession *session) { if (session == nullptr) { return -1; } m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, nullptr, nullptr); LOGGER_DEBUG(session->m->log, "Stopped receiving on session: %p", (void *)session); return 0; } /** * @param data is raw vpx data. * @param length is the length of the raw data. */ int rtp_send_data(RTPSession *session, const uint8_t *data, uint32_t length, bool is_keyframe, const Logger *log) { if (!session) { LOGGER_ERROR(log, "No session!"); return -1; } struct RTPHeader header = {0}; header.ve = 2; // this is unused in toxav header.pe = 0; header.xe = 0; header.cc = 0; header.ma = 0; header.pt = session->payload_type % 128; header.sequnum = session->sequnum; header.timestamp = current_time_monotonic(session->m->mono_time); header.ssrc = session->ssrc; header.offset_lower = 0; // here the highest bits gets stripped anyway, no need to do keyframe bit magic here! header.data_length_lower = length; if (session->payload_type == RTP_TYPE_VIDEO) { header.flags = RTP_LARGE_FRAME; } uint16_t length_safe = (uint16_t)length; if (length > UINT16_MAX) { length_safe = UINT16_MAX; } header.data_length_lower = length_safe; header.data_length_full = length; // without header header.offset_lower = 0; header.offset_full = 0; if (is_keyframe) { header.flags |= RTP_KEY_FRAME; } VLA(uint8_t, rdata, length + RTP_HEADER_SIZE + 1); memset(rdata, 0, SIZEOF_VLA(rdata)); rdata[0] = session->payload_type; // packet id == payload_type if (MAX_CRYPTO_DATA_SIZE > (length + RTP_HEADER_SIZE + 1)) { /** * The length is lesser than the maximum allowed length (including header) * Send the packet in single piece. */ rtp_header_pack(rdata + 1, &header); memcpy(rdata + 1 + RTP_HEADER_SIZE, data, length); if (-1 == rtp_send_custom_lossy_packet(session->tox, session->friend_number, rdata, SIZEOF_VLA(rdata))) { const char *netstrerror = net_new_strerror(net_error()); LOGGER_WARNING(session->m->log, "RTP send failed (len: %u)! std error: %s, net error: %s", (unsigned)SIZEOF_VLA(rdata), strerror(errno), netstrerror); net_kill_strerror(netstrerror); } } else { /** * The length is greater than the maximum allowed length (including header) * Send the packet in multiple pieces. */ uint32_t sent = 0; uint16_t piece = MAX_CRYPTO_DATA_SIZE - (RTP_HEADER_SIZE + 1); while ((length - sent) + RTP_HEADER_SIZE + 1 > MAX_CRYPTO_DATA_SIZE) { rtp_header_pack(rdata + 1, &header); memcpy(rdata + 1 + RTP_HEADER_SIZE, data + sent, piece); if (-1 == rtp_send_custom_lossy_packet(session->tox, session->friend_number, rdata, piece + RTP_HEADER_SIZE + 1)) { const char *netstrerror = net_new_strerror(net_error()); LOGGER_WARNING(session->m->log, "RTP send failed (len: %d)! std error: %s, net error: %s", piece + RTP_HEADER_SIZE + 1, strerror(errno), netstrerror); net_kill_strerror(netstrerror); } sent += piece; header.offset_lower = sent; header.offset_full = sent; // raw data offset, without any header } /* Send remaining */ piece = length - sent; if (piece) { rtp_header_pack(rdata + 1, &header); memcpy(rdata + 1 + RTP_HEADER_SIZE, data + sent, piece); if (-1 == rtp_send_custom_lossy_packet(session->tox, session->friend_number, rdata, piece + RTP_HEADER_SIZE + 1)) { const char *netstrerror = net_new_strerror(net_error()); LOGGER_WARNING(session->m->log, "RTP send failed (len: %d)! std error: %s, net error: %s", piece + RTP_HEADER_SIZE + 1, strerror(errno), netstrerror); net_kill_strerror(netstrerror); } } } ++session->sequnum; return 0; } c-toxcore-0.2.13/toxav/rtp.h000066400000000000000000000134641415350724400156340ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifndef C_TOXCORE_TOXAV_RTP_H #define C_TOXCORE_TOXAV_RTP_H #include "bwcontroller.h" #include "../toxcore/Messenger.h" #include "../toxcore/logger.h" #include #ifdef __cplusplus extern "C" { #endif #ifndef TOX_DEFINED #define TOX_DEFINED typedef struct Tox Tox; #endif /* TOX_DEFINED */ /** * RTPHeader serialised size in bytes. */ #define RTP_HEADER_SIZE 80 /** * Number of 32 bit padding fields between \ref RTPHeader::offset_lower and * everything before it. */ #define RTP_PADDING_FIELDS 11 /** * Payload type identifier. Also used as rtp callback prefix. */ typedef enum RTP_Type { RTP_TYPE_AUDIO = 192, RTP_TYPE_VIDEO = 193, } RTP_Type; /** * A bit mask (up to 64 bits) specifying features of the current frame affecting * the behaviour of the decoder. */ typedef enum RTPFlags { /** * Support frames larger than 64KiB. The full 32 bit length and offset are * set in \ref RTPHeader::data_length_full and \ref RTPHeader::offset_full. */ RTP_LARGE_FRAME = 1 << 0, /** * Whether the packet is part of a key frame. */ RTP_KEY_FRAME = 1 << 1, } RTPFlags; struct RTPHeader { /* Standard RTP header */ unsigned ve: 2; /* Version has only 2 bits! */ unsigned pe: 1; /* Padding */ unsigned xe: 1; /* Extra header */ unsigned cc: 4; /* Contributing sources count */ unsigned ma: 1; /* Marker */ unsigned pt: 7; /* Payload type */ uint16_t sequnum; uint32_t timestamp; uint32_t ssrc; /* Non-standard Tox-specific fields */ /** * Bit mask of \ref RTPFlags setting features of the current frame. */ uint64_t flags; /** * The full 32 bit data offset of the current data chunk. The \ref * offset_lower data member contains the lower 16 bits of this value. For * frames smaller than 64KiB, \ref offset_full and \ref offset_lower are * equal. */ uint32_t offset_full; /** * The full 32 bit payload length without header and packet id. */ uint32_t data_length_full; /** * Only the receiver uses this field (why do we have this?). */ uint32_t received_length_full; /** * Data offset of the current part (lower bits). */ uint16_t offset_lower; /** * Total message length (lower bits). */ uint16_t data_length_lower; }; struct RTPMessage { /** * This is used in the old code that doesn't deal with large frames, i.e. * the audio code or receiving code for old 16 bit messages. We use it to * record the number of bytes received so far in a multi-part message. The * multi-part message in the old code is stored in \ref RTPSession::mp. */ uint16_t len; struct RTPHeader header; uint8_t data[]; }; #define USED_RTP_WORKBUFFER_COUNT 3 /** * One slot in the work buffer list. Represents one frame that is currently * being assembled. */ struct RTPWorkBuffer { /** * Whether this slot contains a key frame. This is true iff * `buf->header.flags & RTP_KEY_FRAME`. */ bool is_keyframe; /** * The number of bytes received so far, regardless of which pieces. I.e. we * could have received the first 1000 bytes and the last 1000 bytes with * 4000 bytes in the middle still to come, and this number would be 2000. */ uint32_t received_len; /** * The message currently being assembled. */ struct RTPMessage *buf; }; struct RTPWorkBufferList { int8_t next_free_entry; struct RTPWorkBuffer work_buffer[USED_RTP_WORKBUFFER_COUNT]; }; #define DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT 10 typedef int rtp_m_cb(Mono_Time *mono_time, void *cs, struct RTPMessage *msg); /** * RTP control session. */ typedef struct RTPSession { uint8_t payload_type; uint16_t sequnum; /* Sending sequence number */ uint16_t rsequnum; /* Receiving sequence number */ uint32_t rtimestamp; uint32_t ssrc; // this seems to be unused!? struct RTPMessage *mp; /* Expected parted message */ struct RTPWorkBufferList *work_buffer_list; uint8_t first_packets_counter; /* dismiss first few lost video packets */ Messenger *m; Tox *tox; uint32_t friend_number; BWController *bwc; void *cs; rtp_m_cb *mcb; } RTPSession; /** * Serialise an RTPHeader to bytes to be sent over the network. * * @param rdata A byte array of length RTP_HEADER_SIZE. Does not need to be * initialised. All RTP_HEADER_SIZE bytes will be initialised after a call * to this function. * @param header The RTPHeader to serialise. */ size_t rtp_header_pack(uint8_t *rdata, const struct RTPHeader *header); /** * Deserialise an RTPHeader from bytes received over the network. * * @param data A byte array of length RTP_HEADER_SIZE. * @param header The RTPHeader to write the unpacked values to. */ size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header); RTPSession *rtp_new(int payload_type, Messenger *m, Tox *tox, uint32_t friendnumber, BWController *bwc, void *cs, rtp_m_cb *mcb); void rtp_kill(RTPSession *session); int rtp_allow_receiving(RTPSession *session); int rtp_stop_receiving(RTPSession *session); /** * Send a frame of audio or video data, chunked in \ref RTPMessage instances. * * @param session The A/V session to send the data for. * @param data A byte array of length \p length. * @param length The number of bytes to send from @p data. * @param is_keyframe Whether this video frame is a key frame. If it is an * audio frame, this parameter is ignored. */ int rtp_send_data(RTPSession *session, const uint8_t *data, uint32_t length, bool is_keyframe, const Logger *log); #ifdef __cplusplus } // extern "C" #endif #endif // C_TOXCORE_TOXAV_RTP_H c-toxcore-0.2.13/toxav/rtp_test.cc000066400000000000000000000043171415350724400170260ustar00rootroot00000000000000#include "rtp.h" #include #include "../toxcore/crypto_core.h" namespace { RTPHeader random_header() { return { random_u16(), random_u16(), random_u16(), random_u16(), random_u16(), random_u16(), random_u16(), random_u32(), random_u32(), random_u64(), random_u32(), random_u32(), random_u32(), random_u16(), random_u16(), }; } TEST(Rtp, Deserialisation) { RTPHeader const header = random_header(); uint8_t rdata[RTP_HEADER_SIZE]; EXPECT_EQ(rtp_header_pack(rdata, &header), RTP_HEADER_SIZE); RTPHeader unpacked = {0}; EXPECT_EQ(rtp_header_unpack(rdata, &unpacked), RTP_HEADER_SIZE); EXPECT_EQ(header.ve, unpacked.ve); EXPECT_EQ(header.pe, unpacked.pe); EXPECT_EQ(header.xe, unpacked.xe); EXPECT_EQ(header.cc, unpacked.cc); EXPECT_EQ(header.ma, unpacked.ma); EXPECT_EQ(header.pt, unpacked.pt); EXPECT_EQ(header.sequnum, unpacked.sequnum); EXPECT_EQ(header.timestamp, unpacked.timestamp); EXPECT_EQ(header.ssrc, unpacked.ssrc); EXPECT_EQ(header.flags, unpacked.flags); EXPECT_EQ(header.offset_full, unpacked.offset_full); EXPECT_EQ(header.data_length_full, unpacked.data_length_full); EXPECT_EQ(header.received_length_full, unpacked.received_length_full); EXPECT_EQ(header.offset_lower, unpacked.offset_lower); EXPECT_EQ(header.data_length_lower, unpacked.data_length_lower); } TEST(Rtp, SerialisingAllOnes) { RTPHeader header; memset(&header, 0xff, sizeof header); uint8_t rdata[RTP_HEADER_SIZE]; rtp_header_pack(rdata, &header); EXPECT_EQ(std::string(reinterpret_cast(rdata), sizeof rdata), std::string("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\xFF\xFF\xFF\xFF", RTP_HEADER_SIZE)); } } // namespace c-toxcore-0.2.13/toxav/toxav.api.h000066400000000000000000000535571415350724400167470ustar00rootroot00000000000000%{ /* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifndef C_TOXCORE_TOXAV_TOXAV_H #define C_TOXCORE_TOXAV_TOXAV_H #include #include #include //!TOKSTYLE- #ifdef __cplusplus extern "C" { #endif %} /** \page av Public audio/video API for Tox clients. * * This API can handle multiple calls. Each call has its state, in very rare * occasions the library can change the state of the call without apps knowledge. * */ /** \subsection events Events and callbacks * * As in Core API, events are handled by callbacks. One callback can be * registered per event. All events have a callback function type named * `toxav_{event}_cb` and a function to register it named `toxav_callback_{event}`. * Passing a NULL callback will result in no callback being registered for that * event. Only one callback per event can be registered, so if a client needs * multiple event listeners, it needs to implement the dispatch functionality * itself. Unlike Core API, lack of some event handlers will cause the the * library to drop calls before they are started. Hanging up call from a * callback causes undefined behaviour. * */ /** \subsection threading Threading implications * * Only ${toxAV.iterate} is thread-safe, all other functions must run from the * tox thread. * * A common way to run ToxAV (multiple or single instance) is to have a thread, * separate from tox instance thread, running a simple ${toxAV.iterate} loop, * sleeping for ${toxAV.iteration_interval} * milliseconds on each iteration. * * An important thing to note is that events are triggered from both tox and * toxav thread (see above). Audio and video receive frame events are triggered * from toxav thread while all the other events are triggered from tox thread. * * Tox thread has priority with mutex mechanisms. Any api function can * fail if mutexes are held by tox thread in which case they will set SYNC * error code. */ /** * External Tox type. */ class tox { struct this; } /** * ToxAV. */ class toxAV { /** * The ToxAV instance type. Each ToxAV instance can be bound to only one Tox * instance, and Tox instance can have only one ToxAV instance. One must make * sure to close ToxAV instance prior closing Tox instance otherwise undefined * behaviour occurs. Upon closing of ToxAV instance, all active calls will be * forcibly terminated without notifying peers. * */ struct this; /******************************************************************************* * * :: Creation and destruction * ******************************************************************************/ /** * Start new A/V session. There can only be only one session per Tox instance. */ static this new(tox::this *tox) { NULL, /** * Memory allocation failure while trying to allocate structures required for * the A/V session. */ MALLOC, /** * Attempted to create a second session for the same Tox instance. */ MULTIPLE, } /** * Releases all resources associated with the A/V session. * * If any calls were ongoing, these will be forcibly terminated without * notifying peers. After calling this function, no other functions may be * called and the av pointer becomes invalid. */ void kill(); /** * Returns the Tox instance the A/V object was created for. */ tox::this *tox { get(); } /******************************************************************************* * * :: A/V event loop * ******************************************************************************/ /** * Returns the interval in milliseconds when the next toxav_iterate call should * be. If no call is active at the moment, this function returns 200. */ const uint32_t iteration_interval(); /** * Main loop for the session. This function needs to be called in intervals of * toxav_iteration_interval() milliseconds. It is best called in the separate * thread from tox_iterate. */ void iterate(); /******************************************************************************* * * :: Call setup * ******************************************************************************/ /** * Call a friend. This will start ringing the friend. * * It is the client's responsibility to stop ringing after a certain timeout, * if such behaviour is desired. If the client does not stop ringing, the * library will not stop until the friend is disconnected. Audio and video * receiving are both enabled by default. * * @param friend_number The friend number of the friend that should be called. * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable * audio sending. * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable * video sending. */ bool call(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) { /** * A resource allocation error occurred while trying to create the structures * required for the call. */ MALLOC, /** * Synchronization error occurred. */ SYNC, /** * The friend number did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * The friend was valid, but not currently connected. */ FRIEND_NOT_CONNECTED, /** * Attempted to call a friend while already in an audio or video call with * them. */ FRIEND_ALREADY_IN_CALL, /** * Audio or video bit rate is invalid. */ INVALID_BIT_RATE, } event call { /** * The function type for the ${event call} callback. * * @param friend_number The friend number from which the call is incoming. * @param audio_enabled True if friend is sending audio. * @param video_enabled True if friend is sending video. */ typedef void(uint32_t friend_number, bool audio_enabled, bool video_enabled); } /** * Accept an incoming call. * * If answering fails for any reason, the call will still be pending and it is * possible to try and answer it later. Audio and video receiving are both * enabled by default. * * @param friend_number The friend number of the friend that is calling. * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable * audio sending. * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable * video sending. */ bool answer(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) { /** * Synchronization error occurred. */ SYNC, /** * Failed to initialize codecs for call session. Note that codec initiation * will fail if there is no receive callback registered for either audio or * video. */ CODEC_INITIALIZATION, /** * The friend number did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * The friend was valid, but they are not currently trying to initiate a call. * This is also returned if this client is already in a call with the friend. */ FRIEND_NOT_CALLING, /** * Audio or video bit rate is invalid. */ INVALID_BIT_RATE, } /******************************************************************************* * * :: Call state graph * ******************************************************************************/ bitmask FRIEND_CALL_STATE { /** * Set by the AV core if an error occurred on the remote end or if friend * timed out. This is the final state after which no more state * transitions can occur for the call. This call state will never be triggered * in combination with other call states. */ ERROR, /** * The call has finished. This is the final state after which no more state * transitions can occur for the call. This call state will never be * triggered in combination with other call states. */ FINISHED, /** * The flag that marks that friend is sending audio. */ SENDING_A, /** * The flag that marks that friend is sending video. */ SENDING_V, /** * The flag that marks that friend is receiving audio. */ ACCEPTING_A, /** * The flag that marks that friend is receiving video. */ ACCEPTING_V, } event call_state { /** * The function type for the ${event call_state} callback. * * @param friend_number The friend number for which the call state changed. * @param state The bitmask of the new call state which is guaranteed to be * different than the previous state. The state is set to 0 when the call is * paused. The bitmask represents all the activities currently performed by the * friend. */ typedef void(uint32_t friend_number, uint32_t state); } /******************************************************************************* * * :: Call control * ******************************************************************************/ enum class CALL_CONTROL { /** * Resume a previously paused call. Only valid if the pause was caused by this * client, if not, this control is ignored. Not valid before the call is accepted. */ RESUME, /** * Put a call on hold. Not valid before the call is accepted. */ PAUSE, /** * Reject a call if it was not answered, yet. Cancel a call after it was * answered. */ CANCEL, /** * Request that the friend stops sending audio. Regardless of the friend's * compliance, this will cause the ${event audio.receive_frame} event to stop being * triggered on receiving an audio frame from the friend. */ MUTE_AUDIO, /** * Calling this control will notify client to start sending audio again. */ UNMUTE_AUDIO, /** * Request that the friend stops sending video. Regardless of the friend's * compliance, this will cause the ${event video.receive_frame} event to stop being * triggered on receiving a video frame from the friend. */ HIDE_VIDEO, /** * Calling this control will notify client to start sending video again. */ SHOW_VIDEO, } /** * Sends a call control command to a friend. * * @param friend_number The friend number of the friend this client is in a call * with. * @param control The control command to send. * * @return true on success. */ bool call_control(uint32_t friend_number, CALL_CONTROL control) { /** * Synchronization error occurred. */ SYNC, /** * The friend_number passed did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not in a call with the friend. Before the call is * answered, only CANCEL is a valid control. */ FRIEND_NOT_IN_CALL, /** * Happens if user tried to pause an already paused call or if trying to * resume a call that is not paused. */ INVALID_TRANSITION, } /******************************************************************************* * * :: Controlling bit rates * ******************************************************************************/ error for bit_rate_set { /** * Synchronization error occurred. */ SYNC, /** * The bit rate passed was not one of the supported values. */ INVALID_BIT_RATE, /** * The friend_number passed did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not in a call with the friend. */ FRIEND_NOT_IN_CALL, } /******************************************************************************* * * :: A/V sending * ******************************************************************************/ error for send_frame { /** * In case of video, one of Y, U, or V was NULL. In case of audio, the samples * data pointer was NULL. */ NULL, /** * The friend_number passed did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not in a call with the friend. */ FRIEND_NOT_IN_CALL, /** * Synchronization error occurred. */ SYNC, /** * One of the frame parameters was invalid. E.g. the resolution may be too * small or too large, or the audio sampling rate may be unsupported. */ INVALID, /** * Either friend turned off audio or video receiving or we turned off sending * for the said payload. */ PAYLOAD_TYPE_DISABLED, /** * Failed to push frame through rtp interface. */ RTP_FAILED, } namespace audio { /** * Send an audio frame to a friend. * * The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... * For mono audio, this has no meaning, every sample is subsequent. For stereo, * this means the expected format is LRLRLR... with samples for left and right * alternating. * * @param friend_number The friend number of the friend to which to send an * audio frame. * @param pcm An array of audio samples. The size of this array must be * sample_count * channels. * @param sample_count Number of samples in this frame. Valid numbers here are * ((sample rate) * (audio length) / 1000), where audio length can be * 2.5, 5, 10, 20, 40 or 60 millseconds. * @param channels Number of audio channels. Supported values are 1 and 2. * @param sampling_rate Audio sampling rate used in this frame. Valid sampling * rates are 8000, 12000, 16000, 24000, or 48000. */ bool send_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate) with error for send_frame; uint32_t bit_rate { /** * Set the bit rate to be used in subsequent video frames. * * @param friend_number The friend number of the friend for which to set the * bit rate. * @param bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable. * * @return true on success. */ set(uint32_t friend_number) with error for bit_rate_set; } event bit_rate { /** * The function type for the ${event bit_rate} callback. The event is triggered * when the network becomes too saturated for current bit rates at which * point core suggests new bit rates. * * @param friend_number The friend number of the friend for which to set the * bit rate. * @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec. */ typedef void(uint32_t friend_number, uint32_t audio_bit_rate); } } namespace video { /** * Send a video frame to a friend. * * Y - plane should be of size: height * width * U - plane should be of size: (height/2) * (width/2) * V - plane should be of size: (height/2) * (width/2) * * @param friend_number The friend number of the friend to which to send a video * frame. * @param width Width of the frame in pixels. * @param height Height of the frame in pixels. * @param y Y (Luminance) plane data. * @param u U (Chroma) plane data. * @param v V (Chroma) plane data. */ bool send_frame(uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v) with error for send_frame; uint32_t bit_rate { /** * Set the bit rate to be used in subsequent video frames. * * @param friend_number The friend number of the friend for which to set the * bit rate. * @param bit_rate The new video bit rate in Kb/sec. Set to 0 to disable. * * @return true on success. */ set(uint32_t friend_number) with error for bit_rate_set; } event bit_rate { /** * The function type for the ${event bit_rate} callback. The event is triggered * when the network becomes too saturated for current bit rates at which * point core suggests new bit rates. * * @param friend_number The friend number of the friend for which to set the * bit rate. * @param video_bit_rate Suggested maximum video bit rate in Kb/sec. */ typedef void(uint32_t friend_number, uint32_t video_bit_rate); } } /******************************************************************************* * * :: A/V receiving * ******************************************************************************/ namespace audio { event receive_frame { /** * The function type for the ${event receive_frame} callback. The callback can be * called multiple times per single iteration depending on the amount of queued * frames in the buffer. The received format is the same as in send function. * * @param friend_number The friend number of the friend who sent an audio frame. * @param pcm An array of audio samples (sample_count * channels elements). * @param sample_count The number of audio samples per channel in the PCM array. * @param channels Number of audio channels. * @param sampling_rate Sampling rate used in this frame. * */ typedef void(uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate); } } namespace video { event receive_frame { /** * The function type for the ${event receive_frame} callback. * * The size of plane data is derived from width and height as documented * below. * * Strides represent padding for each plane that may or may not be present. * You must handle strides in your image processing code. Strides are * negative if the image is bottom-up hence why you MUST abs() it when * calculating plane buffer size. * * @param friend_number The friend number of the friend who sent a video frame. * @param width Width of the frame in pixels. * @param height Height of the frame in pixels. * @param y Luminosity plane. Size = MAX(width, abs(ystride)) * height. * @param u U chroma plane. Size = MAX(width/2, abs(ustride)) * (height/2). * @param v V chroma plane. Size = MAX(width/2, abs(vstride)) * (height/2). * * @param ystride Luminosity plane stride. * @param ustride U chroma plane stride. * @param vstride V chroma plane stride. */ typedef void(uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, int32_t vstride); } } } %{ /** * NOTE Compatibility with old toxav group calls. TODO(iphydf): remove * * TODO(iphydf): Use proper new API guidelines for these. E.g. don't use inline * function types, don't have per-callback userdata, especially don't have one * userdata per group. */ /* Create a new toxav group. * * return group number on success. * return -1 on failure. * * Audio data callback format: * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). */ int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), void *userdata); /* Join a AV group (you need to have been invited first.) * * returns group number on success * returns -1 on failure. * * Audio data callback format (same as the one for toxav_add_av_groupchat()): * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). */ int toxav_join_av_groupchat(Tox *tox, uint32_t friendnumber, const uint8_t *data, uint16_t length, void (*audio_callback)(void *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), void *userdata); /* Send audio to the group chat. * * return 0 on success. * return -1 on failure. * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). * * Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000) * Valid number of channels are 1 or 2. * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. * * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 */ int toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate); /* Enable A/V in a groupchat. * * A/V must be enabled on a groupchat for audio to be sent to it and for * received audio to be handled. * * An A/V group created with toxav_add_av_groupchat or toxav_join_av_groupchat * will start with A/V enabled. * * An A/V group loaded from a savefile will start with A/V disabled. * * return 0 on success. * return -1 on failure. * * Audio data callback format (same as the one for toxav_add_av_groupchat()): * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). */ int toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, void (*audio_callback)(void *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), void *userdata); /* Disable A/V in a groupchat. * * return 0 on success. * return -1 on failure. */ int toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber); /* Return whether A/V is enabled in the groupchat. */ bool toxav_groupchat_av_enabled(Tox *tox, uint32_t groupnumber); #ifdef __cplusplus } #endif typedef void toxav_group_audio_cb(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, uint32_t samples, uint8_t channels, uint32_t sample_rate, void *user_data); typedef TOXAV_ERR_CALL Toxav_Err_Call; typedef TOXAV_ERR_NEW Toxav_Err_New; typedef TOXAV_ERR_ANSWER Toxav_Err_Answer; typedef TOXAV_ERR_CALL_CONTROL Toxav_Err_Call_Control; typedef TOXAV_ERR_BIT_RATE_SET Toxav_Err_Bit_Rate_Set; typedef TOXAV_ERR_SEND_FRAME Toxav_Err_Send_Frame; typedef TOXAV_CALL_CONTROL Toxav_Call_Control; //!TOKSTYLE+ #endif // C_TOXCORE_TOXAV_TOXAV_H %} c-toxcore-0.2.13/toxav/toxav.c000066400000000000000000001237441415350724400161660ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "toxav.h" #include "msi.h" #include "rtp.h" #include "../toxcore/Messenger.h" #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/util.h" #include #include #include #include #include // TODO(zoff99): don't hardcode this, let the application choose it // VPX Info: Time to spend encoding, in microseconds (it's a *soft* deadline) #define WANTED_MAX_ENCODER_FPS 40 #define MAX_ENCODE_TIME_US (1000000 / WANTED_MAX_ENCODER_FPS) // to allow x fps #define VIDEO_SEND_X_KEYFRAMES_FIRST 7 // force the first n frames to be keyframes! /* * VPX_DL_REALTIME (1) deadline parameter analogous to VPx REALTIME mode. * VPX_DL_GOOD_QUALITY (1000000) deadline parameter analogous to VPx GOOD QUALITY mode. * VPX_DL_BEST_QUALITY (0) deadline parameter analogous to VPx BEST QUALITY mode. */ typedef struct ToxAVCall_s { ToxAV *av; pthread_mutex_t mutex_audio[1]; RTPSession *audio_rtp; ACSession *audio; pthread_mutex_t mutex_video[1]; RTPSession *video_rtp; VCSession *video; BWController *bwc; bool active; MSICall *msi_call; uint32_t friend_number; uint32_t audio_bit_rate; /* Sending audio bit rate */ uint32_t video_bit_rate; /* Sending video bit rate */ /** Required for monitoring changes in states */ uint8_t previous_self_capabilities; pthread_mutex_t toxav_call_mutex[1]; struct ToxAVCall_s *prev; struct ToxAVCall_s *next; } ToxAVCall; struct ToxAV { Tox *tox; Messenger *m; MSISession *msi; /* Two-way storage: first is array of calls and second is list of calls with head and tail */ ToxAVCall **calls; uint32_t calls_tail; uint32_t calls_head; pthread_mutex_t mutex[1]; /* Call callback */ toxav_call_cb *ccb; void *ccb_user_data; /* Call state callback */ toxav_call_state_cb *scb; void *scb_user_data; /* Audio frame receive callback */ toxav_audio_receive_frame_cb *acb; void *acb_user_data; /* Video frame receive callback */ toxav_video_receive_frame_cb *vcb; void *vcb_user_data; /* Bit rate control callback */ toxav_audio_bit_rate_cb *abcb; void *abcb_user_data; /* Bit rate control callback */ toxav_video_bit_rate_cb *vbcb; void *vbcb_user_data; /** Decode time measures */ int32_t dmssc; /** Measure count */ int32_t dmsst; /** Last cycle total */ int32_t dmssa; /** Average decoding time in ms */ uint32_t interval; /** Calculated interval */ Mono_Time *toxav_mono_time; /** ToxAV's own mono_time instance */ }; static void callback_bwc(BWController *bwc, uint32_t friend_number, float loss, void *user_data); static int callback_invite(void *toxav_inst, MSICall *call); static int callback_start(void *toxav_inst, MSICall *call); static int callback_end(void *toxav_inst, MSICall *call); static int callback_error(void *toxav_inst, MSICall *call); static int callback_capabilites(void *toxav_inst, MSICall *call); static bool audio_bit_rate_invalid(uint32_t bit_rate); static bool video_bit_rate_invalid(uint32_t bit_rate); static bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state); static ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, Toxav_Err_Call *error); static ToxAVCall *call_get(ToxAV *av, uint32_t friend_number); static ToxAVCall *call_remove(ToxAVCall *call); static bool call_prepare_transmission(ToxAVCall *call); static void call_kill_transmission(ToxAVCall *call); ToxAV *toxav_new(Tox *tox, Toxav_Err_New *error) { Toxav_Err_New rc = TOXAV_ERR_NEW_OK; ToxAV *av = nullptr; if (tox == nullptr) { rc = TOXAV_ERR_NEW_NULL; goto RETURN; } // TODO(iphydf): Don't rely on toxcore internals. Messenger *m; //!TOKSTYLE- m = *(Messenger **)tox; //!TOKSTYLE+ if (m->msi_packet) { rc = TOXAV_ERR_NEW_MULTIPLE; goto RETURN; } av = (ToxAV *)calloc(sizeof(ToxAV), 1); if (av == nullptr) { LOGGER_WARNING(m->log, "Allocation failed!"); rc = TOXAV_ERR_NEW_MALLOC; goto RETURN; } if (create_recursive_mutex(av->mutex) != 0) { LOGGER_WARNING(m->log, "Mutex creation failed!"); rc = TOXAV_ERR_NEW_MALLOC; goto RETURN; } av->tox = tox; av->m = m; av->toxav_mono_time = mono_time_new(); av->msi = msi_new(av->m); if (av->msi == nullptr) { pthread_mutex_destroy(av->mutex); rc = TOXAV_ERR_NEW_MALLOC; goto RETURN; } av->interval = 200; av->msi->av = av; msi_register_callback(av->msi, callback_invite, MSI_ON_INVITE); msi_register_callback(av->msi, callback_start, MSI_ON_START); msi_register_callback(av->msi, callback_end, MSI_ON_END); msi_register_callback(av->msi, callback_error, MSI_ON_ERROR); msi_register_callback(av->msi, callback_error, MSI_ON_PEERTIMEOUT); msi_register_callback(av->msi, callback_capabilites, MSI_ON_CAPABILITIES); RETURN: if (error) { *error = rc; } if (rc != TOXAV_ERR_NEW_OK) { free(av); av = nullptr; } return av; } void toxav_kill(ToxAV *av) { if (av == nullptr) { return; } pthread_mutex_lock(av->mutex); /* To avoid possible deadlocks */ while (av->msi && msi_kill(av->msi, av->m->log) != 0) { pthread_mutex_unlock(av->mutex); pthread_mutex_lock(av->mutex); } /* Msi kill will hang up all calls so just clean these calls */ if (av->calls) { ToxAVCall *it = call_get(av, av->calls_head); while (it) { call_kill_transmission(it); it->msi_call = nullptr; /* msi_kill() frees the call's msi_call handle; which causes #278 */ it = call_remove(it); /* This will eventually free av->calls */ } } mono_time_free(av->toxav_mono_time); pthread_mutex_unlock(av->mutex); pthread_mutex_destroy(av->mutex); free(av); } Tox *toxav_get_tox(const ToxAV *av) { return av->tox; } uint32_t toxav_iteration_interval(const ToxAV *av) { /* If no call is active interval is 200 */ return av->calls ? av->interval : 200; } void toxav_iterate(ToxAV *av) { pthread_mutex_lock(av->mutex); if (av->calls == nullptr) { pthread_mutex_unlock(av->mutex); return; } uint64_t start = current_time_monotonic(av->toxav_mono_time); int32_t rc = 500; ToxAVCall *i = av->calls[av->calls_head]; for (; i; i = i->next) { if (i->active) { pthread_mutex_lock(i->toxav_call_mutex); pthread_mutex_unlock(av->mutex); ac_iterate(i->audio); vc_iterate(i->video); if (i->msi_call->self_capabilities & MSI_CAP_R_AUDIO && i->msi_call->peer_capabilities & MSI_CAP_S_AUDIO) { rc = min_s32(i->audio->lp_frame_duration, rc); } if (i->msi_call->self_capabilities & MSI_CAP_R_VIDEO && i->msi_call->peer_capabilities & MSI_CAP_S_VIDEO) { pthread_mutex_lock(i->video->queue_mutex); rc = min_u32(i->video->lcfd, rc); pthread_mutex_unlock(i->video->queue_mutex); } uint32_t fid = i->friend_number; pthread_mutex_unlock(i->toxav_call_mutex); pthread_mutex_lock(av->mutex); /* In case this call is popped from container stop iteration */ if (call_get(av, fid) != i) { break; } } } av->interval = rc < av->dmssa ? 0 : (rc - av->dmssa); av->dmsst += current_time_monotonic(av->toxav_mono_time) - start; if (++av->dmssc == 3) { av->dmssa = av->dmsst / 3 + 5; /* NOTE Magic Offset 5 for precision */ av->dmssc = 0; av->dmsst = 0; } pthread_mutex_unlock(av->mutex); } bool toxav_call(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, Toxav_Err_Call *error) { Toxav_Err_Call rc = TOXAV_ERR_CALL_OK; ToxAVCall *call; pthread_mutex_lock(av->mutex); if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate)) || (video_bit_rate && video_bit_rate_invalid(video_bit_rate))) { rc = TOXAV_ERR_CALL_INVALID_BIT_RATE; goto RETURN; } call = call_new(av, friend_number, &rc); if (call == nullptr) { goto RETURN; } call->audio_bit_rate = audio_bit_rate; call->video_bit_rate = video_bit_rate; call->previous_self_capabilities = MSI_CAP_R_AUDIO | MSI_CAP_R_VIDEO; call->previous_self_capabilities |= audio_bit_rate > 0 ? MSI_CAP_S_AUDIO : 0; call->previous_self_capabilities |= video_bit_rate > 0 ? MSI_CAP_S_VIDEO : 0; if (msi_invite(av->msi, &call->msi_call, friend_number, call->previous_self_capabilities) != 0) { call_remove(call); rc = TOXAV_ERR_CALL_SYNC; goto RETURN; } call->msi_call->av_call = call; RETURN: pthread_mutex_unlock(av->mutex); if (error) { *error = rc; } return rc == TOXAV_ERR_CALL_OK; } void toxav_callback_call(ToxAV *av, toxav_call_cb *callback, void *user_data) { pthread_mutex_lock(av->mutex); av->ccb = callback; av->ccb_user_data = user_data; pthread_mutex_unlock(av->mutex); } bool toxav_answer(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, Toxav_Err_Answer *error) { pthread_mutex_lock(av->mutex); Toxav_Err_Answer rc = TOXAV_ERR_ANSWER_OK; ToxAVCall *call; if (m_friend_exists(av->m, friend_number) == 0) { rc = TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND; goto RETURN; } if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate)) || (video_bit_rate && video_bit_rate_invalid(video_bit_rate)) ) { rc = TOXAV_ERR_ANSWER_INVALID_BIT_RATE; goto RETURN; } call = call_get(av, friend_number); if (call == nullptr) { rc = TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING; goto RETURN; } if (!call_prepare_transmission(call)) { rc = TOXAV_ERR_ANSWER_CODEC_INITIALIZATION; goto RETURN; } call->audio_bit_rate = audio_bit_rate; call->video_bit_rate = video_bit_rate; call->previous_self_capabilities = MSI_CAP_R_AUDIO | MSI_CAP_R_VIDEO; call->previous_self_capabilities |= audio_bit_rate > 0 ? MSI_CAP_S_AUDIO : 0; call->previous_self_capabilities |= video_bit_rate > 0 ? MSI_CAP_S_VIDEO : 0; if (msi_answer(call->msi_call, call->previous_self_capabilities) != 0) { rc = TOXAV_ERR_ANSWER_SYNC; } RETURN: pthread_mutex_unlock(av->mutex); if (error) { *error = rc; } return rc == TOXAV_ERR_ANSWER_OK; } void toxav_callback_call_state(ToxAV *av, toxav_call_state_cb *callback, void *user_data) { pthread_mutex_lock(av->mutex); av->scb = callback; av->scb_user_data = user_data; pthread_mutex_unlock(av->mutex); } bool toxav_call_control(ToxAV *av, uint32_t friend_number, Toxav_Call_Control control, Toxav_Err_Call_Control *error) { pthread_mutex_lock(av->mutex); Toxav_Err_Call_Control rc = TOXAV_ERR_CALL_CONTROL_OK; ToxAVCall *call; if (m_friend_exists(av->m, friend_number) == 0) { rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND; goto RETURN; } call = call_get(av, friend_number); if (call == nullptr || (!call->active && control != TOXAV_CALL_CONTROL_CANCEL)) { rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL; goto RETURN; } switch (control) { case TOXAV_CALL_CONTROL_RESUME: { /* Only act if paused and had media transfer active before */ if (call->msi_call->self_capabilities == 0 && call->previous_self_capabilities) { if (msi_change_capabilities(call->msi_call, call->previous_self_capabilities) == -1) { rc = TOXAV_ERR_CALL_CONTROL_SYNC; goto RETURN; } rtp_allow_receiving(call->audio_rtp); rtp_allow_receiving(call->video_rtp); } else { rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; goto RETURN; } } break; case TOXAV_CALL_CONTROL_PAUSE: { /* Only act if not already paused */ if (call->msi_call->self_capabilities) { call->previous_self_capabilities = call->msi_call->self_capabilities; if (msi_change_capabilities(call->msi_call, 0) == -1) { rc = TOXAV_ERR_CALL_CONTROL_SYNC; goto RETURN; } rtp_stop_receiving(call->audio_rtp); rtp_stop_receiving(call->video_rtp); } else { rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; goto RETURN; } } break; case TOXAV_CALL_CONTROL_CANCEL: { /* Hang up */ pthread_mutex_lock(call->toxav_call_mutex); if (msi_hangup(call->msi_call) != 0) { rc = TOXAV_ERR_CALL_CONTROL_SYNC; pthread_mutex_unlock(call->toxav_call_mutex); goto RETURN; } call->msi_call = nullptr; pthread_mutex_unlock(call->toxav_call_mutex); /* No mather the case, terminate the call */ call_kill_transmission(call); call_remove(call); } break; case TOXAV_CALL_CONTROL_MUTE_AUDIO: { if (call->msi_call->self_capabilities & MSI_CAP_R_AUDIO) { if (msi_change_capabilities(call->msi_call, call-> msi_call->self_capabilities ^ MSI_CAP_R_AUDIO) == -1) { rc = TOXAV_ERR_CALL_CONTROL_SYNC; goto RETURN; } rtp_stop_receiving(call->audio_rtp); } else { rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; goto RETURN; } } break; case TOXAV_CALL_CONTROL_UNMUTE_AUDIO: { if (call->msi_call->self_capabilities ^ MSI_CAP_R_AUDIO) { if (msi_change_capabilities(call->msi_call, call-> msi_call->self_capabilities | MSI_CAP_R_AUDIO) == -1) { rc = TOXAV_ERR_CALL_CONTROL_SYNC; goto RETURN; } rtp_allow_receiving(call->audio_rtp); } else { rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; goto RETURN; } } break; case TOXAV_CALL_CONTROL_HIDE_VIDEO: { if (call->msi_call->self_capabilities & MSI_CAP_R_VIDEO) { if (msi_change_capabilities(call->msi_call, call-> msi_call->self_capabilities ^ MSI_CAP_R_VIDEO) == -1) { rc = TOXAV_ERR_CALL_CONTROL_SYNC; goto RETURN; } rtp_stop_receiving(call->video_rtp); } else { rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; goto RETURN; } } break; case TOXAV_CALL_CONTROL_SHOW_VIDEO: { if (call->msi_call->self_capabilities ^ MSI_CAP_R_VIDEO) { if (msi_change_capabilities(call->msi_call, call-> msi_call->self_capabilities | MSI_CAP_R_VIDEO) == -1) { rc = TOXAV_ERR_CALL_CONTROL_SYNC; goto RETURN; } rtp_allow_receiving(call->video_rtp); } else { rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION; goto RETURN; } } break; } RETURN: pthread_mutex_unlock(av->mutex); if (error) { *error = rc; } return rc == TOXAV_ERR_CALL_CONTROL_OK; } bool toxav_audio_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, Toxav_Err_Bit_Rate_Set *error) { Toxav_Err_Bit_Rate_Set rc = TOXAV_ERR_BIT_RATE_SET_OK; ToxAVCall *call; if (m_friend_exists(av->m, friend_number) == 0) { rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND; goto RETURN; } if (audio_bit_rate > 0 && audio_bit_rate_invalid(audio_bit_rate)) { rc = TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE; goto RETURN; } pthread_mutex_lock(av->mutex); call = call_get(av, friend_number); if (call == nullptr || !call->active || call->msi_call->state != MSI_CALL_ACTIVE) { pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL; goto RETURN; } LOGGER_DEBUG(av->m->log, "Setting new audio bitrate to: %d", audio_bit_rate); if (call->audio_bit_rate == audio_bit_rate) { LOGGER_DEBUG(av->m->log, "Audio bitrate already set to: %d", audio_bit_rate); } else if (audio_bit_rate == 0) { LOGGER_DEBUG(av->m->log, "Turned off audio sending"); if (msi_change_capabilities(call->msi_call, call->msi_call-> self_capabilities ^ MSI_CAP_S_AUDIO) != 0) { pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_BIT_RATE_SET_SYNC; goto RETURN; } /* Audio sending is turned off; notify peer */ call->audio_bit_rate = 0; } else { pthread_mutex_lock(call->toxav_call_mutex); if (call->audio_bit_rate == 0) { LOGGER_DEBUG(av->m->log, "Turned on audio sending"); /* The audio has been turned off before this */ if (msi_change_capabilities(call->msi_call, call-> msi_call->self_capabilities | MSI_CAP_S_AUDIO) != 0) { pthread_mutex_unlock(call->toxav_call_mutex); pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_BIT_RATE_SET_SYNC; goto RETURN; } } else { LOGGER_DEBUG(av->m->log, "Set new audio bit rate %d", audio_bit_rate); } call->audio_bit_rate = audio_bit_rate; pthread_mutex_unlock(call->toxav_call_mutex); } pthread_mutex_unlock(av->mutex); RETURN: if (error) { *error = rc; } return rc == TOXAV_ERR_BIT_RATE_SET_OK; } bool toxav_video_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t video_bit_rate, Toxav_Err_Bit_Rate_Set *error) { Toxav_Err_Bit_Rate_Set rc = TOXAV_ERR_BIT_RATE_SET_OK; ToxAVCall *call; if (m_friend_exists(av->m, friend_number) == 0) { rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND; goto RETURN; } if (video_bit_rate > 0 && video_bit_rate_invalid(video_bit_rate)) { rc = TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE; goto RETURN; } pthread_mutex_lock(av->mutex); call = call_get(av, friend_number); if (call == nullptr || !call->active || call->msi_call->state != MSI_CALL_ACTIVE) { pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL; goto RETURN; } LOGGER_DEBUG(av->m->log, "Setting new video bitrate to: %d", video_bit_rate); if (call->video_bit_rate == video_bit_rate) { LOGGER_DEBUG(av->m->log, "Video bitrate already set to: %d", video_bit_rate); } else if (video_bit_rate == 0) { LOGGER_DEBUG(av->m->log, "Turned off video sending"); /* Video sending is turned off; notify peer */ if (msi_change_capabilities(call->msi_call, call->msi_call-> self_capabilities ^ MSI_CAP_S_VIDEO) != 0) { pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_BIT_RATE_SET_SYNC; goto RETURN; } call->video_bit_rate = 0; } else { pthread_mutex_lock(call->toxav_call_mutex); if (call->video_bit_rate == 0) { LOGGER_DEBUG(av->m->log, "Turned on video sending"); /* The video has been turned off before this */ if (msi_change_capabilities(call->msi_call, call-> msi_call->self_capabilities | MSI_CAP_S_VIDEO) != 0) { pthread_mutex_unlock(call->toxav_call_mutex); pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_BIT_RATE_SET_SYNC; goto RETURN; } } else { LOGGER_DEBUG(av->m->log, "Set new video bit rate %d", video_bit_rate); } call->video_bit_rate = video_bit_rate; pthread_mutex_unlock(call->toxav_call_mutex); } pthread_mutex_unlock(av->mutex); RETURN: if (error) { *error = rc; } return rc == TOXAV_ERR_BIT_RATE_SET_OK; } void toxav_callback_audio_bit_rate(ToxAV *av, toxav_audio_bit_rate_cb *callback, void *user_data) { pthread_mutex_lock(av->mutex); av->abcb = callback; av->abcb_user_data = user_data; pthread_mutex_unlock(av->mutex); } void toxav_callback_video_bit_rate(ToxAV *av, toxav_video_bit_rate_cb *callback, void *user_data) { pthread_mutex_lock(av->mutex); av->vbcb = callback; av->vbcb_user_data = user_data; pthread_mutex_unlock(av->mutex); } bool toxav_audio_send_frame(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, Toxav_Err_Send_Frame *error) { Toxav_Err_Send_Frame rc = TOXAV_ERR_SEND_FRAME_OK; ToxAVCall *call; if (m_friend_exists(av->m, friend_number) == 0) { rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; goto RETURN; } if (pthread_mutex_trylock(av->mutex) != 0) { rc = TOXAV_ERR_SEND_FRAME_SYNC; goto RETURN; } call = call_get(av, friend_number); if (call == nullptr || !call->active || call->msi_call->state != MSI_CALL_ACTIVE) { pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; goto RETURN; } if (call->audio_bit_rate == 0 || !(call->msi_call->self_capabilities & MSI_CAP_S_AUDIO) || !(call->msi_call->peer_capabilities & MSI_CAP_R_AUDIO)) { pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED; goto RETURN; } pthread_mutex_lock(call->mutex_audio); pthread_mutex_unlock(av->mutex); if (pcm == nullptr) { pthread_mutex_unlock(call->mutex_audio); rc = TOXAV_ERR_SEND_FRAME_NULL; goto RETURN; } if (channels > 2) { pthread_mutex_unlock(call->mutex_audio); rc = TOXAV_ERR_SEND_FRAME_INVALID; goto RETURN; } { /* Encode and send */ if (ac_reconfigure_encoder(call->audio, call->audio_bit_rate * 1000, sampling_rate, channels) != 0) { pthread_mutex_unlock(call->mutex_audio); rc = TOXAV_ERR_SEND_FRAME_INVALID; goto RETURN; } VLA(uint8_t, dest, sample_count + sizeof(sampling_rate)); /* This is more than enough always */ sampling_rate = net_htonl(sampling_rate); memcpy(dest, &sampling_rate, sizeof(sampling_rate)); int vrc = opus_encode(call->audio->encoder, pcm, sample_count, dest + sizeof(sampling_rate), SIZEOF_VLA(dest) - sizeof(sampling_rate)); if (vrc < 0) { LOGGER_WARNING(av->m->log, "Failed to encode frame %s", opus_strerror(vrc)); pthread_mutex_unlock(call->mutex_audio); rc = TOXAV_ERR_SEND_FRAME_INVALID; goto RETURN; } if (rtp_send_data(call->audio_rtp, dest, vrc + sizeof(sampling_rate), false, av->m->log) != 0) { LOGGER_WARNING(av->m->log, "Failed to send audio packet"); rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; } } pthread_mutex_unlock(call->mutex_audio); RETURN: if (error) { *error = rc; } return rc == TOXAV_ERR_SEND_FRAME_OK; } static Toxav_Err_Send_Frame send_frames(const Logger *log, ToxAVCall *call) { vpx_codec_iter_t iter = nullptr; for (const vpx_codec_cx_pkt_t *pkt = vpx_codec_get_cx_data(call->video->encoder, &iter); pkt != nullptr; pkt = vpx_codec_get_cx_data(call->video->encoder, &iter)) { if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) { continue; } const bool is_keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; // https://www.webmproject.org/docs/webm-sdk/structvpx__codec__cx__pkt.html // pkt->data.frame.sz -> size_t const uint32_t frame_length_in_bytes = pkt->data.frame.sz; const int res = rtp_send_data( call->video_rtp, (const uint8_t *)pkt->data.frame.buf, frame_length_in_bytes, is_keyframe, log); LOGGER_DEBUG(log, "+ _sending_FRAME_TYPE_==%s bytes=%d frame_len=%d", is_keyframe ? "K" : ".", (int)pkt->data.frame.sz, (int)frame_length_in_bytes); const uint8_t *const buf = (const uint8_t *)pkt->data.frame.buf; LOGGER_DEBUG(log, "+ _sending_FRAME_ b0=%d b1=%d", buf[0], buf[1]); if (res < 0) { LOGGER_WARNING(log, "Could not send video frame: %s", strerror(errno)); return TOXAV_ERR_SEND_FRAME_RTP_FAILED; } } return TOXAV_ERR_SEND_FRAME_OK; } bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, Toxav_Err_Send_Frame *error) { Toxav_Err_Send_Frame rc = TOXAV_ERR_SEND_FRAME_OK; ToxAVCall *call; int vpx_encode_flags = 0; if (m_friend_exists(av->m, friend_number) == 0) { rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; goto RETURN; } if (pthread_mutex_trylock(av->mutex) != 0) { rc = TOXAV_ERR_SEND_FRAME_SYNC; goto RETURN; } call = call_get(av, friend_number); if (call == nullptr || !call->active || call->msi_call->state != MSI_CALL_ACTIVE) { pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL; goto RETURN; } if (call->video_bit_rate == 0 || !(call->msi_call->self_capabilities & MSI_CAP_S_VIDEO) || !(call->msi_call->peer_capabilities & MSI_CAP_R_VIDEO)) { pthread_mutex_unlock(av->mutex); rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED; goto RETURN; } pthread_mutex_lock(call->mutex_video); pthread_mutex_unlock(av->mutex); if (y == nullptr || u == nullptr || v == nullptr) { pthread_mutex_unlock(call->mutex_video); rc = TOXAV_ERR_SEND_FRAME_NULL; goto RETURN; } if (vc_reconfigure_encoder(call->video, call->video_bit_rate * 1000, width, height, -1) != 0) { pthread_mutex_unlock(call->mutex_video); rc = TOXAV_ERR_SEND_FRAME_INVALID; goto RETURN; } if (call->video_rtp->ssrc < VIDEO_SEND_X_KEYFRAMES_FIRST) { // Key frame flag for first frames vpx_encode_flags = VPX_EFLAG_FORCE_KF; LOGGER_INFO(av->m->log, "I_FRAME_FLAG:%d only-i-frame mode", call->video_rtp->ssrc); ++call->video_rtp->ssrc; } else if (call->video_rtp->ssrc == VIDEO_SEND_X_KEYFRAMES_FIRST) { // normal keyframe placement vpx_encode_flags = 0; LOGGER_INFO(av->m->log, "I_FRAME_FLAG:%d normal mode", call->video_rtp->ssrc); ++call->video_rtp->ssrc; } // we start with I-frames (full frames) and then switch to normal mode later { /* Encode */ vpx_image_t img; img.w = 0; img.h = 0; img.d_w = 0; img.d_h = 0; vpx_img_alloc(&img, VPX_IMG_FMT_I420, width, height, 0); /* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes." * http://fourcc.org/yuv.php#IYUV */ memcpy(img.planes[VPX_PLANE_Y], y, width * height); memcpy(img.planes[VPX_PLANE_U], u, (width / 2) * (height / 2)); memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); vpx_codec_err_t vrc = vpx_codec_encode(call->video->encoder, &img, call->video->frame_counter, 1, vpx_encode_flags, MAX_ENCODE_TIME_US); vpx_img_free(&img); if (vrc != VPX_CODEC_OK) { pthread_mutex_unlock(call->mutex_video); LOGGER_ERROR(av->m->log, "Could not encode video frame: %s", vpx_codec_err_to_string(vrc)); rc = TOXAV_ERR_SEND_FRAME_INVALID; goto RETURN; } } ++call->video->frame_counter; rc = send_frames(av->m->log, call); pthread_mutex_unlock(call->mutex_video); RETURN: if (error) { *error = rc; } return rc == TOXAV_ERR_SEND_FRAME_OK; } void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *callback, void *user_data) { pthread_mutex_lock(av->mutex); av->acb = callback; av->acb_user_data = user_data; pthread_mutex_unlock(av->mutex); } void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *callback, void *user_data) { pthread_mutex_lock(av->mutex); av->vcb = callback; av->vcb_user_data = user_data; pthread_mutex_unlock(av->mutex); } /******************************************************************************* * * :: Internal * ******************************************************************************/ static void callback_bwc(BWController *bwc, uint32_t friend_number, float loss, void *user_data) { /* Callback which is called when the internal measure mechanism reported packet loss. * We report suggested lowered bitrate to an app. If app is sending both audio and video, * we will report lowered bitrate for video only because in that case video probably * takes more than 90% bandwidth. Otherwise, we report lowered bitrate on audio. * The application may choose to disable video totally if the stream is too bad. */ ToxAVCall *call = (ToxAVCall *)user_data; assert(call); LOGGER_DEBUG(call->av->m->log, "Reported loss of %f%%", (double)loss * 100); /* if less than 10% data loss we do nothing! */ if (loss < 0.1f) { return; } pthread_mutex_lock(call->av->mutex); if (call->video_bit_rate) { if (!call->av->vbcb) { pthread_mutex_unlock(call->av->mutex); LOGGER_WARNING(call->av->m->log, "No callback to report loss on"); return; } call->av->vbcb(call->av, friend_number, call->video_bit_rate - (call->video_bit_rate * loss), call->av->vbcb_user_data); } else if (call->audio_bit_rate) { if (!call->av->abcb) { pthread_mutex_unlock(call->av->mutex); LOGGER_WARNING(call->av->m->log, "No callback to report loss on"); return; } call->av->abcb(call->av, friend_number, call->audio_bit_rate - (call->audio_bit_rate * loss), call->av->abcb_user_data); } pthread_mutex_unlock(call->av->mutex); } static int callback_invite(void *toxav_inst, MSICall *call) { ToxAV *toxav = (ToxAV *)toxav_inst; pthread_mutex_lock(toxav->mutex); ToxAVCall *av_call = call_new(toxav, call->friend_number, nullptr); if (av_call == nullptr) { LOGGER_WARNING(toxav->m->log, "Failed to initialize call..."); pthread_mutex_unlock(toxav->mutex); return -1; } call->av_call = av_call; av_call->msi_call = call; if (toxav->ccb) { toxav->ccb(toxav, call->friend_number, call->peer_capabilities & MSI_CAP_S_AUDIO, call->peer_capabilities & MSI_CAP_S_VIDEO, toxav->ccb_user_data); } else { /* No handler to capture the call request, send failure */ pthread_mutex_unlock(toxav->mutex); return -1; } pthread_mutex_unlock(toxav->mutex); return 0; } static int callback_start(void *toxav_inst, MSICall *call) { ToxAV *toxav = (ToxAV *)toxav_inst; pthread_mutex_lock(toxav->mutex); ToxAVCall *av_call = call_get(toxav, call->friend_number); if (av_call == nullptr) { /* Should this ever happen? */ pthread_mutex_unlock(toxav->mutex); return -1; } if (!call_prepare_transmission(av_call)) { callback_error(toxav_inst, call); pthread_mutex_unlock(toxav->mutex); return -1; } if (!invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities)) { callback_error(toxav_inst, call); pthread_mutex_unlock(toxav->mutex); return -1; } pthread_mutex_unlock(toxav->mutex); return 0; } static int callback_end(void *toxav_inst, MSICall *call) { ToxAV *toxav = (ToxAV *)toxav_inst; pthread_mutex_lock(toxav->mutex); invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_FINISHED); if (call->av_call) { call_kill_transmission(call->av_call); call_remove(call->av_call); } pthread_mutex_unlock(toxav->mutex); return 0; } static int callback_error(void *toxav_inst, MSICall *call) { ToxAV *toxav = (ToxAV *)toxav_inst; pthread_mutex_lock(toxav->mutex); invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_ERROR); if (call->av_call) { call_kill_transmission(call->av_call); call_remove(call->av_call); } pthread_mutex_unlock(toxav->mutex); return 0; } static int callback_capabilites(void *toxav_inst, MSICall *call) { ToxAV *toxav = (ToxAV *)toxav_inst; pthread_mutex_lock(toxav->mutex); if (call->peer_capabilities & MSI_CAP_S_AUDIO) { rtp_allow_receiving(call->av_call->audio_rtp); } else { rtp_stop_receiving(call->av_call->audio_rtp); } if (call->peer_capabilities & MSI_CAP_S_VIDEO) { rtp_allow_receiving(call->av_call->video_rtp); } else { rtp_stop_receiving(call->av_call->video_rtp); } invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities); pthread_mutex_unlock(toxav->mutex); return 0; } static bool audio_bit_rate_invalid(uint32_t bit_rate) { /* Opus RFC 6716 section-2.1.1 dictates the following: * Opus supports all bit rates from 6 kbit/s to 510 kbit/s. */ return bit_rate < 6 || bit_rate > 510; } static bool video_bit_rate_invalid(uint32_t bit_rate) { /* https://www.webmproject.org/docs/webm-sdk/structvpx__codec__enc__cfg.html shows the following: * unsigned int rc_target_bitrate * the range of uint varies from platform to platform * though, uint32_t should be large enough to store bitrates, * we may want to prevent from passing overflowed bitrates to libvpx * more in detail, it's the case where bit_rate is larger than uint, but smaller than uint32_t */ return bit_rate > UINT_MAX; } static bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state) { if (av->scb) { av->scb(av, friend_number, state, av->scb_user_data); } else { return false; } return true; } static ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, Toxav_Err_Call *error) { /* Assumes mutex locked */ Toxav_Err_Call rc = TOXAV_ERR_CALL_OK; ToxAVCall *call = nullptr; if (m_friend_exists(av->m, friend_number) == 0) { rc = TOXAV_ERR_CALL_FRIEND_NOT_FOUND; goto RETURN; } if (m_get_friend_connectionstatus(av->m, friend_number) < 1) { rc = TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED; goto RETURN; } if (call_get(av, friend_number) != nullptr) { rc = TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL; goto RETURN; } call = (ToxAVCall *)calloc(sizeof(ToxAVCall), 1); if (call == nullptr) { rc = TOXAV_ERR_CALL_MALLOC; goto RETURN; } call->av = av; call->friend_number = friend_number; if (create_recursive_mutex(call->toxav_call_mutex)) { free(call); call = nullptr; rc = TOXAV_ERR_CALL_MALLOC; goto RETURN; } if (av->calls == nullptr) { /* Creating */ av->calls = (ToxAVCall **)calloc(sizeof(ToxAVCall *), friend_number + 1); if (av->calls == nullptr) { pthread_mutex_destroy(call->toxav_call_mutex); free(call); call = nullptr; rc = TOXAV_ERR_CALL_MALLOC; goto RETURN; } av->calls_tail = friend_number; av->calls_head = friend_number; } else if (av->calls_tail < friend_number) { /* Appending */ ToxAVCall **tmp = (ToxAVCall **)realloc(av->calls, sizeof(ToxAVCall *) * (friend_number + 1)); if (tmp == nullptr) { pthread_mutex_destroy(call->toxav_call_mutex); free(call); call = nullptr; rc = TOXAV_ERR_CALL_MALLOC; goto RETURN; } av->calls = tmp; /* Set fields in between to null */ for (uint32_t i = av->calls_tail + 1; i < friend_number; ++i) { av->calls[i] = nullptr; } call->prev = av->calls[av->calls_tail]; av->calls[av->calls_tail]->next = call; av->calls_tail = friend_number; } else if (av->calls_head > friend_number) { /* Inserting at front */ call->next = av->calls[av->calls_head]; av->calls[av->calls_head]->prev = call; av->calls_head = friend_number; } av->calls[friend_number] = call; RETURN: if (error) { *error = rc; } return call; } static ToxAVCall *call_get(ToxAV *av, uint32_t friend_number) { /* Assumes mutex locked */ if (av->calls == nullptr || av->calls_tail < friend_number) { return nullptr; } return av->calls[friend_number]; } static ToxAVCall *call_remove(ToxAVCall *call) { if (call == nullptr) { return nullptr; } uint32_t friend_number = call->friend_number; ToxAV *av = call->av; ToxAVCall *prev = call->prev; ToxAVCall *next = call->next; /* Set av call in msi to NULL in order to know if call if ToxAVCall is * removed from the msi call. */ if (call->msi_call) { call->msi_call->av_call = nullptr; } pthread_mutex_destroy(call->toxav_call_mutex); free(call); if (prev) { prev->next = next; } else if (next) { av->calls_head = next->friend_number; } else { goto CLEAR; } if (next) { next->prev = prev; } else if (prev) { av->calls_tail = prev->friend_number; } else { goto CLEAR; } av->calls[friend_number] = nullptr; return next; CLEAR: av->calls_head = 0; av->calls_tail = 0; free(av->calls); av->calls = nullptr; return nullptr; } static bool call_prepare_transmission(ToxAVCall *call) { /* Assumes mutex locked */ if (call == nullptr) { return false; } ToxAV *av = call->av; if (av->acb == nullptr && av->vcb == nullptr) { /* It makes no sense to have CSession without callbacks */ return false; } if (call->active) { LOGGER_WARNING(av->m->log, "Call already active!"); return true; } if (create_recursive_mutex(call->mutex_audio) != 0) { return false; } if (create_recursive_mutex(call->mutex_video) != 0) { goto FAILURE_2; } /* Prepare bwc */ call->bwc = bwc_new(av->m, av->tox, call->friend_number, callback_bwc, call, av->toxav_mono_time); if (call->bwc == nullptr) { LOGGER_ERROR(av->m->log, "Failed to create new bwc"); goto FAILURE; } { /* Prepare audio */ call->audio = ac_new(av->toxav_mono_time, av->m->log, av, call->friend_number, av->acb, av->acb_user_data); if (call->audio == nullptr) { LOGGER_ERROR(av->m->log, "Failed to create audio codec session"); goto FAILURE; } call->audio_rtp = rtp_new(RTP_TYPE_AUDIO, av->m, av->tox, call->friend_number, call->bwc, call->audio, ac_queue_message); if (call->audio_rtp == nullptr) { LOGGER_ERROR(av->m->log, "Failed to create audio rtp session"); goto FAILURE; } } { /* Prepare video */ call->video = vc_new(av->toxav_mono_time, av->m->log, av, call->friend_number, av->vcb, av->vcb_user_data); if (call->video == nullptr) { LOGGER_ERROR(av->m->log, "Failed to create video codec session"); goto FAILURE; } call->video_rtp = rtp_new(RTP_TYPE_VIDEO, av->m, av->tox, call->friend_number, call->bwc, call->video, vc_queue_message); if (call->video_rtp == nullptr) { LOGGER_ERROR(av->m->log, "Failed to create video rtp session"); goto FAILURE; } } call->active = 1; return true; FAILURE: bwc_kill(call->bwc); rtp_kill(call->audio_rtp); ac_kill(call->audio); call->audio_rtp = nullptr; call->audio = nullptr; rtp_kill(call->video_rtp); vc_kill(call->video); call->video_rtp = nullptr; call->video = nullptr; pthread_mutex_destroy(call->mutex_video); FAILURE_2: pthread_mutex_destroy(call->mutex_audio); return false; } static void call_kill_transmission(ToxAVCall *call) { if (call == nullptr || call->active == 0) { return; } call->active = 0; pthread_mutex_lock(call->mutex_audio); pthread_mutex_unlock(call->mutex_audio); pthread_mutex_lock(call->mutex_video); pthread_mutex_unlock(call->mutex_video); pthread_mutex_lock(call->toxav_call_mutex); pthread_mutex_unlock(call->toxav_call_mutex); bwc_kill(call->bwc); rtp_kill(call->audio_rtp); ac_kill(call->audio); call->audio_rtp = nullptr; call->audio = nullptr; rtp_kill(call->video_rtp); vc_kill(call->video); call->video_rtp = nullptr; call->video = nullptr; pthread_mutex_destroy(call->mutex_audio); pthread_mutex_destroy(call->mutex_video); } c-toxcore-0.2.13/toxav/toxav.h000066400000000000000000000624231415350724400161670ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifndef C_TOXCORE_TOXAV_TOXAV_H #define C_TOXCORE_TOXAV_TOXAV_H #include #include #include //!TOKSTYLE- #ifdef __cplusplus extern "C" { #endif /** \page av Public audio/video API for Tox clients. * * This API can handle multiple calls. Each call has its state, in very rare * occasions the library can change the state of the call without apps knowledge. * */ /** \subsection events Events and callbacks * * As in Core API, events are handled by callbacks. One callback can be * registered per event. All events have a callback function type named * `toxav_{event}_cb` and a function to register it named `toxav_callback_{event}`. * Passing a NULL callback will result in no callback being registered for that * event. Only one callback per event can be registered, so if a client needs * multiple event listeners, it needs to implement the dispatch functionality * itself. Unlike Core API, lack of some event handlers will cause the the * library to drop calls before they are started. Hanging up call from a * callback causes undefined behaviour. * */ /** \subsection threading Threading implications * * Only toxav_iterate is thread-safe, all other functions must run from the * tox thread. * * A common way to run ToxAV (multiple or single instance) is to have a thread, * separate from tox instance thread, running a simple toxav_iterate loop, * sleeping for toxav_iteration_interval * milliseconds on each iteration. * * An important thing to note is that events are triggered from both tox and * toxav thread (see above). Audio and video receive frame events are triggered * from toxav thread while all the other events are triggered from tox thread. * * Tox thread has priority with mutex mechanisms. Any api function can * fail if mutexes are held by tox thread in which case they will set SYNC * error code. */ /** * External Tox type. */ #ifndef TOX_DEFINED #define TOX_DEFINED typedef struct Tox Tox; #endif /* TOX_DEFINED */ /** * ToxAV. */ /** * The ToxAV instance type. Each ToxAV instance can be bound to only one Tox * instance, and Tox instance can have only one ToxAV instance. One must make * sure to close ToxAV instance prior closing Tox instance otherwise undefined * behaviour occurs. Upon closing of ToxAV instance, all active calls will be * forcibly terminated without notifying peers. * */ #ifndef TOXAV_DEFINED #define TOXAV_DEFINED typedef struct ToxAV ToxAV; #endif /* TOXAV_DEFINED */ /******************************************************************************* * * :: Creation and destruction * ******************************************************************************/ typedef enum TOXAV_ERR_NEW { /** * The function returned successfully. */ TOXAV_ERR_NEW_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOXAV_ERR_NEW_NULL, /** * Memory allocation failure while trying to allocate structures required for * the A/V session. */ TOXAV_ERR_NEW_MALLOC, /** * Attempted to create a second session for the same Tox instance. */ TOXAV_ERR_NEW_MULTIPLE, } TOXAV_ERR_NEW; /** * Start new A/V session. There can only be only one session per Tox instance. */ ToxAV *toxav_new(Tox *tox, TOXAV_ERR_NEW *error); /** * Releases all resources associated with the A/V session. * * If any calls were ongoing, these will be forcibly terminated without * notifying peers. After calling this function, no other functions may be * called and the av pointer becomes invalid. */ void toxav_kill(ToxAV *av); /** * Returns the Tox instance the A/V object was created for. */ Tox *toxav_get_tox(const ToxAV *av); /******************************************************************************* * * :: A/V event loop * ******************************************************************************/ /** * Returns the interval in milliseconds when the next toxav_iterate call should * be. If no call is active at the moment, this function returns 200. */ uint32_t toxav_iteration_interval(const ToxAV *av); /** * Main loop for the session. This function needs to be called in intervals of * toxav_iteration_interval() milliseconds. It is best called in the separate * thread from tox_iterate. */ void toxav_iterate(ToxAV *av); /******************************************************************************* * * :: Call setup * ******************************************************************************/ typedef enum TOXAV_ERR_CALL { /** * The function returned successfully. */ TOXAV_ERR_CALL_OK, /** * A resource allocation error occurred while trying to create the structures * required for the call. */ TOXAV_ERR_CALL_MALLOC, /** * Synchronization error occurred. */ TOXAV_ERR_CALL_SYNC, /** * The friend number did not designate a valid friend. */ TOXAV_ERR_CALL_FRIEND_NOT_FOUND, /** * The friend was valid, but not currently connected. */ TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED, /** * Attempted to call a friend while already in an audio or video call with * them. */ TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL, /** * Audio or video bit rate is invalid. */ TOXAV_ERR_CALL_INVALID_BIT_RATE, } TOXAV_ERR_CALL; /** * Call a friend. This will start ringing the friend. * * It is the client's responsibility to stop ringing after a certain timeout, * if such behaviour is desired. If the client does not stop ringing, the * library will not stop until the friend is disconnected. Audio and video * receiving are both enabled by default. * * @param friend_number The friend number of the friend that should be called. * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable * audio sending. * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable * video sending. */ bool toxav_call(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_CALL *error); /** * The function type for the call callback. * * @param friend_number The friend number from which the call is incoming. * @param audio_enabled True if friend is sending audio. * @param video_enabled True if friend is sending video. */ typedef void toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data); /** * Set the callback for the `call` event. Pass NULL to unset. * */ void toxav_callback_call(ToxAV *av, toxav_call_cb *callback, void *user_data); typedef enum TOXAV_ERR_ANSWER { /** * The function returned successfully. */ TOXAV_ERR_ANSWER_OK, /** * Synchronization error occurred. */ TOXAV_ERR_ANSWER_SYNC, /** * Failed to initialize codecs for call session. Note that codec initiation * will fail if there is no receive callback registered for either audio or * video. */ TOXAV_ERR_ANSWER_CODEC_INITIALIZATION, /** * The friend number did not designate a valid friend. */ TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND, /** * The friend was valid, but they are not currently trying to initiate a call. * This is also returned if this client is already in a call with the friend. */ TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING, /** * Audio or video bit rate is invalid. */ TOXAV_ERR_ANSWER_INVALID_BIT_RATE, } TOXAV_ERR_ANSWER; /** * Accept an incoming call. * * If answering fails for any reason, the call will still be pending and it is * possible to try and answer it later. Audio and video receiving are both * enabled by default. * * @param friend_number The friend number of the friend that is calling. * @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable * audio sending. * @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable * video sending. */ bool toxav_answer(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate, TOXAV_ERR_ANSWER *error); /******************************************************************************* * * :: Call state graph * ******************************************************************************/ enum TOXAV_FRIEND_CALL_STATE { /** * The empty bit mask. None of the bits specified below are set. */ TOXAV_FRIEND_CALL_STATE_NONE = 0, /** * Set by the AV core if an error occurred on the remote end or if friend * timed out. This is the final state after which no more state * transitions can occur for the call. This call state will never be triggered * in combination with other call states. */ TOXAV_FRIEND_CALL_STATE_ERROR = 1, /** * The call has finished. This is the final state after which no more state * transitions can occur for the call. This call state will never be * triggered in combination with other call states. */ TOXAV_FRIEND_CALL_STATE_FINISHED = 2, /** * The flag that marks that friend is sending audio. */ TOXAV_FRIEND_CALL_STATE_SENDING_A = 4, /** * The flag that marks that friend is sending video. */ TOXAV_FRIEND_CALL_STATE_SENDING_V = 8, /** * The flag that marks that friend is receiving audio. */ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A = 16, /** * The flag that marks that friend is receiving video. */ TOXAV_FRIEND_CALL_STATE_ACCEPTING_V = 32, }; /** * The function type for the call_state callback. * * @param friend_number The friend number for which the call state changed. * @param state The bitmask of the new call state which is guaranteed to be * different than the previous state. The state is set to 0 when the call is * paused. The bitmask represents all the activities currently performed by the * friend. */ typedef void toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data); /** * Set the callback for the `call_state` event. Pass NULL to unset. * */ void toxav_callback_call_state(ToxAV *av, toxav_call_state_cb *callback, void *user_data); /******************************************************************************* * * :: Call control * ******************************************************************************/ typedef enum TOXAV_CALL_CONTROL { /** * Resume a previously paused call. Only valid if the pause was caused by this * client, if not, this control is ignored. Not valid before the call is accepted. */ TOXAV_CALL_CONTROL_RESUME, /** * Put a call on hold. Not valid before the call is accepted. */ TOXAV_CALL_CONTROL_PAUSE, /** * Reject a call if it was not answered, yet. Cancel a call after it was * answered. */ TOXAV_CALL_CONTROL_CANCEL, /** * Request that the friend stops sending audio. Regardless of the friend's * compliance, this will cause the audio_receive_frame event to stop being * triggered on receiving an audio frame from the friend. */ TOXAV_CALL_CONTROL_MUTE_AUDIO, /** * Calling this control will notify client to start sending audio again. */ TOXAV_CALL_CONTROL_UNMUTE_AUDIO, /** * Request that the friend stops sending video. Regardless of the friend's * compliance, this will cause the video_receive_frame event to stop being * triggered on receiving a video frame from the friend. */ TOXAV_CALL_CONTROL_HIDE_VIDEO, /** * Calling this control will notify client to start sending video again. */ TOXAV_CALL_CONTROL_SHOW_VIDEO, } TOXAV_CALL_CONTROL; typedef enum TOXAV_ERR_CALL_CONTROL { /** * The function returned successfully. */ TOXAV_ERR_CALL_CONTROL_OK, /** * Synchronization error occurred. */ TOXAV_ERR_CALL_CONTROL_SYNC, /** * The friend_number passed did not designate a valid friend. */ TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND, /** * This client is currently not in a call with the friend. Before the call is * answered, only CANCEL is a valid control. */ TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL, /** * Happens if user tried to pause an already paused call or if trying to * resume a call that is not paused. */ TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION, } TOXAV_ERR_CALL_CONTROL; /** * Sends a call control command to a friend. * * @param friend_number The friend number of the friend this client is in a call * with. * @param control The control command to send. * * @return true on success. */ bool toxav_call_control(ToxAV *av, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error); /******************************************************************************* * * :: Controlling bit rates * ******************************************************************************/ typedef enum TOXAV_ERR_BIT_RATE_SET { /** * The function returned successfully. */ TOXAV_ERR_BIT_RATE_SET_OK, /** * Synchronization error occurred. */ TOXAV_ERR_BIT_RATE_SET_SYNC, /** * The bit rate passed was not one of the supported values. */ TOXAV_ERR_BIT_RATE_SET_INVALID_BIT_RATE, /** * The friend_number passed did not designate a valid friend. */ TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND, /** * This client is currently not in a call with the friend. */ TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL, } TOXAV_ERR_BIT_RATE_SET; /******************************************************************************* * * :: A/V sending * ******************************************************************************/ typedef enum TOXAV_ERR_SEND_FRAME { /** * The function returned successfully. */ TOXAV_ERR_SEND_FRAME_OK, /** * In case of video, one of Y, U, or V was NULL. In case of audio, the samples * data pointer was NULL. */ TOXAV_ERR_SEND_FRAME_NULL, /** * The friend_number passed did not designate a valid friend. */ TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND, /** * This client is currently not in a call with the friend. */ TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL, /** * Synchronization error occurred. */ TOXAV_ERR_SEND_FRAME_SYNC, /** * One of the frame parameters was invalid. E.g. the resolution may be too * small or too large, or the audio sampling rate may be unsupported. */ TOXAV_ERR_SEND_FRAME_INVALID, /** * Either friend turned off audio or video receiving or we turned off sending * for the said payload. */ TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED, /** * Failed to push frame through rtp interface. */ TOXAV_ERR_SEND_FRAME_RTP_FAILED, } TOXAV_ERR_SEND_FRAME; /** * Send an audio frame to a friend. * * The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... * Meaning: sample 1 for channel 1, sample 1 for channel 2, ... * For mono audio, this has no meaning, every sample is subsequent. For stereo, * this means the expected format is LRLRLR... with samples for left and right * alternating. * * @param friend_number The friend number of the friend to which to send an * audio frame. * @param pcm An array of audio samples. The size of this array must be * sample_count * channels. * @param sample_count Number of samples in this frame. Valid numbers here are * ((sample rate) * (audio length) / 1000), where audio length can be * 2.5, 5, 10, 20, 40 or 60 millseconds. * @param channels Number of audio channels. Supported values are 1 and 2. * @param sampling_rate Audio sampling rate used in this frame. Valid sampling * rates are 8000, 12000, 16000, 24000, or 48000. */ bool toxav_audio_send_frame(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error); /** * Set the bit rate to be used in subsequent video frames. * * @param friend_number The friend number of the friend for which to set the * bit rate. * @param bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable. * * @return true on success. */ bool toxav_audio_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error); /** * The function type for the audio_bit_rate callback. The event is triggered * when the network becomes too saturated for current bit rates at which * point core suggests new bit rates. * * @param friend_number The friend number of the friend for which to set the * bit rate. * @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec. */ typedef void toxav_audio_bit_rate_cb(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, void *user_data); /** * Set the callback for the `audio_bit_rate` event. Pass NULL to unset. * */ void toxav_callback_audio_bit_rate(ToxAV *av, toxav_audio_bit_rate_cb *callback, void *user_data); /** * Send a video frame to a friend. * * Y - plane should be of size: height * width * U - plane should be of size: (height/2) * (width/2) * V - plane should be of size: (height/2) * (width/2) * * @param friend_number The friend number of the friend to which to send a video * frame. * @param width Width of the frame in pixels. * @param height Height of the frame in pixels. * @param y Y (Luminance) plane data. * @param u U (Chroma) plane data. * @param v V (Chroma) plane data. */ bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error); /** * Set the bit rate to be used in subsequent video frames. * * @param friend_number The friend number of the friend for which to set the * bit rate. * @param bit_rate The new video bit rate in Kb/sec. Set to 0 to disable. * * @return true on success. */ bool toxav_video_set_bit_rate(ToxAV *av, uint32_t friend_number, uint32_t bit_rate, TOXAV_ERR_BIT_RATE_SET *error); /** * The function type for the video_bit_rate callback. The event is triggered * when the network becomes too saturated for current bit rates at which * point core suggests new bit rates. * * @param friend_number The friend number of the friend for which to set the * bit rate. * @param video_bit_rate Suggested maximum video bit rate in Kb/sec. */ typedef void toxav_video_bit_rate_cb(ToxAV *av, uint32_t friend_number, uint32_t video_bit_rate, void *user_data); /** * Set the callback for the `video_bit_rate` event. Pass NULL to unset. * */ void toxav_callback_video_bit_rate(ToxAV *av, toxav_video_bit_rate_cb *callback, void *user_data); /******************************************************************************* * * :: A/V receiving * ******************************************************************************/ /** * The function type for the audio_receive_frame callback. The callback can be * called multiple times per single iteration depending on the amount of queued * frames in the buffer. The received format is the same as in send function. * * @param friend_number The friend number of the friend who sent an audio frame. * @param pcm An array of audio samples (sample_count * channels elements). * @param sample_count The number of audio samples per channel in the PCM array. * @param channels Number of audio channels. * @param sampling_rate Sampling rate used in this frame. * */ typedef void toxav_audio_receive_frame_cb(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate, void *user_data); /** * Set the callback for the `audio_receive_frame` event. Pass NULL to unset. * */ void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *callback, void *user_data); /** * The function type for the video_receive_frame callback. * * The size of plane data is derived from width and height as documented * below. * * Strides represent padding for each plane that may or may not be present. * You must handle strides in your image processing code. Strides are * negative if the image is bottom-up hence why you MUST abs() it when * calculating plane buffer size. * * @param friend_number The friend number of the friend who sent a video frame. * @param width Width of the frame in pixels. * @param height Height of the frame in pixels. * @param y Luminosity plane. Size = MAX(width, abs(ystride)) * height. * @param u U chroma plane. Size = MAX(width/2, abs(ustride)) * (height/2). * @param v V chroma plane. Size = MAX(width/2, abs(vstride)) * (height/2). * * @param ystride Luminosity plane stride. * @param ustride U chroma plane stride. * @param vstride V chroma plane stride. */ typedef void toxav_video_receive_frame_cb(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, int32_t vstride, void *user_data); /** * Set the callback for the `video_receive_frame` event. Pass NULL to unset. * */ void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *callback, void *user_data); /** * NOTE Compatibility with old toxav group calls. TODO(iphydf): remove * * TODO(iphydf): Use proper new API guidelines for these. E.g. don't use inline * function types, don't have per-callback userdata, especially don't have one * userdata per group. */ /* Create a new toxav group. * * return group number on success. * return -1 on failure. * * Audio data callback format: * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). */ int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), void *userdata); /* Join a AV group (you need to have been invited first.) * * returns group number on success * returns -1 on failure. * * Audio data callback format (same as the one for toxav_add_av_groupchat()): * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). */ int toxav_join_av_groupchat(Tox *tox, uint32_t friendnumber, const uint8_t *data, uint16_t length, void (*audio_callback)(void *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), void *userdata); /* Send audio to the group chat. * * return 0 on success. * return -1 on failure. * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). * * Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000) * Valid number of channels are 1 or 2. * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. * * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 */ int toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate); /* Enable A/V in a groupchat. * * A/V must be enabled on a groupchat for audio to be sent to it and for * received audio to be handled. * * An A/V group created with toxav_add_av_groupchat or toxav_join_av_groupchat * will start with A/V enabled. * * An A/V group loaded from a savefile will start with A/V disabled. * * return 0 on success. * return -1 on failure. * * Audio data callback format (same as the one for toxav_add_av_groupchat()): * audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata) * * Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)). */ int toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, void (*audio_callback)(void *, uint32_t, uint32_t, const int16_t *, unsigned int, uint8_t, uint32_t, void *), void *userdata); /* Disable A/V in a groupchat. * * return 0 on success. * return -1 on failure. */ int toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber); /* Return whether A/V is enabled in the groupchat. */ bool toxav_groupchat_av_enabled(Tox *tox, uint32_t groupnumber); #ifdef __cplusplus } #endif typedef void toxav_group_audio_cb(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, uint32_t samples, uint8_t channels, uint32_t sample_rate, void *user_data); typedef TOXAV_ERR_CALL Toxav_Err_Call; typedef TOXAV_ERR_NEW Toxav_Err_New; typedef TOXAV_ERR_ANSWER Toxav_Err_Answer; typedef TOXAV_ERR_CALL_CONTROL Toxav_Err_Call_Control; typedef TOXAV_ERR_BIT_RATE_SET Toxav_Err_Bit_Rate_Set; typedef TOXAV_ERR_SEND_FRAME Toxav_Err_Send_Frame; typedef TOXAV_CALL_CONTROL Toxav_Call_Control; //!TOKSTYLE+ #endif // C_TOXCORE_TOXAV_TOXAV_H c-toxcore-0.2.13/toxav/toxav_old.c000066400000000000000000000106441415350724400170160ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ /** * This file contains the group chats code for the backwards compatibility. */ #include "toxav.h" #include "groupav.h" /* Create a new toxav group. * * return group number on success. * return -1 on failure. * * Audio data callback format: * `audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata)` * * Note that total size of pcm in bytes is equal to `(samples * channels * sizeof(int16_t))`. */ int toxav_add_av_groupchat(Tox *tox, audio_data_cb *audio_callback, void *userdata) { // TODO(iphydf): Don't rely on toxcore internals. //!TOKSTYLE- Messenger *m = *(Messenger **)tox; //!TOKSTYLE+ return add_av_groupchat(m->log, tox, m->conferences_object, audio_callback, userdata); } /* Join a AV group (you need to have been invited first.) * * returns group number on success * returns -1 on failure. * * Audio data callback format (same as the one for `toxav_add_av_groupchat()`): * `audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata)` * * Note that total size of pcm in bytes is equal to `(samples * channels * sizeof(int16_t))`. */ int toxav_join_av_groupchat(Tox *tox, uint32_t friendnumber, const uint8_t *data, uint16_t length, audio_data_cb *audio_callback, void *userdata) { // TODO(iphydf): Don't rely on toxcore internals. //!TOKSTYLE- Messenger *m = *(Messenger **)tox; //!TOKSTYLE+ return join_av_groupchat(m->log, tox, m->conferences_object, friendnumber, data, length, audio_callback, userdata); } /* Send audio to the group chat. * * return 0 on success. * return -1 on failure. * * Note that total size of pcm in bytes is equal to `(samples * channels * sizeof(int16_t))`. * * Valid number of samples are `((sample rate) * (audio length) / 1000)` (Valid values for audio length: 2.5, 5, 10, 20, 40 or 60 ms) * Valid number of channels are 1 or 2. * Valid sample rates are 8000, 12000, 16000, 24000, or 48000. * * Recommended values are: samples = 960, channels = 1, sample_rate = 48000 */ int toxav_group_send_audio(Tox *tox, uint32_t groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate) { // TODO(iphydf): Don't rely on toxcore internals. //!TOKSTYLE- Messenger *m = *(Messenger **)tox; //!TOKSTYLE+ return group_send_audio(m->conferences_object, groupnumber, pcm, samples, channels, sample_rate); } /* Enable A/V in a groupchat. * * A/V must be enabled on a groupchat for audio to be sent to it and for * received audio to be handled. * * An A/V group created with toxav_add_av_groupchat or toxav_join_av_groupchat * will start with A/V enabled. * * An A/V group loaded from a savefile will start with A/V disabled. * * return 0 on success. * return -1 on failure. * * Audio data callback format (same as the one for toxav_add_av_groupchat()): * `audio_callback(Tox *tox, uint32_t groupnumber, uint32_t peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, uint32_t sample_rate, void *userdata)` * * Note that total size of pcm in bytes is equal to `(samples * channels * sizeof(int16_t))`. */ int toxav_groupchat_enable_av(Tox *tox, uint32_t groupnumber, audio_data_cb *audio_callback, void *userdata) { // TODO(iphydf): Don't rely on toxcore internals. //!TOKSTYLE- Messenger *m = *(Messenger **)tox; //!TOKSTYLE+ return groupchat_enable_av(m->log, tox, m->conferences_object, groupnumber, audio_callback, userdata); } /* Disable A/V in a groupchat. * * return 0 on success. * return -1 on failure. */ int toxav_groupchat_disable_av(Tox *tox, uint32_t groupnumber) { // TODO(iphydf): Don't rely on toxcore internals. //!TOKSTYLE- Messenger *m = *(Messenger **)tox; //!TOKSTYLE+ return groupchat_disable_av(m->conferences_object, groupnumber); } /* Return whether A/V is enabled in the groupchat. */ bool toxav_groupchat_av_enabled(Tox *tox, uint32_t groupnumber) { // TODO(iphydf): Don't rely on toxcore internals. //!TOKSTYLE- Messenger *m = *(Messenger **)tox; //!TOKSTYLE+ return groupchat_av_enabled(m->conferences_object, groupnumber); } c-toxcore-0.2.13/toxav/video.c000066400000000000000000000352271415350724400161310ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "video.h" #include #include #include #include "msi.h" #include "ring_buffer.h" #include "rtp.h" #include "../toxcore/logger.h" #include "../toxcore/mono_time.h" #include "../toxcore/network.h" /** * Soft deadline the decoder should attempt to meet, in "us" (microseconds). * Set to zero for unlimited. * * By convention, the value 1 is used to mean "return as fast as possible." */ // TODO(zoff99): don't hardcode this, let the application choose it #define WANTED_MAX_DECODER_FPS 40 /** * VPX_DL_REALTIME (1) * deadline parameter analogous to VPx REALTIME mode. * * VPX_DL_GOOD_QUALITY (1000000) * deadline parameter analogous to VPx GOOD QUALITY mode. * * VPX_DL_BEST_QUALITY (0) * deadline parameter analogous to VPx BEST QUALITY mode. */ #define MAX_DECODE_TIME_US (1000000 / WANTED_MAX_DECODER_FPS) // to allow x fps /** * Codec control function to set encoder internal speed settings. Changes in * this value influences, among others, the encoder's selection of motion * estimation methods. Values greater than 0 will increase encoder speed at the * expense of quality. * * Note Valid range for VP8: -16..16 */ #define VP8E_SET_CPUUSED_VALUE 16 /** * Initialize encoder with this value. Target bandwidth to use for this stream, in kilobits per second. */ #define VIDEO_BITRATE_INITIAL_VALUE 5000 #define VIDEO_DECODE_BUFFER_SIZE 5 // this buffer has normally max. 1 entry static vpx_codec_iface_t *video_codec_decoder_interface(void) { return vpx_codec_vp8_dx(); } static vpx_codec_iface_t *video_codec_encoder_interface(void) { return vpx_codec_vp8_cx(); } #define VIDEO_CODEC_DECODER_MAX_WIDTH 800 // its a dummy value, because the struct needs a value there #define VIDEO_CODEC_DECODER_MAX_HEIGHT 600 // its a dummy value, because the struct needs a value there #define VPX_MAX_DIST_START 40 #define VPX_MAX_ENCODER_THREADS 4 #define VPX_MAX_DECODER_THREADS 4 #define VIDEO_VP8_DECODER_POST_PROCESSING_ENABLED 0 static void vc_init_encoder_cfg(const Logger *log, vpx_codec_enc_cfg_t *cfg, int16_t kf_max_dist) { vpx_codec_err_t rc = vpx_codec_enc_config_default(video_codec_encoder_interface(), cfg, 0); if (rc != VPX_CODEC_OK) { LOGGER_ERROR(log, "vc_init_encoder_cfg:Failed to get config: %s", vpx_codec_err_to_string(rc)); } /* Target bandwidth to use for this stream, in kilobits per second */ cfg->rc_target_bitrate = VIDEO_BITRATE_INITIAL_VALUE; cfg->g_w = VIDEO_CODEC_DECODER_MAX_WIDTH; cfg->g_h = VIDEO_CODEC_DECODER_MAX_HEIGHT; cfg->g_pass = VPX_RC_ONE_PASS; cfg->g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; cfg->g_lag_in_frames = 0; /* Allow lagged encoding * * If set, this value allows the encoder to consume a number of input * frames before producing output frames. This allows the encoder to * base decisions for the current frame on future frames. This does * increase the latency of the encoding pipeline, so it is not appropriate * in all situations (ex: realtime encoding). * * Note that this is a maximum value -- the encoder may produce frames * sooner than the given limit. Set this value to 0 to disable this * feature. */ cfg->kf_min_dist = 0; cfg->kf_mode = VPX_KF_AUTO; // Encoder determines optimal placement automatically cfg->rc_end_usage = VPX_VBR; // what quality mode? /* * VPX_VBR Variable Bit Rate (VBR) mode * VPX_CBR Constant Bit Rate (CBR) mode * VPX_CQ Constrained Quality (CQ) mode -> give codec a hint that we may be on low bandwidth connection * VPX_Q Constant Quality (Q) mode */ if (kf_max_dist > 1) { cfg->kf_max_dist = kf_max_dist; // a full frame every x frames minimum (can be more often, codec decides automatically) LOGGER_DEBUG(log, "kf_max_dist=%d (1)", cfg->kf_max_dist); } else { cfg->kf_max_dist = VPX_MAX_DIST_START; LOGGER_DEBUG(log, "kf_max_dist=%d (2)", cfg->kf_max_dist); } cfg->g_threads = VPX_MAX_ENCODER_THREADS; // Maximum number of threads to use /* TODO: set these to something reasonable */ // cfg->g_timebase.num = 1; // cfg->g_timebase.den = 60; // 60 fps cfg->rc_resize_allowed = 1; // allow encoder to resize to smaller resolution cfg->rc_resize_up_thresh = 40; cfg->rc_resize_down_thresh = 5; /* TODO: make quality setting an API call, but start with normal quality */ #if 0 /* Highest-resolution encoder settings */ cfg->rc_dropframe_thresh = 0; cfg->rc_resize_allowed = 0; cfg->rc_min_quantizer = 2; cfg->rc_max_quantizer = 56; cfg->rc_undershoot_pct = 100; cfg->rc_overshoot_pct = 15; cfg->rc_buf_initial_sz = 500; cfg->rc_buf_optimal_sz = 600; cfg->rc_buf_sz = 1000; #endif } VCSession *vc_new(Mono_Time *mono_time, const Logger *log, ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data) { VCSession *vc = (VCSession *)calloc(sizeof(VCSession), 1); vpx_codec_err_t rc; if (!vc) { LOGGER_WARNING(log, "Allocation failed! Application might misbehave!"); return nullptr; } if (create_recursive_mutex(vc->queue_mutex) != 0) { LOGGER_WARNING(log, "Failed to create recursive mutex!"); free(vc); return nullptr; } int cpu_used_value = VP8E_SET_CPUUSED_VALUE; vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE); if (!vc->vbuf_raw) { goto BASE_CLEANUP; } /* * VPX_CODEC_USE_FRAME_THREADING * Enable frame-based multi-threading * * VPX_CODEC_USE_ERROR_CONCEALMENT * Conceal errors in decoded frames */ vpx_codec_dec_cfg_t dec_cfg; dec_cfg.threads = VPX_MAX_DECODER_THREADS; // Maximum number of threads to use dec_cfg.w = VIDEO_CODEC_DECODER_MAX_WIDTH; dec_cfg.h = VIDEO_CODEC_DECODER_MAX_HEIGHT; LOGGER_DEBUG(log, "Using VP8 codec for decoder (0)"); rc = vpx_codec_dec_init(vc->decoder, video_codec_decoder_interface(), &dec_cfg, VPX_CODEC_USE_FRAME_THREADING | VPX_CODEC_USE_POSTPROC); if (rc == VPX_CODEC_INCAPABLE) { LOGGER_WARNING(log, "Postproc not supported by this decoder (0)"); rc = vpx_codec_dec_init(vc->decoder, video_codec_decoder_interface(), &dec_cfg, VPX_CODEC_USE_FRAME_THREADING); } if (rc != VPX_CODEC_OK) { LOGGER_ERROR(log, "Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); goto BASE_CLEANUP; } if (VIDEO_VP8_DECODER_POST_PROCESSING_ENABLED == 1) { vp8_postproc_cfg_t pp = {VP8_DEBLOCK, 1, 0}; vpx_codec_err_t cc_res = vpx_codec_control(vc->decoder, VP8_SET_POSTPROC, &pp); if (cc_res != VPX_CODEC_OK) { LOGGER_WARNING(log, "Failed to turn on postproc"); } else { LOGGER_DEBUG(log, "turn on postproc: OK"); } } else { vp8_postproc_cfg_t pp = {0, 0, 0}; vpx_codec_err_t cc_res = vpx_codec_control(vc->decoder, VP8_SET_POSTPROC, &pp); if (cc_res != VPX_CODEC_OK) { LOGGER_WARNING(log, "Failed to turn OFF postproc"); } else { LOGGER_DEBUG(log, "Disable postproc: OK"); } } /* Set encoder to some initial values */ vpx_codec_enc_cfg_t cfg; vc_init_encoder_cfg(log, &cfg, 1); LOGGER_DEBUG(log, "Using VP8 codec for encoder (0.1)"); rc = vpx_codec_enc_init(vc->encoder, video_codec_encoder_interface(), &cfg, VPX_CODEC_USE_FRAME_THREADING); if (rc != VPX_CODEC_OK) { LOGGER_ERROR(log, "Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); goto BASE_CLEANUP_1; } rc = vpx_codec_control(vc->encoder, VP8E_SET_CPUUSED, cpu_used_value); if (rc != VPX_CODEC_OK) { LOGGER_ERROR(log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); vpx_codec_destroy(vc->encoder); goto BASE_CLEANUP_1; } /* * VPX_CTRL_USE_TYPE(VP8E_SET_NOISE_SENSITIVITY, unsigned int) * control function to set noise sensitivity * 0: off, 1: OnYOnly, 2: OnYUV, 3: OnYUVAggressive, 4: Adaptive */ #if 0 rc = vpx_codec_control(vc->encoder, VP8E_SET_NOISE_SENSITIVITY, 2); if (rc != VPX_CODEC_OK) { LOGGER_ERROR(log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); vpx_codec_destroy(vc->encoder); goto BASE_CLEANUP_1; } #endif vc->linfts = current_time_monotonic(mono_time); vc->lcfd = 60; vc->vcb = cb; vc->vcb_user_data = cb_data; vc->friend_number = friend_number; vc->av = av; vc->log = log; return vc; BASE_CLEANUP_1: vpx_codec_destroy(vc->decoder); BASE_CLEANUP: pthread_mutex_destroy(vc->queue_mutex); rb_kill(vc->vbuf_raw); free(vc); return nullptr; } void vc_kill(VCSession *vc) { if (!vc) { return; } vpx_codec_destroy(vc->encoder); vpx_codec_destroy(vc->decoder); void *p; while (rb_read(vc->vbuf_raw, &p)) { free(p); } rb_kill(vc->vbuf_raw); pthread_mutex_destroy(vc->queue_mutex); LOGGER_DEBUG(vc->log, "Terminated video handler: %p", (void *)vc); free(vc); } void vc_iterate(VCSession *vc) { if (!vc) { return; } pthread_mutex_lock(vc->queue_mutex); struct RTPMessage *p; if (!rb_read(vc->vbuf_raw, (void **)&p)) { LOGGER_TRACE(vc->log, "no Video frame data available"); pthread_mutex_unlock(vc->queue_mutex); return; } uint16_t log_rb_size = rb_size(vc->vbuf_raw); pthread_mutex_unlock(vc->queue_mutex); const struct RTPHeader *const header = &p->header; uint32_t full_data_len; if (header->flags & RTP_LARGE_FRAME) { full_data_len = header->data_length_full; LOGGER_DEBUG(vc->log, "vc_iterate:001:full_data_len=%d", (int)full_data_len); } else { full_data_len = p->len; LOGGER_DEBUG(vc->log, "vc_iterate:002"); } LOGGER_DEBUG(vc->log, "vc_iterate: rb_read p->len=%d p->header.xe=%d", (int)full_data_len, p->header.xe); LOGGER_DEBUG(vc->log, "vc_iterate: rb_read rb size=%d", (int)log_rb_size); const vpx_codec_err_t rc = vpx_codec_decode(vc->decoder, p->data, full_data_len, nullptr, MAX_DECODE_TIME_US); free(p); if (rc != VPX_CODEC_OK) { LOGGER_ERROR(vc->log, "Error decoding video: %d %s", (int)rc, vpx_codec_err_to_string(rc)); return; } /* Play decoded images */ vpx_codec_iter_t iter = nullptr; for (vpx_image_t *dest = vpx_codec_get_frame(vc->decoder, &iter); dest != nullptr; dest = vpx_codec_get_frame(vc->decoder, &iter)) { if (vc->vcb) { vc->vcb(vc->av, vc->friend_number, dest->d_w, dest->d_h, (const uint8_t *)dest->planes[0], (const uint8_t *)dest->planes[1], (const uint8_t *)dest->planes[2], dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb_user_data); } vpx_img_free(dest); // is this needed? none of the VPx examples show that } } int vc_queue_message(Mono_Time *mono_time, void *vcp, struct RTPMessage *msg) { /* This function is called with complete messages * they have already been assembled. * this function gets called from handle_rtp_packet() and handle_rtp_packet_v3() */ if (!vcp || !msg) { if (msg) { free(msg); } return -1; } VCSession *vc = (VCSession *)vcp; const struct RTPHeader *const header = &msg->header; if (msg->header.pt == (RTP_TYPE_VIDEO + 2) % 128) { LOGGER_WARNING(vc->log, "Got dummy!"); free(msg); return 0; } if (msg->header.pt != RTP_TYPE_VIDEO % 128) { LOGGER_WARNING(vc->log, "Invalid payload type! pt=%d", (int)msg->header.pt); free(msg); return -1; } pthread_mutex_lock(vc->queue_mutex); if ((header->flags & RTP_LARGE_FRAME) && header->pt == RTP_TYPE_VIDEO % 128) { LOGGER_DEBUG(vc->log, "rb_write msg->len=%d b0=%d b1=%d", (int)msg->len, (int)msg->data[0], (int)msg->data[1]); } free(rb_write(vc->vbuf_raw, msg)); /* Calculate time it took for peer to send us this frame */ uint32_t t_lcfd = current_time_monotonic(mono_time) - vc->linfts; vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd; vc->linfts = current_time_monotonic(mono_time); pthread_mutex_unlock(vc->queue_mutex); return 0; } int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height, int16_t kf_max_dist) { if (!vc) { return -1; } vpx_codec_enc_cfg_t cfg2 = *vc->encoder->config.enc; vpx_codec_err_t rc; if (cfg2.rc_target_bitrate == bit_rate && cfg2.g_w == width && cfg2.g_h == height && kf_max_dist == -1) { return 0; /* Nothing changed */ } if (cfg2.g_w == width && cfg2.g_h == height && kf_max_dist == -1) { /* Only bit rate changed */ LOGGER_INFO(vc->log, "bitrate change from: %u to: %u", (uint32_t)cfg2.rc_target_bitrate, (uint32_t)bit_rate); cfg2.rc_target_bitrate = bit_rate; rc = vpx_codec_enc_config_set(vc->encoder, &cfg2); if (rc != VPX_CODEC_OK) { LOGGER_ERROR(vc->log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); return -1; } } else { /* Resolution is changed, must reinitialize encoder since libvpx v1.4 doesn't support * reconfiguring encoder to use resolutions greater than initially set. */ LOGGER_DEBUG(vc->log, "Have to reinitialize vpx encoder on session %p", (void *)vc); vpx_codec_ctx_t new_c; vpx_codec_enc_cfg_t cfg; vc_init_encoder_cfg(vc->log, &cfg, kf_max_dist); cfg.rc_target_bitrate = bit_rate; cfg.g_w = width; cfg.g_h = height; LOGGER_DEBUG(vc->log, "Using VP8 codec for encoder"); rc = vpx_codec_enc_init(&new_c, video_codec_encoder_interface(), &cfg, VPX_CODEC_USE_FRAME_THREADING); if (rc != VPX_CODEC_OK) { LOGGER_ERROR(vc->log, "Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); return -1; } int cpu_used_value = VP8E_SET_CPUUSED_VALUE; rc = vpx_codec_control(&new_c, VP8E_SET_CPUUSED, cpu_used_value); if (rc != VPX_CODEC_OK) { LOGGER_ERROR(vc->log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); vpx_codec_destroy(&new_c); return -1; } vpx_codec_destroy(vc->encoder); memcpy(vc->encoder, &new_c, sizeof(new_c)); } return 0; } c-toxcore-0.2.13/toxav/video.h000066400000000000000000000027431415350724400161330ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2015 Tox project. */ #ifndef C_TOXCORE_TOXAV_VIDEO_H #define C_TOXCORE_TOXAV_VIDEO_H #include "toxav.h" #include "../toxcore/logger.h" #include "../toxcore/util.h" #include "ring_buffer.h" #include "rtp.h" #include #include #include #include #include #include typedef struct VCSession_s { /* encoding */ vpx_codec_ctx_t encoder[1]; uint32_t frame_counter; /* decoding */ vpx_codec_ctx_t decoder[1]; struct RingBuffer *vbuf_raw; /* Un-decoded data */ uint64_t linfts; /* Last received frame time stamp */ uint32_t lcfd; /* Last calculated frame duration for incoming video payload */ const Logger *log; ToxAV *av; uint32_t friend_number; /* Video frame receive callback */ toxav_video_receive_frame_cb *vcb; void *vcb_user_data; pthread_mutex_t queue_mutex[1]; } VCSession; VCSession *vc_new(Mono_Time *mono_time, const Logger *log, ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data); void vc_kill(VCSession *vc); void vc_iterate(VCSession *vc); int vc_queue_message(Mono_Time *mono_time, void *vcp, struct RTPMessage *msg); int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height, int16_t kf_max_dist); #endif // C_TOXCORE_TOXAV_VIDEO_H c-toxcore-0.2.13/toxcore/000077500000000000000000000000001415350724400151705ustar00rootroot00000000000000c-toxcore-0.2.13/toxcore/BUILD.bazel000066400000000000000000000126641415350724400170570ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_test") load("//tools:no_undefined.bzl", "cc_library") filegroup( name = "public_headers", srcs = ["tox.h"], visibility = ["//c-toxcore:__pkg__"], ) cc_library( name = "ccompat", hdrs = ["ccompat.h"], visibility = ["//c-toxcore:__subpackages__"], ) cc_library( name = "crypto_core", srcs = [ "crypto_core.c", "crypto_core_mem.c", ], hdrs = [ "crypto_core.h", ], visibility = ["//c-toxcore:__subpackages__"], deps = [ ":ccompat", "@libsodium", ], ) cc_test( name = "crypto_core_test", size = "small", srcs = ["crypto_core_test.cc"], flaky = True, deps = [ ":crypto_core", "@com_google_googletest//:gtest_main", ], ) cc_library( name = "list", srcs = ["list.c"], hdrs = ["list.h"], deps = [":ccompat"], ) cc_library( name = "logger", srcs = ["logger.c"], hdrs = ["logger.h"], deps = [":ccompat"], ) cc_library( name = "state", srcs = ["state.c"], hdrs = ["state.h"], deps = [":logger"], ) cc_library( name = "mono_time", srcs = ["mono_time.c"], hdrs = ["mono_time.h"], deps = [ ":ccompat", "@pthread", ], ) cc_test( name = "mono_time_test", size = "small", srcs = ["mono_time_test.cc"], deps = [ ":mono_time", "@com_google_googletest//:gtest_main", ], ) cc_library( name = "network", srcs = [ "network.c", "util.c", ], hdrs = [ "network.h", "util.h", ], visibility = [ "//c-toxcore/other:__pkg__", "//c-toxcore/toxav:__pkg__", ], deps = [ ":ccompat", ":crypto_core", ":logger", ":mono_time", "@psocket", "@pthread", ], ) cc_test( name = "util_test", size = "small", srcs = ["util_test.cc"], deps = [ ":network", "@com_google_googletest//:gtest_main", ], ) cc_library( name = "ping_array", srcs = ["ping_array.c"], hdrs = ["ping_array.h"], deps = [":network"], ) cc_test( name = "ping_array_test", size = "small", srcs = ["ping_array_test.cc"], deps = [ ":ping_array", "@com_google_googletest//:gtest_main", ], ) cc_library( name = "DHT", srcs = [ "DHT.c", "LAN_discovery.c", "ping.c", ], hdrs = [ "DHT.h", "LAN_discovery.h", "ping.h", ], visibility = ["//c-toxcore/other/bootstrap_daemon:__pkg__"], deps = [ ":crypto_core", ":logger", ":ping_array", ":state", ], ) cc_test( name = "DHT_test", size = "small", srcs = ["DHT_test.cc"], deps = [ ":DHT", "@com_google_googletest//:gtest_main", ], ) cc_library( name = "DHT_srcs", hdrs = [ "DHT.c", "DHT.h", "LAN_discovery.c", "LAN_discovery.h", "ping.c", "ping.h", ], visibility = [ "//c-toxcore/auto_tests:__pkg__", "//c-toxcore/other/bootstrap_daemon:__pkg__", ], deps = [ ":logger", ":ping_array", ":state", ], ) cc_library( name = "onion", srcs = ["onion.c"], hdrs = ["onion.h"], deps = [":DHT"], ) cc_library( name = "TCP_connection", srcs = [ "TCP_client.c", "TCP_connection.c", "TCP_server.c", ], hdrs = [ "TCP_client.h", "TCP_connection.h", "TCP_server.h", ], copts = select({ "//tools/config:linux": ["-DTCP_SERVER_USE_EPOLL=1"], "//conditions:default": [], }), deps = [ ":crypto_core", ":list", ":onion", ], ) cc_library( name = "net_crypto", srcs = ["net_crypto.c"], hdrs = ["net_crypto.h"], deps = [ ":DHT", ":TCP_connection", ], ) cc_library( name = "onion_announce", srcs = ["onion_announce.c"], hdrs = ["onion_announce.h"], deps = [":onion"], ) cc_library( name = "onion_client", srcs = ["onion_client.c"], hdrs = ["onion_client.h"], deps = [ ":net_crypto", ":onion_announce", ], ) cc_library( name = "friend_connection", srcs = ["friend_connection.c"], hdrs = ["friend_connection.h"], deps = [ ":DHT", ":net_crypto", ":onion_client", ], ) cc_library( name = "friend_requests", srcs = ["friend_requests.c"], hdrs = ["friend_requests.h"], deps = [":friend_connection"], ) cc_library( name = "Messenger", srcs = ["Messenger.c"], hdrs = ["Messenger.h"], visibility = ["//c-toxcore/toxav:__pkg__"], deps = [ ":friend_requests", ":state", ], ) cc_library( name = "group", srcs = ["group.c"], hdrs = ["group.h"], visibility = ["//c-toxcore/toxav:__pkg__"], deps = [":Messenger"], ) cc_library( name = "toxcore", srcs = [ "tox.c", "tox.h", "tox_api.c", "tox_private.h", ], visibility = ["//c-toxcore:__subpackages__"], deps = [ ":group", "//c-toxcore/toxencryptsave:defines", ], ) CIMPLE_SRCS = glob( [ "*.c", "*.h", ], exclude = ["*.api.h"], ) sh_test( name = "cimple_test", size = "small", srcs = ["//hs-tokstyle/tools:check-cimple"], args = ["$(location %s)" % f for f in CIMPLE_SRCS], data = CIMPLE_SRCS, tags = ["haskell"], ) c-toxcore-0.2.13/toxcore/DHT.c000066400000000000000000002744601415350724400157700ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * An implementation of the DHT as seen in docs/updates/DHT.md */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "DHT.h" #include "LAN_discovery.h" #include "logger.h" #include "mono_time.h" #include "network.h" #include "ping.h" #include "state.h" #include "util.h" #include #include #include /* The timeout after which a node is discarded completely. */ #define KILL_NODE_TIMEOUT (BAD_NODE_TIMEOUT + PING_INTERVAL) /* Ping interval in seconds for each random sending of a get nodes request. */ #define GET_NODE_INTERVAL 20 #define MAX_PUNCHING_PORTS 48 /* Interval in seconds between punching attempts*/ #define PUNCH_INTERVAL 3 /* Time in seconds after which punching parameters will be reset */ #define PUNCH_RESET_TIME 40 #define MAX_NORMAL_PUNCHING_TRIES 5 #define NAT_PING_REQUEST 0 #define NAT_PING_RESPONSE 1 /* Number of get node requests to send to quickly find close nodes. */ #define MAX_BOOTSTRAP_TIMES 5 typedef struct DHT_Friend_Callback { dht_ip_cb *ip_callback; void *data; int32_t number; } DHT_Friend_Callback; struct DHT_Friend { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; Client_data client_list[MAX_FRIEND_CLIENTS]; /* Time at which the last get_nodes request was sent. */ uint64_t lastgetnode; /* number of times get_node packets were sent. */ uint32_t bootstrap_times; /* Symmetric NAT hole punching stuff. */ NAT nat; uint16_t lock_count; DHT_Friend_Callback callbacks[DHT_FRIEND_MAX_LOCKS]; Node_format to_bootstrap[MAX_SENT_NODES]; unsigned int num_to_bootstrap; }; typedef struct Cryptopacket_Handler { cryptopacket_handler_cb *function; void *object; } Cryptopacket_Handler; struct DHT { const Logger *log; Mono_Time *mono_time; Networking_Core *net; bool hole_punching_enabled; Client_data close_clientlist[LCLIENT_LIST]; uint64_t close_lastgetnodes; uint32_t close_bootstrap_times; /* DHT keypair */ uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; DHT_Friend *friends_list; uint16_t num_friends; Node_format *loaded_nodes_list; uint32_t loaded_num_nodes; unsigned int loaded_nodes_index; Shared_Keys shared_keys_recv; Shared_Keys shared_keys_sent; struct Ping *ping; Ping_Array *dht_ping_array; Ping_Array *dht_harden_ping_array; uint64_t last_run; Cryptopacket_Handler cryptopackethandlers[256]; Node_format to_bootstrap[MAX_CLOSE_TO_BOOTSTRAP_NODES]; unsigned int num_to_bootstrap; }; const uint8_t *dht_friend_public_key(const DHT_Friend *dht_friend) { return dht_friend->public_key; } const Client_data *dht_friend_client(const DHT_Friend *dht_friend, size_t index) { return &dht_friend->client_list[index]; } const uint8_t *dht_get_self_public_key(const DHT *dht) { return dht->self_public_key; } const uint8_t *dht_get_self_secret_key(const DHT *dht) { return dht->self_secret_key; } void dht_set_self_public_key(DHT *dht, const uint8_t *key) { memcpy(dht->self_public_key, key, CRYPTO_PUBLIC_KEY_SIZE); } void dht_set_self_secret_key(DHT *dht, const uint8_t *key) { memcpy(dht->self_secret_key, key, CRYPTO_SECRET_KEY_SIZE); } Networking_Core *dht_get_net(const DHT *dht) { return dht->net; } struct Ping *dht_get_ping(const DHT *dht) { return dht->ping; } const Client_data *dht_get_close_clientlist(const DHT *dht) { return dht->close_clientlist; } const Client_data *dht_get_close_client(const DHT *dht, uint32_t client_num) { assert(client_num < sizeof(dht->close_clientlist) / sizeof(dht->close_clientlist[0])); return &dht->close_clientlist[client_num]; } uint16_t dht_get_num_friends(const DHT *dht) { return dht->num_friends; } DHT_Friend *dht_get_friend(DHT *dht, uint32_t friend_num) { assert(friend_num < dht->num_friends); return &dht->friends_list[friend_num]; } const uint8_t *dht_get_friend_public_key(const DHT *dht, uint32_t friend_num) { assert(friend_num < dht->num_friends); return dht->friends_list[friend_num].public_key; } static bool assoc_timeout(uint64_t cur_time, const IPPTsPng *assoc) { return (assoc->timestamp + BAD_NODE_TIMEOUT) <= cur_time; } /* Compares pk1 and pk2 with pk. * * return 0 if both are same distance. * return 1 if pk1 is closer. * return 2 if pk2 is closer. */ int id_closest(const uint8_t *pk, const uint8_t *pk1, const uint8_t *pk2) { for (size_t i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; ++i) { const uint8_t distance1 = pk[i] ^ pk1[i]; const uint8_t distance2 = pk[i] ^ pk2[i]; if (distance1 < distance2) { return 1; } if (distance1 > distance2) { return 2; } } return 0; } /* Return index of first unequal bit number. */ static unsigned int bit_by_bit_cmp(const uint8_t *pk1, const uint8_t *pk2) { unsigned int i; unsigned int j = 0; for (i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; ++i) { if (pk1[i] == pk2[i]) { continue; } for (j = 0; j < 8; ++j) { const uint8_t mask = 1 << (7 - j); if ((pk1[i] & mask) != (pk2[i] & mask)) { break; } } break; } return i * 8 + j; } /* Shared key generations are costly, it is therefore smart to store commonly used * ones so that they can re used later without being computed again. * * If shared key is already in shared_keys, copy it to shared_key. * else generate it into shared_key and copy it to shared_keys */ void get_shared_key(const Mono_Time *mono_time, Shared_Keys *shared_keys, uint8_t *shared_key, const uint8_t *secret_key, const uint8_t *public_key) { uint32_t num = -1; uint32_t curr = 0; for (uint32_t i = 0; i < MAX_KEYS_PER_SLOT; ++i) { const int index = public_key[30] * MAX_KEYS_PER_SLOT + i; Shared_Key *const key = &shared_keys->keys[index]; if (key->stored) { if (id_equal(public_key, key->public_key)) { memcpy(shared_key, key->shared_key, CRYPTO_SHARED_KEY_SIZE); ++key->times_requested; key->time_last_requested = mono_time_get(mono_time); return; } if (num != 0) { if (mono_time_is_timeout(mono_time, key->time_last_requested, KEYS_TIMEOUT)) { num = 0; curr = index; } else if (num > key->times_requested) { num = key->times_requested; curr = index; } } } else if (num != 0) { num = 0; curr = index; } } encrypt_precompute(public_key, secret_key, shared_key); if (num != UINT32_MAX) { Shared_Key *const key = &shared_keys->keys[curr]; key->stored = true; key->times_requested = 1; memcpy(key->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(key->shared_key, shared_key, CRYPTO_SHARED_KEY_SIZE); key->time_last_requested = mono_time_get(mono_time); } } /* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key * for packets that we receive. */ void dht_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *public_key) { get_shared_key(dht->mono_time, &dht->shared_keys_recv, shared_key, dht->self_secret_key, public_key); } /* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key * for packets that we send. */ void dht_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *public_key) { get_shared_key(dht->mono_time, &dht->shared_keys_sent, shared_key, dht->self_secret_key, public_key); } #define CRYPTO_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE * 2 + CRYPTO_NONCE_SIZE) /* Create a request to peer. * send_public_key and send_secret_key are the pub/secret keys of the sender. * recv_public_key is public key of receiver. * packet must be an array of MAX_CRYPTO_REQUEST_SIZE big. * Data represents the data we send with the request with length being the length of the data. * request_id is the id of the request (32 = friend request, 254 = ping request). * * return -1 on failure. * return the length of the created packet on success. */ int create_request(const uint8_t *send_public_key, const uint8_t *send_secret_key, uint8_t *packet, const uint8_t *recv_public_key, const uint8_t *data, uint32_t length, uint8_t request_id) { if (!send_public_key || !packet || !recv_public_key || !data) { return -1; } if (MAX_CRYPTO_REQUEST_SIZE < length + CRYPTO_SIZE + 1 + CRYPTO_MAC_SIZE) { return -1; } uint8_t *const nonce = packet + 1 + CRYPTO_PUBLIC_KEY_SIZE * 2; random_nonce(nonce); uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; memcpy(temp + 1, data, length); temp[0] = request_id; const int len = encrypt_data(recv_public_key, send_secret_key, nonce, temp, length + 1, CRYPTO_SIZE + packet); if (len == -1) { crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); return -1; } packet[0] = NET_PACKET_CRYPTO; memcpy(packet + 1, recv_public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, send_public_key, CRYPTO_PUBLIC_KEY_SIZE); crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); return len + CRYPTO_SIZE; } /* Puts the senders public key in the request in public_key, the data from the request * in data if a friend or ping request was sent to us and returns the length of the data. * packet is the request packet and length is its length. * * return -1 if not valid request. */ int handle_request(const uint8_t *self_public_key, const uint8_t *self_secret_key, uint8_t *public_key, uint8_t *data, uint8_t *request_id, const uint8_t *packet, uint16_t length) { if (!self_public_key || !public_key || !data || !request_id || !packet) { return -1; } if (length <= CRYPTO_SIZE + CRYPTO_MAC_SIZE || length > MAX_CRYPTO_REQUEST_SIZE) { return -1; } if (!id_equal(packet + 1, self_public_key)) { return -1; } memcpy(public_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); const uint8_t *const nonce = packet + 1 + CRYPTO_PUBLIC_KEY_SIZE * 2; uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; int len1 = decrypt_data(public_key, self_secret_key, nonce, packet + CRYPTO_SIZE, length - CRYPTO_SIZE, temp); if (len1 == -1 || len1 == 0) { crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); return -1; } request_id[0] = temp[0]; --len1; memcpy(data, temp + 1, len1); crypto_memzero(temp, MAX_CRYPTO_REQUEST_SIZE); return len1; } #define PACKED_NODE_SIZE_IP4 (1 + SIZE_IP4 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE) #define PACKED_NODE_SIZE_IP6 (1 + SIZE_IP6 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE) /* Return packet size of packed node with ip_family on success. * Return -1 on failure. */ int packed_node_size(Family ip_family) { if (net_family_is_ipv4(ip_family) || net_family_is_tcp_ipv4(ip_family)) { return PACKED_NODE_SIZE_IP4; } if (net_family_is_ipv6(ip_family) || net_family_is_tcp_ipv6(ip_family)) { return PACKED_NODE_SIZE_IP6; } return -1; } /* Packs an IP_Port structure into data of max size length. * * Returns size of packed IP_Port data on success * Return -1 on failure. */ int pack_ip_port(uint8_t *data, uint16_t length, const IP_Port *ip_port) { if (data == nullptr) { return -1; } bool is_ipv4; uint8_t net_family; if (net_family_is_ipv4(ip_port->ip.family)) { // TODO(irungentoo): use functions to convert endianness is_ipv4 = true; net_family = TOX_AF_INET; } else if (net_family_is_tcp_ipv4(ip_port->ip.family)) { is_ipv4 = true; net_family = TOX_TCP_INET; } else if (net_family_is_ipv6(ip_port->ip.family)) { is_ipv4 = false; net_family = TOX_AF_INET6; } else if (net_family_is_tcp_ipv6(ip_port->ip.family)) { is_ipv4 = false; net_family = TOX_TCP_INET6; } else { return -1; } if (is_ipv4) { const uint32_t size = 1 + SIZE_IP4 + sizeof(uint16_t); if (size > length) { return -1; } data[0] = net_family; memcpy(data + 1, &ip_port->ip.ip.v4, SIZE_IP4); memcpy(data + 1 + SIZE_IP4, &ip_port->port, sizeof(uint16_t)); return size; } else { const uint32_t size = 1 + SIZE_IP6 + sizeof(uint16_t); if (size > length) { return -1; } data[0] = net_family; memcpy(data + 1, &ip_port->ip.ip.v6, SIZE_IP6); memcpy(data + 1 + SIZE_IP6, &ip_port->port, sizeof(uint16_t)); return size; } } static int dht_create_packet(const uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE], const uint8_t *shared_key, const uint8_t type, uint8_t *plain, size_t plain_length, uint8_t *packet) { VLA(uint8_t, encrypted, plain_length + CRYPTO_MAC_SIZE); uint8_t nonce[CRYPTO_NONCE_SIZE]; random_nonce(nonce); const int encrypted_length = encrypt_data_symmetric(shared_key, nonce, plain, plain_length, encrypted); if (encrypted_length == -1) { return -1; } packet[0] = type; memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, encrypted, encrypted_length); return 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + encrypted_length; } /* Unpack IP_Port structure from data of max size length into ip_port. * * Return size of unpacked ip_port on success. * Return -1 on failure. */ int unpack_ip_port(IP_Port *ip_port, const uint8_t *data, uint16_t length, bool tcp_enabled) { if (data == nullptr) { return -1; } bool is_ipv4; Family host_family; if (data[0] == TOX_AF_INET) { is_ipv4 = true; host_family = net_family_ipv4; } else if (data[0] == TOX_TCP_INET) { if (!tcp_enabled) { return -1; } is_ipv4 = true; host_family = net_family_tcp_ipv4; } else if (data[0] == TOX_AF_INET6) { is_ipv4 = false; host_family = net_family_ipv6; } else if (data[0] == TOX_TCP_INET6) { if (!tcp_enabled) { return -1; } is_ipv4 = false; host_family = net_family_tcp_ipv6; } else { return -1; } if (is_ipv4) { const uint32_t size = 1 + SIZE_IP4 + sizeof(uint16_t); if (size > length) { return -1; } ip_port->ip.family = host_family; memcpy(&ip_port->ip.ip.v4, data + 1, SIZE_IP4); memcpy(&ip_port->port, data + 1 + SIZE_IP4, sizeof(uint16_t)); return size; } else { const uint32_t size = 1 + SIZE_IP6 + sizeof(uint16_t); if (size > length) { return -1; } ip_port->ip.family = host_family; memcpy(&ip_port->ip.ip.v6, data + 1, SIZE_IP6); memcpy(&ip_port->port, data + 1 + SIZE_IP6, sizeof(uint16_t)); return size; } } /* Pack number of nodes into data of maxlength length. * * return length of packed nodes on success. * return -1 on failure. */ int pack_nodes(uint8_t *data, uint16_t length, const Node_format *nodes, uint16_t number) { uint32_t packed_length = 0; for (uint32_t i = 0; i < number && packed_length < length; ++i) { const int ipp_size = pack_ip_port(data + packed_length, length - packed_length, &nodes[i].ip_port); if (ipp_size == -1) { return -1; } packed_length += ipp_size; if (packed_length + CRYPTO_PUBLIC_KEY_SIZE > length) { return -1; } memcpy(data + packed_length, nodes[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); packed_length += CRYPTO_PUBLIC_KEY_SIZE; #ifndef NDEBUG const uint32_t increment = ipp_size + CRYPTO_PUBLIC_KEY_SIZE; #endif assert(increment == PACKED_NODE_SIZE_IP4 || increment == PACKED_NODE_SIZE_IP6); } return packed_length; } /* Unpack data of length into nodes of size max_num_nodes. * Put the length of the data processed in processed_data_len. * tcp_enabled sets if TCP nodes are expected (true) or not (false). * * return number of unpacked nodes on success. * return -1 on failure. */ int unpack_nodes(Node_format *nodes, uint16_t max_num_nodes, uint16_t *processed_data_len, const uint8_t *data, uint16_t length, bool tcp_enabled) { uint32_t num = 0; uint32_t len_processed = 0; while (num < max_num_nodes && len_processed < length) { const int ipp_size = unpack_ip_port(&nodes[num].ip_port, data + len_processed, length - len_processed, tcp_enabled); if (ipp_size == -1) { return -1; } len_processed += ipp_size; if (len_processed + CRYPTO_PUBLIC_KEY_SIZE > length) { return -1; } memcpy(nodes[num].public_key, data + len_processed, CRYPTO_PUBLIC_KEY_SIZE); len_processed += CRYPTO_PUBLIC_KEY_SIZE; ++num; #ifndef NDEBUG const uint32_t increment = ipp_size + CRYPTO_PUBLIC_KEY_SIZE; #endif assert(increment == PACKED_NODE_SIZE_IP4 || increment == PACKED_NODE_SIZE_IP6); } if (processed_data_len) { *processed_data_len = len_processed; } return num; } /* Find index in an array with public_key equal to pk. * * return index or UINT32_MAX if not found. */ #define INDEX_OF_PK(array, size, pk) \ do { \ for (uint32_t i = 0; i < size; ++i) { \ if (id_equal(array[i].public_key, pk)) { \ return i; \ } \ } \ \ return UINT32_MAX; \ } while (0) static uint32_t index_of_client_pk(const Client_data *array, uint32_t size, const uint8_t *pk) { INDEX_OF_PK(array, size, pk); } static uint32_t index_of_friend_pk(const DHT_Friend *array, uint32_t size, const uint8_t *pk) { INDEX_OF_PK(array, size, pk); } static uint32_t index_of_node_pk(const Node_format *array, uint32_t size, const uint8_t *pk) { INDEX_OF_PK(array, size, pk); } /* Find index of Client_data with ip_port equal to param ip_port. * * return index or UINT32_MAX if not found. */ static uint32_t index_of_client_ip_port(const Client_data *array, uint32_t size, const IP_Port *ip_port) { for (uint32_t i = 0; i < size; ++i) { if ((net_family_is_ipv4(ip_port->ip.family) && ipport_equal(&array[i].assoc4.ip_port, ip_port)) || (net_family_is_ipv6(ip_port->ip.family) && ipport_equal(&array[i].assoc6.ip_port, ip_port))) { return i; } } return UINT32_MAX; } /* Update ip_port of client if it's needed. */ static void update_client(const Logger *log, const Mono_Time *mono_time, int index, Client_data *client, IP_Port ip_port) { IPPTsPng *assoc; int ip_version; if (net_family_is_ipv4(ip_port.ip.family)) { assoc = &client->assoc4; ip_version = 4; } else if (net_family_is_ipv6(ip_port.ip.family)) { assoc = &client->assoc6; ip_version = 6; } else { return; } if (!ipport_equal(&assoc->ip_port, &ip_port)) { char ip_str[IP_NTOA_LEN]; LOGGER_TRACE(log, "coipil[%u]: switching ipv%d from %s:%u to %s:%u", index, ip_version, ip_ntoa(&assoc->ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(assoc->ip_port.port), ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(ip_port.port)); } if (!ip_is_lan(assoc->ip_port.ip) && ip_is_lan(ip_port.ip)) { return; } assoc->ip_port = ip_port; assoc->timestamp = mono_time_get(mono_time); } /* Check if client with public_key is already in list of length length. * If it is then set its corresponding timestamp to current time. * If the id is already in the list with a different ip_port, update it. * TODO(irungentoo): Maybe optimize this. * * return True(1) or False(0) */ static int client_or_ip_port_in_list(const Logger *log, const Mono_Time *mono_time, Client_data *list, uint16_t length, const uint8_t *public_key, IP_Port ip_port) { const uint64_t temp_time = mono_time_get(mono_time); uint32_t index = index_of_client_pk(list, length, public_key); /* if public_key is in list, find it and maybe overwrite ip_port */ if (index != UINT32_MAX) { update_client(log, mono_time, index, &list[index], ip_port); return 1; } /* public_key not in list yet: see if we can find an identical ip_port, in * that case we kill the old public_key by overwriting it with the new one * TODO(irungentoo): maybe we SHOULDN'T do that if that public_key is in a friend_list * and the one who is the actual friend's public_key/address set? * MAYBE: check the other address, if valid, don't nuke? */ index = index_of_client_ip_port(list, length, &ip_port); if (index == UINT32_MAX) { return 0; } IPPTsPng *assoc; int ip_version; if (net_family_is_ipv4(ip_port.ip.family)) { assoc = &list[index].assoc4; ip_version = 4; } else { assoc = &list[index].assoc6; ip_version = 6; } /* Initialize client timestamp. */ assoc->timestamp = temp_time; memcpy(list[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); LOGGER_DEBUG(log, "coipil[%u]: switching public_key (ipv%d)", index, ip_version); /* kill the other address, if it was set */ memset(assoc, 0, sizeof(IPPTsPng)); return 1; } bool add_to_list(Node_format *nodes_list, uint32_t length, const uint8_t *pk, IP_Port ip_port, const uint8_t *cmp_pk) { for (uint32_t i = 0; i < length; ++i) { if (id_closest(cmp_pk, nodes_list[i].public_key, pk) == 2) { uint8_t pk_bak[CRYPTO_PUBLIC_KEY_SIZE]; memcpy(pk_bak, nodes_list[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); const IP_Port ip_port_bak = nodes_list[i].ip_port; memcpy(nodes_list[i].public_key, pk, CRYPTO_PUBLIC_KEY_SIZE); nodes_list[i].ip_port = ip_port; if (i != length - 1) { add_to_list(nodes_list, length, pk_bak, ip_port_bak, cmp_pk); } return true; } } return false; } /* TODO(irungentoo): change this to 7 when done*/ #define HARDENING_ALL_OK 2 /* return 0 if not. * return 1 if route request are ok * return 2 if it responds to send node packets correctly * return 4 if it can test other nodes correctly * return HARDENING_ALL_OK if all ok. */ static uint8_t hardening_correct(const Hardening *h) { return h->routes_requests_ok + (h->send_nodes_ok << 1) + (h->testing_requests << 2); } /* * helper for get_close_nodes(). argument list is a monster :D */ static void get_close_nodes_inner(uint64_t cur_time, const uint8_t *public_key, Node_format *nodes_list, Family sa_family, const Client_data *client_list, uint32_t client_list_length, uint32_t *num_nodes_ptr, bool is_LAN, uint8_t want_good) { if (!net_family_is_ipv4(sa_family) && !net_family_is_ipv6(sa_family) && !net_family_is_unspec(sa_family)) { return; } uint32_t num_nodes = *num_nodes_ptr; for (uint32_t i = 0; i < client_list_length; ++i) { const Client_data *const client = &client_list[i]; /* node already in list? */ if (index_of_node_pk(nodes_list, MAX_SENT_NODES, client->public_key) != UINT32_MAX) { continue; } const IPPTsPng *ipptp; if (net_family_is_ipv4(sa_family)) { ipptp = &client->assoc4; } else if (net_family_is_ipv6(sa_family)) { ipptp = &client->assoc6; } else if (client->assoc4.timestamp >= client->assoc6.timestamp) { ipptp = &client->assoc4; } else { ipptp = &client->assoc6; } /* node not in a good condition? */ if (assoc_timeout(cur_time, ipptp)) { continue; } /* don't send LAN ips to non LAN peers */ if (ip_is_lan(ipptp->ip_port.ip) && !is_LAN) { continue; } if (!ip_is_lan(ipptp->ip_port.ip) && want_good && hardening_correct(&ipptp->hardening) != HARDENING_ALL_OK && !id_equal(public_key, client->public_key)) { continue; } if (num_nodes < MAX_SENT_NODES) { memcpy(nodes_list[num_nodes].public_key, client->public_key, CRYPTO_PUBLIC_KEY_SIZE); nodes_list[num_nodes].ip_port = ipptp->ip_port; ++num_nodes; } else { add_to_list(nodes_list, MAX_SENT_NODES, client->public_key, ipptp->ip_port, public_key); } } *num_nodes_ptr = num_nodes; } /* Find MAX_SENT_NODES nodes closest to the public_key for the send nodes request: * put them in the nodes_list and return how many were found. * * TODO(irungentoo): For the love of based make this function cleaner and much more efficient. * * want_good : do we want only good nodes as checked with the hardening returned or not? */ static int get_somewhat_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, Family sa_family, bool is_LAN, uint8_t want_good) { uint32_t num_nodes = 0; get_close_nodes_inner(dht->last_run, public_key, nodes_list, sa_family, dht->close_clientlist, LCLIENT_LIST, &num_nodes, is_LAN, 0); /* TODO(irungentoo): uncomment this when hardening is added to close friend clients */ #if 0 for (uint32_t i = 0; i < dht->num_friends; ++i) { get_close_nodes_inner(dht->mono_time, public_key, nodes_list, sa_family, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &num_nodes, is_LAN, want_good); } #endif for (uint32_t i = 0; i < dht->num_friends; ++i) { get_close_nodes_inner(dht->last_run, public_key, nodes_list, sa_family, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &num_nodes, is_LAN, 0); } return num_nodes; } int get_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, Family sa_family, bool is_LAN, uint8_t want_good) { memset(nodes_list, 0, MAX_SENT_NODES * sizeof(Node_format)); return get_somewhat_close_nodes(dht, public_key, nodes_list, sa_family, is_LAN, want_good); } typedef struct DHT_Cmp_data { uint64_t cur_time; const uint8_t *base_public_key; Client_data entry; } DHT_Cmp_data; static bool incorrect_hardening(const IPPTsPng *assoc) { return hardening_correct(&assoc->hardening) != HARDENING_ALL_OK; } static int cmp_dht_entry(const void *a, const void *b) { DHT_Cmp_data cmp1; DHT_Cmp_data cmp2; memcpy(&cmp1, a, sizeof(DHT_Cmp_data)); memcpy(&cmp2, b, sizeof(DHT_Cmp_data)); const Client_data entry1 = cmp1.entry; const Client_data entry2 = cmp2.entry; const uint8_t *cmp_public_key = cmp1.base_public_key; bool t1 = assoc_timeout(cmp1.cur_time, &entry1.assoc4) && assoc_timeout(cmp1.cur_time, &entry1.assoc6); bool t2 = assoc_timeout(cmp2.cur_time, &entry2.assoc4) && assoc_timeout(cmp2.cur_time, &entry2.assoc6); if (t1 && t2) { return 0; } if (t1) { return -1; } if (t2) { return 1; } t1 = incorrect_hardening(&entry1.assoc4) && incorrect_hardening(&entry1.assoc6); t2 = incorrect_hardening(&entry2.assoc4) && incorrect_hardening(&entry2.assoc6); if (t1 && !t2) { return -1; } if (!t1 && t2) { return 1; } const int close = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); if (close == 1) { return 1; } if (close == 2) { return -1; } return 0; } /* Is it ok to store node with public_key in client. * * return 0 if node can't be stored. * return 1 if it can. */ static unsigned int store_node_ok(const Client_data *client, uint64_t cur_time, const uint8_t *public_key, const uint8_t *comp_public_key) { return (assoc_timeout(cur_time, &client->assoc4) && assoc_timeout(cur_time, &client->assoc6)) || id_closest(comp_public_key, client->public_key, public_key) == 2; } static void sort_client_list(Client_data *list, uint64_t cur_time, unsigned int length, const uint8_t *comp_public_key) { // Pass comp_public_key to qsort with each Client_data entry, so the // comparison function can use it as the base of comparison. VLA(DHT_Cmp_data, cmp_list, length); for (uint32_t i = 0; i < length; ++i) { cmp_list[i].cur_time = cur_time; cmp_list[i].base_public_key = comp_public_key; cmp_list[i].entry = list[i]; } qsort(cmp_list, length, sizeof(DHT_Cmp_data), cmp_dht_entry); for (uint32_t i = 0; i < length; ++i) { list[i] = cmp_list[i].entry; } } static void update_client_with_reset(const Mono_Time *mono_time, Client_data *client, const IP_Port *ip_port) { IPPTsPng *ipptp_write = nullptr; IPPTsPng *ipptp_clear = nullptr; if (net_family_is_ipv4(ip_port->ip.family)) { ipptp_write = &client->assoc4; ipptp_clear = &client->assoc6; } else { ipptp_write = &client->assoc6; ipptp_clear = &client->assoc4; } ipptp_write->ip_port = *ip_port; ipptp_write->timestamp = mono_time_get(mono_time); ip_reset(&ipptp_write->ret_ip_port.ip); ipptp_write->ret_ip_port.port = 0; ipptp_write->ret_timestamp = 0; /* zero out other address */ memset(ipptp_clear, 0, sizeof(*ipptp_clear)); } /* Replace a first bad (or empty) node with this one * or replace a possibly bad node (tests failed or not done yet) * that is further than any other in the list * from the comp_public_key * or replace a good node that is further * than any other in the list from the comp_public_key * and further than public_key. * * Do not replace any node if the list has no bad or possibly bad nodes * and all nodes in the list are closer to comp_public_key * than public_key. * * returns true when the item was stored, false otherwise */ static bool replace_all(const DHT *dht, Client_data *list, uint16_t length, const uint8_t *public_key, IP_Port ip_port, const uint8_t *comp_public_key) { if (!net_family_is_ipv4(ip_port.ip.family) && !net_family_is_ipv6(ip_port.ip.family)) { return false; } if (!store_node_ok(&list[1], dht->last_run, public_key, comp_public_key) && !store_node_ok(&list[0], dht->last_run, public_key, comp_public_key)) { return false; } sort_client_list(list, dht->last_run, length, comp_public_key); Client_data *const client = &list[0]; id_copy(client->public_key, public_key); update_client_with_reset(dht->mono_time, client, &ip_port); return true; } /* Add node to close list. * * simulate is set to 1 if we want to check if a node can be added to the list without adding it. * * return -1 on failure. * return 0 on success. */ static int add_to_close(DHT *dht, const uint8_t *public_key, IP_Port ip_port, bool simulate) { unsigned int index = bit_by_bit_cmp(public_key, dht->self_public_key); if (index >= LCLIENT_LENGTH) { index = LCLIENT_LENGTH - 1; } for (uint32_t i = 0; i < LCLIENT_NODES; ++i) { /* TODO(iphydf): write bounds checking test to catch the case that * index is left as >= LCLIENT_LENGTH */ Client_data *const client = &dht->close_clientlist[(index * LCLIENT_NODES) + i]; if (!assoc_timeout(dht->last_run, &client->assoc4) || !assoc_timeout(dht->last_run, &client->assoc6)) { continue; } if (simulate) { return 0; } id_copy(client->public_key, public_key); update_client_with_reset(dht->mono_time, client, &ip_port); return 0; } return -1; } /* Return 1 if node can be added to close list, 0 if it can't. */ bool node_addable_to_close_list(DHT *dht, const uint8_t *public_key, IP_Port ip_port) { return add_to_close(dht, public_key, ip_port, 1) == 0; } static bool is_pk_in_client_list(const Client_data *list, unsigned int client_list_length, uint64_t cur_time, const uint8_t *public_key, IP_Port ip_port) { const uint32_t index = index_of_client_pk(list, client_list_length, public_key); if (index == UINT32_MAX) { return 0; } const IPPTsPng *assoc = net_family_is_ipv4(ip_port.ip.family) ? &list[index].assoc4 : &list[index].assoc6; return !assoc_timeout(cur_time, assoc); } static bool is_pk_in_close_list(DHT *dht, const uint8_t *public_key, IP_Port ip_port) { unsigned int index = bit_by_bit_cmp(public_key, dht->self_public_key); if (index >= LCLIENT_LENGTH) { index = LCLIENT_LENGTH - 1; } return is_pk_in_client_list(dht->close_clientlist + index * LCLIENT_NODES, LCLIENT_NODES, dht->last_run, public_key, ip_port); } /* Check if the node obtained with a get_nodes with public_key should be pinged. * NOTE: for best results call it after addto_lists. * * return false if the node should not be pinged. * return true if it should. */ static bool ping_node_from_getnodes_ok(DHT *dht, const uint8_t *public_key, IP_Port ip_port) { bool ret = false; if (add_to_close(dht, public_key, ip_port, 1) == 0) { ret = true; } { unsigned int *const num = &dht->num_to_bootstrap; const uint32_t index = index_of_node_pk(dht->to_bootstrap, *num, public_key); const bool in_close_list = is_pk_in_close_list(dht, public_key, ip_port); if (ret && index == UINT32_MAX && !in_close_list) { if (*num < MAX_CLOSE_TO_BOOTSTRAP_NODES) { memcpy(dht->to_bootstrap[*num].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); dht->to_bootstrap[*num].ip_port = ip_port; ++*num; } else { // TODO(irungentoo): ipv6 vs v4 add_to_list(dht->to_bootstrap, MAX_CLOSE_TO_BOOTSTRAP_NODES, public_key, ip_port, dht->self_public_key); } } } for (uint32_t i = 0; i < dht->num_friends; ++i) { DHT_Friend *dht_friend = &dht->friends_list[i]; bool store_ok = false; if (store_node_ok(&dht_friend->client_list[1], dht->last_run, public_key, dht_friend->public_key)) { store_ok = true; } if (store_node_ok(&dht_friend->client_list[0], dht->last_run, public_key, dht_friend->public_key)) { store_ok = true; } unsigned int *const friend_num = &dht_friend->num_to_bootstrap; const uint32_t index = index_of_node_pk(dht_friend->to_bootstrap, *friend_num, public_key); const bool pk_in_list = is_pk_in_client_list(dht_friend->client_list, MAX_FRIEND_CLIENTS, dht->last_run, public_key, ip_port); if (store_ok && index == UINT32_MAX && !pk_in_list) { if (*friend_num < MAX_SENT_NODES) { Node_format *const format = &dht_friend->to_bootstrap[*friend_num]; memcpy(format->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); format->ip_port = ip_port; ++*friend_num; } else { add_to_list(dht_friend->to_bootstrap, MAX_SENT_NODES, public_key, ip_port, dht_friend->public_key); } ret = true; } } return ret; } /* Attempt to add client with ip_port and public_key to the friends client list * and close_clientlist. * * returns 1+ if the item is used in any list, 0 else */ uint32_t addto_lists(DHT *dht, IP_Port ip_port, const uint8_t *public_key) { uint32_t used = 0; /* convert IPv4-in-IPv6 to IPv4 */ if (net_family_is_ipv6(ip_port.ip.family) && ipv6_ipv4_in_v6(ip_port.ip.ip.v6)) { ip_port.ip.family = net_family_ipv4; ip_port.ip.ip.v4.uint32 = ip_port.ip.ip.v6.uint32[3]; } /* NOTE: Current behavior if there are two clients with the same id is * to replace the first ip by the second. */ const bool in_close_list = client_or_ip_port_in_list(dht->log, dht->mono_time, dht->close_clientlist, LCLIENT_LIST, public_key, ip_port); /* add_to_close should be called only if !in_list (don't extract to variable) */ if (in_close_list || add_to_close(dht, public_key, ip_port, 0)) { ++used; } DHT_Friend *friend_foundip = nullptr; for (uint32_t i = 0; i < dht->num_friends; ++i) { const bool in_list = client_or_ip_port_in_list(dht->log, dht->mono_time, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, public_key, ip_port); /* replace_all should be called only if !in_list (don't extract to variable) */ if (in_list || replace_all(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, public_key, ip_port, dht->friends_list[i].public_key)) { DHT_Friend *dht_friend = &dht->friends_list[i]; if (id_equal(public_key, dht_friend->public_key)) { friend_foundip = dht_friend; } ++used; } } if (!friend_foundip) { return used; } for (uint32_t i = 0; i < friend_foundip->lock_count; ++i) { if (friend_foundip->callbacks[i].ip_callback) { friend_foundip->callbacks[i].ip_callback(friend_foundip->callbacks[i].data, friend_foundip->callbacks[i].number, ip_port); } } return used; } static bool update_client_data(const Mono_Time *mono_time, Client_data *array, size_t size, IP_Port ip_port, const uint8_t *pk) { const uint64_t temp_time = mono_time_get(mono_time); const uint32_t index = index_of_client_pk(array, size, pk); if (index == UINT32_MAX) { return false; } Client_data *const data = &array[index]; IPPTsPng *assoc; if (net_family_is_ipv4(ip_port.ip.family)) { assoc = &data->assoc4; } else if (net_family_is_ipv6(ip_port.ip.family)) { assoc = &data->assoc6; } else { return true; } assoc->ret_ip_port = ip_port; assoc->ret_timestamp = temp_time; return true; } /* If public_key is a friend or us, update ret_ip_port * nodepublic_key is the id of the node that sent us this info. */ static void returnedip_ports(DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *nodepublic_key) { /* convert IPv4-in-IPv6 to IPv4 */ if (net_family_is_ipv6(ip_port.ip.family) && ipv6_ipv4_in_v6(ip_port.ip.ip.v6)) { ip_port.ip.family = net_family_ipv4; ip_port.ip.ip.v4.uint32 = ip_port.ip.ip.v6.uint32[3]; } if (id_equal(public_key, dht->self_public_key)) { update_client_data(dht->mono_time, dht->close_clientlist, LCLIENT_LIST, ip_port, nodepublic_key); return; } for (uint32_t i = 0; i < dht->num_friends; ++i) { if (id_equal(public_key, dht->friends_list[i].public_key)) { Client_data *const client_list = dht->friends_list[i].client_list; if (update_client_data(dht->mono_time, client_list, MAX_FRIEND_CLIENTS, ip_port, nodepublic_key)) { return; } } } } /* Send a getnodes request. * sendback_node is the node that it will send back the response to (set to NULL to disable this) */ static int getnodes(DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *client_id, const Node_format *sendback_node) { /* Check if packet is going to be sent to ourself. */ if (id_equal(public_key, dht->self_public_key)) { return -1; } uint8_t plain_message[sizeof(Node_format) * 2] = {0}; Node_format receiver; memcpy(receiver.public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); receiver.ip_port = ip_port; memcpy(plain_message, &receiver, sizeof(receiver)); uint64_t ping_id = 0; if (sendback_node != nullptr) { memcpy(plain_message + sizeof(receiver), sendback_node, sizeof(Node_format)); ping_id = ping_array_add(dht->dht_harden_ping_array, dht->mono_time, plain_message, sizeof(plain_message)); } else { ping_id = ping_array_add(dht->dht_ping_array, dht->mono_time, plain_message, sizeof(receiver)); } if (ping_id == 0) { return -1; } uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + sizeof(ping_id)]; uint8_t data[1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE]; memcpy(plain, client_id, CRYPTO_PUBLIC_KEY_SIZE); memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, &ping_id, sizeof(ping_id)); uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; dht_get_shared_key_sent(dht, shared_key, public_key); const int len = dht_create_packet(dht->self_public_key, shared_key, NET_PACKET_GET_NODES, plain, sizeof(plain), data); if (len != sizeof(data)) { return -1; } return sendpacket(dht->net, ip_port, data, len); } /* Send a send nodes response: message for IPv6 nodes */ static int sendnodes_ipv6(const DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *client_id, const uint8_t *sendback_data, uint16_t length, const uint8_t *shared_encryption_key) { /* Check if packet is going to be sent to ourself. */ if (id_equal(public_key, dht->self_public_key)) { return -1; } if (length != sizeof(uint64_t)) { return -1; } const size_t node_format_size = sizeof(Node_format); Node_format nodes_list[MAX_SENT_NODES]; const uint32_t num_nodes = get_close_nodes(dht, client_id, nodes_list, net_family_unspec, ip_is_lan(ip_port.ip), 1); VLA(uint8_t, plain, 1 + node_format_size * MAX_SENT_NODES + length); int nodes_length = 0; if (num_nodes) { nodes_length = pack_nodes(plain + 1, node_format_size * MAX_SENT_NODES, nodes_list, num_nodes); if (nodes_length <= 0) { return -1; } } plain[0] = num_nodes; memcpy(plain + 1 + nodes_length, sendback_data, length); const uint32_t crypto_size = 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_MAC_SIZE; VLA(uint8_t, data, 1 + nodes_length + length + crypto_size); const int len = dht_create_packet(dht->self_public_key, shared_encryption_key, NET_PACKET_SEND_NODES_IPV6, plain, 1 + nodes_length + length, data); if (len != SIZEOF_VLA(data)) { return -1; } return sendpacket(dht->net, ip_port, data, len); } #define CRYPTO_NODE_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint64_t)) static int handle_getnodes(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { if (length != (CRYPTO_SIZE + CRYPTO_MAC_SIZE + sizeof(uint64_t))) { return true; } DHT *const dht = (DHT *)object; /* Check if packet is from ourself. */ if (id_equal(packet + 1, dht->self_public_key)) { return true; } uint8_t plain[CRYPTO_NODE_SIZE]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; dht_get_shared_key_recv(dht, shared_key, packet + 1); const int len = decrypt_data_symmetric( shared_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, CRYPTO_NODE_SIZE + CRYPTO_MAC_SIZE, plain); if (len != CRYPTO_NODE_SIZE) { return true; } sendnodes_ipv6(dht, source, packet + 1, plain, plain + CRYPTO_PUBLIC_KEY_SIZE, sizeof(uint64_t), shared_key); ping_add(dht->ping, packet + 1, source); return false; } /* return false if no * return true if yes */ static bool sent_getnode_to_node(DHT *dht, const uint8_t *public_key, IP_Port node_ip_port, uint64_t ping_id, Node_format *sendback_node) { uint8_t data[sizeof(Node_format) * 2]; if (ping_array_check(dht->dht_ping_array, dht->mono_time, data, sizeof(data), ping_id) == sizeof(Node_format)) { memset(sendback_node, 0, sizeof(Node_format)); } else if (ping_array_check(dht->dht_harden_ping_array, dht->mono_time, data, sizeof(data), ping_id) == sizeof(data)) { memcpy(sendback_node, data + sizeof(Node_format), sizeof(Node_format)); } else { return false; } Node_format test; memcpy(&test, data, sizeof(Node_format)); if (!ipport_equal(&test.ip_port, &node_ip_port) || !id_equal(test.public_key, public_key)) { return false; } return true; } /* Function is needed in following functions. */ static int send_hardening_getnode_res(const DHT *dht, const Node_format *sendto, const uint8_t *queried_client_id, const uint8_t *nodes_data, uint16_t nodes_data_length); static int handle_sendnodes_core(void *object, IP_Port source, const uint8_t *packet, uint16_t length, Node_format *plain_nodes, uint16_t size_plain_nodes, uint32_t *num_nodes_out) { DHT *const dht = (DHT *)object; const uint32_t cid_size = 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + 1 + sizeof(uint64_t) + CRYPTO_MAC_SIZE; if (length < cid_size) { /* too short */ return 1; } const uint32_t data_size = length - cid_size; if (data_size == 0) { return 1; } if (data_size > sizeof(Node_format) * MAX_SENT_NODES) { /* invalid length */ return 1; } VLA(uint8_t, plain, 1 + data_size + sizeof(uint64_t)); uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; dht_get_shared_key_sent(dht, shared_key, packet + 1); const int len = decrypt_data_symmetric( shared_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, 1 + data_size + sizeof(uint64_t) + CRYPTO_MAC_SIZE, plain); if ((unsigned int)len != SIZEOF_VLA(plain)) { return 1; } if (plain[0] > size_plain_nodes) { return 1; } Node_format sendback_node; uint64_t ping_id; memcpy(&ping_id, plain + 1 + data_size, sizeof(ping_id)); if (!sent_getnode_to_node(dht, packet + 1, source, ping_id, &sendback_node)) { return 1; } uint16_t length_nodes = 0; const int num_nodes = unpack_nodes(plain_nodes, plain[0], &length_nodes, plain + 1, data_size, 0); if (length_nodes != data_size) { return 1; } if (num_nodes != plain[0]) { return 1; } if (num_nodes < 0) { return 1; } /* store the address the *request* was sent to */ addto_lists(dht, source, packet + 1); *num_nodes_out = num_nodes; send_hardening_getnode_res(dht, &sendback_node, packet + 1, plain + 1, data_size); return 0; } static int handle_sendnodes_ipv6(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { DHT *const dht = (DHT *)object; Node_format plain_nodes[MAX_SENT_NODES]; uint32_t num_nodes; if (handle_sendnodes_core(object, source, packet, length, plain_nodes, MAX_SENT_NODES, &num_nodes)) { return 1; } if (num_nodes == 0) { return 0; } for (uint32_t i = 0; i < num_nodes; ++i) { if (ipport_isset(&plain_nodes[i].ip_port)) { ping_node_from_getnodes_ok(dht, plain_nodes[i].public_key, plain_nodes[i].ip_port); returnedip_ports(dht, plain_nodes[i].ip_port, plain_nodes[i].public_key, packet + 1); } } return 0; } /*----------------------------------------------------------------------------------*/ /*------------------------END of packet handling functions--------------------------*/ int dht_addfriend(DHT *dht, const uint8_t *public_key, dht_ip_cb *ip_callback, void *data, int32_t number, uint16_t *lock_count) { const uint32_t friend_num = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); uint16_t lock_num; if (friend_num != UINT32_MAX) { /* Is friend already in DHT? */ DHT_Friend *const dht_friend = &dht->friends_list[friend_num]; if (dht_friend->lock_count == DHT_FRIEND_MAX_LOCKS) { return -1; } lock_num = dht_friend->lock_count; ++dht_friend->lock_count; dht_friend->callbacks[lock_num].ip_callback = ip_callback; dht_friend->callbacks[lock_num].data = data; dht_friend->callbacks[lock_num].number = number; if (lock_count) { *lock_count = lock_num + 1; } return 0; } DHT_Friend *const temp = (DHT_Friend *)realloc(dht->friends_list, sizeof(DHT_Friend) * (dht->num_friends + 1)); if (temp == nullptr) { return -1; } dht->friends_list = temp; DHT_Friend *const dht_friend = &dht->friends_list[dht->num_friends]; memset(dht_friend, 0, sizeof(DHT_Friend)); memcpy(dht_friend->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); dht_friend->nat.nat_ping_id = random_u64(); ++dht->num_friends; lock_num = dht_friend->lock_count; ++dht_friend->lock_count; dht_friend->callbacks[lock_num].ip_callback = ip_callback; dht_friend->callbacks[lock_num].data = data; dht_friend->callbacks[lock_num].number = number; if (lock_count) { *lock_count = lock_num + 1; } dht_friend->num_to_bootstrap = get_close_nodes(dht, dht_friend->public_key, dht_friend->to_bootstrap, net_family_unspec, 1, 0); return 0; } int dht_delfriend(DHT *dht, const uint8_t *public_key, uint16_t lock_count) { const uint32_t friend_num = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); if (friend_num == UINT32_MAX) { return -1; } DHT_Friend *const dht_friend = &dht->friends_list[friend_num]; --dht_friend->lock_count; if (dht_friend->lock_count && lock_count) { /* DHT friend is still in use.*/ --lock_count; dht_friend->callbacks[lock_count].ip_callback = nullptr; dht_friend->callbacks[lock_count].data = nullptr; dht_friend->callbacks[lock_count].number = 0; return 0; } --dht->num_friends; if (dht->num_friends != friend_num) { memcpy(&dht->friends_list[friend_num], &dht->friends_list[dht->num_friends], sizeof(DHT_Friend)); } if (dht->num_friends == 0) { free(dht->friends_list); dht->friends_list = nullptr; return 0; } DHT_Friend *const temp = (DHT_Friend *)realloc(dht->friends_list, sizeof(DHT_Friend) * (dht->num_friends)); if (temp == nullptr) { return -1; } dht->friends_list = temp; return 0; } /* TODO(irungentoo): Optimize this. */ int dht_getfriendip(const DHT *dht, const uint8_t *public_key, IP_Port *ip_port) { ip_reset(&ip_port->ip); ip_port->port = 0; const uint32_t friend_index = index_of_friend_pk(dht->friends_list, dht->num_friends, public_key); if (friend_index == UINT32_MAX) { return -1; } DHT_Friend *const frnd = &dht->friends_list[friend_index]; const uint32_t client_index = index_of_client_pk(frnd->client_list, MAX_FRIEND_CLIENTS, public_key); if (client_index == -1) { return 0; } const Client_data *const client = &frnd->client_list[client_index]; const IPPTsPng *const assocs[] = { &client->assoc6, &client->assoc4, nullptr }; for (const IPPTsPng * const *it = assocs; *it; ++it) { const IPPTsPng *const assoc = *it; if (!assoc_timeout(dht->last_run, assoc)) { *ip_port = assoc->ip_port; return 1; } } return -1; } /* returns number of nodes not in kill-timeout */ static uint8_t do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, const uint8_t *public_key, Client_data *list, uint32_t list_count, uint32_t *bootstrap_times, bool sortable) { uint8_t not_kill = 0; const uint64_t temp_time = mono_time_get(dht->mono_time); uint32_t num_nodes = 0; VLA(Client_data *, client_list, list_count * 2); VLA(IPPTsPng *, assoc_list, list_count * 2); unsigned int sort = 0; bool sort_ok = false; for (uint32_t i = 0; i < list_count; ++i) { /* If node is not dead. */ Client_data *client = &list[i]; IPPTsPng *const assocs[] = { &client->assoc6, &client->assoc4 }; for (uint32_t j = 0; j < sizeof(assocs) / sizeof(assocs[0]); ++j) { IPPTsPng *const assoc = assocs[j]; if (!mono_time_is_timeout(dht->mono_time, assoc->timestamp, KILL_NODE_TIMEOUT)) { sort = 0; ++not_kill; if (mono_time_is_timeout(dht->mono_time, assoc->last_pinged, PING_INTERVAL)) { getnodes(dht, assoc->ip_port, client->public_key, public_key, nullptr); assoc->last_pinged = temp_time; } /* If node is good. */ if (!assoc_timeout(dht->last_run, assoc)) { client_list[num_nodes] = client; assoc_list[num_nodes] = assoc; ++num_nodes; } } else { ++sort; /* Timed out should be at beginning, if they are not, sort the list. */ if (sort > 1 && sort < (((j + 1) * 2) - 1)) { sort_ok = true; } } } } if (sortable && sort_ok) { sort_client_list(list, dht->last_run, list_count, public_key); } if ((num_nodes != 0) && (mono_time_is_timeout(dht->mono_time, *lastgetnode, GET_NODE_INTERVAL) || *bootstrap_times < MAX_BOOTSTRAP_TIMES)) { uint32_t rand_node = random_u32() % num_nodes; if ((num_nodes - 1) != rand_node) { rand_node += random_u32() % (num_nodes - (rand_node + 1)); } getnodes(dht, assoc_list[rand_node]->ip_port, client_list[rand_node]->public_key, public_key, nullptr); *lastgetnode = temp_time; ++*bootstrap_times; } return not_kill; } /* Ping each client in the "friends" list every PING_INTERVAL seconds. Send a get nodes request * every GET_NODE_INTERVAL seconds to a random good node for each "friend" in our "friends" list. */ static void do_dht_friends(DHT *dht) { for (size_t i = 0; i < dht->num_friends; ++i) { DHT_Friend *const dht_friend = &dht->friends_list[i]; for (size_t j = 0; j < dht_friend->num_to_bootstrap; ++j) { getnodes(dht, dht_friend->to_bootstrap[j].ip_port, dht_friend->to_bootstrap[j].public_key, dht_friend->public_key, nullptr); } dht_friend->num_to_bootstrap = 0; do_ping_and_sendnode_requests(dht, &dht_friend->lastgetnode, dht_friend->public_key, dht_friend->client_list, MAX_FRIEND_CLIENTS, &dht_friend->bootstrap_times, 1); } } /* Ping each client in the close nodes list every PING_INTERVAL seconds. * Send a get nodes request every GET_NODE_INTERVAL seconds to a random good node in the list. */ static void do_Close(DHT *dht) { for (size_t i = 0; i < dht->num_to_bootstrap; ++i) { getnodes(dht, dht->to_bootstrap[i].ip_port, dht->to_bootstrap[i].public_key, dht->self_public_key, nullptr); } dht->num_to_bootstrap = 0; uint8_t not_killed = do_ping_and_sendnode_requests( dht, &dht->close_lastgetnodes, dht->self_public_key, dht->close_clientlist, LCLIENT_LIST, &dht->close_bootstrap_times, 0); if (not_killed != 0) { return; } /* all existing nodes are at least KILL_NODE_TIMEOUT, * which means we are mute, as we only send packets to * nodes NOT in KILL_NODE_TIMEOUT * * so: reset all nodes to be BAD_NODE_TIMEOUT, but not * KILL_NODE_TIMEOUT, so we at least keep trying pings */ const uint64_t badonly = mono_time_get(dht->mono_time) - BAD_NODE_TIMEOUT; for (size_t i = 0; i < LCLIENT_LIST; ++i) { Client_data *const client = &dht->close_clientlist[i]; IPPTsPng *const assocs[] = { &client->assoc6, &client->assoc4, nullptr }; for (IPPTsPng * const *it = assocs; *it; ++it) { IPPTsPng *const assoc = *it; if (assoc->timestamp) { assoc->timestamp = badonly; } } } } void dht_getnodes(DHT *dht, const IP_Port *from_ipp, const uint8_t *from_id, const uint8_t *which_id) { getnodes(dht, *from_ipp, from_id, which_id, nullptr); } void dht_bootstrap(DHT *dht, IP_Port ip_port, const uint8_t *public_key) { getnodes(dht, ip_port, public_key, dht->self_public_key, nullptr); } int dht_bootstrap_from_address(DHT *dht, const char *address, uint8_t ipv6enabled, uint16_t port, const uint8_t *public_key) { IP_Port ip_port_v64; IP *ip_extra = nullptr; IP_Port ip_port_v4; ip_init(&ip_port_v64.ip, ipv6enabled); if (ipv6enabled) { /* setup for getting BOTH: an IPv6 AND an IPv4 address */ ip_port_v64.ip.family = net_family_unspec; ip_reset(&ip_port_v4.ip); ip_extra = &ip_port_v4.ip; } if (addr_resolve_or_parse_ip(address, &ip_port_v64.ip, ip_extra)) { ip_port_v64.port = port; dht_bootstrap(dht, ip_port_v64, public_key); if ((ip_extra != nullptr) && ip_isset(ip_extra)) { ip_port_v4.port = port; dht_bootstrap(dht, ip_port_v4, public_key); } return 1; } return 0; } /* Send the given packet to node with public_key * * return -1 if failure. */ int route_packet(const DHT *dht, const uint8_t *public_key, const uint8_t *packet, uint16_t length) { for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { if (id_equal(public_key, dht->close_clientlist[i].public_key)) { const Client_data *const client = &dht->close_clientlist[i]; const IPPTsPng *const assocs[] = { &client->assoc6, &client->assoc4, nullptr }; for (const IPPTsPng * const *it = assocs; *it; ++it) { const IPPTsPng *const assoc = *it; if (ip_isset(&assoc->ip_port.ip)) { return sendpacket(dht->net, assoc->ip_port, packet, length); } } break; } } return -1; } /* Puts all the different ips returned by the nodes for a friend_num into array ip_portlist. * ip_portlist must be at least MAX_FRIEND_CLIENTS big. * * return the number of ips returned. * return 0 if we are connected to friend or if no ips were found. * return -1 if no such friend. */ static int friend_iplist(const DHT *dht, IP_Port *ip_portlist, uint16_t friend_num) { if (friend_num >= dht->num_friends) { return -1; } const DHT_Friend *const dht_friend = &dht->friends_list[friend_num]; IP_Port ipv4s[MAX_FRIEND_CLIENTS]; int num_ipv4s = 0; IP_Port ipv6s[MAX_FRIEND_CLIENTS]; int num_ipv6s = 0; for (size_t i = 0; i < MAX_FRIEND_CLIENTS; ++i) { const Client_data *const client = &dht_friend->client_list[i]; /* If ip is not zero and node is good. */ if (ip_isset(&client->assoc4.ret_ip_port.ip) && !mono_time_is_timeout(dht->mono_time, client->assoc4.ret_timestamp, BAD_NODE_TIMEOUT)) { ipv4s[num_ipv4s] = client->assoc4.ret_ip_port; ++num_ipv4s; } if (ip_isset(&client->assoc6.ret_ip_port.ip) && !mono_time_is_timeout(dht->mono_time, client->assoc6.ret_timestamp, BAD_NODE_TIMEOUT)) { ipv6s[num_ipv6s] = client->assoc6.ret_ip_port; ++num_ipv6s; } if (id_equal(client->public_key, dht_friend->public_key)) { if (!assoc_timeout(dht->last_run, &client->assoc6) || !assoc_timeout(dht->last_run, &client->assoc4)) { return 0; /* direct connectivity */ } } } #ifdef FRIEND_IPLIST_PAD memcpy(ip_portlist, ipv6s, num_ipv6s * sizeof(IP_Port)); if (num_ipv6s == MAX_FRIEND_CLIENTS) { return MAX_FRIEND_CLIENTS; } int num_ipv4s_used = MAX_FRIEND_CLIENTS - num_ipv6s; if (num_ipv4s_used > num_ipv4s) { num_ipv4s_used = num_ipv4s; } memcpy(&ip_portlist[num_ipv6s], ipv4s, num_ipv4s_used * sizeof(IP_Port)); return num_ipv6s + num_ipv4s_used; #else /* !FRIEND_IPLIST_PAD */ /* there must be some secret reason why we can't pad the longer list * with the shorter one... */ if (num_ipv6s >= num_ipv4s) { memcpy(ip_portlist, ipv6s, num_ipv6s * sizeof(IP_Port)); return num_ipv6s; } memcpy(ip_portlist, ipv4s, num_ipv4s * sizeof(IP_Port)); return num_ipv4s; #endif /* !FRIEND_IPLIST_PAD */ } /* Send the following packet to everyone who tells us they are connected to friend_id. * * return ip for friend. * return number of nodes the packet was sent to. (Only works if more than (MAX_FRIEND_CLIENTS / 4). */ int route_tofriend(const DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length) { const uint32_t num = index_of_friend_pk(dht->friends_list, dht->num_friends, friend_id); if (num == UINT32_MAX) { return 0; } uint32_t sent = 0; IP_Port ip_list[MAX_FRIEND_CLIENTS]; const int ip_num = friend_iplist(dht, ip_list, num); if (ip_num < (MAX_FRIEND_CLIENTS / 4)) { return 0; /* Reason for that? */ } const DHT_Friend *const dht_friend = &dht->friends_list[num]; /* extra legwork, because having the outside allocating the space for us * is *usually* good(tm) (bites us in the behind in this case though) */ for (uint32_t i = 0; i < MAX_FRIEND_CLIENTS; ++i) { const Client_data *const client = &dht_friend->client_list[i]; const IPPTsPng *const assocs[] = { &client->assoc4, &client->assoc6, nullptr }; for (const IPPTsPng * const *it = assocs; *it; ++it) { const IPPTsPng *const assoc = *it; /* If ip is not zero and node is good. */ if (ip_isset(&assoc->ret_ip_port.ip) && !mono_time_is_timeout(dht->mono_time, assoc->ret_timestamp, BAD_NODE_TIMEOUT)) { const int retval = sendpacket(dht->net, assoc->ip_port, packet, length); if ((unsigned int)retval == length) { ++sent; break; /* Send one packet per client.*/ } } } } return sent; } /* Send the following packet to one random person who tells us they are connected to friend_id. * * return number of nodes the packet was sent to. */ static int routeone_tofriend(DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length) { const uint32_t num = index_of_friend_pk(dht->friends_list, dht->num_friends, friend_id); if (num == UINT32_MAX) { return 0; } const DHT_Friend *const dht_friend = &dht->friends_list[num]; IP_Port ip_list[MAX_FRIEND_CLIENTS * 2]; int n = 0; /* extra legwork, because having the outside allocating the space for us * is *usually* good(tm) (bites us in the behind in this case though) */ for (uint32_t i = 0; i < MAX_FRIEND_CLIENTS; ++i) { const Client_data *const client = &dht_friend->client_list[i]; const IPPTsPng *const assocs[] = { &client->assoc4, &client->assoc6, nullptr }; for (const IPPTsPng * const *it = assocs; *it; ++it) { const IPPTsPng *const assoc = *it; /* If ip is not zero and node is good. */ if (ip_isset(&assoc->ret_ip_port.ip) && !mono_time_is_timeout(dht->mono_time, assoc->ret_timestamp, BAD_NODE_TIMEOUT)) { ip_list[n] = assoc->ip_port; ++n; } } } if (n < 1) { return 0; } const int retval = sendpacket(dht->net, ip_list[random_u32() % n], packet, length); if ((unsigned int)retval == length) { return 1; } return 0; } /*----------------------------------------------------------------------------------*/ /*---------------------BEGINNING OF NAT PUNCHING FUNCTIONS--------------------------*/ static int send_NATping(DHT *dht, const uint8_t *public_key, uint64_t ping_id, uint8_t type) { uint8_t data[sizeof(uint64_t) + 1]; uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; int num = 0; data[0] = type; memcpy(data + 1, &ping_id, sizeof(uint64_t)); /* 254 is NAT ping request packet id */ const int len = create_request( dht->self_public_key, dht->self_secret_key, packet, public_key, data, sizeof(uint64_t) + 1, CRYPTO_PACKET_NAT_PING); if (len == -1) { return -1; } if (type == 0) { /* If packet is request use many people to route it. */ num = route_tofriend(dht, public_key, packet, len); } else if (type == 1) { /* If packet is response use only one person to route it */ num = routeone_tofriend(dht, public_key, packet, len); } if (num == 0) { return -1; } return num; } /* Handle a received ping request for. */ static int handle_NATping(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet, uint16_t length, void *userdata) { if (length != sizeof(uint64_t) + 1) { return 1; } DHT *const dht = (DHT *)object; uint64_t ping_id; memcpy(&ping_id, packet + 1, sizeof(uint64_t)); uint32_t friendnumber = index_of_friend_pk(dht->friends_list, dht->num_friends, source_pubkey); if (friendnumber == UINT32_MAX) { return 1; } DHT_Friend *const dht_friend = &dht->friends_list[friendnumber]; if (packet[0] == NAT_PING_REQUEST) { /* 1 is reply */ send_NATping(dht, source_pubkey, ping_id, NAT_PING_RESPONSE); dht_friend->nat.recv_nat_ping_timestamp = mono_time_get(dht->mono_time); return 0; } if (packet[0] == NAT_PING_RESPONSE) { if (dht_friend->nat.nat_ping_id == ping_id) { dht_friend->nat.nat_ping_id = random_u64(); dht_friend->nat.hole_punching = 1; return 0; } } return 1; } /* Get the most common ip in the ip_portlist. * Only return ip if it appears in list min_num or more. * len must not be bigger than MAX_FRIEND_CLIENTS. * * return ip of 0 if failure. */ static IP nat_commonip(IP_Port *ip_portlist, uint16_t len, uint16_t min_num) { IP zero; ip_reset(&zero); if (len > MAX_FRIEND_CLIENTS) { return zero; } uint16_t numbers[MAX_FRIEND_CLIENTS] = {0}; for (uint32_t i = 0; i < len; ++i) { for (uint32_t j = 0; j < len; ++j) { if (ip_equal(&ip_portlist[i].ip, &ip_portlist[j].ip)) { ++numbers[i]; } } if (numbers[i] >= min_num) { return ip_portlist[i].ip; } } return zero; } /* Return all the ports for one ip in a list. * portlist must be at least len long, * where len is the length of ip_portlist. * * return number of ports and puts the list of ports in portlist. */ static uint16_t nat_getports(uint16_t *portlist, IP_Port *ip_portlist, uint16_t len, IP ip) { uint16_t num = 0; for (uint32_t i = 0; i < len; ++i) { if (ip_equal(&ip_portlist[i].ip, &ip)) { portlist[num] = net_ntohs(ip_portlist[i].port); ++num; } } return num; } static void punch_holes(DHT *dht, IP ip, uint16_t *port_list, uint16_t numports, uint16_t friend_num) { if (!dht->hole_punching_enabled) { return; } if (numports > MAX_FRIEND_CLIENTS || numports == 0) { return; } const uint16_t first_port = port_list[0]; uint32_t i; for (i = 0; i < numports; ++i) { if (first_port != port_list[i]) { break; } } if (i == numports) { /* If all ports are the same, only try that one port. */ IP_Port pinging; ip_copy(&pinging.ip, &ip); pinging.port = net_htons(first_port); ping_send_request(dht->ping, pinging, dht->friends_list[friend_num].public_key); } else { for (i = 0; i < MAX_PUNCHING_PORTS; ++i) { /* TODO(irungentoo): Improve port guessing algorithm. */ const uint32_t it = i + dht->friends_list[friend_num].nat.punching_index; const int8_t sign = (it % 2) ? -1 : 1; const uint32_t delta = sign * (it / (2 * numports)); const uint32_t index = (it / 2) % numports; const uint16_t port = port_list[index] + delta; IP_Port pinging; ip_copy(&pinging.ip, &ip); pinging.port = net_htons(port); ping_send_request(dht->ping, pinging, dht->friends_list[friend_num].public_key); } dht->friends_list[friend_num].nat.punching_index += i; } if (dht->friends_list[friend_num].nat.tries > MAX_NORMAL_PUNCHING_TRIES) { const uint16_t port = 1024; IP_Port pinging; ip_copy(&pinging.ip, &ip); for (i = 0; i < MAX_PUNCHING_PORTS; ++i) { uint32_t it = i + dht->friends_list[friend_num].nat.punching_index2; pinging.port = net_htons(port + it); ping_send_request(dht->ping, pinging, dht->friends_list[friend_num].public_key); } dht->friends_list[friend_num].nat.punching_index2 += i - (MAX_PUNCHING_PORTS / 2); } ++dht->friends_list[friend_num].nat.tries; } static void do_NAT(DHT *dht) { const uint64_t temp_time = mono_time_get(dht->mono_time); for (uint32_t i = 0; i < dht->num_friends; ++i) { IP_Port ip_list[MAX_FRIEND_CLIENTS]; const int num = friend_iplist(dht, ip_list, i); /* If already connected or friend is not online don't try to hole punch. */ if (num < MAX_FRIEND_CLIENTS / 2) { continue; } if (dht->friends_list[i].nat.nat_ping_timestamp + PUNCH_INTERVAL < temp_time) { send_NATping(dht, dht->friends_list[i].public_key, dht->friends_list[i].nat.nat_ping_id, NAT_PING_REQUEST); dht->friends_list[i].nat.nat_ping_timestamp = temp_time; } if (dht->friends_list[i].nat.hole_punching == 1 && dht->friends_list[i].nat.punching_timestamp + PUNCH_INTERVAL < temp_time && dht->friends_list[i].nat.recv_nat_ping_timestamp + PUNCH_INTERVAL * 2 >= temp_time) { const IP ip = nat_commonip(ip_list, num, MAX_FRIEND_CLIENTS / 2); if (!ip_isset(&ip)) { continue; } if (dht->friends_list[i].nat.punching_timestamp + PUNCH_RESET_TIME < temp_time) { dht->friends_list[i].nat.tries = 0; dht->friends_list[i].nat.punching_index = 0; dht->friends_list[i].nat.punching_index2 = 0; } uint16_t port_list[MAX_FRIEND_CLIENTS]; const uint16_t numports = nat_getports(port_list, ip_list, num, ip); punch_holes(dht, ip, port_list, numports, i); dht->friends_list[i].nat.punching_timestamp = temp_time; dht->friends_list[i].nat.hole_punching = 0; } } } /*----------------------------------------------------------------------------------*/ /*-----------------------END OF NAT PUNCHING FUNCTIONS------------------------------*/ #define DHT_HARDENING 0 #define HARDREQ_DATA_SIZE 384 // Attempt to prevent amplification/other attacks typedef enum Check_Type { CHECK_TYPE_ROUTE_REQ = 0, CHECK_TYPE_ROUTE_RES = 1, CHECK_TYPE_GETNODE_REQ = 2, CHECK_TYPE_GETNODE_RES = 3, CHECK_TYPE_TEST_REQ = 4, CHECK_TYPE_TEST_RES = 5, } Check_Type; #if DHT_HARDENING static int send_hardening_req(DHT *dht, Node_format *sendto, uint8_t type, uint8_t *contents, uint16_t length) { if (length > HARDREQ_DATA_SIZE - 1) { return -1; } uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; uint8_t data[HARDREQ_DATA_SIZE] = {0}; data[0] = type; memcpy(data + 1, contents, length); const int len = create_request( dht->self_public_key, dht->self_secret_key, packet, sendto->public_key, data, sizeof(data), CRYPTO_PACKET_HARDENING); if (len == -1) { return -1; } return sendpacket(dht->net, sendto->ip_port, packet, len); } /* Send a get node hardening request */ static int send_hardening_getnode_req(DHT *dht, Node_format *dest, Node_format *node_totest, uint8_t *search_id) { uint8_t data[sizeof(Node_format) + CRYPTO_PUBLIC_KEY_SIZE]; memcpy(data, node_totest, sizeof(Node_format)); memcpy(data + sizeof(Node_format), search_id, CRYPTO_PUBLIC_KEY_SIZE); return send_hardening_req(dht, dest, CHECK_TYPE_GETNODE_REQ, data, sizeof(Node_format) + CRYPTO_PUBLIC_KEY_SIZE); } #endif /* Send a get node hardening response */ static int send_hardening_getnode_res(const DHT *dht, const Node_format *sendto, const uint8_t *queried_client_id, const uint8_t *nodes_data, uint16_t nodes_data_length) { if (!ip_isset(&sendto->ip_port.ip)) { return -1; } uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; VLA(uint8_t, data, 1 + CRYPTO_PUBLIC_KEY_SIZE + nodes_data_length); data[0] = CHECK_TYPE_GETNODE_RES; memcpy(data + 1, queried_client_id, CRYPTO_PUBLIC_KEY_SIZE); memcpy(data + 1 + CRYPTO_PUBLIC_KEY_SIZE, nodes_data, nodes_data_length); const int len = create_request( dht->self_public_key, dht->self_secret_key, packet, sendto->public_key, data, SIZEOF_VLA(data), CRYPTO_PACKET_HARDENING); if (len == -1) { return -1; } return sendpacket(dht->net, sendto->ip_port, packet, len); } /* TODO(irungentoo): improve */ static IPPTsPng *get_closelist_IPPTsPng(DHT *dht, const uint8_t *public_key, Family sa_family) { for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { if (!id_equal(dht->close_clientlist[i].public_key, public_key)) { continue; } if (net_family_is_ipv4(sa_family)) { return &dht->close_clientlist[i].assoc4; } if (net_family_is_ipv6(sa_family)) { return &dht->close_clientlist[i].assoc6; } } return nullptr; } /* * check how many nodes in nodes are also present in the closelist. * TODO(irungentoo): make this function better. */ static uint32_t have_nodes_closelist(DHT *dht, Node_format *nodes, uint16_t num) { uint32_t counter = 0; for (uint32_t i = 0; i < num; ++i) { if (id_equal(nodes[i].public_key, dht->self_public_key)) { ++counter; continue; } const IPPTsPng *const temp = get_closelist_IPPTsPng(dht, nodes[i].public_key, nodes[i].ip_port.ip.family); if (temp) { if (!assoc_timeout(dht->last_run, temp)) { ++counter; } } } return counter; } /* Interval in seconds between hardening checks */ #define HARDENING_INTERVAL 120 /* Handle a received hardening packet */ static int handle_hardening(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet, uint16_t length, void *userdata) { DHT *const dht = (DHT *)object; if (length < 2) { return 1; } switch (packet[0]) { case CHECK_TYPE_GETNODE_REQ: { if (length != HARDREQ_DATA_SIZE) { return 1; } Node_format node; Node_format tocheck_node; node.ip_port = source; memcpy(node.public_key, source_pubkey, CRYPTO_PUBLIC_KEY_SIZE); memcpy(&tocheck_node, packet + 1, sizeof(Node_format)); if (getnodes(dht, tocheck_node.ip_port, tocheck_node.public_key, packet + 1 + sizeof(Node_format), &node) == -1) { return 1; } return 0; } case CHECK_TYPE_GETNODE_RES: { if (length <= CRYPTO_PUBLIC_KEY_SIZE + 1) { return 1; } if (length > 1 + CRYPTO_PUBLIC_KEY_SIZE + sizeof(Node_format) * MAX_SENT_NODES) { return 1; } uint16_t length_nodes = length - 1 - CRYPTO_PUBLIC_KEY_SIZE; Node_format nodes[MAX_SENT_NODES]; const int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, nullptr, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, length_nodes, 0); /* TODO(irungentoo): MAX_SENT_NODES nodes should be returned at all times * (right now we have a small network size so it could cause problems for testing and etc..) */ if (num_nodes <= 0) { return 1; } /* NOTE: This should work for now but should be changed to something better. */ if (have_nodes_closelist(dht, nodes, num_nodes) < (uint32_t)((num_nodes + 2) / 2)) { return 1; } IPPTsPng *const temp = get_closelist_IPPTsPng(dht, packet + 1, nodes[0].ip_port.ip.family); if (temp == nullptr) { return 1; } if (mono_time_is_timeout(dht->mono_time, temp->hardening.send_nodes_timestamp, HARDENING_INTERVAL)) { return 1; } if (!id_equal(temp->hardening.send_nodes_pingedid, source_pubkey)) { return 1; } /* If Nodes look good and the request checks out */ temp->hardening.send_nodes_ok = 1; return 0;/* success*/ } } return 1; } #if DHT_HARDENING #define HARDEN_TIMEOUT 1200 /* Return a random node from all the nodes we are connected to. * TODO(irungentoo): improve this function. */ static Node_format random_node(DHT *dht, Family sa_family) { uint8_t id[CRYPTO_PUBLIC_KEY_SIZE]; for (uint32_t i = 0; i < CRYPTO_PUBLIC_KEY_SIZE / 4; ++i) { /* populate the id with pseudorandom bytes.*/ const uint32_t t = random_u32(); memcpy(id + i * sizeof(t), &t, sizeof(t)); } Node_format nodes_list[MAX_SENT_NODES]; memset(nodes_list, 0, sizeof(nodes_list)); const uint32_t num_nodes = get_close_nodes(dht, id, nodes_list, sa_family, 1, 0); if (num_nodes == 0) { return nodes_list[0]; } return nodes_list[random_u32() % num_nodes]; } #endif /* Put up to max_num nodes in nodes from the closelist. * * return the number of nodes. */ static uint16_t list_nodes(Client_data *list, size_t length, uint64_t cur_time, Node_format *nodes, uint16_t max_num) { if (max_num == 0) { return 0; } uint16_t count = 0; for (size_t i = length; i != 0; --i) { const IPPTsPng *assoc = nullptr; if (!assoc_timeout(cur_time, &list[i - 1].assoc4)) { assoc = &list[i - 1].assoc4; } if (!assoc_timeout(cur_time, &list[i - 1].assoc6)) { if (assoc == nullptr) { assoc = &list[i - 1].assoc6; } else if (random_u08() % 2) { assoc = &list[i - 1].assoc6; } } if (assoc != nullptr) { memcpy(nodes[count].public_key, list[i - 1].public_key, CRYPTO_PUBLIC_KEY_SIZE); nodes[count].ip_port = assoc->ip_port; ++count; if (count >= max_num) { return count; } } } return count; } /* Put up to max_num nodes in nodes from the random friends. * * return the number of nodes. */ uint16_t randfriends_nodes(DHT *dht, Node_format *nodes, uint16_t max_num) { if (max_num == 0) { return 0; } uint16_t count = 0; const uint32_t r = random_u32(); for (size_t i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) { count += list_nodes(dht->friends_list[(i + r) % DHT_FAKE_FRIEND_NUMBER].client_list, MAX_FRIEND_CLIENTS, dht->last_run, nodes + count, max_num - count); if (count >= max_num) { break; } } return count; } /* Put up to max_num nodes in nodes from the closelist. * * return the number of nodes. */ uint16_t closelist_nodes(DHT *dht, Node_format *nodes, uint16_t max_num) { return list_nodes(dht->close_clientlist, LCLIENT_LIST, dht->last_run, nodes, max_num); } #if DHT_HARDENING static void do_hardening(DHT *dht) { for (uint32_t i = 0; i < LCLIENT_LIST * 2; ++i) { IPPTsPng *cur_iptspng; Family sa_family; const uint8_t *const public_key = dht->close_clientlist[i / 2].public_key; if (i % 2 == 0) { cur_iptspng = &dht->close_clientlist[i / 2].assoc4; sa_family = net_family_ipv4; } else { cur_iptspng = &dht->close_clientlist[i / 2].assoc6; sa_family = net_family_ipv6; } if (assoc_timeout(dht->mono_time, cur_iptspng)) { continue; } if (cur_iptspng->hardening.send_nodes_ok == 0) { if (mono_time_is_timeout(dht->mono_time, cur_iptspng->hardening.send_nodes_timestamp, HARDENING_INTERVAL)) { Node_format rand_node = random_node(dht, sa_family); if (!ipport_isset(&rand_node.ip_port)) { continue; } if (id_equal(public_key, rand_node.public_key)) { continue; } Node_format to_test; to_test.ip_port = cur_iptspng->ip_port; memcpy(to_test.public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); // TODO(irungentoo): The search id should maybe not be ours? if (send_hardening_getnode_req(dht, &rand_node, &to_test, dht->self_public_key) > 0) { memcpy(cur_iptspng->hardening.send_nodes_pingedid, rand_node.public_key, CRYPTO_PUBLIC_KEY_SIZE); cur_iptspng->hardening.send_nodes_timestamp = mono_time_get(dht->mono_time); } } } else { if (mono_time_is_timeout(dht->mono_time, cur_iptspng->hardening.send_nodes_timestamp, HARDEN_TIMEOUT)) { cur_iptspng->hardening.send_nodes_ok = 0; } } // TODO(irungentoo): add the 2 other testers. } } #endif /*----------------------------------------------------------------------------------*/ void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_cb *cb, void *object) { dht->cryptopackethandlers[byte].function = cb; dht->cryptopackethandlers[byte].object = object; } static int cryptopacket_handle(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { DHT *const dht = (DHT *)object; assert(packet[0] == NET_PACKET_CRYPTO); if (length <= CRYPTO_PUBLIC_KEY_SIZE * 2 + CRYPTO_NONCE_SIZE + 1 + CRYPTO_MAC_SIZE || length > MAX_CRYPTO_REQUEST_SIZE + CRYPTO_MAC_SIZE) { return 1; } // Check if request is for us. if (id_equal(packet + 1, dht->self_public_key)) { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t data[MAX_CRYPTO_REQUEST_SIZE]; uint8_t number; const int len = handle_request(dht->self_public_key, dht->self_secret_key, public_key, data, &number, packet, length); if (len == -1 || len == 0) { return 1; } if (!dht->cryptopackethandlers[number].function) { return 1; } return dht->cryptopackethandlers[number].function( dht->cryptopackethandlers[number].object, source, public_key, data, len, userdata); } /* If request is not for us, try routing it. */ const int retval = route_packet(dht, packet + 1, packet, length); if ((unsigned int)retval == length) { return 0; } return 1; } /*----------------------------------------------------------------------------------*/ DHT *new_dht(const Logger *log, Mono_Time *mono_time, Networking_Core *net, bool holepunching_enabled) { if (net == nullptr) { return nullptr; } DHT *const dht = (DHT *)calloc(1, sizeof(DHT)); if (dht == nullptr) { return nullptr; } dht->mono_time = mono_time; dht->last_run = mono_time_get(mono_time); dht->log = log; dht->net = net; dht->hole_punching_enabled = holepunching_enabled; dht->ping = ping_new(mono_time, dht); if (dht->ping == nullptr) { kill_dht(dht); return nullptr; } networking_registerhandler(dht->net, NET_PACKET_GET_NODES, &handle_getnodes, dht); networking_registerhandler(dht->net, NET_PACKET_SEND_NODES_IPV6, &handle_sendnodes_ipv6, dht); networking_registerhandler(dht->net, NET_PACKET_CRYPTO, &cryptopacket_handle, dht); cryptopacket_registerhandler(dht, CRYPTO_PACKET_NAT_PING, &handle_NATping, dht); cryptopacket_registerhandler(dht, CRYPTO_PACKET_HARDENING, &handle_hardening, dht); crypto_new_keypair(dht->self_public_key, dht->self_secret_key); dht->dht_ping_array = ping_array_new(DHT_PING_ARRAY_SIZE, PING_TIMEOUT); dht->dht_harden_ping_array = ping_array_new(DHT_PING_ARRAY_SIZE, PING_TIMEOUT); if (dht->dht_ping_array == nullptr || dht->dht_harden_ping_array == nullptr) { kill_dht(dht); return nullptr; } for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) { uint8_t random_public_key_bytes[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t random_secret_key_bytes[CRYPTO_SECRET_KEY_SIZE]; crypto_new_keypair(random_public_key_bytes, random_secret_key_bytes); if (dht_addfriend(dht, random_public_key_bytes, nullptr, nullptr, 0, nullptr) != 0) { kill_dht(dht); return nullptr; } } return dht; } void do_dht(DHT *dht) { const uint64_t cur_time = mono_time_get(dht->mono_time); if (dht->last_run == cur_time) { return; } dht->last_run = cur_time; // Load friends/clients if first call to do_dht if (dht->loaded_num_nodes) { dht_connect_after_load(dht); } do_Close(dht); do_dht_friends(dht); do_NAT(dht); ping_iterate(dht->ping); #if DHT_HARDENING do_hardening(dht); #endif } void kill_dht(DHT *dht) { networking_registerhandler(dht->net, NET_PACKET_GET_NODES, nullptr, nullptr); networking_registerhandler(dht->net, NET_PACKET_SEND_NODES_IPV6, nullptr, nullptr); cryptopacket_registerhandler(dht, CRYPTO_PACKET_NAT_PING, nullptr, nullptr); cryptopacket_registerhandler(dht, CRYPTO_PACKET_HARDENING, nullptr, nullptr); ping_array_kill(dht->dht_ping_array); ping_array_kill(dht->dht_harden_ping_array); ping_kill(dht->ping); free(dht->friends_list); free(dht->loaded_nodes_list); free(dht); } /* new DHT format for load/save, more robust and forward compatible */ // TODO(irungentoo): Move this closer to Messenger. #define DHT_STATE_COOKIE_GLOBAL 0x159000d #define DHT_STATE_COOKIE_TYPE 0x11ce #define DHT_STATE_TYPE_NODES 4 #define MAX_SAVED_DHT_NODES (((DHT_FAKE_FRIEND_NUMBER * MAX_FRIEND_CLIENTS) + LCLIENT_LIST) * 2) /* Get the size of the DHT (for saving). */ uint32_t dht_size(const DHT *dht) { uint32_t numv4 = 0; uint32_t numv6 = 0; for (uint32_t i = 0; i < dht->loaded_num_nodes; ++i) { numv4 += net_family_is_ipv4(dht->loaded_nodes_list[i].ip_port.ip.family); numv6 += net_family_is_ipv6(dht->loaded_nodes_list[i].ip_port.ip.family); } for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { numv4 += (dht->close_clientlist[i].assoc4.timestamp != 0); numv6 += (dht->close_clientlist[i].assoc6.timestamp != 0); } for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER && i < dht->num_friends; ++i) { const DHT_Friend *const fr = &dht->friends_list[i]; for (uint32_t j = 0; j < MAX_FRIEND_CLIENTS; ++j) { numv4 += (fr->client_list[j].assoc4.timestamp != 0); numv6 += (fr->client_list[j].assoc6.timestamp != 0); } } const uint32_t size32 = sizeof(uint32_t); const uint32_t sizesubhead = size32 * 2; return size32 + sizesubhead + packed_node_size(net_family_ipv4) * numv4 + packed_node_size(net_family_ipv6) * numv6; } /* Save the DHT in data where data is an array of size dht_size(). */ void dht_save(const DHT *dht, uint8_t *data) { host_to_lendian_bytes32(data, DHT_STATE_COOKIE_GLOBAL); data += sizeof(uint32_t); uint8_t *const old_data = data; /* get right offset. we write the actual header later. */ data = state_write_section_header(data, DHT_STATE_COOKIE_TYPE, 0, 0); Node_format *clients = (Node_format *)malloc(MAX_SAVED_DHT_NODES * sizeof(Node_format)); if (clients == nullptr) { LOGGER_ERROR(dht->log, "could not allocate %u nodes", MAX_SAVED_DHT_NODES); return; } uint32_t num = 0; if (dht->loaded_num_nodes > 0) { memcpy(clients, dht->loaded_nodes_list, sizeof(Node_format) * dht->loaded_num_nodes); num += dht->loaded_num_nodes; } for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { if (dht->close_clientlist[i].assoc4.timestamp != 0) { memcpy(clients[num].public_key, dht->close_clientlist[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); clients[num].ip_port = dht->close_clientlist[i].assoc4.ip_port; ++num; } if (dht->close_clientlist[i].assoc6.timestamp != 0) { memcpy(clients[num].public_key, dht->close_clientlist[i].public_key, CRYPTO_PUBLIC_KEY_SIZE); clients[num].ip_port = dht->close_clientlist[i].assoc6.ip_port; ++num; } } for (uint32_t i = 0; i < DHT_FAKE_FRIEND_NUMBER && i < dht->num_friends; ++i) { const DHT_Friend *const fr = &dht->friends_list[i]; for (uint32_t j = 0; j < MAX_FRIEND_CLIENTS; ++j) { if (fr->client_list[j].assoc4.timestamp != 0) { memcpy(clients[num].public_key, fr->client_list[j].public_key, CRYPTO_PUBLIC_KEY_SIZE); clients[num].ip_port = fr->client_list[j].assoc4.ip_port; ++num; } if (fr->client_list[j].assoc6.timestamp != 0) { memcpy(clients[num].public_key, fr->client_list[j].public_key, CRYPTO_PUBLIC_KEY_SIZE); clients[num].ip_port = fr->client_list[j].assoc6.ip_port; ++num; } } } state_write_section_header(old_data, DHT_STATE_COOKIE_TYPE, pack_nodes(data, sizeof(Node_format) * num, clients, num), DHT_STATE_TYPE_NODES); free(clients); } /* Bootstrap from this number of nodes every time dht_connect_after_load() is called */ #define SAVE_BOOTSTAP_FREQUENCY 8 /* Start sending packets after DHT loaded_friends_list and loaded_clients_list are set */ int dht_connect_after_load(DHT *dht) { if (dht == nullptr) { return -1; } if (!dht->loaded_nodes_list) { return -1; } /* DHT is connected, stop. */ if (dht_non_lan_connected(dht)) { free(dht->loaded_nodes_list); dht->loaded_nodes_list = nullptr; dht->loaded_num_nodes = 0; return 0; } for (uint32_t i = 0; i < dht->loaded_num_nodes && i < SAVE_BOOTSTAP_FREQUENCY; ++i) { const unsigned int index = dht->loaded_nodes_index % dht->loaded_num_nodes; dht_bootstrap(dht, dht->loaded_nodes_list[index].ip_port, dht->loaded_nodes_list[index].public_key); ++dht->loaded_nodes_index; } return 0; } static State_Load_Status dht_load_state_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type) { DHT *dht = (DHT *)outer; switch (type) { case DHT_STATE_TYPE_NODES: { if (length == 0) { break; } free(dht->loaded_nodes_list); // Copy to loaded_clients_list dht->loaded_nodes_list = (Node_format *)calloc(MAX_SAVED_DHT_NODES, sizeof(Node_format)); if (dht->loaded_nodes_list == nullptr) { LOGGER_ERROR(dht->log, "could not allocate %u nodes", MAX_SAVED_DHT_NODES); dht->loaded_num_nodes = 0; break; } const int num = unpack_nodes(dht->loaded_nodes_list, MAX_SAVED_DHT_NODES, nullptr, data, length, 0); if (num > 0) { dht->loaded_num_nodes = num; } else { dht->loaded_num_nodes = 0; } break; } default: LOGGER_ERROR(dht->log, "Load state (DHT): contains unrecognized part (len %u, type %u)", length, type); break; } return STATE_LOAD_STATUS_CONTINUE; } /* Load the DHT from data of size size. * * return -1 if failure. * return 0 if success. */ int dht_load(DHT *dht, const uint8_t *data, uint32_t length) { const uint32_t cookie_len = sizeof(uint32_t); if (length > cookie_len) { uint32_t data32; lendian_bytes_to_host32(&data32, data); if (data32 == DHT_STATE_COOKIE_GLOBAL) { return state_load(dht->log, dht_load_state_callback, dht, data + cookie_len, length - cookie_len, DHT_STATE_COOKIE_TYPE); } } return -1; } /* return false if we are not connected to the DHT. * return true if we are. */ bool dht_isconnected(const DHT *dht) { for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { const Client_data *const client = &dht->close_clientlist[i]; if (!assoc_timeout(dht->last_run, &client->assoc4) || !assoc_timeout(dht->last_run, &client->assoc6)) { return true; } } return false; } /* return false if we are not connected or only connected to lan peers with the DHT. * return true if we are. */ bool dht_non_lan_connected(const DHT *dht) { for (uint32_t i = 0; i < LCLIENT_LIST; ++i) { const Client_data *const client = &dht->close_clientlist[i]; if (!assoc_timeout(dht->last_run, &client->assoc4) && !ip_is_lan(client->assoc4.ip_port.ip)) { return true; } if (!assoc_timeout(dht->last_run, &client->assoc6) && !ip_is_lan(client->assoc6.ip_port.ip)) { return true; } } return false; } c-toxcore-0.2.13/toxcore/DHT.h000066400000000000000000000341431415350724400157650ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * An implementation of the DHT as seen in docs/updates/DHT.md */ #ifndef C_TOXCORE_TOXCORE_DHT_H #define C_TOXCORE_TOXCORE_DHT_H #include "crypto_core.h" #include "logger.h" #include "mono_time.h" #include "network.h" #include "ping_array.h" #include #ifdef __cplusplus extern "C" { #endif /* Maximum number of clients stored per friend. */ #define MAX_FRIEND_CLIENTS 8 #define LCLIENT_NODES MAX_FRIEND_CLIENTS #define LCLIENT_LENGTH 128 /* A list of the clients mathematically closest to ours. */ #define LCLIENT_LIST (LCLIENT_LENGTH * LCLIENT_NODES) #define MAX_CLOSE_TO_BOOTSTRAP_NODES 8 /* The max number of nodes to send with send nodes. */ #define MAX_SENT_NODES 4 /* Ping timeout in seconds */ #define PING_TIMEOUT 5 /* size of DHT ping arrays. */ #define DHT_PING_ARRAY_SIZE 512 /* Ping interval in seconds for each node in our lists. */ #define PING_INTERVAL 60 /* The number of seconds for a non responsive node to become bad. */ #define PINGS_MISSED_NODE_GOES_BAD 1 #define PING_ROUNDTRIP 2 #define BAD_NODE_TIMEOUT (PING_INTERVAL + PINGS_MISSED_NODE_GOES_BAD * (PING_INTERVAL + PING_ROUNDTRIP)) /* The number of "fake" friends to add (for optimization purposes and so our paths for the onion part are more random) */ #define DHT_FAKE_FRIEND_NUMBER 2 #define MAX_CRYPTO_REQUEST_SIZE 1024 #define CRYPTO_PACKET_FRIEND_REQ 32 // Friend request crypto packet ID. #define CRYPTO_PACKET_HARDENING 48 // Hardening crypto packet ID. #define CRYPTO_PACKET_DHTPK 156 #define CRYPTO_PACKET_NAT_PING 254 // NAT ping crypto packet ID. /* Create a request to peer. * send_public_key and send_secret_key are the pub/secret keys of the sender. * recv_public_key is public key of receiver. * packet must be an array of MAX_CRYPTO_REQUEST_SIZE big. * Data represents the data we send with the request with length being the length of the data. * request_id is the id of the request (32 = friend request, 254 = ping request). * * return -1 on failure. * return the length of the created packet on success. */ int create_request(const uint8_t *send_public_key, const uint8_t *send_secret_key, uint8_t *packet, const uint8_t *recv_public_key, const uint8_t *data, uint32_t length, uint8_t request_id); /* puts the senders public key in the request in public_key, the data from the request * in data if a friend or ping request was sent to us and returns the length of the data. * packet is the request packet and length is its length * return -1 if not valid request. */ int handle_request(const uint8_t *self_public_key, const uint8_t *self_secret_key, uint8_t *public_key, uint8_t *data, uint8_t *request_id, const uint8_t *packet, uint16_t length); typedef struct IPPTs { IP_Port ip_port; uint64_t timestamp; } IPPTs; typedef struct Hardening { /* Node routes request correctly (true (1) or false/didn't check (0)) */ uint8_t routes_requests_ok; /* Time which we last checked this.*/ uint64_t routes_requests_timestamp; uint8_t routes_requests_pingedid[CRYPTO_PUBLIC_KEY_SIZE]; /* Node sends correct send_node (true (1) or false/didn't check (0)) */ uint8_t send_nodes_ok; /* Time which we last checked this.*/ uint64_t send_nodes_timestamp; uint8_t send_nodes_pingedid[CRYPTO_PUBLIC_KEY_SIZE]; /* Node can be used to test other nodes (true (1) or false/didn't check (0)) */ uint8_t testing_requests; /* Time which we last checked this.*/ uint64_t testing_timestamp; uint8_t testing_pingedid[CRYPTO_PUBLIC_KEY_SIZE]; } Hardening; typedef struct IPPTsPng { IP_Port ip_port; uint64_t timestamp; uint64_t last_pinged; Hardening hardening; /* Returned by this node. Either our friend or us. */ IP_Port ret_ip_port; uint64_t ret_timestamp; } IPPTsPng; typedef struct Client_data { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; IPPTsPng assoc4; IPPTsPng assoc6; } Client_data; /*----------------------------------------------------------------------------------*/ typedef struct NAT { /* 1 if currently hole punching, otherwise 0 */ uint8_t hole_punching; uint32_t punching_index; uint32_t tries; uint32_t punching_index2; uint64_t punching_timestamp; uint64_t recv_nat_ping_timestamp; uint64_t nat_ping_id; uint64_t nat_ping_timestamp; } NAT; #define DHT_FRIEND_MAX_LOCKS 32 typedef struct Node_format { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; IP_Port ip_port; } Node_format; typedef struct DHT_Friend DHT_Friend; const uint8_t *dht_friend_public_key(const DHT_Friend *dht_friend); const Client_data *dht_friend_client(const DHT_Friend *dht_friend, size_t index); /* Return packet size of packed node with ip_family on success. * Return -1 on failure. */ int packed_node_size(Family ip_family); /* Packs an IP_Port structure into data of max size length. * * Returns size of packed IP_Port data on success * Return -1 on failure. */ int pack_ip_port(uint8_t *data, uint16_t length, const IP_Port *ip_port); /* Unpack IP_Port structure from data of max size length into ip_port. * * Return size of unpacked ip_port on success. * Return -1 on failure. */ int unpack_ip_port(IP_Port *ip_port, const uint8_t *data, uint16_t length, bool tcp_enabled); /* Pack number of nodes into data of maxlength length. * * return length of packed nodes on success. * return -1 on failure. */ int pack_nodes(uint8_t *data, uint16_t length, const Node_format *nodes, uint16_t number); /* Unpack data of length into nodes of size max_num_nodes. * Put the length of the data processed in processed_data_len. * tcp_enabled sets if TCP nodes are expected (true) or not (false). * * return number of unpacked nodes on success. * return -1 on failure. */ int unpack_nodes(Node_format *nodes, uint16_t max_num_nodes, uint16_t *processed_data_len, const uint8_t *data, uint16_t length, bool tcp_enabled); /*----------------------------------------------------------------------------------*/ /* struct to store some shared keys so we don't have to regenerate them for each request. */ #define MAX_KEYS_PER_SLOT 4 #define KEYS_TIMEOUT 600 typedef struct Shared_Key { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint32_t times_requested; bool stored; uint64_t time_last_requested; } Shared_Key; typedef struct Shared_Keys { Shared_Key keys[256 * MAX_KEYS_PER_SLOT]; } Shared_Keys; /*----------------------------------------------------------------------------------*/ typedef int cryptopacket_handler_cb(void *object, IP_Port ip_port, const uint8_t *source_pubkey, const uint8_t *data, uint16_t len, void *userdata); #define DHT_DEFINED typedef struct DHT DHT; const uint8_t *dht_get_self_public_key(const DHT *dht); const uint8_t *dht_get_self_secret_key(const DHT *dht); void dht_set_self_public_key(DHT *dht, const uint8_t *key); void dht_set_self_secret_key(DHT *dht, const uint8_t *key); Networking_Core *dht_get_net(const DHT *dht); struct Ping *dht_get_ping(const DHT *dht); const Client_data *dht_get_close_clientlist(const DHT *dht); const Client_data *dht_get_close_client(const DHT *dht, uint32_t client_num); uint16_t dht_get_num_friends(const DHT *dht); DHT_Friend *dht_get_friend(DHT *dht, uint32_t friend_num); const uint8_t *dht_get_friend_public_key(const DHT *dht, uint32_t friend_num); /*----------------------------------------------------------------------------------*/ /* Shared key generations are costly, it is therefore smart to store commonly used * ones so that they can re used later without being computed again. * * If shared key is already in shared_keys, copy it to shared_key. * else generate it into shared_key and copy it to shared_keys */ void get_shared_key(const Mono_Time *mono_time, Shared_Keys *shared_keys, uint8_t *shared_key, const uint8_t *secret_key, const uint8_t *public_key); /* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key * for packets that we receive. */ void dht_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *public_key); /* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key * for packets that we send. */ void dht_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *public_key); void dht_getnodes(DHT *dht, const IP_Port *from_ipp, const uint8_t *from_id, const uint8_t *which_id); typedef void dht_ip_cb(void *object, int32_t number, IP_Port ip_port); /* Add a new friend to the friends list. * public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. * * ip_callback is the callback of a function that will be called when the ip address * is found along with arguments data and number. * * lock_count will be set to a non zero number that must be passed to dht_delfriend() * to properly remove the callback. * * return 0 if success. * return -1 if failure (friends list is full). */ int dht_addfriend(DHT *dht, const uint8_t *public_key, dht_ip_cb *ip_callback, void *data, int32_t number, uint16_t *lock_count); /* Delete a friend from the friends list. * public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. * * return 0 if success. * return -1 if failure (public_key not in friends list). */ int dht_delfriend(DHT *dht, const uint8_t *public_key, uint16_t lock_count); /* Get ip of friend. * public_key must be CRYPTO_PUBLIC_KEY_SIZE bytes long. * ip must be 4 bytes long. * port must be 2 bytes long. * * return -1, -- if public_key does NOT refer to a friend * return 0, -- if public_key refers to a friend and we failed to find the friend (yet) * return 1, ip if public_key refers to a friend and we found him */ int dht_getfriendip(const DHT *dht, const uint8_t *public_key, IP_Port *ip_port); /* Compares pk1 and pk2 with pk. * * return 0 if both are same distance. * return 1 if pk1 is closer. * return 2 if pk2 is closer. */ int id_closest(const uint8_t *pk, const uint8_t *pk1, const uint8_t *pk2); /** * Add node to the node list making sure only the nodes closest to cmp_pk are in the list. * * @return true iff the node was added to the list. */ bool add_to_list(Node_format *nodes_list, uint32_t length, const uint8_t *pk, IP_Port ip_port, const uint8_t *cmp_pk); /* Return 1 if node can be added to close list, 0 if it can't. */ bool node_addable_to_close_list(DHT *dht, const uint8_t *public_key, IP_Port ip_port); /* Get the (maximum MAX_SENT_NODES) closest nodes to public_key we know * and put them in nodes_list (must be MAX_SENT_NODES big). * * sa_family = family (IPv4 or IPv6) (0 if we don't care)? * is_LAN = return some LAN ips (true or false) * want_good = do we want tested nodes or not? (TODO(irungentoo)) * * return the number of nodes returned. * */ int get_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, Family sa_family, bool is_LAN, uint8_t want_good); /* Put up to max_num nodes in nodes from the random friends. * * return the number of nodes. */ uint16_t randfriends_nodes(DHT *dht, Node_format *nodes, uint16_t max_num); /* Put up to max_num nodes in nodes from the closelist. * * return the number of nodes. */ uint16_t closelist_nodes(DHT *dht, Node_format *nodes, uint16_t max_num); /* Run this function at least a couple times per second (It's the main loop). */ void do_dht(DHT *dht); /* * Use these two functions to bootstrap the client. */ /* Sends a "get nodes" request to the given node with ip, port and public_key * to setup connections */ void dht_bootstrap(DHT *dht, IP_Port ip_port, const uint8_t *public_key); /* Resolves address into an IP address. If successful, sends a "get nodes" * request to the given node with ip, port and public_key to setup connections * * address can be a hostname or an IP address (IPv4 or IPv6). * if ipv6enabled is 0 (zero), the resolving sticks STRICTLY to IPv4 addresses * if ipv6enabled is not 0 (zero), the resolving looks for IPv6 addresses first, * then IPv4 addresses. * * returns 1 if the address could be converted into an IP address * returns 0 otherwise */ int dht_bootstrap_from_address(DHT *dht, const char *address, uint8_t ipv6enabled, uint16_t port, const uint8_t *public_key); /* Start sending packets after DHT loaded_friends_list and loaded_clients_list are set. * * returns 0 if successful * returns -1 otherwise */ int dht_connect_after_load(DHT *dht); /* ROUTING FUNCTIONS */ /* Send the given packet to node with public_key. * * return -1 if failure. */ int route_packet(const DHT *dht, const uint8_t *public_key, const uint8_t *packet, uint16_t length); /* Send the following packet to everyone who tells us they are connected to friend_id. * * return number of nodes it sent the packet to. */ int route_tofriend(const DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length); /* Function to handle crypto packets. */ void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_cb *cb, void *object); /* SAVE/LOAD functions */ /* Get the size of the DHT (for saving). */ uint32_t dht_size(const DHT *dht); /* Save the DHT in data where data is an array of size dht_size(). */ void dht_save(const DHT *dht, uint8_t *data); /* Load the DHT from data of size size. * * return -1 if failure. * return 0 if success. */ int dht_load(DHT *dht, const uint8_t *data, uint32_t length); /* Initialize DHT. */ DHT *new_dht(const Logger *log, Mono_Time *mono_time, Networking_Core *net, bool holepunching_enabled); void kill_dht(DHT *dht); /* return false if we are not connected to the DHT. * return true if we are. */ bool dht_isconnected(const DHT *dht); /* return false if we are not connected or only connected to lan peers with the DHT. * return true if we are. */ bool dht_non_lan_connected(const DHT *dht); uint32_t addto_lists(DHT *dht, IP_Port ip_port, const uint8_t *public_key); #ifdef __cplusplus } // extern "C" #endif #endif c-toxcore-0.2.13/toxcore/DHT_test.cc000066400000000000000000000064331415350724400171630ustar00rootroot00000000000000#include "DHT.h" #include #include #include #include "crypto_core.h" namespace { using PublicKey = std::array; template std::array to_array(T const (&arr)[N]) { std::array stdarr; memcpy(stdarr.data(), arr, N); return stdarr; } TEST(IdClosest, IdenticalKeysAreSameDistance) { PublicKey pk0; random_bytes(pk0.data(), CRYPTO_PUBLIC_KEY_SIZE); PublicKey pk1; random_bytes(pk1.data(), CRYPTO_PUBLIC_KEY_SIZE); PublicKey pk2 = pk1; EXPECT_EQ(id_closest(pk0.data(), pk1.data(), pk2.data()), 0); } TEST(IdClosest, DistanceIsCommutative) { for (uint32_t i = 0; i < 100; ++i) { PublicKey pk0; random_bytes(pk0.data(), CRYPTO_PUBLIC_KEY_SIZE); PublicKey pk1; random_bytes(pk1.data(), CRYPTO_PUBLIC_KEY_SIZE); PublicKey pk2; random_bytes(pk2.data(), CRYPTO_PUBLIC_KEY_SIZE); ASSERT_NE(pk1, pk2); // RNG can't produce the same random key twice // Two non-equal keys can't have the same distance from any given key. EXPECT_NE(id_closest(pk0.data(), pk1.data(), pk2.data()), 0); if (id_closest(pk0.data(), pk1.data(), pk2.data()) == 1) { EXPECT_EQ(id_closest(pk0.data(), pk2.data(), pk1.data()), 2); } if (id_closest(pk0.data(), pk1.data(), pk2.data()) == 2) { EXPECT_EQ(id_closest(pk0.data(), pk2.data(), pk1.data()), 1); } } } TEST(IdClosest, SmallXorDistanceIsCloser) { PublicKey const pk0 = {{0xaa}}; PublicKey const pk1 = {{0xa0}}; PublicKey const pk2 = {{0x0a}}; EXPECT_EQ(id_closest(pk0.data(), pk1.data(), pk2.data()), 1); } TEST(IdClosest, DistinctKeysCannotHaveTheSameDistance) { PublicKey const pk0 = {{0x06}}; PublicKey const pk1 = {{0x00}}; PublicKey pk2 = {{0x00}}; for (uint8_t i = 1; i < 0xff; ++i) { pk2[0] = i; EXPECT_NE(id_closest(pk0.data(), pk1.data(), pk2.data()), 0); } } TEST(AddToList, OverridesKeysWithCloserKeys) { PublicKey const self_pk = {{0xaa}}; PublicKey const keys[] = { {{0xa0}}, // closest {{0x0a}}, // {{0x0b}}, // {{0x0c}}, // {{0x0d}}, // {{0xa1}}, // closer than the 4 keys above }; std::array nodes{}; EXPECT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[0].data(), IP_Port(), self_pk.data())); EXPECT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[1].data(), IP_Port(), self_pk.data())); EXPECT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[2].data(), IP_Port(), self_pk.data())); EXPECT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[3].data(), IP_Port(), self_pk.data())); EXPECT_EQ(to_array(nodes[0].public_key), keys[0]); EXPECT_EQ(to_array(nodes[1].public_key), keys[1]); EXPECT_EQ(to_array(nodes[2].public_key), keys[2]); EXPECT_EQ(to_array(nodes[3].public_key), keys[3]); // key 4 is less close than keys 0-3 EXPECT_FALSE(add_to_list(nodes.data(), nodes.size(), keys[4].data(), IP_Port(), self_pk.data())); // 5 is closer than all except key 0 EXPECT_TRUE(add_to_list(nodes.data(), nodes.size(), keys[5].data(), IP_Port(), self_pk.data())); EXPECT_EQ(to_array(nodes[0].public_key), keys[0]); EXPECT_EQ(to_array(nodes[1].public_key), keys[5]); EXPECT_EQ(to_array(nodes[2].public_key), keys[1]); EXPECT_EQ(to_array(nodes[3].public_key), keys[2]); } } // namespace c-toxcore-0.2.13/toxcore/LAN_discovery.api.h000066400000000000000000000017651415350724400206230ustar00rootroot00000000000000%{ /* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * LAN discovery implementation. */ #ifndef C_TOXCORE_TOXCORE_LAN_DISCOVERY_H #define C_TOXCORE_TOXCORE_LAN_DISCOVERY_H #include "DHT.h" %} class dHT { struct this; } class iP { struct this; } namespace lan_discovery { /** * Interval in seconds between LAN discovery packet sending. */ #define LAN_DISCOVERY_INTERVAL 10 /** * Send a LAN discovery pcaket to the broadcast address with port port. */ static int32_t send(uint16_t port, dHT::this *dht); /** * Sets up packet handlers. */ static void init(dHT::this *dht); /** * Clear packet handlers. */ static void kill(dHT::this *dht); } /** * Is IP a local ip or not. */ static bool ip_is_local(iP::this ip); /** * Checks if a given IP isn't routable. * * @return true if ip is a LAN ip, false if it is not. */ static bool ip_is_lan(iP::this ip); %{ #endif // C_TOXCORE_TOXCORE_LAN_DISCOVERY_H %} c-toxcore-0.2.13/toxcore/LAN_discovery.c000066400000000000000000000264311415350724400200430ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * LAN discovery implementation. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "LAN_discovery.h" #include #include "util.h" #define MAX_INTERFACES 16 /* TODO: multiple threads might concurrently try to set these, and it isn't clear that this couldn't lead to undesirable * behaviour. Consider storing the data in per-instance variables instead. */ //!TOKSTYLE- // No global mutable state in Tokstyle. static int broadcast_count = -1; static IP_Port broadcast_ip_ports[MAX_INTERFACES]; //!TOKSTYLE+ #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) // The mingw32/64 Windows library warns about including winsock2.h after // windows.h even though with the above it's a valid thing to do. So, to make // mingw32 headers happy, we include winsock2.h first. #include #include #include #include static void fetch_broadcast_info(uint16_t port) { IP_ADAPTER_INFO *pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); unsigned long ulOutBufLen = sizeof(IP_ADAPTER_INFO); if (pAdapterInfo == nullptr) { return; } if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { free(pAdapterInfo); pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen); if (pAdapterInfo == nullptr) { return; } } /* We copy these to the static variables `broadcast_*` only at the end of `fetch_broadcast_info()`. * The intention is to ensure that even if multiple threads enter `fetch_broadcast_info()` concurrently, only valid * interfaces will be set to be broadcast to. * */ int count = 0; IP_Port ip_ports[MAX_INTERFACES]; const int ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen); if (ret == NO_ERROR) { IP_ADAPTER_INFO *pAdapter = pAdapterInfo; while (pAdapter) { IP gateway = {0}; IP subnet_mask = {0}; if (addr_parse_ip(pAdapter->IpAddressList.IpMask.String, &subnet_mask) && addr_parse_ip(pAdapter->GatewayList.IpAddress.String, &gateway)) { if (net_family_is_ipv4(gateway.family) && net_family_is_ipv4(subnet_mask.family)) { IP_Port *ip_port = &ip_ports[count]; ip_port->ip.family = net_family_ipv4; uint32_t gateway_ip = net_ntohl(gateway.ip.v4.uint32); uint32_t subnet_ip = net_ntohl(subnet_mask.ip.v4.uint32); uint32_t broadcast_ip = gateway_ip + ~subnet_ip - 1; ip_port->ip.ip.v4.uint32 = net_htonl(broadcast_ip); ip_port->port = port; ++count; if (count >= MAX_INTERFACES) { break; } } } pAdapter = pAdapter->Next; } } if (pAdapterInfo) { free(pAdapterInfo); } broadcast_count = count; for (uint32_t i = 0; i < count; ++i) { broadcast_ip_ports[i] = ip_ports[i]; } } #elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) #include #include #include #include #include #ifdef __linux__ #include #endif #if defined(__FreeBSD__) || defined(__DragonFly__) #include #endif static void fetch_broadcast_info(uint16_t port) { /* Not sure how many platforms this will run on, * so it's wrapped in `__linux__` for now. * Definitely won't work like this on Windows... */ broadcast_count = 0; const Socket sock = net_socket(net_family_ipv4, TOX_SOCK_STREAM, 0); if (!sock_valid(sock)) { return; } /* Configure ifconf for the ioctl call. */ struct ifreq i_faces[MAX_INTERFACES]; memset(i_faces, 0, sizeof(struct ifreq) * MAX_INTERFACES); struct ifconf ifc; ifc.ifc_buf = (char *)i_faces; ifc.ifc_len = sizeof(i_faces); if (ioctl(sock.socket, SIOCGIFCONF, &ifc) < 0) { kill_sock(sock); return; } /* We copy these to the static variables `broadcast_*` only at the end of `fetch_broadcast_info()`. * The intention is to ensure that even if multiple threads enter `fetch_broadcast_info()` concurrently, only valid * interfaces will be set to be broadcast to. * */ int count = 0; IP_Port ip_ports[MAX_INTERFACES]; /* `ifc.ifc_len` is set by the `ioctl()` to the actual length used. * On usage of the complete array the call should be repeated with * a larger array, not done (640kB and 16 interfaces shall be * enough, for everybody!) */ int n = ifc.ifc_len / sizeof(struct ifreq); for (int i = 0; i < n; ++i) { /* there are interfaces with are incapable of broadcast */ if (ioctl(sock.socket, SIOCGIFBRDADDR, &i_faces[i]) < 0) { continue; } /* moot check: only AF_INET returned (backwards compat.) */ if (i_faces[i].ifr_broadaddr.sa_family != AF_INET) { continue; } struct sockaddr_in *sock4 = (struct sockaddr_in *)(void *)&i_faces[i].ifr_broadaddr; if (count >= MAX_INTERFACES) { break; } IP_Port *ip_port = &ip_ports[count]; ip_port->ip.family = net_family_ipv4; ip_port->ip.ip.v4.uint32 = sock4->sin_addr.s_addr; if (ip_port->ip.ip.v4.uint32 == 0) { continue; } ip_port->port = port; ++count; } kill_sock(sock); broadcast_count = count; for (uint32_t i = 0; i < count; ++i) { broadcast_ip_ports[i] = ip_ports[i]; } } #else // TODO(irungentoo): Other platforms? static void fetch_broadcast_info(uint16_t port) { broadcast_count = 0; } #endif /* Send packet to all IPv4 broadcast addresses * * return 1 if sent to at least one broadcast target. * return 0 on failure to find any valid broadcast target. */ static uint32_t send_broadcasts(Networking_Core *net, uint16_t port, const uint8_t *data, uint16_t length) { /* fetch only once? on every packet? every X seconds? * old: every packet, new: once */ if (broadcast_count < 0) { fetch_broadcast_info(port); } if (!broadcast_count) { return 0; } for (int i = 0; i < broadcast_count; ++i) { sendpacket(net, broadcast_ip_ports[i], data, length); } return 1; } /* Return the broadcast ip. */ static IP broadcast_ip(Family family_socket, Family family_broadcast) { IP ip; ip_reset(&ip); if (net_family_is_ipv6(family_socket)) { if (net_family_is_ipv6(family_broadcast)) { ip.family = net_family_ipv6; /* `FF02::1` is - according to RFC 4291 - multicast all-nodes link-local */ /* `FE80::*:` MUST be exact, for that we would need to look over all * interfaces and check in which status they are */ ip.ip.v6.uint8[ 0] = 0xFF; ip.ip.v6.uint8[ 1] = 0x02; ip.ip.v6.uint8[15] = 0x01; } else if (net_family_is_ipv4(family_broadcast)) { ip.family = net_family_ipv6; ip.ip.v6 = ip6_broadcast; } } else if (net_family_is_ipv4(family_socket) && net_family_is_ipv4(family_broadcast)) { ip.family = net_family_ipv4; ip.ip.v4 = ip4_broadcast; } return ip; } static bool ip4_is_local(IP4 ip4) { /* Loopback. */ return ip4.uint8[0] == 127; } /* Is IP a local ip or not. */ bool ip_is_local(IP ip) { if (net_family_is_ipv4(ip.family)) { return ip4_is_local(ip.ip.v4); } /* embedded IPv4-in-IPv6 */ if (ipv6_ipv4_in_v6(ip.ip.v6)) { IP4 ip4; ip4.uint32 = ip.ip.v6.uint32[3]; return ip4_is_local(ip4); } /* localhost in IPv6 (::1) */ if (ip.ip.v6.uint64[0] == 0 && ip.ip.v6.uint32[2] == 0 && ip.ip.v6.uint32[3] == net_htonl(1)) { return true; } return false; } static bool ip4_is_lan(IP4 ip4) { /* 10.0.0.0 to 10.255.255.255 range. */ if (ip4.uint8[0] == 10) { return true; } /* 172.16.0.0 to 172.31.255.255 range. */ if (ip4.uint8[0] == 172 && ip4.uint8[1] >= 16 && ip4.uint8[1] <= 31) { return true; } /* 192.168.0.0 to 192.168.255.255 range. */ if (ip4.uint8[0] == 192 && ip4.uint8[1] == 168) { return true; } /* 169.254.1.0 to 169.254.254.255 range. */ if (ip4.uint8[0] == 169 && ip4.uint8[1] == 254 && ip4.uint8[2] != 0 && ip4.uint8[2] != 255) { return true; } /* RFC 6598: 100.64.0.0 to 100.127.255.255 (100.64.0.0/10) * (shared address space to stack another layer of NAT) */ if ((ip4.uint8[0] == 100) && ((ip4.uint8[1] & 0xC0) == 0x40)) { return true; } return false; } bool ip_is_lan(IP ip) { if (ip_is_local(ip)) { return true; } if (net_family_is_ipv4(ip.family)) { return ip4_is_lan(ip.ip.v4); } if (net_family_is_ipv6(ip.family)) { /* autogenerated for each interface: `FE80::*` (up to `FEBF::*`) * `FF02::1` is - according to RFC 4291 - multicast all-nodes link-local */ if (((ip.ip.v6.uint8[0] == 0xFF) && (ip.ip.v6.uint8[1] < 3) && (ip.ip.v6.uint8[15] == 1)) || ((ip.ip.v6.uint8[0] == 0xFE) && ((ip.ip.v6.uint8[1] & 0xC0) == 0x80))) { return true; } /* embedded IPv4-in-IPv6 */ if (ipv6_ipv4_in_v6(ip.ip.v6)) { IP4 ip4; ip4.uint32 = ip.ip.v6.uint32[3]; return ip4_is_lan(ip4); } } return false; } static int handle_LANdiscovery(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { DHT *dht = (DHT *)object; char ip_str[IP_NTOA_LEN] = { 0 }; ip_ntoa(&source.ip, ip_str, sizeof(ip_str)); if (!ip_is_lan(source.ip)) { return 1; } if (length != CRYPTO_PUBLIC_KEY_SIZE + 1) { return 1; } dht_bootstrap(dht, source, packet + 1); return 0; } int lan_discovery_send(uint16_t port, DHT *dht) { uint8_t data[CRYPTO_PUBLIC_KEY_SIZE + 1]; data[0] = NET_PACKET_LAN_DISCOVERY; id_copy(data + 1, dht_get_self_public_key(dht)); send_broadcasts(dht_get_net(dht), port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE); int res = -1; IP_Port ip_port; ip_port.port = port; /* IPv6 multicast */ if (net_family_is_ipv6(net_family(dht_get_net(dht)))) { ip_port.ip = broadcast_ip(net_family_ipv6, net_family_ipv6); if (ip_isset(&ip_port.ip)) { if (sendpacket(dht_get_net(dht), ip_port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE) > 0) { res = 1; } } } /* IPv4 broadcast (has to be IPv4-in-IPv6 mapping if socket is IPv6 */ ip_port.ip = broadcast_ip(net_family(dht_get_net(dht)), net_family_ipv4); if (ip_isset(&ip_port.ip)) { if (sendpacket(dht_get_net(dht), ip_port, data, 1 + CRYPTO_PUBLIC_KEY_SIZE)) { res = 1; } } return res; } void lan_discovery_init(DHT *dht) { networking_registerhandler(dht_get_net(dht), NET_PACKET_LAN_DISCOVERY, &handle_LANdiscovery, dht); } void lan_discovery_kill(DHT *dht) { networking_registerhandler(dht_get_net(dht), NET_PACKET_LAN_DISCOVERY, nullptr, nullptr); } c-toxcore-0.2.13/toxcore/LAN_discovery.h000066400000000000000000000020551415350724400200440ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * LAN discovery implementation. */ #ifndef C_TOXCORE_TOXCORE_LAN_DISCOVERY_H #define C_TOXCORE_TOXCORE_LAN_DISCOVERY_H #include "DHT.h" #ifndef DHT_DEFINED #define DHT_DEFINED typedef struct DHT DHT; #endif /* DHT_DEFINED */ #ifndef IP_DEFINED #define IP_DEFINED typedef struct IP IP; #endif /* IP_DEFINED */ /** * Interval in seconds between LAN discovery packet sending. */ #define LAN_DISCOVERY_INTERVAL 10 /** * Send a LAN discovery pcaket to the broadcast address with port port. */ int32_t lan_discovery_send(uint16_t port, DHT *dht); /** * Sets up packet handlers. */ void lan_discovery_init(DHT *dht); /** * Clear packet handlers. */ void lan_discovery_kill(DHT *dht); /** * Is IP a local ip or not. */ bool ip_is_local(IP ip); /** * Checks if a given IP isn't routable. * * @return true if ip is a LAN ip, false if it is not. */ bool ip_is_lan(IP ip); #endif // C_TOXCORE_TOXCORE_LAN_DISCOVERY_H c-toxcore-0.2.13/toxcore/Makefile.inc000066400000000000000000000064321415350724400174050ustar00rootroot00000000000000lib_LTLIBRARIES += libtoxcore.la libtoxcore_la_include_HEADERS = \ ../toxcore/tox.h libtoxcore_la_includedir = $(includedir)/tox libtoxcore_la_SOURCES = ../toxcore/ccompat.h \ ../toxcore/DHT.h \ ../toxcore/DHT.c \ ../toxcore/mono_time.h \ ../toxcore/mono_time.c \ ../toxcore/network.h \ ../toxcore/network.c \ ../toxcore/crypto_core.h \ ../toxcore/crypto_core.c \ ../toxcore/crypto_core_mem.c \ ../toxcore/ping_array.h \ ../toxcore/ping_array.c \ ../toxcore/net_crypto.h \ ../toxcore/net_crypto.c \ ../toxcore/friend_requests.h \ ../toxcore/friend_requests.c \ ../toxcore/LAN_discovery.h \ ../toxcore/LAN_discovery.c \ ../toxcore/friend_connection.h \ ../toxcore/friend_connection.c \ ../toxcore/Messenger.h \ ../toxcore/Messenger.c \ ../toxcore/ping.h \ ../toxcore/ping.c \ ../toxcore/state.h \ ../toxcore/state.c \ ../toxcore/tox.h \ ../toxcore/tox_private.h \ ../toxcore/tox.c \ ../toxcore/tox_api.c \ ../toxcore/util.h \ ../toxcore/util.c \ ../toxcore/group.h \ ../toxcore/group.c \ ../toxcore/onion.h \ ../toxcore/onion.c \ ../toxcore/logger.h \ ../toxcore/logger.c \ ../toxcore/onion_announce.h \ ../toxcore/onion_announce.c \ ../toxcore/onion_client.h \ ../toxcore/onion_client.c \ ../toxcore/TCP_client.h \ ../toxcore/TCP_client.c \ ../toxcore/TCP_server.h \ ../toxcore/TCP_server.c \ ../toxcore/TCP_connection.h \ ../toxcore/TCP_connection.c \ ../toxcore/list.c \ ../toxcore/list.h libtoxcore_la_CFLAGS = -I$(top_srcdir) \ -I$(top_srcdir)/toxcore \ $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) \ $(PTHREAD_CFLAGS) libtoxcore_la_LDFLAGS = $(LT_LDFLAGS) \ $(EXTRA_LT_LDFLAGS) \ $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ $(MATH_LDFLAGS) \ $(RT_LIBS) \ $(WINSOCK2_LIBS) libtoxcore_la_LIBADD = $(LIBSODIUM_LIBS) \ $(NACL_OBJECTS) \ $(NACL_LIBS) \ $(PTHREAD_LIBS) if SET_SO_VERSION EXTRA_libtoxcore_la_DEPENDENCIES = ../so.version endif c-toxcore-0.2.13/toxcore/Messenger.c000066400000000000000000002756611415350724400173050ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * An implementation of a simple text chat only messenger on the tox network core. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "Messenger.h" #include #include #include #include #include #include "logger.h" #include "mono_time.h" #include "network.h" #include "state.h" #include "util.h" static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, uint32_t length, uint8_t congestion_control); static void m_register_default_plugins(Messenger *m); /** * Determines if the friendnumber passed is valid in the Messenger object. * * @param friendnumber The index in the friend list. */ static bool friend_is_valid(const Messenger *m, int32_t friendnumber) { return (unsigned int)friendnumber < m->numfriends && m->friendlist[friendnumber].status != 0; } /* Set the size of the friend list to numfriends. * * return -1 if realloc fails. */ static int realloc_friendlist(Messenger *m, uint32_t num) { if (num == 0) { free(m->friendlist); m->friendlist = nullptr; return 0; } Friend *newfriendlist = (Friend *)realloc(m->friendlist, num * sizeof(Friend)); if (newfriendlist == nullptr) { return -1; } m->friendlist = newfriendlist; return 0; } /* return the friend id associated to that public key. * return -1 if no such friend. */ int32_t getfriend_id(const Messenger *m, const uint8_t *real_pk) { uint32_t i; for (i = 0; i < m->numfriends; ++i) { if (m->friendlist[i].status > 0) { if (id_equal(real_pk, m->friendlist[i].real_pk)) { return i; } } } return -1; } /* Copies the public key associated to that friend id into real_pk buffer. * Make sure that real_pk is of size CRYPTO_PUBLIC_KEY_SIZE. * * return 0 if success. * return -1 if failure. */ int get_real_pk(const Messenger *m, int32_t friendnumber, uint8_t *real_pk) { if (!friend_is_valid(m, friendnumber)) { return -1; } memcpy(real_pk, m->friendlist[friendnumber].real_pk, CRYPTO_PUBLIC_KEY_SIZE); return 0; } /* return friend connection id on success. * return -1 if failure. */ int getfriendcon_id(const Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return -1; } return m->friendlist[friendnumber].friendcon_id; } /* * return a uint16_t that represents the checksum of address of length len. */ static uint16_t address_checksum(const uint8_t *address, uint32_t len) { uint8_t checksum[2] = {0}; uint16_t check; uint32_t i; for (i = 0; i < len; ++i) { checksum[i % 2] ^= address[i]; } memcpy(&check, checksum, sizeof(check)); return check; } /* Format: `[real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]` * * return FRIEND_ADDRESS_SIZE byte address to give to others. */ void getaddress(const Messenger *m, uint8_t *address) { id_copy(address, nc_get_self_public_key(m->net_crypto)); uint32_t nospam = get_nospam(m->fr); memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &nospam, sizeof(nospam)); uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(nospam), &checksum, sizeof(checksum)); } static int send_online_packet(Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return 0; } uint8_t packet = PACKET_ID_ONLINE; return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id), &packet, sizeof(packet), 0) != -1; } static int send_offline_packet(Messenger *m, int friendcon_id) { uint8_t packet = PACKET_ID_OFFLINE; return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, friendcon_id), &packet, sizeof(packet), 0) != -1; } static int m_handle_status(void *object, int i, uint8_t status, void *userdata); static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t len, void *userdata); static int m_handle_lossy_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, void *userdata); static int32_t init_new_friend(Messenger *m, const uint8_t *real_pk, uint8_t status) { if (m->numfriends == UINT32_MAX) { LOGGER_ERROR(m->log, "Friend list full: we have more than 4 billion friends"); /* This is technically incorrect, but close enough. */ return FAERR_NOMEM; } /* Resize the friend list if necessary. */ if (realloc_friendlist(m, m->numfriends + 1) != 0) { return FAERR_NOMEM; } memset(&m->friendlist[m->numfriends], 0, sizeof(Friend)); int friendcon_id = new_friend_connection(m->fr_c, real_pk); if (friendcon_id == -1) { return FAERR_NOMEM; } uint32_t i; for (i = 0; i <= m->numfriends; ++i) { if (m->friendlist[i].status == NOFRIEND) { m->friendlist[i].status = status; m->friendlist[i].friendcon_id = friendcon_id; m->friendlist[i].friendrequest_lastsent = 0; id_copy(m->friendlist[i].real_pk, real_pk); m->friendlist[i].statusmessage_length = 0; m->friendlist[i].userstatus = USERSTATUS_NONE; m->friendlist[i].is_typing = 0; m->friendlist[i].message_id = 0; friend_connection_callbacks(m->fr_c, friendcon_id, MESSENGER_CALLBACK_INDEX, &m_handle_status, &m_handle_packet, &m_handle_lossy_packet, m, i); if (m->numfriends == i) { ++m->numfriends; } if (friend_con_connected(m->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { send_online_packet(m, i); } return i; } } return FAERR_NOMEM; } /* * Add a friend. * Set the data that will be sent along with friend request. * Address is the address of the friend (returned by getaddress of the friend you wish to add) it must be FRIEND_ADDRESS_SIZE bytes. * data is the data and length is the length. * * return the friend number if success. * return FA_TOOLONG if message length is too long. * return FAERR_NOMESSAGE if no message (message length must be >= 1 byte). * return FAERR_OWNKEY if user's own key. * return FAERR_ALREADYSENT if friend request already sent or already a friend. * return FAERR_BADCHECKSUM if bad checksum in address. * return FAERR_SETNEWNOSPAM if the friend was already there but the nospam was different. * (the nospam for that friend was set to the new one). * return FAERR_NOMEM if increasing the friend list size fails. */ int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, uint16_t length) { if (length > MAX_FRIEND_REQUEST_DATA_SIZE) { return FAERR_TOOLONG; } uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; id_copy(real_pk, address); if (!public_key_valid(real_pk)) { return FAERR_BADCHECKSUM; } uint16_t check; uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); memcpy(&check, address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), sizeof(check)); if (check != checksum) { return FAERR_BADCHECKSUM; } if (length < 1) { return FAERR_NOMESSAGE; } if (id_equal(real_pk, nc_get_self_public_key(m->net_crypto))) { return FAERR_OWNKEY; } int32_t friend_id = getfriend_id(m, real_pk); if (friend_id != -1) { if (m->friendlist[friend_id].status >= FRIEND_CONFIRMED) { return FAERR_ALREADYSENT; } uint32_t nospam; memcpy(&nospam, address + CRYPTO_PUBLIC_KEY_SIZE, sizeof(nospam)); if (m->friendlist[friend_id].friendrequest_nospam == nospam) { return FAERR_ALREADYSENT; } m->friendlist[friend_id].friendrequest_nospam = nospam; return FAERR_SETNEWNOSPAM; } int32_t ret = init_new_friend(m, real_pk, FRIEND_ADDED); if (ret < 0) { return ret; } m->friendlist[ret].friendrequest_timeout = FRIENDREQUEST_TIMEOUT; memcpy(m->friendlist[ret].info, data, length); m->friendlist[ret].info_size = length; memcpy(&m->friendlist[ret].friendrequest_nospam, address + CRYPTO_PUBLIC_KEY_SIZE, sizeof(uint32_t)); return ret; } int32_t m_addfriend_norequest(Messenger *m, const uint8_t *real_pk) { if (getfriend_id(m, real_pk) != -1) { return FAERR_ALREADYSENT; } if (!public_key_valid(real_pk)) { return FAERR_BADCHECKSUM; } if (id_equal(real_pk, nc_get_self_public_key(m->net_crypto))) { return FAERR_OWNKEY; } return init_new_friend(m, real_pk, FRIEND_CONFIRMED); } static int clear_receipts(Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return -1; } struct Receipts *receipts = m->friendlist[friendnumber].receipts_start; while (receipts) { struct Receipts *temp_r = receipts->next; free(receipts); receipts = temp_r; } m->friendlist[friendnumber].receipts_start = nullptr; m->friendlist[friendnumber].receipts_end = nullptr; return 0; } static int add_receipt(Messenger *m, int32_t friendnumber, uint32_t packet_num, uint32_t msg_id) { if (!friend_is_valid(m, friendnumber)) { return -1; } struct Receipts *new_receipts = (struct Receipts *)calloc(1, sizeof(struct Receipts)); if (!new_receipts) { return -1; } new_receipts->packet_num = packet_num; new_receipts->msg_id = msg_id; if (!m->friendlist[friendnumber].receipts_start) { m->friendlist[friendnumber].receipts_start = new_receipts; } else { m->friendlist[friendnumber].receipts_end->next = new_receipts; } m->friendlist[friendnumber].receipts_end = new_receipts; new_receipts->next = nullptr; return 0; } /* * return -1 on failure. * return 0 if packet was received. */ static int friend_received_packet(const Messenger *m, int32_t friendnumber, uint32_t number) { if (!friend_is_valid(m, friendnumber)) { return -1; } return cryptpacket_received(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id), number); } static int do_receipts(Messenger *m, int32_t friendnumber, void *userdata) { if (!friend_is_valid(m, friendnumber)) { return -1; } struct Receipts *receipts = m->friendlist[friendnumber].receipts_start; while (receipts) { if (friend_received_packet(m, friendnumber, receipts->packet_num) == -1) { break; } if (m->read_receipt) { m->read_receipt(m, friendnumber, receipts->msg_id, userdata); } struct Receipts *r_next = receipts->next; free(receipts); m->friendlist[friendnumber].receipts_start = r_next; receipts = r_next; } if (!m->friendlist[friendnumber].receipts_start) { m->friendlist[friendnumber].receipts_end = nullptr; } return 0; } /* Remove a friend. * * return 0 if success. * return -1 if failure. */ int m_delfriend(Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (m->friend_connectionstatuschange_internal) { m->friend_connectionstatuschange_internal(m, friendnumber, 0, m->friend_connectionstatuschange_internal_userdata); } clear_receipts(m, friendnumber); remove_request_received(m->fr, m->friendlist[friendnumber].real_pk); friend_connection_callbacks(m->fr_c, m->friendlist[friendnumber].friendcon_id, MESSENGER_CALLBACK_INDEX, nullptr, nullptr, nullptr, nullptr, 0); if (friend_con_connected(m->fr_c, m->friendlist[friendnumber].friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { send_offline_packet(m, m->friendlist[friendnumber].friendcon_id); } kill_friend_connection(m->fr_c, m->friendlist[friendnumber].friendcon_id); memset(&m->friendlist[friendnumber], 0, sizeof(Friend)); uint32_t i; for (i = m->numfriends; i != 0; --i) { if (m->friendlist[i - 1].status != NOFRIEND) { break; } } m->numfriends = i; if (realloc_friendlist(m, m->numfriends) != 0) { return FAERR_NOMEM; } return 0; } int m_get_friend_connectionstatus(const Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { return CONNECTION_NONE; } bool direct_connected = 0; unsigned int num_online_relays = 0; int crypt_conn_id = friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id); if (!crypto_connection_status(m->net_crypto, crypt_conn_id, &direct_connected, &num_online_relays)) { return CONNECTION_NONE; } if (direct_connected) { return CONNECTION_UDP; } if (num_online_relays) { return CONNECTION_TCP; } /* if we have a valid friend connection but do not have an established connection * we leave the connection status unchanged until the friend connection is either * established or dropped. */ return m->friendlist[friendnumber].last_connection_udp_tcp; } int m_friend_exists(const Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return 0; } return 1; } /* Send a message of type. * * return -1 if friend not valid. * return -2 if too large. * return -3 if friend not online. * return -4 if send failed (because queue is full). * return -5 if bad type. * return 0 if success. */ int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length, uint32_t *message_id) { if (type > MESSAGE_ACTION) { LOGGER_ERROR(m->log, "Message type %d is invalid", type); return -5; } if (!friend_is_valid(m, friendnumber)) { LOGGER_ERROR(m->log, "Friend number %d is invalid", friendnumber); return -1; } if (length >= MAX_CRYPTO_DATA_SIZE) { LOGGER_ERROR(m->log, "Message length %u is too large", length); return -2; } if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { LOGGER_ERROR(m->log, "Friend %d is not online", friendnumber); return -3; } VLA(uint8_t, packet, length + 1); packet[0] = PACKET_ID_MESSAGE + type; if (length != 0) { memcpy(packet + 1, message, length); } int64_t packet_num = write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id), packet, length + 1, 0); if (packet_num == -1) { LOGGER_ERROR(m->log, "Failed to write crypto packet for message of length %d to friend %d", length, friendnumber); return -4; } uint32_t msg_id = ++m->friendlist[friendnumber].message_id; add_receipt(m, friendnumber, packet_num, msg_id); if (message_id) { *message_id = msg_id; } return 0; } /* Send a name packet to friendnumber. * length is the length with the NULL terminator. */ static int m_sendname(const Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length) { if (length > MAX_NAME_LENGTH) { return 0; } return write_cryptpacket_id(m, friendnumber, PACKET_ID_NICKNAME, name, length, 0); } /* Set the name and name_length of a friend. * * return 0 if success. * return -1 if failure. */ int setfriendname(Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (length > MAX_NAME_LENGTH || length == 0) { return -1; } m->friendlist[friendnumber].name_length = length; memcpy(m->friendlist[friendnumber].name, name, length); return 0; } /* Set our nickname * name must be a string of maximum MAX_NAME_LENGTH length. * length must be at least 1 byte. * length is the length of name with the NULL terminator. * * return 0 if success. * return -1 if failure. */ int setname(Messenger *m, const uint8_t *name, uint16_t length) { if (length > MAX_NAME_LENGTH) { return -1; } if (m->name_length == length && (length == 0 || memcmp(name, m->name, length) == 0)) { return 0; } if (length) { memcpy(m->name, name, length); } m->name_length = length; uint32_t i; for (i = 0; i < m->numfriends; ++i) { m->friendlist[i].name_sent = 0; } return 0; } /* Get our nickname and put it in name. * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes. * * return the length of the name. */ uint16_t getself_name(const Messenger *m, uint8_t *name) { if (name == nullptr) { return 0; } memcpy(name, m->name, m->name_length); return m->name_length; } /* Get name of friendnumber and put it in name. * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes. * * return length of name if success. * return -1 if failure. */ int getname(const Messenger *m, int32_t friendnumber, uint8_t *name) { if (!friend_is_valid(m, friendnumber)) { return -1; } memcpy(name, m->friendlist[friendnumber].name, m->friendlist[friendnumber].name_length); return m->friendlist[friendnumber].name_length; } int m_get_name_size(const Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return -1; } return m->friendlist[friendnumber].name_length; } int m_get_self_name_size(const Messenger *m) { return m->name_length; } int m_set_statusmessage(Messenger *m, const uint8_t *status, uint16_t length) { if (length > MAX_STATUSMESSAGE_LENGTH) { return -1; } if (m->statusmessage_length == length && (length == 0 || memcmp(m->statusmessage, status, length) == 0)) { return 0; } if (length) { memcpy(m->statusmessage, status, length); } m->statusmessage_length = length; uint32_t i; for (i = 0; i < m->numfriends; ++i) { m->friendlist[i].statusmessage_sent = 0; } return 0; } int m_set_userstatus(Messenger *m, uint8_t status) { if (status >= USERSTATUS_INVALID) { return -1; } if (m->userstatus == status) { return 0; } m->userstatus = (Userstatus)status; uint32_t i; for (i = 0; i < m->numfriends; ++i) { m->friendlist[i].userstatus_sent = 0; } return 0; } /* return the size of friendnumber's user status. * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. */ int m_get_statusmessage_size(const Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return -1; } return m->friendlist[friendnumber].statusmessage_length; } /* Copy the user status of friendnumber into buf, truncating if needed to maxlen * bytes, use m_get_statusmessage_size to find out how much you need to allocate. */ int m_copy_statusmessage(const Messenger *m, int32_t friendnumber, uint8_t *buf, uint32_t maxlen) { if (!friend_is_valid(m, friendnumber)) { return -1; } // TODO(iphydf): This should be uint16_t and min_u16. If maxlen exceeds // uint16_t's range, it won't affect the result. uint32_t msglen = min_u32(maxlen, m->friendlist[friendnumber].statusmessage_length); memcpy(buf, m->friendlist[friendnumber].statusmessage, msglen); memset(buf + msglen, 0, maxlen - msglen); return msglen; } /* return the size of friendnumber's user status. * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. */ int m_get_self_statusmessage_size(const Messenger *m) { return m->statusmessage_length; } int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf) { memcpy(buf, m->statusmessage, m->statusmessage_length); return m->statusmessage_length; } uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return USERSTATUS_INVALID; } uint8_t status = m->friendlist[friendnumber].userstatus; if (status >= USERSTATUS_INVALID) { status = USERSTATUS_NONE; } return status; } uint8_t m_get_self_userstatus(const Messenger *m) { return m->userstatus; } uint64_t m_get_last_online(const Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return UINT64_MAX; } return m->friendlist[friendnumber].last_seen_time; } int m_set_usertyping(Messenger *m, int32_t friendnumber, uint8_t is_typing) { if (is_typing != 0 && is_typing != 1) { return -1; } if (!friend_is_valid(m, friendnumber)) { return -1; } if (m->friendlist[friendnumber].user_istyping == is_typing) { return 0; } m->friendlist[friendnumber].user_istyping = is_typing; m->friendlist[friendnumber].user_istyping_sent = 0; return 0; } int m_get_istyping(const Messenger *m, int32_t friendnumber) { if (!friend_is_valid(m, friendnumber)) { return -1; } return m->friendlist[friendnumber].is_typing; } static int send_statusmessage(const Messenger *m, int32_t friendnumber, const uint8_t *status, uint16_t length) { return write_cryptpacket_id(m, friendnumber, PACKET_ID_STATUSMESSAGE, status, length, 0); } static int send_userstatus(const Messenger *m, int32_t friendnumber, uint8_t status) { return write_cryptpacket_id(m, friendnumber, PACKET_ID_USERSTATUS, &status, sizeof(status), 0); } static int send_user_istyping(const Messenger *m, int32_t friendnumber, uint8_t is_typing) { uint8_t typing = is_typing; return write_cryptpacket_id(m, friendnumber, PACKET_ID_TYPING, &typing, sizeof(typing), 0); } static int set_friend_statusmessage(const Messenger *m, int32_t friendnumber, const uint8_t *status, uint16_t length) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (length > MAX_STATUSMESSAGE_LENGTH) { return -1; } if (length) { memcpy(m->friendlist[friendnumber].statusmessage, status, length); } m->friendlist[friendnumber].statusmessage_length = length; return 0; } static void set_friend_userstatus(const Messenger *m, int32_t friendnumber, uint8_t status) { m->friendlist[friendnumber].userstatus = (Userstatus)status; } static void set_friend_typing(const Messenger *m, int32_t friendnumber, uint8_t is_typing) { m->friendlist[friendnumber].is_typing = is_typing; } /* Set the function that will be executed when a friend request is received. */ void m_callback_friendrequest(Messenger *m, m_friend_request_cb *function) { callback_friendrequest(m->fr, (fr_friend_request_cb *)function, m); } /* Set the function that will be executed when a message from a friend is received. */ void m_callback_friendmessage(Messenger *m, m_friend_message_cb *function) { m->friend_message = function; } void m_callback_namechange(Messenger *m, m_friend_name_cb *function) { m->friend_namechange = function; } void m_callback_statusmessage(Messenger *m, m_friend_status_message_cb *function) { m->friend_statusmessagechange = function; } void m_callback_userstatus(Messenger *m, m_friend_status_cb *function) { m->friend_userstatuschange = function; } void m_callback_typingchange(Messenger *m, m_friend_typing_cb *function) { m->friend_typingchange = function; } void m_callback_read_receipt(Messenger *m, m_friend_read_receipt_cb *function) { m->read_receipt = function; } void m_callback_connectionstatus(Messenger *m, m_friend_connection_status_cb *function) { m->friend_connectionstatuschange = function; } void m_callback_core_connection(Messenger *m, m_self_connection_status_cb *function) { m->core_connection_change = function; } void m_callback_connectionstatus_internal_av(Messenger *m, m_friend_connectionstatuschange_internal_cb *function, void *userdata) { m->friend_connectionstatuschange_internal = function; m->friend_connectionstatuschange_internal_userdata = userdata; } static void check_friend_tcp_udp(Messenger *m, int32_t friendnumber, void *userdata) { int last_connection_udp_tcp = m->friendlist[friendnumber].last_connection_udp_tcp; int ret = m_get_friend_connectionstatus(m, friendnumber); if (ret == -1) { return; } if (last_connection_udp_tcp != ret) { if (m->friend_connectionstatuschange) { m->friend_connectionstatuschange(m, friendnumber, ret, userdata); } } m->friendlist[friendnumber].last_connection_udp_tcp = (Connection_Status)ret; } static void break_files(const Messenger *m, int32_t friendnumber); static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status, void *userdata) { if (status == NOFRIEND) { return; } const uint8_t was_online = m->friendlist[friendnumber].status == FRIEND_ONLINE; const uint8_t is_online = status == FRIEND_ONLINE; if (is_online != was_online) { if (was_online) { break_files(m, friendnumber); clear_receipts(m, friendnumber); } else { m->friendlist[friendnumber].name_sent = 0; m->friendlist[friendnumber].userstatus_sent = 0; m->friendlist[friendnumber].statusmessage_sent = 0; m->friendlist[friendnumber].user_istyping_sent = 0; } m->friendlist[friendnumber].status = status; check_friend_tcp_udp(m, friendnumber, userdata); if (m->friend_connectionstatuschange_internal) { m->friend_connectionstatuschange_internal(m, friendnumber, is_online, m->friend_connectionstatuschange_internal_userdata); } } } static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status, void *userdata) { check_friend_connectionstatus(m, friendnumber, status, userdata); m->friendlist[friendnumber].status = status; } static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, uint32_t length, uint8_t congestion_control) { if (!friend_is_valid(m, friendnumber)) { return 0; } if (length >= MAX_CRYPTO_DATA_SIZE || m->friendlist[friendnumber].status != FRIEND_ONLINE) { return 0; } VLA(uint8_t, packet, length + 1); packet[0] = packet_id; if (length != 0) { memcpy(packet + 1, data, length); } return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id), packet, length + 1, congestion_control) != -1; } /** CONFERENCES */ /* Set the callback for conference invites. */ void m_callback_conference_invite(Messenger *m, m_conference_invite_cb *function) { m->conference_invite = function; } /* Send a conference invite packet. * * return 1 on success * return 0 on failure */ int send_conference_invite_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length) { return write_cryptpacket_id(m, friendnumber, PACKET_ID_INVITE_CONFERENCE, data, length, 0); } /** FILE SENDING */ /* Set the callback for file send requests. */ void callback_file_sendrequest(Messenger *m, m_file_recv_cb *function) { m->file_sendrequest = function; } /* Set the callback for file control requests. */ void callback_file_control(Messenger *m, m_file_recv_control_cb *function) { m->file_filecontrol = function; } /* Set the callback for file data. */ void callback_file_data(Messenger *m, m_file_recv_chunk_cb *function) { m->file_filedata = function; } /* Set the callback for file request chunk. */ void callback_file_reqchunk(Messenger *m, m_file_chunk_request_cb *function) { m->file_reqchunk = function; } #define MAX_FILENAME_LENGTH 255 /* Copy the file transfer file id to file_id * * return 0 on success. * return -1 if friend not valid. * return -2 if filenumber not valid */ int file_get_id(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint8_t *file_id) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { return -2; } uint32_t temp_filenum; uint8_t send_receive; uint8_t file_number; if (filenumber >= (1 << 16)) { send_receive = 1; temp_filenum = (filenumber >> 16) - 1; } else { send_receive = 0; temp_filenum = filenumber; } if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { return -2; } file_number = temp_filenum; struct File_Transfers *ft; if (send_receive) { ft = &m->friendlist[friendnumber].file_receiving[file_number]; } else { ft = &m->friendlist[friendnumber].file_sending[file_number]; } if (ft->status == FILESTATUS_NONE) { return -2; } memcpy(file_id, ft->id, FILE_ID_LENGTH); return 0; } /* Send a file send request. * Maximum filename length is 255 bytes. * return 1 on success * return 0 on failure */ static int file_sendrequest(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint32_t file_type, uint64_t filesize, const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length) { if (!friend_is_valid(m, friendnumber)) { return 0; } if (filename_length > MAX_FILENAME_LENGTH) { return 0; } VLA(uint8_t, packet, 1 + sizeof(file_type) + sizeof(filesize) + FILE_ID_LENGTH + filename_length); packet[0] = filenumber; file_type = net_htonl(file_type); memcpy(packet + 1, &file_type, sizeof(file_type)); net_pack_u64(packet + 1 + sizeof(file_type), filesize); memcpy(packet + 1 + sizeof(file_type) + sizeof(filesize), file_id, FILE_ID_LENGTH); if (filename_length) { memcpy(packet + 1 + sizeof(file_type) + sizeof(filesize) + FILE_ID_LENGTH, filename, filename_length); } return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_SENDREQUEST, packet, SIZEOF_VLA(packet), 0); } /* Send a file send request. * Maximum filename length is 255 bytes. * return file number on success * return -1 if friend not found. * return -2 if filename length invalid. * return -3 if no more file sending slots left. * return -4 if could not send packet (friend offline). * */ long int new_filesender(const Messenger *m, int32_t friendnumber, uint32_t file_type, uint64_t filesize, const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (filename_length > MAX_FILENAME_LENGTH) { return -2; } uint32_t i; for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { if (m->friendlist[friendnumber].file_sending[i].status == FILESTATUS_NONE) { break; } } if (i == MAX_CONCURRENT_FILE_PIPES) { return -3; } if (file_sendrequest(m, friendnumber, i, file_type, filesize, file_id, filename, filename_length) == 0) { return -4; } struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[i]; ft->status = FILESTATUS_NOT_ACCEPTED; ft->size = filesize; ft->transferred = 0; ft->requested = 0; ft->paused = FILE_PAUSE_NOT; memcpy(ft->id, file_id, FILE_ID_LENGTH); ++m->friendlist[friendnumber].num_sending_files; return i; } static int send_file_control_packet(const Messenger *m, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t control_type, uint8_t *data, uint16_t data_length) { if ((unsigned int)(1 + 3 + data_length) > MAX_CRYPTO_DATA_SIZE) { return -1; } VLA(uint8_t, packet, 3 + data_length); packet[0] = send_receive; packet[1] = filenumber; packet[2] = control_type; if (data_length) { memcpy(packet + 3, data, data_length); } return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_CONTROL, packet, SIZEOF_VLA(packet), 0); } /* Send a file control request. * * return 0 on success * return -1 if friend not valid. * return -2 if friend not online. * return -3 if file number invalid. * return -4 if file control is bad. * return -5 if file already paused. * return -6 if resume file failed because it was only paused by the other. * return -7 if resume file failed because it wasn't paused. * return -8 if packet failed to send. */ int file_control(const Messenger *m, int32_t friendnumber, uint32_t filenumber, unsigned int control) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { return -2; } uint32_t temp_filenum; uint8_t send_receive; uint8_t file_number; if (filenumber >= (1 << 16)) { send_receive = 1; temp_filenum = (filenumber >> 16) - 1; } else { send_receive = 0; temp_filenum = filenumber; } if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { return -3; } file_number = temp_filenum; struct File_Transfers *ft; if (send_receive) { ft = &m->friendlist[friendnumber].file_receiving[file_number]; } else { ft = &m->friendlist[friendnumber].file_sending[file_number]; } if (ft->status == FILESTATUS_NONE) { return -3; } if (control > FILECONTROL_KILL) { return -4; } if (control == FILECONTROL_PAUSE && ((ft->paused & FILE_PAUSE_US) || ft->status != FILESTATUS_TRANSFERRING)) { return -5; } if (control == FILECONTROL_ACCEPT) { if (ft->status == FILESTATUS_TRANSFERRING) { if (!(ft->paused & FILE_PAUSE_US)) { if (ft->paused & FILE_PAUSE_OTHER) { return -6; } return -7; } } else { if (ft->status != FILESTATUS_NOT_ACCEPTED) { return -7; } if (!send_receive) { return -6; } } } if (send_file_control_packet(m, friendnumber, send_receive, file_number, control, nullptr, 0)) { if (control == FILECONTROL_KILL) { ft->status = FILESTATUS_NONE; if (send_receive == 0) { --m->friendlist[friendnumber].num_sending_files; } } else if (control == FILECONTROL_PAUSE) { ft->paused |= FILE_PAUSE_US; } else if (control == FILECONTROL_ACCEPT) { ft->status = FILESTATUS_TRANSFERRING; if (ft->paused & FILE_PAUSE_US) { ft->paused ^= FILE_PAUSE_US; } } } else { return -8; } return 0; } /* Send a seek file control request. * * return 0 on success * return -1 if friend not valid. * return -2 if friend not online. * return -3 if file number invalid. * return -4 if not receiving file. * return -5 if file status wrong. * return -6 if position bad. * return -8 if packet failed to send. */ int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { return -2; } if (filenumber < (1 << 16)) { // Not receiving. return -4; } uint32_t temp_filenum = (filenumber >> 16) - 1; if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES) { return -3; } assert(temp_filenum <= UINT8_MAX); uint8_t file_number = temp_filenum; // We're always receiving at this point. struct File_Transfers *ft = &m->friendlist[friendnumber].file_receiving[file_number]; if (ft->status == FILESTATUS_NONE) { return -3; } if (ft->status != FILESTATUS_NOT_ACCEPTED) { return -5; } if (position >= ft->size) { return -6; } uint8_t sending_pos[sizeof(uint64_t)]; net_pack_u64(sending_pos, position); if (send_file_control_packet(m, friendnumber, 1, file_number, FILECONTROL_SEEK, sending_pos, sizeof(sending_pos))) { ft->transferred = position; } else { return -8; } return 0; } /* return packet number on success. * return -1 on failure. */ static int64_t send_file_data_packet(const Messenger *m, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length) { if (!friend_is_valid(m, friendnumber)) { return -1; } VLA(uint8_t, packet, 2 + length); packet[0] = PACKET_ID_FILE_DATA; packet[1] = filenumber; if (length) { memcpy(packet + 2, data, length); } return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id), packet, SIZEOF_VLA(packet), 1); } #define MAX_FILE_DATA_SIZE (MAX_CRYPTO_DATA_SIZE - 2) #define MIN_SLOTS_FREE (CRYPTO_MIN_QUEUE_LENGTH / 4) /* Send file data. * * return 0 on success * return -1 if friend not valid. * return -2 if friend not online. * return -3 if filenumber invalid. * return -4 if file transfer not transferring. * return -5 if bad data size. * return -6 if packet queue full. * return -7 if wrong position. */ int file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data, uint16_t length) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { return -2; } if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { return -3; } struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[filenumber]; if (ft->status != FILESTATUS_TRANSFERRING) { return -4; } if (length > MAX_FILE_DATA_SIZE) { return -5; } if (ft->size - ft->transferred < length) { return -5; } if (ft->size != UINT64_MAX && length != MAX_FILE_DATA_SIZE && (ft->transferred + length) != ft->size) { return -5; } if (position != ft->transferred || (ft->requested <= position && ft->size != 0)) { return -7; } /* Prevent file sending from filling up the entire buffer preventing messages from being sent. * TODO(irungentoo): remove */ if (crypto_num_free_sendqueue_slots(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id)) < MIN_SLOTS_FREE) { return -6; } int64_t ret = send_file_data_packet(m, friendnumber, filenumber, data, length); if (ret != -1) { // TODO(irungentoo): record packet ids to check if other received complete file. ft->transferred += length; if (length != MAX_FILE_DATA_SIZE || ft->size == ft->transferred) { ft->status = FILESTATUS_FINISHED; ft->last_packet_number = ret; } return 0; } return -6; } /** * Iterate over all file transfers and request chunks (from the client) for each * of them. * * The free_slots parameter is updated by this function. * * @param m Our messenger object. * @param friendnumber The friend we're sending files to. * @param userdata The client userdata to pass along to chunk request callbacks. * @param free_slots A pointer to the number of free send queue slots in the * crypto connection. * @return true if there's still work to do, false otherwise. * */ static bool do_all_filetransfers(Messenger *m, int32_t friendnumber, void *userdata, uint32_t *free_slots) { Friend *const friendcon = &m->friendlist[friendnumber]; // Iterate over file transfers as long as we're sending files for (uint32_t i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { if (friendcon->num_sending_files == 0) { // no active file transfers anymore return false; } if (*free_slots == 0) { // send buffer full enough return false; } if (max_speed_reached(m->net_crypto, friend_connection_crypt_connection_id( m->fr_c, friendcon->friendcon_id))) { LOGGER_TRACE(m->log, "Maximum connection speed reached"); // connection doesn't support any more data return false; } struct File_Transfers *const ft = &friendcon->file_sending[i]; // If the file transfer is complete, we request a chunk of size 0. if (ft->status == FILESTATUS_FINISHED && friend_received_packet(m, friendnumber, ft->last_packet_number) == 0) { if (m->file_reqchunk) { m->file_reqchunk(m, friendnumber, i, ft->transferred, 0, userdata); } // Now it's inactive, we're no longer sending this. ft->status = FILESTATUS_NONE; --friendcon->num_sending_files; } if (ft->status == FILESTATUS_TRANSFERRING && ft->paused == FILE_PAUSE_NOT) { if (ft->size == 0) { /* Send 0 data to friend if file is 0 length. */ file_data(m, friendnumber, i, 0, nullptr, 0); continue; } if (ft->size == ft->requested) { // This file transfer is done. continue; } const uint16_t length = min_u64(ft->size - ft->requested, MAX_FILE_DATA_SIZE); const uint64_t position = ft->requested; ft->requested += length; if (m->file_reqchunk) { m->file_reqchunk(m, friendnumber, i, position, length, userdata); } // The allocated slot is no longer free. --*free_slots; } } return true; } static void do_reqchunk_filecb(Messenger *m, int32_t friendnumber, void *userdata) { // We're not currently doing any file transfers. if (m->friendlist[friendnumber].num_sending_files == 0) { return; } // The number of packet slots left in the sendbuffer. // This is a per friend count (CRYPTO_PACKET_BUFFER_SIZE). uint32_t free_slots = crypto_num_free_sendqueue_slots( m->net_crypto, friend_connection_crypt_connection_id( m->fr_c, m->friendlist[friendnumber].friendcon_id)); // We keep MIN_SLOTS_FREE slots free for other packets, otherwise file // transfers might block other traffic for a long time. free_slots = max_s32(0, (int32_t)free_slots - MIN_SLOTS_FREE); // Maximum number of outer loops below. If the client doesn't send file // chunks from within the chunk request callback handler, we never realise // that the file transfer has finished and may end up in an infinite loop. // // Request up to that number of chunks per file from the client const uint32_t max_ft_loops = 16; for (uint32_t i = 0; i < max_ft_loops; ++i) { if (!do_all_filetransfers(m, friendnumber, userdata, &free_slots)) { break; } if (free_slots == 0) { // stop when the buffer is full enough break; } } } /* Run this when the friend disconnects. * Kill all current file transfers. */ static void break_files(const Messenger *m, int32_t friendnumber) { // TODO(irungentoo): Inform the client which file transfers get killed with a callback? for (uint32_t i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) { if (m->friendlist[friendnumber].file_sending[i].status != FILESTATUS_NONE) { m->friendlist[friendnumber].file_sending[i].status = FILESTATUS_NONE; } if (m->friendlist[friendnumber].file_receiving[i].status != FILESTATUS_NONE) { m->friendlist[friendnumber].file_receiving[i].status = FILESTATUS_NONE; } } } static struct File_Transfers *get_file_transfer(uint8_t receive_send, uint8_t filenumber, uint32_t *real_filenumber, Friend *sender) { struct File_Transfers *ft; if (receive_send == 0) { *real_filenumber = (filenumber + 1) << 16; ft = &sender->file_receiving[filenumber]; } else { *real_filenumber = filenumber; ft = &sender->file_sending[filenumber]; } if (ft->status == FILESTATUS_NONE) { return nullptr; } return ft; } /* return -1 on failure, 0 on success. */ static int handle_filecontrol(Messenger *m, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, uint8_t control_type, const uint8_t *data, uint16_t length, void *userdata) { if (receive_send > 1) { LOGGER_DEBUG(m->log, "file control (friend %d, file %d): receive_send value is invalid (should be 0 or 1): %d", friendnumber, filenumber, receive_send); return -1; } uint32_t real_filenumber; struct File_Transfers *ft = get_file_transfer(receive_send, filenumber, &real_filenumber, &m->friendlist[friendnumber]); if (ft == nullptr) { LOGGER_DEBUG(m->log, "file control (friend %d, file %d): file transfer does not exist; telling the other to kill it", friendnumber, filenumber); send_file_control_packet(m, friendnumber, !receive_send, filenumber, FILECONTROL_KILL, nullptr, 0); return -1; } switch (control_type) { case FILECONTROL_ACCEPT: { if (receive_send && ft->status == FILESTATUS_NOT_ACCEPTED) { ft->status = FILESTATUS_TRANSFERRING; } else { if (ft->paused & FILE_PAUSE_OTHER) { ft->paused ^= FILE_PAUSE_OTHER; } else { LOGGER_DEBUG(m->log, "file control (friend %d, file %d): friend told us to resume file transfer that wasn't paused", friendnumber, filenumber); return -1; } } if (m->file_filecontrol) { m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); } return 0; } case FILECONTROL_PAUSE: { if ((ft->paused & FILE_PAUSE_OTHER) || ft->status != FILESTATUS_TRANSFERRING) { LOGGER_DEBUG(m->log, "file control (friend %d, file %d): friend told us to pause file transfer that is already paused", friendnumber, filenumber); return -1; } ft->paused |= FILE_PAUSE_OTHER; if (m->file_filecontrol) { m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); } return 0; } case FILECONTROL_KILL: { if (m->file_filecontrol) { m->file_filecontrol(m, friendnumber, real_filenumber, control_type, userdata); } ft->status = FILESTATUS_NONE; if (receive_send) { --m->friendlist[friendnumber].num_sending_files; } return 0; } case FILECONTROL_SEEK: { uint64_t position; if (length != sizeof(position)) { LOGGER_DEBUG(m->log, "file control (friend %d, file %d): expected payload of length %d, but got %d", friendnumber, filenumber, (uint32_t)sizeof(position), length); return -1; } /* seek can only be sent by the receiver to seek before resuming broken transfers. */ if (ft->status != FILESTATUS_NOT_ACCEPTED || !receive_send) { LOGGER_DEBUG(m->log, "file control (friend %d, file %d): seek was either sent by a sender or by the receiver after accepting", friendnumber, filenumber); return -1; } net_unpack_u64(data, &position); if (position >= ft->size) { LOGGER_DEBUG(m->log, "file control (friend %d, file %d): seek position %ld exceeds file size %ld", friendnumber, filenumber, (unsigned long)position, (unsigned long)ft->size); return -1; } ft->requested = position; ft->transferred = position; return 0; } default: { LOGGER_DEBUG(m->log, "file control (friend %d, file %d): invalid file control: %d", friendnumber, filenumber, control_type); return -1; } } } /* Set the callback for msi packets. */ void m_callback_msi_packet(Messenger *m, m_msi_packet_cb *function, void *userdata) { m->msi_packet = function; m->msi_packet_userdata = userdata; } /* Send an msi packet. * * return 1 on success * return 0 on failure */ int m_msi_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length) { return write_cryptpacket_id(m, friendnumber, PACKET_ID_MSI, data, length, 0); } static int m_handle_lossy_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, void *userdata) { Messenger *m = (Messenger *)object; if (!friend_is_valid(m, friend_num)) { return 1; } if (packet[0] <= PACKET_ID_RANGE_LOSSY_AV_END) { const RTP_Packet_Handler *const ph = &m->friendlist[friend_num].lossy_rtp_packethandlers[packet[0] % PACKET_ID_RANGE_LOSSY_AV_SIZE]; if (ph->function) { return ph->function(m, friend_num, packet, length, ph->object); } return 1; } if (m->lossy_packethandler) { m->lossy_packethandler(m, friend_num, packet[0], packet, length, userdata); } return 1; } void custom_lossy_packet_registerhandler(Messenger *m, m_friend_lossy_packet_cb *lossy_packethandler) { m->lossy_packethandler = lossy_packethandler; } int m_callback_rtp_packet(Messenger *m, int32_t friendnumber, uint8_t byte, m_lossy_rtp_packet_cb *function, void *object) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (byte < PACKET_ID_RANGE_LOSSY_AV_START || byte > PACKET_ID_RANGE_LOSSY_AV_END) { return -1; } m->friendlist[friendnumber].lossy_rtp_packethandlers[byte % PACKET_ID_RANGE_LOSSY_AV_SIZE].function = function; m->friendlist[friendnumber].lossy_rtp_packethandlers[byte % PACKET_ID_RANGE_LOSSY_AV_SIZE].object = object; return 0; } /* TODO(oxij): this name is confusing, because this function sends both av and custom lossy packets. * Meanwhile, m_handle_lossy_packet routes custom packets to custom_lossy_packet_registerhandler * as you would expect from its name. * * I.e. custom_lossy_packet_registerhandler's "custom lossy packet" and this "custom lossy packet" * are not the same set of packets. */ int m_send_custom_lossy_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { return -2; } // TODO(oxij): send_lossy_cryptpacket makes this check already, similarly for other similar places if (data[0] < PACKET_ID_RANGE_LOSSY_START || data[0] > PACKET_ID_RANGE_LOSSY_END) { return -3; } if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { return -4; } if (send_lossy_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id), data, length) == -1) { return -5; } return 0; } static int handle_custom_lossless_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length, void *userdata) { Messenger *m = (Messenger *)object; if (!friend_is_valid(m, friend_num)) { return -1; } if (packet[0] < PACKET_ID_RANGE_LOSSLESS_CUSTOM_START || packet[0] > PACKET_ID_RANGE_LOSSLESS_CUSTOM_END) { return -1; } if (m->lossless_packethandler) { m->lossless_packethandler(m, friend_num, packet[0], packet, length, userdata); } return 1; } void custom_lossless_packet_registerhandler(Messenger *m, m_friend_lossless_packet_cb *lossless_packethandler) { m->lossless_packethandler = lossless_packethandler; } int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length) { if (!friend_is_valid(m, friendnumber)) { return -1; } if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { return -2; } if ((data[0] < PACKET_ID_RANGE_LOSSLESS_CUSTOM_START || data[0] > PACKET_ID_RANGE_LOSSLESS_CUSTOM_END) && data[0] != PACKET_ID_MSI) { return -3; } if (m->friendlist[friendnumber].status != FRIEND_ONLINE) { return -4; } if (write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, m->friendlist[friendnumber].friendcon_id), data, length, 1) == -1) { return -5; } return 0; } /* Function to filter out some friend requests*/ static int friend_already_added(const uint8_t *real_pk, void *data) { const Messenger *m = (const Messenger *)data; if (getfriend_id(m, real_pk) == -1) { return 0; } return -1; } /* Run this at startup. */ Messenger *new_messenger(Mono_Time *mono_time, Messenger_Options *options, unsigned int *error) { if (!options) { return nullptr; } if (error) { *error = MESSENGER_ERROR_OTHER; } Messenger *m = (Messenger *)calloc(1, sizeof(Messenger)); if (!m) { return nullptr; } m->mono_time = mono_time; m->fr = friendreq_new(); if (!m->fr) { free(m); return nullptr; } m->log = logger_new(); if (m->log == nullptr) { friendreq_kill(m->fr); free(m); return nullptr; } logger_callback_log(m->log, options->log_callback, options->log_context, options->log_user_data); unsigned int net_err = 0; if (!options->udp_disabled && options->proxy_info.proxy_type != TCP_PROXY_NONE) { // We don't currently support UDP over proxy. LOGGER_WARNING(m->log, "UDP enabled and proxy set: disabling UDP"); options->udp_disabled = true; } if (options->udp_disabled) { m->net = new_networking_no_udp(m->log); } else { IP ip; ip_init(&ip, options->ipv6enabled); m->net = new_networking_ex(m->log, ip, options->port_range[0], options->port_range[1], &net_err); } if (m->net == nullptr) { friendreq_kill(m->fr); logger_kill(m->log); free(m); if (error && net_err == 1) { *error = MESSENGER_ERROR_PORT; } return nullptr; } m->dht = new_dht(m->log, m->mono_time, m->net, options->hole_punching_enabled); if (m->dht == nullptr) { kill_networking(m->net); friendreq_kill(m->fr); logger_kill(m->log); free(m); return nullptr; } m->net_crypto = new_net_crypto(m->log, m->mono_time, m->dht, &options->proxy_info); if (m->net_crypto == nullptr) { kill_dht(m->dht); kill_networking(m->net); friendreq_kill(m->fr); logger_kill(m->log); free(m); return nullptr; } m->onion = new_onion(m->mono_time, m->dht); m->onion_a = new_onion_announce(m->mono_time, m->dht); m->onion_c = new_onion_client(m->log, m->mono_time, m->net_crypto); m->fr_c = new_friend_connections(m->log, m->mono_time, m->onion_c, options->local_discovery_enabled); if (!(m->onion && m->onion_a && m->onion_c && m->fr_c)) { kill_friend_connections(m->fr_c); kill_onion(m->onion); kill_onion_announce(m->onion_a); kill_onion_client(m->onion_c); kill_net_crypto(m->net_crypto); kill_dht(m->dht); kill_networking(m->net); friendreq_kill(m->fr); logger_kill(m->log); free(m); return nullptr; } if (options->tcp_server_port) { m->tcp_server = new_TCP_server(m->log, options->ipv6enabled, 1, &options->tcp_server_port, dht_get_self_secret_key(m->dht), m->onion); if (m->tcp_server == nullptr) { kill_friend_connections(m->fr_c); kill_onion(m->onion); kill_onion_announce(m->onion_a); kill_onion_client(m->onion_c); kill_net_crypto(m->net_crypto); kill_dht(m->dht); kill_networking(m->net); friendreq_kill(m->fr); logger_kill(m->log); free(m); if (error) { *error = MESSENGER_ERROR_TCP_SERVER; } return nullptr; } } m->options = *options; friendreq_init(m->fr, m->fr_c); set_nospam(m->fr, random_u32()); set_filter_function(m->fr, &friend_already_added, m); m->lastdump = 0; m_register_default_plugins(m); if (error) { *error = MESSENGER_ERROR_NONE; } return m; } /* Run this before closing shop. */ void kill_messenger(Messenger *m) { if (!m) { return; } uint32_t i; if (m->tcp_server) { kill_TCP_server(m->tcp_server); } kill_friend_connections(m->fr_c); kill_onion(m->onion); kill_onion_announce(m->onion_a); kill_onion_client(m->onion_c); kill_net_crypto(m->net_crypto); kill_dht(m->dht); kill_networking(m->net); for (i = 0; i < m->numfriends; ++i) { clear_receipts(m, i); } logger_kill(m->log); free(m->friendlist); friendreq_kill(m->fr); free(m->options.state_plugins); free(m); } /* Check for and handle a timed-out friend request. If the request has * timed-out then the friend status is set back to FRIEND_ADDED. * i: friendlist index of the timed-out friend * t: time */ static void check_friend_request_timed_out(Messenger *m, uint32_t i, uint64_t t, void *userdata) { Friend *f = &m->friendlist[i]; if (f->friendrequest_lastsent + f->friendrequest_timeout < t) { set_friend_status(m, i, FRIEND_ADDED, userdata); /* Double the default timeout every time if friendrequest is assumed * to have been sent unsuccessfully. */ f->friendrequest_timeout *= 2; } } static int m_handle_status(void *object, int i, uint8_t status, void *userdata) { Messenger *m = (Messenger *)object; if (status) { /* Went online. */ send_online_packet(m, i); } else { /* Went offline. */ if (m->friendlist[i].status == FRIEND_ONLINE) { set_friend_status(m, i, FRIEND_CONFIRMED, userdata); } } return 0; } static int m_handle_packet(void *object, int i, const uint8_t *temp, uint16_t len, void *userdata) { if (len == 0) { return -1; } Messenger *m = (Messenger *)object; uint8_t packet_id = temp[0]; const uint8_t *data = temp + 1; uint32_t data_length = len - 1; if (m->friendlist[i].status != FRIEND_ONLINE) { if (packet_id == PACKET_ID_ONLINE && len == 1) { set_friend_status(m, i, FRIEND_ONLINE, userdata); send_online_packet(m, i); } else { return -1; } } switch (packet_id) { case PACKET_ID_OFFLINE: { if (data_length != 0) { break; } set_friend_status(m, i, FRIEND_CONFIRMED, userdata); break; } case PACKET_ID_NICKNAME: { if (data_length > MAX_NAME_LENGTH) { break; } /* Make sure the NULL terminator is present. */ VLA(uint8_t, data_terminated, data_length + 1); memcpy(data_terminated, data, data_length); data_terminated[data_length] = 0; /* inform of namechange before we overwrite the old name */ if (m->friend_namechange) { m->friend_namechange(m, i, data_terminated, data_length, userdata); } memcpy(m->friendlist[i].name, data_terminated, data_length); m->friendlist[i].name_length = data_length; break; } case PACKET_ID_STATUSMESSAGE: { if (data_length > MAX_STATUSMESSAGE_LENGTH) { break; } /* Make sure the NULL terminator is present. */ VLA(uint8_t, data_terminated, data_length + 1); memcpy(data_terminated, data, data_length); data_terminated[data_length] = 0; if (m->friend_statusmessagechange) { m->friend_statusmessagechange(m, i, data_terminated, data_length, userdata); } set_friend_statusmessage(m, i, data_terminated, data_length); break; } case PACKET_ID_USERSTATUS: { if (data_length != 1) { break; } Userstatus status = (Userstatus)data[0]; if (status >= USERSTATUS_INVALID) { break; } if (m->friend_userstatuschange) { m->friend_userstatuschange(m, i, status, userdata); } set_friend_userstatus(m, i, status); break; } case PACKET_ID_TYPING: { if (data_length != 1) { break; } bool typing = !!data[0]; set_friend_typing(m, i, typing); if (m->friend_typingchange) { m->friend_typingchange(m, i, typing, userdata); } break; } case PACKET_ID_MESSAGE: // fall-through case PACKET_ID_ACTION: { if (data_length == 0) { break; } const uint8_t *message = data; uint16_t message_length = data_length; /* Make sure the NULL terminator is present. */ VLA(uint8_t, message_terminated, message_length + 1); memcpy(message_terminated, message, message_length); message_terminated[message_length] = 0; uint8_t type = packet_id - PACKET_ID_MESSAGE; if (m->friend_message) { (*m->friend_message)(m, i, type, message_terminated, message_length, userdata); } break; } case PACKET_ID_INVITE_CONFERENCE: { if (data_length == 0) { break; } if (m->conference_invite) { (*m->conference_invite)(m, i, data, data_length, userdata); } break; } case PACKET_ID_FILE_SENDREQUEST: { const unsigned int head_length = 1 + sizeof(uint32_t) + sizeof(uint64_t) + FILE_ID_LENGTH; if (data_length < head_length) { break; } uint8_t filenumber = data[0]; #if UINT8_MAX >= MAX_CONCURRENT_FILE_PIPES if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { break; } #endif uint64_t filesize; uint32_t file_type; uint16_t filename_length = data_length - head_length; if (filename_length > MAX_FILENAME_LENGTH) { break; } memcpy(&file_type, data + 1, sizeof(file_type)); file_type = net_ntohl(file_type); net_unpack_u64(data + 1 + sizeof(uint32_t), &filesize); struct File_Transfers *ft = &m->friendlist[i].file_receiving[filenumber]; if (ft->status != FILESTATUS_NONE) { break; } ft->status = FILESTATUS_NOT_ACCEPTED; ft->size = filesize; ft->transferred = 0; ft->paused = FILE_PAUSE_NOT; memcpy(ft->id, data + 1 + sizeof(uint32_t) + sizeof(uint64_t), FILE_ID_LENGTH); VLA(uint8_t, filename_terminated, filename_length + 1); uint8_t *filename = nullptr; if (filename_length) { /* Force NULL terminate file name. */ memcpy(filename_terminated, data + head_length, filename_length); filename_terminated[filename_length] = 0; filename = filename_terminated; } uint32_t real_filenumber = filenumber; real_filenumber += 1; real_filenumber <<= 16; if (m->file_sendrequest) { (*m->file_sendrequest)(m, i, real_filenumber, file_type, filesize, filename, filename_length, userdata); } break; } case PACKET_ID_FILE_CONTROL: { if (data_length < 3) { break; } uint8_t send_receive = data[0]; uint8_t filenumber = data[1]; uint8_t control_type = data[2]; #if UINT8_MAX >= MAX_CONCURRENT_FILE_PIPES if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { break; } #endif if (handle_filecontrol(m, i, send_receive, filenumber, control_type, data + 3, data_length - 3, userdata) == -1) { // TODO(iphydf): Do something different here? Right now, this // check is pointless. break; } break; } case PACKET_ID_FILE_DATA: { if (data_length < 1) { break; } uint8_t filenumber = data[0]; #if UINT8_MAX >= MAX_CONCURRENT_FILE_PIPES if (filenumber >= MAX_CONCURRENT_FILE_PIPES) { break; } #endif struct File_Transfers *ft = &m->friendlist[i].file_receiving[filenumber]; if (ft->status != FILESTATUS_TRANSFERRING) { break; } uint64_t position = ft->transferred; uint32_t real_filenumber = filenumber; real_filenumber += 1; real_filenumber <<= 16; uint16_t file_data_length = (data_length - 1); const uint8_t *file_data; if (file_data_length == 0) { file_data = nullptr; } else { file_data = data + 1; } /* Prevent more data than the filesize from being passed to clients. */ if ((ft->transferred + file_data_length) > ft->size) { file_data_length = ft->size - ft->transferred; } if (m->file_filedata) { (*m->file_filedata)(m, i, real_filenumber, position, file_data, file_data_length, userdata); } ft->transferred += file_data_length; if (file_data_length && (ft->transferred >= ft->size || file_data_length != MAX_FILE_DATA_SIZE)) { file_data_length = 0; file_data = nullptr; position = ft->transferred; /* Full file received. */ if (m->file_filedata) { (*m->file_filedata)(m, i, real_filenumber, position, file_data, file_data_length, userdata); } } /* Data is zero, filetransfer is over. */ if (file_data_length == 0) { ft->status = FILESTATUS_NONE; } break; } case PACKET_ID_MSI: { if (data_length == 0) { break; } if (m->msi_packet) { (*m->msi_packet)(m, i, data, data_length, m->msi_packet_userdata); } break; } default: { handle_custom_lossless_packet(object, i, temp, len, userdata); break; } } return 0; } static void do_friends(Messenger *m, void *userdata) { uint32_t i; uint64_t temp_time = mono_time_get(m->mono_time); for (i = 0; i < m->numfriends; ++i) { if (m->friendlist[i].status == FRIEND_ADDED) { int fr = send_friend_request_packet(m->fr_c, m->friendlist[i].friendcon_id, m->friendlist[i].friendrequest_nospam, m->friendlist[i].info, m->friendlist[i].info_size); if (fr >= 0) { set_friend_status(m, i, FRIEND_REQUESTED, userdata); m->friendlist[i].friendrequest_lastsent = temp_time; } } if (m->friendlist[i].status == FRIEND_REQUESTED || m->friendlist[i].status == FRIEND_CONFIRMED) { /* friend is not online. */ if (m->friendlist[i].status == FRIEND_REQUESTED) { /* If we didn't connect to friend after successfully sending him a friend request the request is deemed * unsuccessful so we set the status back to FRIEND_ADDED and try again. */ check_friend_request_timed_out(m, i, temp_time, userdata); } } if (m->friendlist[i].status == FRIEND_ONLINE) { /* friend is online. */ if (m->friendlist[i].name_sent == 0) { if (m_sendname(m, i, m->name, m->name_length)) { m->friendlist[i].name_sent = 1; } } if (m->friendlist[i].statusmessage_sent == 0) { if (send_statusmessage(m, i, m->statusmessage, m->statusmessage_length)) { m->friendlist[i].statusmessage_sent = 1; } } if (m->friendlist[i].userstatus_sent == 0) { if (send_userstatus(m, i, m->userstatus)) { m->friendlist[i].userstatus_sent = 1; } } if (m->friendlist[i].user_istyping_sent == 0) { if (send_user_istyping(m, i, m->friendlist[i].user_istyping)) { m->friendlist[i].user_istyping_sent = 1; } } check_friend_tcp_udp(m, i, userdata); do_receipts(m, i, userdata); do_reqchunk_filecb(m, i, userdata); m->friendlist[i].last_seen_time = (uint64_t) time(nullptr); } } } static void connection_status_callback(Messenger *m, void *userdata) { unsigned int conn_status = onion_connection_status(m->onion_c); if (conn_status != m->last_connection_status) { if (m->core_connection_change) { (*m->core_connection_change)(m, conn_status, userdata); } m->last_connection_status = conn_status; } } #define DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS 60UL #define IDSTRING_LEN (CRYPTO_PUBLIC_KEY_SIZE * 2 + 1) /* id_str should be of length at least IDSTRING_LEN */ static char *id_to_string(const uint8_t *pk, char *id_str, size_t length) { if (length < IDSTRING_LEN) { snprintf(id_str, length, "Bad buf length"); return id_str; } for (uint32_t i = 0; i < CRYPTO_PUBLIC_KEY_SIZE; ++i) { sprintf(&id_str[i * 2], "%02X", pk[i]); } id_str[CRYPTO_PUBLIC_KEY_SIZE * 2] = 0; return id_str; } /* Minimum messenger run interval in ms * TODO(mannol): A/V */ #define MIN_RUN_INTERVAL 50 /* Return the time in milliseconds before do_messenger() should be called again * for optimal performance. * * returns time (in ms) before the next do_messenger() needs to be run on success. */ uint32_t messenger_run_interval(const Messenger *m) { uint32_t crypto_interval = crypto_run_interval(m->net_crypto); if (crypto_interval > MIN_RUN_INTERVAL) { return MIN_RUN_INTERVAL; } return crypto_interval; } /* The main loop that needs to be run at least 20 times per second. */ void do_messenger(Messenger *m, void *userdata) { // Add the TCP relays, but only if this is the first time calling do_messenger if (!m->has_added_relays) { m->has_added_relays = true; for (uint16_t i = 0; i < m->num_loaded_relays; ++i) { add_tcp_relay(m->net_crypto, m->loaded_relays[i].ip_port, m->loaded_relays[i].public_key); } m->num_loaded_relays = 0; if (m->tcp_server) { /* Add self tcp server. */ IP_Port local_ip_port; local_ip_port.port = m->options.tcp_server_port; local_ip_port.ip.family = net_family_ipv4; local_ip_port.ip.ip.v4 = get_ip4_loopback(); add_tcp_relay(m->net_crypto, local_ip_port, tcp_server_public_key(m->tcp_server)); } } if (!m->options.udp_disabled) { networking_poll(m->net, userdata); do_dht(m->dht); } if (m->tcp_server) { do_TCP_server(m->tcp_server, m->mono_time); } do_net_crypto(m->net_crypto, userdata); do_onion_client(m->onion_c); do_friend_connections(m->fr_c, userdata); do_friends(m, userdata); connection_status_callback(m, userdata); if (mono_time_get(m->mono_time) > m->lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) { m->lastdump = mono_time_get(m->mono_time); uint32_t last_pinged; for (uint32_t client = 0; client < LCLIENT_LIST; ++client) { const Client_data *cptr = dht_get_close_client(m->dht, client); const IPPTsPng *const assocs[] = { &cptr->assoc4, &cptr->assoc6, nullptr }; for (const IPPTsPng * const *it = assocs; *it; ++it) { const IPPTsPng *const assoc = *it; if (ip_isset(&assoc->ip_port.ip)) { last_pinged = m->lastdump - assoc->last_pinged; if (last_pinged > 999) { last_pinged = 999; } char ip_str[IP_NTOA_LEN]; char id_str[IDSTRING_LEN]; LOGGER_TRACE(m->log, "C[%2u] %s:%u [%3u] %s", client, ip_ntoa(&assoc->ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(assoc->ip_port.port), last_pinged, id_to_string(cptr->public_key, id_str, sizeof(id_str))); } } } /* dht contains additional "friends" (requests) */ uint32_t num_dhtfriends = dht_get_num_friends(m->dht); VLA(int32_t, m2dht, num_dhtfriends); VLA(int32_t, dht2m, num_dhtfriends); for (uint32_t friend_idx = 0; friend_idx < num_dhtfriends; ++friend_idx) { m2dht[friend_idx] = -1; dht2m[friend_idx] = -1; if (friend_idx >= m->numfriends) { continue; } for (uint32_t dhtfriend = 0; dhtfriend < dht_get_num_friends(m->dht); ++dhtfriend) { if (id_equal(m->friendlist[friend_idx].real_pk, dht_get_friend_public_key(m->dht, dhtfriend))) { m2dht[friend_idx] = dhtfriend; break; } } } for (uint32_t friend_idx = 0; friend_idx < num_dhtfriends; ++friend_idx) { if (m2dht[friend_idx] >= 0) { dht2m[m2dht[friend_idx]] = friend_idx; } } if (m->numfriends != dht_get_num_friends(m->dht)) { LOGGER_TRACE(m->log, "Friend num in DHT %u != friend num in msger %u", dht_get_num_friends(m->dht), m->numfriends); } Friend *msgfptr; DHT_Friend *dhtfptr; for (uint32_t friend_idx = 0; friend_idx < num_dhtfriends; ++friend_idx) { if (dht2m[friend_idx] >= 0) { msgfptr = &m->friendlist[dht2m[friend_idx]]; } else { msgfptr = nullptr; } dhtfptr = dht_get_friend(m->dht, friend_idx); if (msgfptr) { char id_str[IDSTRING_LEN]; LOGGER_TRACE(m->log, "F[%2u:%2u] <%s> %s", dht2m[friend_idx], friend_idx, msgfptr->name, id_to_string(msgfptr->real_pk, id_str, sizeof(id_str))); } else { char id_str[IDSTRING_LEN]; LOGGER_TRACE(m->log, "F[--:%2u] %s", friend_idx, id_to_string(dht_friend_public_key(dhtfptr), id_str, sizeof(id_str))); } for (uint32_t client = 0; client < MAX_FRIEND_CLIENTS; ++client) { const Client_data *cptr = dht_friend_client(dhtfptr, client); const IPPTsPng *const assocs[] = {&cptr->assoc4, &cptr->assoc6}; for (size_t a = 0; a < sizeof(assocs) / sizeof(assocs[0]); ++a) { const IPPTsPng *const assoc = assocs[a]; if (ip_isset(&assoc->ip_port.ip)) { last_pinged = m->lastdump - assoc->last_pinged; if (last_pinged > 999) { last_pinged = 999; } char ip_str[IP_NTOA_LEN]; char id_str[IDSTRING_LEN]; LOGGER_TRACE(m->log, "F[%2u] => C[%2u] %s:%u [%3u] %s", friend_idx, client, ip_ntoa(&assoc->ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(assoc->ip_port.port), last_pinged, id_to_string(cptr->public_key, id_str, sizeof(id_str))); } } } } } } /* new messenger format for load/save, more robust and forward compatible */ #define SAVED_FRIEND_REQUEST_SIZE 1024 #define NUM_SAVED_PATH_NODES 8 struct Saved_Friend { uint8_t status; uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t info[SAVED_FRIEND_REQUEST_SIZE]; // the data that is sent during the friend requests we do. uint16_t info_size; // Length of the info. uint8_t name[MAX_NAME_LENGTH]; uint16_t name_length; uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; uint16_t statusmessage_length; uint8_t userstatus; uint32_t friendrequest_nospam; uint8_t last_seen_time[sizeof(uint64_t)]; }; static uint32_t friend_size(void) { uint32_t data = 0; const struct Saved_Friend *const temp = nullptr; #define VALUE_MEMBER(name) do { data += sizeof(temp->name); } while (0) #define ARRAY_MEMBER(name) do { data += sizeof(temp->name); } while (0) // Exactly the same in friend_load, friend_save, and friend_size VALUE_MEMBER(status); ARRAY_MEMBER(real_pk); ARRAY_MEMBER(info); ++data; // padding VALUE_MEMBER(info_size); ARRAY_MEMBER(name); VALUE_MEMBER(name_length); ARRAY_MEMBER(statusmessage); ++data; // padding VALUE_MEMBER(statusmessage_length); VALUE_MEMBER(userstatus); data += 3; // padding VALUE_MEMBER(friendrequest_nospam); ARRAY_MEMBER(last_seen_time); #undef VALUE_MEMBER #undef ARRAY_MEMBER return data; } static uint8_t *friend_save(const struct Saved_Friend *temp, uint8_t *data) { #define VALUE_MEMBER(name) do { \ memcpy(data, &temp->name, sizeof(temp->name)); \ data += sizeof(temp->name); \ } while (0) #define ARRAY_MEMBER(name) do { \ memcpy(data, temp->name, sizeof(temp->name)); \ data += sizeof(temp->name); \ } while (0) // Exactly the same in friend_load, friend_save, and friend_size VALUE_MEMBER(status); ARRAY_MEMBER(real_pk); ARRAY_MEMBER(info); ++data; // padding VALUE_MEMBER(info_size); ARRAY_MEMBER(name); VALUE_MEMBER(name_length); ARRAY_MEMBER(statusmessage); ++data; // padding VALUE_MEMBER(statusmessage_length); VALUE_MEMBER(userstatus); data += 3; // padding VALUE_MEMBER(friendrequest_nospam); ARRAY_MEMBER(last_seen_time); #undef VALUE_MEMBER #undef ARRAY_MEMBER return data; } static const uint8_t *friend_load(struct Saved_Friend *temp, const uint8_t *data) { #define VALUE_MEMBER(name) do { \ memcpy(&temp->name, data, sizeof(temp->name)); \ data += sizeof(temp->name); \ } while (0) #define ARRAY_MEMBER(name) do { \ memcpy(temp->name, data, sizeof(temp->name)); \ data += sizeof(temp->name); \ } while (0) // Exactly the same in friend_load, friend_save, and friend_size VALUE_MEMBER(status); ARRAY_MEMBER(real_pk); ARRAY_MEMBER(info); ++data; // padding VALUE_MEMBER(info_size); ARRAY_MEMBER(name); VALUE_MEMBER(name_length); ARRAY_MEMBER(statusmessage); ++data; // padding VALUE_MEMBER(statusmessage_length); VALUE_MEMBER(userstatus); data += 3; // padding VALUE_MEMBER(friendrequest_nospam); ARRAY_MEMBER(last_seen_time); #undef VALUE_MEMBER #undef ARRAY_MEMBER return data; } static uint32_t m_state_plugins_size(const Messenger *m) { const uint32_t size32 = sizeof(uint32_t); const uint32_t sizesubhead = size32 * 2; uint32_t size = 0; for (const Messenger_State_Plugin *plugin = m->options.state_plugins; plugin != m->options.state_plugins + m->options.state_plugins_length; ++plugin) { size += sizesubhead + plugin->size(m); } return size; } /* * Registers a state plugin with the messenger * returns true on success * returns false on failure */ bool m_register_state_plugin(Messenger *m, State_Type type, m_state_size_cb size_callback, m_state_load_cb load_callback, m_state_save_cb save_callback) { Messenger_State_Plugin *temp = (Messenger_State_Plugin *)realloc(m->options.state_plugins, sizeof(Messenger_State_Plugin) * (m->options.state_plugins_length + 1)); if (!temp) { return false; } m->options.state_plugins = temp; ++m->options.state_plugins_length; const uint8_t index = m->options.state_plugins_length - 1; m->options.state_plugins[index].type = type; m->options.state_plugins[index].size = size_callback; m->options.state_plugins[index].load = load_callback; m->options.state_plugins[index].save = save_callback; return true; } static uint32_t m_plugin_size(const Messenger *m, State_Type type) { for (uint8_t i = 0; i < m->options.state_plugins_length; ++i) { const Messenger_State_Plugin plugin = m->options.state_plugins[i]; if (plugin.type == type) { return plugin.size(m); } } LOGGER_ERROR(m->log, "Unknown type encountered: %u", type); return UINT32_MAX; } /* return size of the messenger data (for saving) */ uint32_t messenger_size(const Messenger *m) { return m_state_plugins_size(m); } /* Save the messenger in data of size messenger_size(). */ uint8_t *messenger_save(const Messenger *m, uint8_t *data) { for (uint8_t i = 0; i < m->options.state_plugins_length; ++i) { const Messenger_State_Plugin plugin = m->options.state_plugins[i]; data = plugin.save(m, data); } return data; } // nospam state plugin static uint32_t nospam_keys_size(const Messenger *m) { return sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE; } static State_Load_Status load_nospam_keys(Messenger *m, const uint8_t *data, uint32_t length) { if (length != m_plugin_size(m, STATE_TYPE_NOSPAMKEYS)) { return STATE_LOAD_STATUS_ERROR; } uint32_t nospam; lendian_bytes_to_host32(&nospam, data); set_nospam(m->fr, nospam); load_secret_key(m->net_crypto, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE); if (public_key_cmp(data + sizeof(uint32_t), nc_get_self_public_key(m->net_crypto)) != 0) { return STATE_LOAD_STATUS_ERROR; } return STATE_LOAD_STATUS_CONTINUE; } static uint8_t *save_nospam_keys(const Messenger *m, uint8_t *data) { const uint32_t len = m_plugin_size(m, STATE_TYPE_NOSPAMKEYS); assert(sizeof(get_nospam(m->fr)) == sizeof(uint32_t)); data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_NOSPAMKEYS); uint32_t nospam = get_nospam(m->fr); host_to_lendian_bytes32(data, nospam); save_keys(m->net_crypto, data + sizeof(uint32_t)); data += len; return data; } // DHT state plugin static uint32_t m_dht_size(const Messenger *m) { return dht_size(m->dht); } static uint8_t *save_dht(const Messenger *m, uint8_t *data) { const uint32_t len = m_plugin_size(m, STATE_TYPE_DHT); data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_DHT); dht_save(m->dht, data); data += len; return data; } static State_Load_Status m_dht_load(Messenger *m, const uint8_t *data, uint32_t length) { dht_load(m->dht, data, length); // TODO(endoffile78): Should we throw an error if dht_load fails? return STATE_LOAD_STATUS_CONTINUE; } // friendlist state plugin static uint32_t saved_friendslist_size(const Messenger *m) { return count_friendlist(m) * friend_size(); } static uint8_t *friends_list_save(const Messenger *m, uint8_t *data) { const uint32_t len = m_plugin_size(m, STATE_TYPE_FRIENDS); data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_FRIENDS); uint32_t num = 0; uint8_t *cur_data = data; for (uint32_t i = 0; i < m->numfriends; ++i) { if (m->friendlist[i].status > 0) { struct Saved_Friend temp = { 0 }; temp.status = m->friendlist[i].status; memcpy(temp.real_pk, m->friendlist[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE); if (temp.status < 3) { // TODO(iphydf): Use uint16_t and min_u16 here. const size_t friendrequest_length = min_u32(m->friendlist[i].info_size, min_u32(SAVED_FRIEND_REQUEST_SIZE, MAX_FRIEND_REQUEST_DATA_SIZE)); memcpy(temp.info, m->friendlist[i].info, friendrequest_length); temp.info_size = net_htons(m->friendlist[i].info_size); temp.friendrequest_nospam = m->friendlist[i].friendrequest_nospam; } else { temp.status = 3; memcpy(temp.name, m->friendlist[i].name, m->friendlist[i].name_length); temp.name_length = net_htons(m->friendlist[i].name_length); memcpy(temp.statusmessage, m->friendlist[i].statusmessage, m->friendlist[i].statusmessage_length); temp.statusmessage_length = net_htons(m->friendlist[i].statusmessage_length); temp.userstatus = m->friendlist[i].userstatus; net_pack_u64(temp.last_seen_time, m->friendlist[i].last_seen_time); } uint8_t *next_data = friend_save(&temp, cur_data); assert(next_data - cur_data == friend_size()); #ifdef __LP64__ assert(memcmp(cur_data, &temp, friend_size()) == 0); #endif cur_data = next_data; ++num; } } assert(cur_data - data == num * friend_size()); data += len; return data; } static State_Load_Status friends_list_load(Messenger *m, const uint8_t *data, uint32_t length) { if (length % friend_size() != 0) { return STATE_LOAD_STATUS_ERROR; // TODO(endoffile78): error or continue? } uint32_t num = length / friend_size(); uint32_t i; const uint8_t *cur_data = data; for (i = 0; i < num; ++i) { struct Saved_Friend temp = { 0 }; const uint8_t *next_data = friend_load(&temp, cur_data); assert(next_data - cur_data == friend_size()); #ifdef __LP64__ assert(memcmp(&temp, cur_data, friend_size()) == 0); #endif cur_data = next_data; if (temp.status >= 3) { int fnum = m_addfriend_norequest(m, temp.real_pk); if (fnum < 0) { continue; } setfriendname(m, fnum, temp.name, net_ntohs(temp.name_length)); set_friend_statusmessage(m, fnum, temp.statusmessage, net_ntohs(temp.statusmessage_length)); set_friend_userstatus(m, fnum, temp.userstatus); net_unpack_u64(temp.last_seen_time, &m->friendlist[fnum].last_seen_time); } else if (temp.status != 0) { /* TODO(irungentoo): This is not a good way to do this. */ uint8_t address[FRIEND_ADDRESS_SIZE]; id_copy(address, temp.real_pk); memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &temp.friendrequest_nospam, sizeof(uint32_t)); uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum)); memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), &checksum, sizeof(checksum)); m_addfriend(m, address, temp.info, net_ntohs(temp.info_size)); } } return STATE_LOAD_STATUS_CONTINUE; } // name state plugin static uint32_t name_size(const Messenger *m) { return m->name_length; } static uint8_t *save_name(const Messenger *m, uint8_t *data) { const uint32_t len = m_plugin_size(m, STATE_TYPE_NAME); data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_NAME); memcpy(data, m->name, len); data += len; return data; } static State_Load_Status load_name(Messenger *m, const uint8_t *data, uint32_t length) { if (length > 0 && length <= MAX_NAME_LENGTH) { setname(m, data, length); } return STATE_LOAD_STATUS_CONTINUE; } // status message state plugin static uint32_t status_message_size(const Messenger *m) { return m->statusmessage_length; } static uint8_t *save_status_message(const Messenger *m, uint8_t *data) { const uint32_t len = m_plugin_size(m, STATE_TYPE_STATUSMESSAGE); data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_STATUSMESSAGE); memcpy(data, m->statusmessage, len); data += len; return data; } static State_Load_Status load_status_message(Messenger *m, const uint8_t *data, uint32_t length) { if (length > 0 && length <= MAX_STATUSMESSAGE_LENGTH) { m_set_statusmessage(m, data, length); } return STATE_LOAD_STATUS_CONTINUE; } // status state plugin static uint32_t status_size(const Messenger *m) { return 1; } static uint8_t *save_status(const Messenger *m, uint8_t *data) { const uint32_t len = m_plugin_size(m, STATE_TYPE_STATUS); data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_STATUS); *data = m->userstatus; data += len; return data; } static State_Load_Status load_status(Messenger *m, const uint8_t *data, uint32_t length) { if (length == 1) { m_set_userstatus(m, *data); } return STATE_LOAD_STATUS_CONTINUE; } // TCP Relay state plugin static uint32_t tcp_relay_size(const Messenger *m) { return NUM_SAVED_TCP_RELAYS * packed_node_size(net_family_tcp_ipv6); } static uint8_t *save_tcp_relays(const Messenger *m, uint8_t *data) { Node_format relays[NUM_SAVED_TCP_RELAYS]; uint8_t *temp_data = data; data = state_write_section_header(temp_data, STATE_COOKIE_TYPE, 0, STATE_TYPE_TCP_RELAY); if (m->num_loaded_relays > 0) { memcpy(relays, m->loaded_relays, sizeof(Node_format) * m->num_loaded_relays); } uint32_t num = m->num_loaded_relays; num += copy_connected_tcp_relays(m->net_crypto, relays + num, NUM_SAVED_TCP_RELAYS - num); int l = pack_nodes(data, NUM_SAVED_TCP_RELAYS * packed_node_size(net_family_tcp_ipv6), relays, num); if (l > 0) { const uint32_t len = l; data = state_write_section_header(temp_data, STATE_COOKIE_TYPE, len, STATE_TYPE_TCP_RELAY); data += len; } return data; } static State_Load_Status load_tcp_relays(Messenger *m, const uint8_t *data, uint32_t length) { if (length != 0) { const int num = unpack_nodes(m->loaded_relays, NUM_SAVED_TCP_RELAYS, nullptr, data, length, 1); if (num == -1) { m->num_loaded_relays = 0; return STATE_LOAD_STATUS_CONTINUE; } m->num_loaded_relays = num; m->has_added_relays = false; } return STATE_LOAD_STATUS_CONTINUE; } // path node state plugin static uint32_t path_node_size(const Messenger *m) { return NUM_SAVED_PATH_NODES * packed_node_size(net_family_tcp_ipv6); } static uint8_t *save_path_nodes(const Messenger *m, uint8_t *data) { Node_format nodes[NUM_SAVED_PATH_NODES]; uint8_t *temp_data = data; data = state_write_section_header(data, STATE_COOKIE_TYPE, 0, STATE_TYPE_PATH_NODE); memset(nodes, 0, sizeof(nodes)); const unsigned int num = onion_backup_nodes(m->onion_c, nodes, NUM_SAVED_PATH_NODES); const int l = pack_nodes(data, NUM_SAVED_PATH_NODES * packed_node_size(net_family_tcp_ipv6), nodes, num); if (l > 0) { const uint32_t len = l; data = state_write_section_header(temp_data, STATE_COOKIE_TYPE, len, STATE_TYPE_PATH_NODE); data += len; } return data; } static State_Load_Status load_path_nodes(Messenger *m, const uint8_t *data, uint32_t length) { Node_format nodes[NUM_SAVED_PATH_NODES]; if (length != 0) { const int num = unpack_nodes(nodes, NUM_SAVED_PATH_NODES, nullptr, data, length, 0); if (num == -1) { return STATE_LOAD_STATUS_CONTINUE; } for (int i = 0; i < num; ++i) { onion_add_bs_path_node(m->onion_c, nodes[i].ip_port, nodes[i].public_key); } } return STATE_LOAD_STATUS_CONTINUE; } static void m_register_default_plugins(Messenger *m) { m_register_state_plugin(m, STATE_TYPE_NOSPAMKEYS, nospam_keys_size, load_nospam_keys, save_nospam_keys); m_register_state_plugin(m, STATE_TYPE_DHT, m_dht_size, m_dht_load, save_dht); m_register_state_plugin(m, STATE_TYPE_FRIENDS, saved_friendslist_size, friends_list_load, friends_list_save); m_register_state_plugin(m, STATE_TYPE_NAME, name_size, load_name, save_name); m_register_state_plugin(m, STATE_TYPE_STATUSMESSAGE, status_message_size, load_status_message, save_status_message); m_register_state_plugin(m, STATE_TYPE_STATUS, status_size, load_status, save_status); m_register_state_plugin(m, STATE_TYPE_TCP_RELAY, tcp_relay_size, load_tcp_relays, save_tcp_relays); m_register_state_plugin(m, STATE_TYPE_PATH_NODE, path_node_size, load_path_nodes, save_path_nodes); } bool messenger_load_state_section(Messenger *m, const uint8_t *data, uint32_t length, uint16_t type, State_Load_Status *status) { for (uint8_t i = 0; i < m->options.state_plugins_length; ++i) { const Messenger_State_Plugin *const plugin = &m->options.state_plugins[i]; if (plugin->type == type) { *status = plugin->load(m, data, length); return true; } } return false; } /* Return the number of friends in the instance m. * You should use this to determine how much memory to allocate * for copy_friendlist. */ uint32_t count_friendlist(const Messenger *m) { uint32_t ret = 0; uint32_t i; for (i = 0; i < m->numfriends; ++i) { if (m->friendlist[i].status > 0) { ++ret; } } return ret; } /* Copy a list of valid friend IDs into the array out_list. * If out_list is NULL, returns 0. * Otherwise, returns the number of elements copied. * If the array was too small, the contents * of out_list will be truncated to list_size. */ uint32_t copy_friendlist(Messenger const *m, uint32_t *out_list, uint32_t list_size) { if (!out_list) { return 0; } if (m->numfriends == 0) { return 0; } uint32_t i; uint32_t ret = 0; for (i = 0; i < m->numfriends; ++i) { if (ret >= list_size) { break; /* Abandon ship */ } if (m->friendlist[i].status > 0) { out_list[ret] = i; ++ret; } } return ret; } c-toxcore-0.2.13/toxcore/Messenger.h000066400000000000000000000647261415350724400173100ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * An implementation of a simple text chat only messenger on the tox network * core. */ #ifndef C_TOXCORE_TOXCORE_MESSENGER_H #define C_TOXCORE_TOXCORE_MESSENGER_H #include "friend_connection.h" #include "friend_requests.h" #include "logger.h" #include "net_crypto.h" #include "state.h" #define MAX_NAME_LENGTH 128 /* TODO(irungentoo): this must depend on other variable. */ #define MAX_STATUSMESSAGE_LENGTH 1007 /* Used for TCP relays in Messenger struct (may need to be `% 2 == 0`)*/ #define NUM_SAVED_TCP_RELAYS 8 /* This cannot be bigger than 256 */ #define MAX_CONCURRENT_FILE_PIPES 256 #if !defined(__SPLINT__) && MAX_CONCURRENT_FILE_PIPES > UINT8_MAX + 1 #error "uint8_t cannot represent all file transfer numbers" #endif #define FRIEND_ADDRESS_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t) + sizeof(uint16_t)) typedef enum Message_Type { MESSAGE_NORMAL, MESSAGE_ACTION, } Message_Type; typedef struct Messenger Messenger; // Returns the size of the data typedef uint32_t m_state_size_cb(const Messenger *m); // Returns the new pointer to data typedef uint8_t *m_state_save_cb(const Messenger *m, uint8_t *data); // Returns if there were any erros during loading typedef State_Load_Status m_state_load_cb(Messenger *m, const uint8_t *data, uint32_t length); typedef struct Messenger_State_Plugin { State_Type type; m_state_size_cb *size; m_state_save_cb *save; m_state_load_cb *load; } Messenger_State_Plugin; typedef struct Messenger_Options { bool ipv6enabled; bool udp_disabled; TCP_Proxy_Info proxy_info; uint16_t port_range[2]; uint16_t tcp_server_port; bool hole_punching_enabled; bool local_discovery_enabled; logger_cb *log_callback; void *log_context; void *log_user_data; Messenger_State_Plugin *state_plugins; uint8_t state_plugins_length; } Messenger_Options; struct Receipts { uint32_t packet_num; uint32_t msg_id; struct Receipts *next; }; /* Status definitions. */ typedef enum Friend_Status { NOFRIEND, FRIEND_ADDED, FRIEND_REQUESTED, FRIEND_CONFIRMED, FRIEND_ONLINE, } Friend_Status; /* Errors for m_addfriend * FAERR - Friend Add Error */ typedef enum Friend_Add_Error { FAERR_TOOLONG = -1, FAERR_NOMESSAGE = -2, FAERR_OWNKEY = -3, FAERR_ALREADYSENT = -4, FAERR_BADCHECKSUM = -6, FAERR_SETNEWNOSPAM = -7, FAERR_NOMEM = -8, } Friend_Add_Error; /* Default start timeout in seconds between friend requests. */ #define FRIENDREQUEST_TIMEOUT 5 typedef enum Connection_Status { CONNECTION_NONE, CONNECTION_TCP, CONNECTION_UDP, } Connection_Status; /* USERSTATUS - * Represents userstatuses someone can have. */ typedef enum Userstatus { USERSTATUS_NONE, USERSTATUS_AWAY, USERSTATUS_BUSY, USERSTATUS_INVALID, } Userstatus; #define FILE_ID_LENGTH 32 struct File_Transfers { uint64_t size; uint64_t transferred; uint8_t status; /* 0 == no transfer, 1 = not accepted, 3 = transferring, 4 = broken, 5 = finished */ uint8_t paused; /* 0: not paused, 1 = paused by us, 2 = paused by other, 3 = paused by both. */ uint32_t last_packet_number; /* number of the last packet sent. */ uint64_t requested; /* total data requested by the request chunk callback */ uint8_t id[FILE_ID_LENGTH]; }; typedef enum Filestatus { FILESTATUS_NONE, FILESTATUS_NOT_ACCEPTED, FILESTATUS_TRANSFERRING, // FILESTATUS_BROKEN, FILESTATUS_FINISHED, } Filestatus; typedef enum File_Pause { FILE_PAUSE_NOT, FILE_PAUSE_US, FILE_PAUSE_OTHER, FILE_PAUSE_BOTH, } File_Pause; typedef enum Filecontrol { FILECONTROL_ACCEPT, FILECONTROL_PAUSE, FILECONTROL_KILL, FILECONTROL_SEEK, } Filecontrol; typedef enum Filekind { FILEKIND_DATA, FILEKIND_AVATAR, } Filekind; typedef void m_self_connection_status_cb(Messenger *m, unsigned int connection_status, void *user_data); typedef void m_friend_status_cb(Messenger *m, uint32_t friend_number, unsigned int status, void *user_data); typedef void m_friend_connection_status_cb(Messenger *m, uint32_t friend_number, unsigned int connection_status, void *user_data); typedef void m_friend_message_cb(Messenger *m, uint32_t friend_number, unsigned int message_type, const uint8_t *message, size_t length, void *user_data); typedef void m_file_recv_control_cb(Messenger *m, uint32_t friend_number, uint32_t file_number, unsigned int control, void *user_data); typedef void m_friend_request_cb(Messenger *m, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data); typedef void m_friend_name_cb(Messenger *m, uint32_t friend_number, const uint8_t *name, size_t length, void *user_data); typedef void m_friend_status_message_cb(Messenger *m, uint32_t friend_number, const uint8_t *message, size_t length, void *user_data); typedef void m_friend_typing_cb(Messenger *m, uint32_t friend_number, bool is_typing, void *user_data); typedef void m_friend_read_receipt_cb(Messenger *m, uint32_t friend_number, uint32_t message_id, void *user_data); typedef void m_file_recv_cb(Messenger *m, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, const uint8_t *filename, size_t filename_length, void *user_data); typedef void m_file_chunk_request_cb(Messenger *m, uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length, void *user_data); typedef void m_file_recv_chunk_cb(Messenger *m, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, void *user_data); typedef void m_friend_lossy_packet_cb(Messenger *m, uint32_t friend_number, uint8_t packet_id, const uint8_t *data, size_t length, void *user_data); typedef void m_friend_lossless_packet_cb(Messenger *m, uint32_t friend_number, uint8_t packet_id, const uint8_t *data, size_t length, void *user_data); typedef void m_friend_connectionstatuschange_internal_cb(Messenger *m, uint32_t friend_number, uint8_t connection_status, void *user_data); typedef void m_conference_invite_cb(Messenger *m, uint32_t friend_number, const uint8_t *cookie, uint16_t length, void *user_data); typedef void m_msi_packet_cb(Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *user_data); typedef int m_lossy_rtp_packet_cb(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t len, void *object); typedef struct RTP_Packet_Handler { m_lossy_rtp_packet_cb *function; void *object; } RTP_Packet_Handler; typedef struct Friend { uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; int friendcon_id; uint64_t friendrequest_lastsent; // Time at which the last friend request was sent. uint32_t friendrequest_timeout; // The timeout between successful friendrequest sending attempts. uint8_t status; // 0 if no friend, 1 if added, 2 if friend request sent, 3 if confirmed friend, 4 if online. uint8_t info[MAX_FRIEND_REQUEST_DATA_SIZE]; // the data that is sent during the friend requests we do. uint8_t name[MAX_NAME_LENGTH]; uint16_t name_length; uint8_t name_sent; // 0 if we didn't send our name to this friend 1 if we have. uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; uint16_t statusmessage_length; uint8_t statusmessage_sent; Userstatus userstatus; uint8_t userstatus_sent; uint8_t user_istyping; uint8_t user_istyping_sent; uint8_t is_typing; uint16_t info_size; // Length of the info. uint32_t message_id; // a semi-unique id used in read receipts. uint32_t friendrequest_nospam; // The nospam number used in the friend request. uint64_t last_seen_time; Connection_Status last_connection_udp_tcp; struct File_Transfers file_sending[MAX_CONCURRENT_FILE_PIPES]; uint32_t num_sending_files; struct File_Transfers file_receiving[MAX_CONCURRENT_FILE_PIPES]; RTP_Packet_Handler lossy_rtp_packethandlers[PACKET_ID_RANGE_LOSSY_AV_SIZE]; struct Receipts *receipts_start; struct Receipts *receipts_end; } Friend; struct Messenger { Logger *log; Mono_Time *mono_time; Networking_Core *net; Net_Crypto *net_crypto; DHT *dht; Onion *onion; Onion_Announce *onion_a; Onion_Client *onion_c; Friend_Connections *fr_c; TCP_Server *tcp_server; Friend_Requests *fr; uint8_t name[MAX_NAME_LENGTH]; uint16_t name_length; uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH]; uint16_t statusmessage_length; Userstatus userstatus; Friend *friendlist; uint32_t numfriends; time_t lastdump; bool has_added_relays; // If the first connection has occurred in do_messenger uint16_t num_loaded_relays; Node_format loaded_relays[NUM_SAVED_TCP_RELAYS]; // Relays loaded from config m_friend_message_cb *friend_message; m_friend_name_cb *friend_namechange; m_friend_status_message_cb *friend_statusmessagechange; m_friend_status_cb *friend_userstatuschange; m_friend_typing_cb *friend_typingchange; m_friend_read_receipt_cb *read_receipt; m_friend_connection_status_cb *friend_connectionstatuschange; m_friend_connectionstatuschange_internal_cb *friend_connectionstatuschange_internal; void *friend_connectionstatuschange_internal_userdata; struct Group_Chats *conferences_object; /* Set by new_groupchats()*/ m_conference_invite_cb *conference_invite; m_file_recv_cb *file_sendrequest; m_file_recv_control_cb *file_filecontrol; m_file_recv_chunk_cb *file_filedata; m_file_chunk_request_cb *file_reqchunk; m_msi_packet_cb *msi_packet; void *msi_packet_userdata; m_friend_lossy_packet_cb *lossy_packethandler; m_friend_lossless_packet_cb *lossless_packethandler; m_self_connection_status_cb *core_connection_change; unsigned int last_connection_status; Messenger_Options options; }; /* Format: `[real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]` * * return FRIEND_ADDRESS_SIZE byte address to give to others. */ void getaddress(const Messenger *m, uint8_t *address); /* Add a friend. * Set the data that will be sent along with friend request. * address is the address of the friend (returned by getaddress of the friend * you wish to add) it must be FRIEND_ADDRESS_SIZE bytes. * TODO(irungentoo): add checksum. * data is the data and length is the length. * * return the friend number if success. * return -1 if message length is too long. * return -2 if no message (message length must be >= 1 byte). * return -3 if user's own key. * return -4 if friend request already sent or already a friend. * return -6 if bad checksum in address. * return -7 if the friend was already there but the nospam was different. * (the nospam for that friend was set to the new one). * return -8 if increasing the friend list size fails. */ int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, uint16_t length); /* Add a friend without sending a friendrequest. * return the friend number if success. * return -3 if user's own key. * return -4 if friend request already sent or already a friend. * return -6 if bad checksum in address. * return -8 if increasing the friend list size fails. */ int32_t m_addfriend_norequest(Messenger *m, const uint8_t *real_pk); /* return the friend number associated to that client id. * return -1 if no such friend. */ int32_t getfriend_id(const Messenger *m, const uint8_t *real_pk); /* Copies the public key associated to that friend id into real_pk buffer. * Make sure that real_pk is of size CRYPTO_PUBLIC_KEY_SIZE. * * return 0 if success * return -1 if failure */ int get_real_pk(const Messenger *m, int32_t friendnumber, uint8_t *real_pk); /* return friend connection id on success. * return -1 if failure. */ int getfriendcon_id(const Messenger *m, int32_t friendnumber); /* Remove a friend. * * return 0 if success * return -1 if failure */ int m_delfriend(Messenger *m, int32_t friendnumber); /* Checks friend's connection status. * * return CONNECTION_UDP (2) if friend is directly connected to us (Online UDP). * return CONNECTION_TCP (1) if friend is connected to us (Online TCP). * return CONNECTION_NONE (0) if friend is not connected to us (Offline). * return -1 on failure. */ int m_get_friend_connectionstatus(const Messenger *m, int32_t friendnumber); /* Checks if there exists a friend with given friendnumber. * * return 1 if friend exists. * return 0 if friend doesn't exist. */ int m_friend_exists(const Messenger *m, int32_t friendnumber); /* Send a message of type to an online friend. * * return -1 if friend not valid. * return -2 if too large. * return -3 if friend not online. * return -4 if send failed (because queue is full). * return -5 if bad type. * return 0 if success. * * the value in message_id will be passed to your read_receipt callback when the other receives the message. */ int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length, uint32_t *message_id); /* Set the name and name_length of a friend. * name must be a string of maximum MAX_NAME_LENGTH length. * length must be at least 1 byte. * length is the length of name with the NULL terminator. * * return 0 if success. * return -1 if failure. */ int setfriendname(Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length); /* Set our nickname. * name must be a string of maximum MAX_NAME_LENGTH length. * length must be at least 1 byte. * length is the length of name with the NULL terminator. * * return 0 if success. * return -1 if failure. */ int setname(Messenger *m, const uint8_t *name, uint16_t length); /* * Get your nickname. * m - The messenger context to use. * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes. * * return length of the name. * return 0 on error. */ uint16_t getself_name(const Messenger *m, uint8_t *name); /* Get name of friendnumber and put it in name. * name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. * * return length of name if success. * return -1 if failure. */ int getname(const Messenger *m, int32_t friendnumber, uint8_t *name); /* return the length of name, including null on success. * return -1 on failure. */ int m_get_name_size(const Messenger *m, int32_t friendnumber); int m_get_self_name_size(const Messenger *m); /* Set our user status. * You are responsible for freeing status after. * * returns 0 on success. * returns -1 on failure. */ int m_set_statusmessage(Messenger *m, const uint8_t *status, uint16_t length); int m_set_userstatus(Messenger *m, uint8_t status); /* return the length of friendnumber's status message, including null on success. * return -1 on failure. */ int m_get_statusmessage_size(const Messenger *m, int32_t friendnumber); int m_get_self_statusmessage_size(const Messenger *m); /* Copy friendnumber's status message into buf, truncating if size is over maxlen. * Get the size you need to allocate from m_get_statusmessage_size. * The self variant will copy our own status message. * * returns the length of the copied data on success * retruns -1 on failure. */ int m_copy_statusmessage(const Messenger *m, int32_t friendnumber, uint8_t *buf, uint32_t maxlen); int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf); /* return one of Userstatus values. * Values unknown to your application should be represented as USERSTATUS_NONE. * As above, the self variant will return our own Userstatus. * If friendnumber is invalid, this shall return USERSTATUS_INVALID. */ uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber); uint8_t m_get_self_userstatus(const Messenger *m); /* returns timestamp of last time friendnumber was seen online or 0 if never seen. * if friendnumber is invalid this function will return UINT64_MAX. */ uint64_t m_get_last_online(const Messenger *m, int32_t friendnumber); /* Set our typing status for a friend. * You are responsible for turning it on or off. * * returns 0 on success. * returns -1 on failure. */ int m_set_usertyping(Messenger *m, int32_t friendnumber, uint8_t is_typing); /* Get the typing status of a friend. * * returns 0 if friend is not typing. * returns 1 if friend is typing. */ int m_get_istyping(const Messenger *m, int32_t friendnumber); /* Set the function that will be executed when a friend request is received. * Function format is `function(uint8_t * public_key, uint8_t * data, size_t length)` */ void m_callback_friendrequest(Messenger *m, m_friend_request_cb *function); /* Set the function that will be executed when a message from a friend is received. * Function format is: `function(uint32_t friendnumber, unsigned int type, uint8_t * message, uint32_t length)` */ void m_callback_friendmessage(Messenger *m, m_friend_message_cb *function); /* Set the callback for name changes. * `Function(uint32_t friendnumber, uint8_t *newname, size_t length)` * You are not responsible for freeing newname. */ void m_callback_namechange(Messenger *m, m_friend_name_cb *function); /* Set the callback for status message changes. * `Function(uint32_t friendnumber, uint8_t *newstatus, size_t length)` * * You are not responsible for freeing newstatus */ void m_callback_statusmessage(Messenger *m, m_friend_status_message_cb *function); /* Set the callback for status type changes. * `Function(uint32_t friendnumber, Userstatus kind)` */ void m_callback_userstatus(Messenger *m, m_friend_status_cb *function); /* Set the callback for typing changes. * `Function(uint32_t friendnumber, uint8_t is_typing)` */ void m_callback_typingchange(Messenger *m, m_friend_typing_cb *function); /* Set the callback for read receipts. * `Function(uint32_t friendnumber, uint32_t receipt)` * * If you are keeping a record of returns from m_sendmessage, * receipt might be one of those values, meaning the message * has been received on the other side. * Since core doesn't track ids for you, receipt may not correspond to any message. * In that case, you should discard it. */ void m_callback_read_receipt(Messenger *m, m_friend_read_receipt_cb *function); /* Set the callback for connection status changes. * `function(uint32_t friendnumber, uint8_t status)` * * Status: * 0 -- friend went offline after being previously online. * 1 -- friend went online. * * Note that this callback is not called when adding friends, thus the * "after being previously online" part. * It's assumed that when adding friends, their connection status is offline. */ void m_callback_connectionstatus(Messenger *m, m_friend_connection_status_cb *function); /* Same as previous but for internal A/V core usage only */ void m_callback_connectionstatus_internal_av(Messenger *m, m_friend_connectionstatuschange_internal_cb *function, void *userdata); /* Set the callback for typing changes. * Function(unsigned int connection_status (0 = not connected, 1 = TCP only, 2 = UDP + TCP)) */ void m_callback_core_connection(Messenger *m, m_self_connection_status_cb *function); /** CONFERENCES */ /* Set the callback for conference invites. */ void m_callback_conference_invite(Messenger *m, m_conference_invite_cb *function); /* Send a conference invite packet. * * return 1 on success * return 0 on failure */ int send_conference_invite_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length); /** FILE SENDING */ /* Set the callback for file send requests. */ void callback_file_sendrequest(Messenger *m, m_file_recv_cb *function); /* Set the callback for file control requests. */ void callback_file_control(Messenger *m, m_file_recv_control_cb *function); /* Set the callback for file data. */ void callback_file_data(Messenger *m, m_file_recv_chunk_cb *function); /* Set the callback for file request chunk. */ void callback_file_reqchunk(Messenger *m, m_file_chunk_request_cb *function); /* Copy the file transfer file id to file_id * * return 0 on success. * return -1 if friend not valid. * return -2 if filenumber not valid */ int file_get_id(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint8_t *file_id); /* Send a file send request. * Maximum filename length is 255 bytes. * return file number on success * return -1 if friend not found. * return -2 if filename length invalid. * return -3 if no more file sending slots left. * return -4 if could not send packet (friend offline). * */ long int new_filesender(const Messenger *m, int32_t friendnumber, uint32_t file_type, uint64_t filesize, const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length); /* Send a file control request. * * return 0 on success * return -1 if friend not valid. * return -2 if friend not online. * return -3 if file number invalid. * return -4 if file control is bad. * return -5 if file already paused. * return -6 if resume file failed because it was only paused by the other. * return -7 if resume file failed because it wasn't paused. * return -8 if packet failed to send. */ int file_control(const Messenger *m, int32_t friendnumber, uint32_t filenumber, unsigned int control); /* Send a seek file control request. * * return 0 on success * return -1 if friend not valid. * return -2 if friend not online. * return -3 if file number invalid. * return -4 if not receiving file. * return -5 if file status wrong. * return -6 if position bad. * return -8 if packet failed to send. */ int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position); /* Send file data. * * return 0 on success * return -1 if friend not valid. * return -2 if friend not online. * return -3 if filenumber invalid. * return -4 if file transfer not transferring. * return -5 if bad data size. * return -6 if packet queue full. * return -7 if wrong position. */ int file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data, uint16_t length); /** A/V related */ /* Set the callback for msi packets. */ void m_callback_msi_packet(Messenger *m, m_msi_packet_cb *function, void *userdata); /* Send an msi packet. * * return 1 on success * return 0 on failure */ int m_msi_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length); /* Set handlers for lossy rtp packets. * * return -1 on failure. * return 0 on success. */ int m_callback_rtp_packet(Messenger *m, int32_t friendnumber, uint8_t byte, m_lossy_rtp_packet_cb *function, void *object); /** CUSTOM PACKETS */ /* Set handlers for custom lossy packets. * */ void custom_lossy_packet_registerhandler(Messenger *m, m_friend_lossy_packet_cb *lossy_packethandler); /* High level function to send custom lossy packets. * * return -1 if friend invalid. * return -2 if length wrong. * return -3 if first byte invalid. * return -4 if friend offline. * return -5 if packet failed to send because of other error. * return 0 on success. */ int m_send_custom_lossy_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length); /* Set handlers for custom lossless packets. * */ void custom_lossless_packet_registerhandler(Messenger *m, m_friend_lossless_packet_cb *lossless_packethandler); /* High level function to send custom lossless packets. * * return -1 if friend invalid. * return -2 if length wrong. * return -3 if first byte invalid. * return -4 if friend offline. * return -5 if packet failed to send because of other error. * return 0 on success. */ int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length); /** Messenger constructor/destructor/operations. */ typedef enum Messenger_Error { MESSENGER_ERROR_NONE, MESSENGER_ERROR_PORT, MESSENGER_ERROR_TCP_SERVER, MESSENGER_ERROR_OTHER, } Messenger_Error; /* Run this at startup. * return allocated instance of Messenger on success. * return 0 if there are problems. * * if error is not NULL it will be set to one of the values in the enum above. */ Messenger *new_messenger(Mono_Time *mono_time, Messenger_Options *options, unsigned int *error); /* Run this before closing shop * Free all datastructures. */ void kill_messenger(Messenger *m); /* The main loop that needs to be run at least 20 times per second. */ void do_messenger(Messenger *m, void *userdata); /* Return the time in milliseconds before do_messenger() should be called again * for optimal performance. * * returns time (in ms) before the next do_messenger() needs to be run on success. */ uint32_t messenger_run_interval(const Messenger *m); /* SAVING AND LOADING FUNCTIONS: */ /* Registers a state plugin for saving, loadding, and getting the size of a section of the save * * returns true on success * returns false on error */ bool m_register_state_plugin(Messenger *m, State_Type type, m_state_size_cb size_callback, m_state_load_cb load_callback, m_state_save_cb save_callback); /* return size of the messenger data (for saving). */ uint32_t messenger_size(const Messenger *m); /* Save the messenger in data (must be allocated memory of size at least Messenger_size()) */ uint8_t *messenger_save(const Messenger *m, uint8_t *data); /* Load a state section. * * @param data Data to load. * @param length Length of data. * @param type Type of section (`STATE_TYPE_*`). * @param status Result of loading section is stored here if the section is handled. * @return true iff section handled. */ bool messenger_load_state_section(Messenger *m, const uint8_t *data, uint32_t length, uint16_t type, State_Load_Status *status); /* Return the number of friends in the instance m. * You should use this to determine how much memory to allocate * for copy_friendlist. */ uint32_t count_friendlist(const Messenger *m); /* Copy a list of valid friend IDs into the array out_list. * If out_list is NULL, returns 0. * Otherwise, returns the number of elements copied. * If the array was too small, the contents * of out_list will be truncated to list_size. */ uint32_t copy_friendlist(const Messenger *m, uint32_t *out_list, uint32_t list_size); #endif c-toxcore-0.2.13/toxcore/TCP_client.c000066400000000000000000000727231415350724400173330ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Implementation of the TCP relay client part of Tox. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "TCP_client.h" #include #include #include #include "mono_time.h" #include "util.h" typedef struct TCP_Client_Conn { // TODO(iphydf): Add an enum for this. uint8_t status; /* 0 if not used, 1 if other is offline, 2 if other is online. */ uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint32_t number; } TCP_Client_Conn; struct TCP_Client_Connection { TCP_Client_Status status; Socket sock; uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* our public key */ uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* public key of the server */ IP_Port ip_port; /* The ip and port of the server */ TCP_Proxy_Info proxy_info; uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint16_t next_packet_length; uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; uint8_t last_packet[2 + MAX_PACKET_SIZE]; uint16_t last_packet_length; uint16_t last_packet_sent; TCP_Priority_List *priority_queue_start; TCP_Priority_List *priority_queue_end; uint64_t kill_at; uint64_t last_pinged; uint64_t ping_id; uint64_t ping_response_id; uint64_t ping_request_id; TCP_Client_Conn connections[NUM_CLIENT_CONNECTIONS]; tcp_routing_response_cb *response_callback; void *response_callback_object; tcp_routing_status_cb *status_callback; void *status_callback_object; tcp_routing_data_cb *data_callback; void *data_callback_object; tcp_oob_data_cb *oob_data_callback; void *oob_data_callback_object; tcp_onion_response_cb *onion_callback; void *onion_callback_object; /* Can be used by user. */ void *custom_object; uint32_t custom_uint; }; const uint8_t *tcp_con_public_key(const TCP_Client_Connection *con) { return con->public_key; } IP_Port tcp_con_ip_port(const TCP_Client_Connection *con) { return con->ip_port; } TCP_Client_Status tcp_con_status(const TCP_Client_Connection *con) { return con->status; } void *tcp_con_custom_object(const TCP_Client_Connection *con) { return con->custom_object; } uint32_t tcp_con_custom_uint(const TCP_Client_Connection *con) { return con->custom_uint; } void tcp_con_set_custom_object(TCP_Client_Connection *con, void *object) { con->custom_object = object; } void tcp_con_set_custom_uint(TCP_Client_Connection *con, uint32_t value) { con->custom_uint = value; } /* return 1 on success * return 0 on failure */ static int connect_sock_to(Socket sock, IP_Port ip_port, TCP_Proxy_Info *proxy_info) { if (proxy_info->proxy_type != TCP_PROXY_NONE) { ip_port = proxy_info->ip_port; } /* nonblocking socket, connect will never return success */ net_connect(sock, ip_port); return 1; } /* return 1 on success. * return 0 on failure. */ static int proxy_http_generate_connection_request(TCP_Client_Connection *tcp_conn) { char one[] = "CONNECT "; char two[] = " HTTP/1.1\nHost: "; char three[] = "\r\n\r\n"; char ip[TOX_INET6_ADDRSTRLEN]; if (!ip_parse_addr(&tcp_conn->ip_port.ip, ip, sizeof(ip))) { return 0; } const uint16_t port = net_ntohs(tcp_conn->ip_port.port); const int written = snprintf((char *)tcp_conn->last_packet, MAX_PACKET_SIZE, "%s%s:%hu%s%s:%hu%s", one, ip, port, two, ip, port, three); if (written < 0 || MAX_PACKET_SIZE < written) { return 0; } tcp_conn->last_packet_length = written; tcp_conn->last_packet_sent = 0; return 1; } /* return 1 on success. * return 0 if no data received. * return -1 on failure (connection refused). */ static int proxy_http_read_connection_response(const Logger *logger, TCP_Client_Connection *tcp_conn) { char success[] = "200"; uint8_t data[16]; // draining works the best if the length is a power of 2 int ret = read_TCP_packet(logger, tcp_conn->sock, data, sizeof(data) - 1); if (ret == -1) { return 0; } data[sizeof(data) - 1] = 0; if (strstr((char *)data, success)) { // drain all data unsigned int data_left = net_socket_data_recv_buffer(tcp_conn->sock); if (data_left) { VLA(uint8_t, temp_data, data_left); read_TCP_packet(logger, tcp_conn->sock, temp_data, data_left); } return 1; } return -1; } static void proxy_socks5_generate_handshake(TCP_Client_Connection *tcp_conn) { tcp_conn->last_packet[0] = 5; /* SOCKSv5 */ tcp_conn->last_packet[1] = 1; /* number of authentication methods supported */ tcp_conn->last_packet[2] = 0; /* No authentication */ tcp_conn->last_packet_length = 3; tcp_conn->last_packet_sent = 0; } /* return 1 on success. * return 0 if no data received. * return -1 on failure (connection refused). */ static int socks5_read_handshake_response(const Logger *logger, TCP_Client_Connection *tcp_conn) { uint8_t data[2]; int ret = read_TCP_packet(logger, tcp_conn->sock, data, sizeof(data)); if (ret == -1) { return 0; } if (data[0] == 5 && data[1] == 0) { // TODO(irungentoo): magic numbers return 1; } return -1; } static void proxy_socks5_generate_connection_request(TCP_Client_Connection *tcp_conn) { tcp_conn->last_packet[0] = 5; /* SOCKSv5 */ tcp_conn->last_packet[1] = 1; /* command code: establish a TCP/IP stream connection */ tcp_conn->last_packet[2] = 0; /* reserved, must be 0 */ uint16_t length = 3; if (net_family_is_ipv4(tcp_conn->ip_port.ip.family)) { tcp_conn->last_packet[3] = 1; /* IPv4 address */ ++length; memcpy(tcp_conn->last_packet + length, tcp_conn->ip_port.ip.ip.v4.uint8, sizeof(IP4)); length += sizeof(IP4); } else { tcp_conn->last_packet[3] = 4; /* IPv6 address */ ++length; memcpy(tcp_conn->last_packet + length, tcp_conn->ip_port.ip.ip.v6.uint8, sizeof(IP6)); length += sizeof(IP6); } memcpy(tcp_conn->last_packet + length, &tcp_conn->ip_port.port, sizeof(uint16_t)); length += sizeof(uint16_t); tcp_conn->last_packet_length = length; tcp_conn->last_packet_sent = 0; } /* return 1 on success. * return 0 if no data received. * return -1 on failure (connection refused). */ static int proxy_socks5_read_connection_response(const Logger *logger, TCP_Client_Connection *tcp_conn) { if (net_family_is_ipv4(tcp_conn->ip_port.ip.family)) { uint8_t data[4 + sizeof(IP4) + sizeof(uint16_t)]; int ret = read_TCP_packet(logger, tcp_conn->sock, data, sizeof(data)); if (ret == -1) { return 0; } if (data[0] == 5 && data[1] == 0) { return 1; } } else { uint8_t data[4 + sizeof(IP6) + sizeof(uint16_t)]; int ret = read_TCP_packet(logger, tcp_conn->sock, data, sizeof(data)); if (ret == -1) { return 0; } if (data[0] == 5 && data[1] == 0) { return 1; } } return -1; } /* return 0 on success. * return -1 on failure. */ static int generate_handshake(TCP_Client_Connection *tcp_conn) { uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE]; crypto_new_keypair(plain, tcp_conn->temp_secret_key); random_nonce(tcp_conn->sent_nonce); memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, tcp_conn->sent_nonce, CRYPTO_NONCE_SIZE); memcpy(tcp_conn->last_packet, tcp_conn->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); random_nonce(tcp_conn->last_packet + CRYPTO_PUBLIC_KEY_SIZE); int len = encrypt_data_symmetric(tcp_conn->shared_key, tcp_conn->last_packet + CRYPTO_PUBLIC_KEY_SIZE, plain, sizeof(plain), tcp_conn->last_packet + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); if (len != sizeof(plain) + CRYPTO_MAC_SIZE) { return -1; } tcp_conn->last_packet_length = CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + sizeof(plain) + CRYPTO_MAC_SIZE; tcp_conn->last_packet_sent = 0; return 0; } /* data must be of length TCP_SERVER_HANDSHAKE_SIZE * * return 0 on success. * return -1 on failure. */ static int handle_handshake(TCP_Client_Connection *tcp_conn, const uint8_t *data) { uint8_t plain[CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE]; int len = decrypt_data_symmetric(tcp_conn->shared_key, data, data + CRYPTO_NONCE_SIZE, TCP_SERVER_HANDSHAKE_SIZE - CRYPTO_NONCE_SIZE, plain); if (len != sizeof(plain)) { return -1; } memcpy(tcp_conn->recv_nonce, plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_NONCE_SIZE); encrypt_precompute(plain, tcp_conn->temp_secret_key, tcp_conn->shared_key); crypto_memzero(tcp_conn->temp_secret_key, CRYPTO_SECRET_KEY_SIZE); return 0; } /* return 0 if pending data was sent completely * return -1 if it wasn't */ static int client_send_pending_data_nonpriority(TCP_Client_Connection *con) { if (con->last_packet_length == 0) { return 0; } const uint16_t left = con->last_packet_length - con->last_packet_sent; const int len = net_send(con->sock, con->last_packet + con->last_packet_sent, left); if (len <= 0) { return -1; } if (len == left) { con->last_packet_length = 0; con->last_packet_sent = 0; return 0; } con->last_packet_sent += len; return -1; } /* return 0 if pending data was sent completely * return -1 if it wasn't */ static int client_send_pending_data(TCP_Client_Connection *con) { /* finish sending current non-priority packet */ if (client_send_pending_data_nonpriority(con) == -1) { return -1; } TCP_Priority_List *p = con->priority_queue_start; while (p) { const uint16_t left = p->size - p->sent; const int len = net_send(con->sock, p->data + p->sent, left); if (len != left) { if (len > 0) { p->sent += len; } break; } TCP_Priority_List *pp = p; p = p->next; free(pp); } con->priority_queue_start = p; if (!p) { con->priority_queue_end = nullptr; return 0; } return -1; } /* return 0 on failure (only if malloc fails) * return 1 on success */ static bool client_add_priority(TCP_Client_Connection *con, const uint8_t *packet, uint16_t size, uint16_t sent) { TCP_Priority_List *p = con->priority_queue_end; TCP_Priority_List *new_list = (TCP_Priority_List *)malloc(sizeof(TCP_Priority_List) + size); if (!new_list) { return 0; } new_list->next = nullptr; new_list->size = size; new_list->sent = sent; memcpy(new_list->data, packet, size); if (p) { p->next = new_list; } else { con->priority_queue_start = new_list; } con->priority_queue_end = new_list; return 1; } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ static int write_packet_TCP_client_secure_connection(TCP_Client_Connection *con, const uint8_t *data, uint16_t length, bool priority) { if (length + CRYPTO_MAC_SIZE > MAX_PACKET_SIZE) { return -1; } bool sendpriority = 1; if (client_send_pending_data(con) == -1) { if (priority) { sendpriority = 0; } else { return 0; } } 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; } if (priority) { len = sendpriority ? net_send(con->sock, packet, SIZEOF_VLA(packet)) : 0; if (len <= 0) { len = 0; } increment_nonce(con->sent_nonce); if ((unsigned int)len == SIZEOF_VLA(packet)) { return 1; } return client_add_priority(con, packet, SIZEOF_VLA(packet), len); } len = net_send(con->sock, packet, SIZEOF_VLA(packet)); if (len <= 0) { return 0; } increment_nonce(con->sent_nonce); if ((unsigned int)len == SIZEOF_VLA(packet)) { return 1; } memcpy(con->last_packet, packet, SIZEOF_VLA(packet)); con->last_packet_length = SIZEOF_VLA(packet); con->last_packet_sent = len; return 1; } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ int send_routing_request(TCP_Client_Connection *con, uint8_t *public_key) { uint8_t packet[1 + CRYPTO_PUBLIC_KEY_SIZE]; packet[0] = TCP_PACKET_ROUTING_REQUEST; memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); return write_packet_TCP_client_secure_connection(con, packet, sizeof(packet), 1); } void routing_response_handler(TCP_Client_Connection *con, tcp_routing_response_cb *response_callback, void *object) { con->response_callback = response_callback; con->response_callback_object = object; } void routing_status_handler(TCP_Client_Connection *con, tcp_routing_status_cb *status_callback, void *object) { con->status_callback = status_callback; con->status_callback_object = object; } static int tcp_send_ping_response(TCP_Client_Connection *con); static int tcp_send_ping_request(TCP_Client_Connection *con); /* return 1 on success. * return 0 if could not send packet. * return -1 on failure. */ int send_data(TCP_Client_Connection *con, uint8_t con_id, const uint8_t *data, uint16_t length) { if (con_id >= NUM_CLIENT_CONNECTIONS) { return -1; } if (con->connections[con_id].status != 2) { return -1; } if (tcp_send_ping_response(con) == 0 || tcp_send_ping_request(con) == 0) { return 0; } VLA(uint8_t, packet, 1 + length); packet[0] = con_id + NUM_RESERVED_PORTS; memcpy(packet + 1, data, length); return write_packet_TCP_client_secure_connection(con, packet, SIZEOF_VLA(packet), 0); } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure. */ int send_oob_packet(TCP_Client_Connection *con, const uint8_t *public_key, const uint8_t *data, uint16_t length) { if (length == 0 || length > TCP_MAX_OOB_DATA_LENGTH) { return -1; } VLA(uint8_t, packet, 1 + CRYPTO_PUBLIC_KEY_SIZE + length); packet[0] = TCP_PACKET_OOB_SEND; memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length); return write_packet_TCP_client_secure_connection(con, packet, SIZEOF_VLA(packet), 0); } /* Set the number that will be used as an argument in the callbacks related to con_id. * * When not set by this function, the number is -1. * * return 0 on success. * return -1 on failure. */ int set_tcp_connection_number(TCP_Client_Connection *con, uint8_t con_id, uint32_t number) { if (con_id >= NUM_CLIENT_CONNECTIONS) { return -1; } if (con->connections[con_id].status == 0) { return -1; } con->connections[con_id].number = number; return 0; } void routing_data_handler(TCP_Client_Connection *con, tcp_routing_data_cb *data_callback, void *object) { con->data_callback = data_callback; con->data_callback_object = object; } void oob_data_handler(TCP_Client_Connection *con, tcp_oob_data_cb *oob_data_callback, void *object) { con->oob_data_callback = oob_data_callback; con->oob_data_callback_object = object; } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ static int client_send_disconnect_notification(TCP_Client_Connection *con, uint8_t id) { uint8_t packet[1 + 1]; packet[0] = TCP_PACKET_DISCONNECT_NOTIFICATION; packet[1] = id; return write_packet_TCP_client_secure_connection(con, packet, sizeof(packet), 1); } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ static int tcp_send_ping_request(TCP_Client_Connection *con) { if (!con->ping_request_id) { return 1; } uint8_t packet[1 + sizeof(uint64_t)]; packet[0] = TCP_PACKET_PING; memcpy(packet + 1, &con->ping_request_id, sizeof(uint64_t)); const int ret = write_packet_TCP_client_secure_connection(con, packet, sizeof(packet), 1); if (ret == 1) { con->ping_request_id = 0; } return ret; } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ static int tcp_send_ping_response(TCP_Client_Connection *con) { if (!con->ping_response_id) { return 1; } uint8_t packet[1 + sizeof(uint64_t)]; packet[0] = TCP_PACKET_PONG; memcpy(packet + 1, &con->ping_response_id, sizeof(uint64_t)); const int ret = write_packet_TCP_client_secure_connection(con, packet, sizeof(packet), 1); if (ret == 1) { con->ping_response_id = 0; } return ret; } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ int send_disconnect_request(TCP_Client_Connection *con, uint8_t con_id) { if (con_id >= NUM_CLIENT_CONNECTIONS) { return -1; } con->connections[con_id].status = 0; con->connections[con_id].number = 0; return client_send_disconnect_notification(con, con_id + NUM_RESERVED_PORTS); } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ int send_onion_request(TCP_Client_Connection *con, const uint8_t *data, uint16_t length) { VLA(uint8_t, packet, 1 + length); packet[0] = TCP_PACKET_ONION_REQUEST; memcpy(packet + 1, data, length); return write_packet_TCP_client_secure_connection(con, packet, SIZEOF_VLA(packet), 0); } void onion_response_handler(TCP_Client_Connection *con, tcp_onion_response_cb *onion_callback, void *object) { con->onion_callback = onion_callback; con->onion_callback_object = object; } /* Create new TCP connection to ip_port/public_key */ TCP_Client_Connection *new_TCP_connection(const Mono_Time *mono_time, IP_Port ip_port, const uint8_t *public_key, const uint8_t *self_public_key, const uint8_t *self_secret_key, TCP_Proxy_Info *proxy_info) { if (networking_at_startup() != 0) { return nullptr; } if (!net_family_is_ipv4(ip_port.ip.family) && !net_family_is_ipv6(ip_port.ip.family)) { return nullptr; } TCP_Proxy_Info default_proxyinfo; if (proxy_info == nullptr) { default_proxyinfo.proxy_type = TCP_PROXY_NONE; proxy_info = &default_proxyinfo; } Family family = ip_port.ip.family; if (proxy_info->proxy_type != TCP_PROXY_NONE) { family = proxy_info->ip_port.ip.family; } Socket sock = net_socket(family, TOX_SOCK_STREAM, TOX_PROTO_TCP); if (!sock_valid(sock)) { return nullptr; } if (!set_socket_nosigpipe(sock)) { kill_sock(sock); return nullptr; } if (!(set_socket_nonblock(sock) && connect_sock_to(sock, ip_port, proxy_info))) { kill_sock(sock); return nullptr; } TCP_Client_Connection *temp = (TCP_Client_Connection *)calloc(sizeof(TCP_Client_Connection), 1); if (temp == nullptr) { kill_sock(sock); return nullptr; } temp->sock = sock; memcpy(temp->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(temp->self_public_key, self_public_key, CRYPTO_PUBLIC_KEY_SIZE); encrypt_precompute(temp->public_key, self_secret_key, temp->shared_key); temp->ip_port = ip_port; temp->proxy_info = *proxy_info; switch (proxy_info->proxy_type) { case TCP_PROXY_HTTP: temp->status = TCP_CLIENT_PROXY_HTTP_CONNECTING; proxy_http_generate_connection_request(temp); break; case TCP_PROXY_SOCKS5: temp->status = TCP_CLIENT_PROXY_SOCKS5_CONNECTING; proxy_socks5_generate_handshake(temp); break; case TCP_PROXY_NONE: temp->status = TCP_CLIENT_CONNECTING; if (generate_handshake(temp) == -1) { kill_sock(sock); free(temp); return nullptr; } break; } temp->kill_at = mono_time_get(mono_time) + TCP_CONNECTION_TIMEOUT; return temp; } /* return 0 on success * return -1 on failure */ static int handle_TCP_client_packet(TCP_Client_Connection *conn, const uint8_t *data, uint16_t length, void *userdata) { if (length <= 1) { return -1; } switch (data[0]) { case TCP_PACKET_ROUTING_RESPONSE: { if (length != 1 + 1 + CRYPTO_PUBLIC_KEY_SIZE) { return -1; } if (data[1] < NUM_RESERVED_PORTS) { return 0; } uint8_t con_id = data[1] - NUM_RESERVED_PORTS; if (conn->connections[con_id].status != 0) { return 0; } conn->connections[con_id].status = 1; conn->connections[con_id].number = -1; memcpy(conn->connections[con_id].public_key, data + 2, CRYPTO_PUBLIC_KEY_SIZE); if (conn->response_callback) { conn->response_callback(conn->response_callback_object, con_id, conn->connections[con_id].public_key); } return 0; } case TCP_PACKET_CONNECTION_NOTIFICATION: { if (length != 1 + 1) { return -1; } if (data[1] < NUM_RESERVED_PORTS) { return -1; } uint8_t con_id = data[1] - NUM_RESERVED_PORTS; if (conn->connections[con_id].status != 1) { return 0; } conn->connections[con_id].status = 2; if (conn->status_callback) { conn->status_callback(conn->status_callback_object, conn->connections[con_id].number, con_id, conn->connections[con_id].status); } return 0; } case TCP_PACKET_DISCONNECT_NOTIFICATION: { if (length != 1 + 1) { return -1; } if (data[1] < NUM_RESERVED_PORTS) { return -1; } uint8_t con_id = data[1] - NUM_RESERVED_PORTS; if (conn->connections[con_id].status == 0) { return 0; } if (conn->connections[con_id].status != 2) { return 0; } conn->connections[con_id].status = 1; if (conn->status_callback) { conn->status_callback(conn->status_callback_object, conn->connections[con_id].number, con_id, conn->connections[con_id].status); } return 0; } case TCP_PACKET_PING: { if (length != 1 + sizeof(uint64_t)) { return -1; } uint64_t ping_id; memcpy(&ping_id, data + 1, sizeof(uint64_t)); conn->ping_response_id = ping_id; tcp_send_ping_response(conn); return 0; } case TCP_PACKET_PONG: { if (length != 1 + sizeof(uint64_t)) { return -1; } uint64_t ping_id; memcpy(&ping_id, data + 1, sizeof(uint64_t)); if (ping_id) { if (ping_id == conn->ping_id) { conn->ping_id = 0; } return 0; } return -1; } case TCP_PACKET_OOB_RECV: { if (length <= 1 + CRYPTO_PUBLIC_KEY_SIZE) { return -1; } if (conn->oob_data_callback) { conn->oob_data_callback(conn->oob_data_callback_object, data + 1, data + 1 + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_PUBLIC_KEY_SIZE), userdata); } return 0; } case TCP_PACKET_ONION_RESPONSE: { conn->onion_callback(conn->onion_callback_object, data + 1, length - 1, userdata); return 0; } default: { if (data[0] < NUM_RESERVED_PORTS) { return -1; } uint8_t con_id = data[0] - NUM_RESERVED_PORTS; if (conn->data_callback) { conn->data_callback(conn->data_callback_object, conn->connections[con_id].number, con_id, data + 1, length - 1, userdata); } } } return 0; } static bool tcp_process_packet(const Logger *logger, TCP_Client_Connection *conn, void *userdata) { uint8_t packet[MAX_PACKET_SIZE]; const int len = read_packet_TCP_secure_connection(logger, conn->sock, &conn->next_packet_length, conn->shared_key, conn->recv_nonce, packet, sizeof(packet)); if (len == 0) { return false; } if (len == -1) { conn->status = TCP_CLIENT_DISCONNECTED; return false; } if (handle_TCP_client_packet(conn, packet, len, userdata) == -1) { conn->status = TCP_CLIENT_DISCONNECTED; return false; } return true; } static int do_confirmed_TCP(const Logger *logger, TCP_Client_Connection *conn, const Mono_Time *mono_time, void *userdata) { client_send_pending_data(conn); tcp_send_ping_response(conn); tcp_send_ping_request(conn); if (mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_FREQUENCY)) { uint64_t ping_id = random_u64(); if (!ping_id) { ++ping_id; } conn->ping_request_id = ping_id; conn->ping_id = ping_id; tcp_send_ping_request(conn); conn->last_pinged = mono_time_get(mono_time); } if (conn->ping_id && mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_TIMEOUT)) { conn->status = TCP_CLIENT_DISCONNECTED; return 0; } while (tcp_process_packet(logger, conn, userdata)) { // Keep reading until error or out of data. continue; } return 0; } /* Run the TCP connection */ void do_TCP_connection(const Logger *logger, Mono_Time *mono_time, TCP_Client_Connection *tcp_connection, void *userdata) { if (tcp_connection->status == TCP_CLIENT_DISCONNECTED) { return; } if (tcp_connection->status == TCP_CLIENT_PROXY_HTTP_CONNECTING) { if (client_send_pending_data(tcp_connection) == 0) { int ret = proxy_http_read_connection_response(logger, tcp_connection); if (ret == -1) { tcp_connection->kill_at = 0; tcp_connection->status = TCP_CLIENT_DISCONNECTED; } if (ret == 1) { generate_handshake(tcp_connection); tcp_connection->status = TCP_CLIENT_CONNECTING; } } } if (tcp_connection->status == TCP_CLIENT_PROXY_SOCKS5_CONNECTING) { if (client_send_pending_data(tcp_connection) == 0) { int ret = socks5_read_handshake_response(logger, tcp_connection); if (ret == -1) { tcp_connection->kill_at = 0; tcp_connection->status = TCP_CLIENT_DISCONNECTED; } if (ret == 1) { proxy_socks5_generate_connection_request(tcp_connection); tcp_connection->status = TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED; } } } if (tcp_connection->status == TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED) { if (client_send_pending_data(tcp_connection) == 0) { int ret = proxy_socks5_read_connection_response(logger, tcp_connection); if (ret == -1) { tcp_connection->kill_at = 0; tcp_connection->status = TCP_CLIENT_DISCONNECTED; } if (ret == 1) { generate_handshake(tcp_connection); tcp_connection->status = TCP_CLIENT_CONNECTING; } } } if (tcp_connection->status == TCP_CLIENT_CONNECTING) { if (client_send_pending_data(tcp_connection) == 0) { tcp_connection->status = TCP_CLIENT_UNCONFIRMED; } } if (tcp_connection->status == TCP_CLIENT_UNCONFIRMED) { uint8_t data[TCP_SERVER_HANDSHAKE_SIZE]; int len = read_TCP_packet(logger, tcp_connection->sock, data, sizeof(data)); if (sizeof(data) == len) { if (handle_handshake(tcp_connection, data) == 0) { tcp_connection->kill_at = -1; tcp_connection->status = TCP_CLIENT_CONFIRMED; } else { tcp_connection->kill_at = 0; tcp_connection->status = TCP_CLIENT_DISCONNECTED; } } } if (tcp_connection->status == TCP_CLIENT_CONFIRMED) { do_confirmed_TCP(logger, tcp_connection, mono_time, userdata); } if (tcp_connection->kill_at <= mono_time_get(mono_time)) { tcp_connection->status = TCP_CLIENT_DISCONNECTED; } } /* Kill the TCP connection */ void kill_TCP_connection(TCP_Client_Connection *tcp_connection) { if (tcp_connection == nullptr) { return; } wipe_priority_list(tcp_connection->priority_queue_start); kill_sock(tcp_connection->sock); crypto_memzero(tcp_connection, sizeof(TCP_Client_Connection)); free(tcp_connection); } c-toxcore-0.2.13/toxcore/TCP_client.h000066400000000000000000000105461415350724400173330ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Implementation of the TCP relay client part of Tox. */ #ifndef C_TOXCORE_TOXCORE_TCP_CLIENT_H #define C_TOXCORE_TOXCORE_TCP_CLIENT_H #include "TCP_server.h" #include "crypto_core.h" #define TCP_CONNECTION_TIMEOUT 10 typedef enum TCP_Proxy_Type { TCP_PROXY_NONE, TCP_PROXY_HTTP, TCP_PROXY_SOCKS5, } TCP_Proxy_Type; typedef struct TCP_Proxy_Info { IP_Port ip_port; uint8_t proxy_type; // a value from TCP_PROXY_TYPE } TCP_Proxy_Info; typedef enum TCP_Client_Status { TCP_CLIENT_NO_STATUS, TCP_CLIENT_PROXY_HTTP_CONNECTING, TCP_CLIENT_PROXY_SOCKS5_CONNECTING, TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED, TCP_CLIENT_CONNECTING, TCP_CLIENT_UNCONFIRMED, TCP_CLIENT_CONFIRMED, TCP_CLIENT_DISCONNECTED, } TCP_Client_Status; typedef struct TCP_Client_Connection TCP_Client_Connection; const uint8_t *tcp_con_public_key(const TCP_Client_Connection *con); IP_Port tcp_con_ip_port(const TCP_Client_Connection *con); TCP_Client_Status tcp_con_status(const TCP_Client_Connection *con); void *tcp_con_custom_object(const TCP_Client_Connection *con); uint32_t tcp_con_custom_uint(const TCP_Client_Connection *con); void tcp_con_set_custom_object(TCP_Client_Connection *con, void *object); void tcp_con_set_custom_uint(TCP_Client_Connection *con, uint32_t value); /* Create new TCP connection to ip_port/public_key */ TCP_Client_Connection *new_TCP_connection(const Mono_Time *mono_time, IP_Port ip_port, const uint8_t *public_key, const uint8_t *self_public_key, const uint8_t *self_secret_key, TCP_Proxy_Info *proxy_info); /* Run the TCP connection */ void do_TCP_connection(const Logger *logger, Mono_Time *mono_time, TCP_Client_Connection *tcp_connection, void *userdata); /* Kill the TCP connection */ void kill_TCP_connection(TCP_Client_Connection *tcp_connection); typedef int tcp_onion_response_cb(void *object, const uint8_t *data, uint16_t length, void *userdata); /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ int send_onion_request(TCP_Client_Connection *con, const uint8_t *data, uint16_t length); void onion_response_handler(TCP_Client_Connection *con, tcp_onion_response_cb *onion_callback, void *object); typedef int tcp_routing_response_cb(void *object, uint8_t connection_id, const uint8_t *public_key); typedef int tcp_routing_status_cb(void *object, uint32_t number, uint8_t connection_id, uint8_t status); /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ int send_routing_request(TCP_Client_Connection *con, uint8_t *public_key); void routing_response_handler(TCP_Client_Connection *con, tcp_routing_response_cb *response_callback, void *object); void routing_status_handler(TCP_Client_Connection *con, tcp_routing_status_cb *status_callback, void *object); /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ int send_disconnect_request(TCP_Client_Connection *con, uint8_t con_id); /* Set the number that will be used as an argument in the callbacks related to con_id. * * When not set by this function, the number is -1. * * return 0 on success. * return -1 on failure. */ int set_tcp_connection_number(TCP_Client_Connection *con, uint8_t con_id, uint32_t number); typedef int tcp_routing_data_cb(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, uint16_t length, void *userdata); /* return 1 on success. * return 0 if could not send packet. * return -1 on failure. */ int send_data(TCP_Client_Connection *con, uint8_t con_id, const uint8_t *data, uint16_t length); void routing_data_handler(TCP_Client_Connection *con, tcp_routing_data_cb *data_callback, void *object); typedef int tcp_oob_data_cb(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length, void *userdata); /* return 1 on success. * return 0 if could not send packet. * return -1 on failure. */ int send_oob_packet(TCP_Client_Connection *con, const uint8_t *public_key, const uint8_t *data, uint16_t length); void oob_data_handler(TCP_Client_Connection *con, tcp_oob_data_cb *oob_data_callback, void *object); #endif c-toxcore-0.2.13/toxcore/TCP_connection.c000066400000000000000000001175471415350724400202200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015 Tox project. */ /* * Handles TCP relay connections between two Tox clients. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "TCP_connection.h" #include #include #include #include "mono_time.h" #include "util.h" struct TCP_Connections { Mono_Time *mono_time; DHT *dht; uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; TCP_Connection_to *connections; uint32_t connections_length; /* Length of connections array. */ TCP_con *tcp_connections; uint32_t tcp_connections_length; /* Length of tcp_connections array. */ tcp_data_cb *tcp_data_callback; void *tcp_data_callback_object; tcp_oob_cb *tcp_oob_callback; void *tcp_oob_callback_object; tcp_onion_cb *tcp_onion_callback; void *tcp_onion_callback_object; TCP_Proxy_Info proxy_info; bool onion_status; uint16_t onion_num_conns; }; const uint8_t *tcp_connections_public_key(const TCP_Connections *tcp_c) { return tcp_c->self_public_key; } /* Set the size of the array to num. * * return -1 if realloc fails. * return 0 if it succeeds. */ static int realloc_TCP_Connection_to(TCP_Connection_to **array, size_t num) { if (!num) { free(*array); *array = nullptr; return 0; } TCP_Connection_to *temp_pointer = (TCP_Connection_to *)realloc(*array, num * sizeof(TCP_Connection_to)); if (!temp_pointer) { return -1; } *array = temp_pointer; return 0; } static int realloc_TCP_con(TCP_con **array, size_t num) { if (!num) { free(*array); *array = nullptr; return 0; } TCP_con *temp_pointer = (TCP_con *)realloc(*array, num * sizeof(TCP_con)); if (!temp_pointer) { return -1; } *array = temp_pointer; return 0; } /** * Return true if the connections_number is valid. */ static bool connections_number_is_valid(const TCP_Connections *tcp_c, int connections_number) { if ((unsigned int)connections_number >= tcp_c->connections_length) { return false; } if (tcp_c->connections == nullptr) { return false; } if (tcp_c->connections[connections_number].status == TCP_CONN_NONE) { return false; } return true; } /** * Return true if the tcp_connections_number is valid. */ static bool tcp_connections_number_is_valid(const TCP_Connections *tcp_c, int tcp_connections_number) { if ((uint32_t)tcp_connections_number >= tcp_c->tcp_connections_length) { return false; } if (tcp_c->tcp_connections == nullptr) { return false; } if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_NONE) { return false; } return true; } /* Create a new empty connection. * * return -1 on failure. * return connections_number on success. */ static int create_connection(TCP_Connections *tcp_c) { uint32_t i; for (i = 0; i < tcp_c->connections_length; ++i) { if (tcp_c->connections[i].status == TCP_CONN_NONE) { return i; } } int id = -1; if (realloc_TCP_Connection_to(&tcp_c->connections, tcp_c->connections_length + 1) == 0) { id = tcp_c->connections_length; ++tcp_c->connections_length; memset(&tcp_c->connections[id], 0, sizeof(TCP_Connection_to)); } return id; } /* Create a new empty tcp connection. * * return -1 on failure. * return tcp_connections_number on success. */ static int create_tcp_connection(TCP_Connections *tcp_c) { for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { if (tcp_c->tcp_connections[i].status == TCP_CONN_NONE) { return i; } } int id = -1; if (realloc_TCP_con(&tcp_c->tcp_connections, tcp_c->tcp_connections_length + 1) == 0) { id = tcp_c->tcp_connections_length; ++tcp_c->tcp_connections_length; memset(&tcp_c->tcp_connections[id], 0, sizeof(TCP_con)); } return id; } /* Wipe a connection. * * return -1 on failure. * return 0 on success. */ static int wipe_connection(TCP_Connections *tcp_c, int connections_number) { if (!connections_number_is_valid(tcp_c, connections_number)) { return -1; } uint32_t i; memset(&tcp_c->connections[connections_number], 0, sizeof(TCP_Connection_to)); for (i = tcp_c->connections_length; i != 0; --i) { if (tcp_c->connections[i - 1].status != TCP_CONN_NONE) { break; } } if (tcp_c->connections_length != i) { tcp_c->connections_length = i; realloc_TCP_Connection_to(&tcp_c->connections, tcp_c->connections_length); } return 0; } /* Wipe a connection. * * return -1 on failure. * return 0 on success. */ static int wipe_tcp_connection(TCP_Connections *tcp_c, int tcp_connections_number) { if (!tcp_connections_number_is_valid(tcp_c, tcp_connections_number)) { return -1; } memset(&tcp_c->tcp_connections[tcp_connections_number], 0, sizeof(TCP_con)); uint32_t i; for (i = tcp_c->tcp_connections_length; i != 0; --i) { if (tcp_c->tcp_connections[i - 1].status != TCP_CONN_NONE) { break; } } if (tcp_c->tcp_connections_length != i) { tcp_c->tcp_connections_length = i; realloc_TCP_con(&tcp_c->tcp_connections, tcp_c->tcp_connections_length); } return 0; } static TCP_Connection_to *get_connection(const TCP_Connections *tcp_c, int connections_number) { if (!connections_number_is_valid(tcp_c, connections_number)) { return nullptr; } return &tcp_c->connections[connections_number]; } static TCP_con *get_tcp_connection(const TCP_Connections *tcp_c, int tcp_connections_number) { if (!tcp_connections_number_is_valid(tcp_c, tcp_connections_number)) { return nullptr; } return &tcp_c->tcp_connections[tcp_connections_number]; } /* Send a packet to the TCP connection. * * return -1 on failure. * return 0 on success. */ int send_packet_tcp_connection(TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, uint16_t length) { TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (!con_to) { return -1; } // TODO(irungentoo): detect and kill bad relays. // TODO(irungentoo): thread safety? unsigned int i; int ret = -1; bool limit_reached = 0; for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { uint32_t tcp_con_num = con_to->connections[i].tcp_connection; uint8_t status = con_to->connections[i].status; uint8_t connection_id = con_to->connections[i].connection_id; if (tcp_con_num && status == TCP_CONNECTIONS_STATUS_ONLINE) { tcp_con_num -= 1; TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num); if (!tcp_con) { continue; } ret = send_data(tcp_con->connection, connection_id, packet, length); if (ret == 0) { limit_reached = 1; } if (ret == 1) { break; } } } if (ret == 1) { return 0; } if (!limit_reached) { ret = 0; /* Send oob packets to all relays tied to the connection. */ for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { uint32_t tcp_con_num = con_to->connections[i].tcp_connection; uint8_t status = con_to->connections[i].status; if (tcp_con_num && status == TCP_CONNECTIONS_STATUS_REGISTERED) { tcp_con_num -= 1; TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num); if (!tcp_con) { continue; } if (send_oob_packet(tcp_con->connection, con_to->public_key, packet, length) == 1) { ret += 1; } } } if (ret >= 1) { return 0; } return -1; } return -1; } /* Return a random TCP connection number for use in send_tcp_onion_request. * * TODO(irungentoo): This number is just the index of an array that the elements * can change without warning. * * return TCP connection number on success. * return -1 on failure. */ int get_random_tcp_onion_conn_number(TCP_Connections *tcp_c) { const uint32_t r = random_u32(); for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { uint32_t index = ((i + r) % tcp_c->tcp_connections_length); if (tcp_c->tcp_connections[index].onion && tcp_c->tcp_connections[index].status == TCP_CONN_CONNECTED) { return index; } } return -1; } /* Send an onion packet via the TCP relay corresponding to tcp_connections_number. * * return 0 on success. * return -1 on failure. */ int tcp_send_onion_request(TCP_Connections *tcp_c, uint32_t tcp_connections_number, const uint8_t *data, uint16_t length) { if (tcp_connections_number >= tcp_c->tcp_connections_length) { return -1; } if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_CONNECTED) { int ret = send_onion_request(tcp_c->tcp_connections[tcp_connections_number].connection, data, length); if (ret == 1) { return 0; } } return -1; } /* Send an oob packet via the TCP relay corresponding to tcp_connections_number. * * return 0 on success. * return -1 on failure. */ int tcp_send_oob_packet(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *public_key, const uint8_t *packet, uint16_t length) { TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } if (tcp_con->status != TCP_CONN_CONNECTED) { return -1; } int ret = send_oob_packet(tcp_con->connection, public_key, packet, length); if (ret == 1) { return 0; } return -1; } /* Set the callback for TCP data packets. */ void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_data_cb *tcp_data_callback, void *object) { tcp_c->tcp_data_callback = tcp_data_callback; tcp_c->tcp_data_callback_object = object; } /* Set the callback for TCP onion packets. */ void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_oob_cb *tcp_oob_callback, void *object) { tcp_c->tcp_oob_callback = tcp_oob_callback; tcp_c->tcp_oob_callback_object = object; } /* Set the callback for TCP oob data packets. */ void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_onion_cb *tcp_onion_callback, void *object) { tcp_c->tcp_onion_callback = tcp_onion_callback; tcp_c->tcp_onion_callback_object = object; } /* Find the TCP connection with public_key. * * return connections_number on success. * return -1 on failure. */ static int find_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key) { unsigned int i; for (i = 0; i < tcp_c->connections_length; ++i) { TCP_Connection_to *con_to = get_connection(tcp_c, i); if (con_to) { if (public_key_cmp(con_to->public_key, public_key) == 0) { return i; } } } return -1; } /* Find the TCP connection to a relay with relay_pk. * * return connections_number on success. * return -1 on failure. */ static int find_tcp_connection_relay(TCP_Connections *tcp_c, const uint8_t *relay_pk) { for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { TCP_con *tcp_con = get_tcp_connection(tcp_c, i); if (tcp_con) { if (tcp_con->status == TCP_CONN_SLEEPING) { if (public_key_cmp(tcp_con->relay_pk, relay_pk) == 0) { return i; } } else { if (public_key_cmp(tcp_con_public_key(tcp_con->connection), relay_pk) == 0) { return i; } } } } return -1; } /* Create a new TCP connection to public_key. * * public_key must be the counterpart to the secret key that the other peer used with new_tcp_connections(). * * id is the id in the callbacks for that connection. * * return connections_number on success. * return -1 on failure. */ int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id) { if (find_tcp_connection_to(tcp_c, public_key) != -1) { return -1; } int connections_number = create_connection(tcp_c); if (connections_number == -1) { return -1; } TCP_Connection_to *con_to = &tcp_c->connections[connections_number]; con_to->status = TCP_CONN_VALID; memcpy(con_to->public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); con_to->id = id; return connections_number; } /* return 0 on success. * return -1 on failure. */ int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number) { TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (!con_to) { return -1; } unsigned int i; for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { if (con_to->connections[i].tcp_connection) { unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { continue; } if (tcp_con->status == TCP_CONN_CONNECTED) { send_disconnect_request(tcp_con->connection, con_to->connections[i].connection_id); } if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { --tcp_con->lock_count; if (con_to->status == TCP_CONN_SLEEPING) { --tcp_con->sleep_count; } } } } return wipe_connection(tcp_c, connections_number); } /* Set connection status. * * status of 1 means we are using the connection. * status of 0 means we are not using it. * * Unused tcp connections will be disconnected from but kept in case they are needed. * * return 0 on success. * return -1 on failure. */ int set_tcp_connection_to_status(TCP_Connections *tcp_c, int connections_number, bool status) { TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (!con_to) { return -1; } if (status) { /* Connection is unsleeping. */ if (con_to->status != TCP_CONN_SLEEPING) { return -1; } unsigned int i; for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { if (con_to->connections[i].tcp_connection) { unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { continue; } if (tcp_con->status == TCP_CONN_SLEEPING) { tcp_con->unsleep = 1; } } } con_to->status = TCP_CONN_VALID; return 0; } /* Connection is going to sleep. */ if (con_to->status != TCP_CONN_VALID) { return -1; } unsigned int i; for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { if (con_to->connections[i].tcp_connection) { unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1; TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { continue; } if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { ++tcp_con->sleep_count; } } } con_to->status = TCP_CONN_SLEEPING; return 0; } static bool tcp_connection_in_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number) { unsigned int i; for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { return 1; } } return 0; } /* return index on success. * return -1 on failure. */ static int add_tcp_connection_to_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number) { unsigned int i; if (tcp_connection_in_conn(con_to, tcp_connections_number)) { return -1; } for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { if (con_to->connections[i].tcp_connection == 0) { con_to->connections[i].tcp_connection = tcp_connections_number + 1; con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE; con_to->connections[i].connection_id = 0; return i; } } return -1; } /* return index on success. * return -1 on failure. */ static int rm_tcp_connection_from_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number) { unsigned int i; for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { con_to->connections[i].tcp_connection = 0; con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE; con_to->connections[i].connection_id = 0; return i; } } return -1; } /* return number of online connections on success. * return -1 on failure. */ static unsigned int online_tcp_connection_from_conn(TCP_Connection_to *con_to) { unsigned int count = 0; for (unsigned int i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { if (con_to->connections[i].tcp_connection) { if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) { ++count; } } } return count; } /* return index on success. * return -1 on failure. */ static int set_tcp_connection_status(TCP_Connection_to *con_to, unsigned int tcp_connections_number, unsigned int status, uint8_t connection_id) { unsigned int i; for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) { if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) { if (con_to->connections[i].status == status) { return -1; } con_to->connections[i].status = status; con_to->connections[i].connection_id = connection_id; return i; } } return -1; } /* Kill a TCP relay connection. * * return 0 on success. * return -1 on failure. */ static int kill_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) { TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } unsigned int i; for (i = 0; i < tcp_c->connections_length; ++i) { TCP_Connection_to *con_to = get_connection(tcp_c, i); if (con_to) { rm_tcp_connection_from_conn(con_to, tcp_connections_number); } } if (tcp_con->onion) { --tcp_c->onion_num_conns; } kill_TCP_connection(tcp_con->connection); return wipe_tcp_connection(tcp_c, tcp_connections_number); } static int reconnect_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) { TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } if (tcp_con->status == TCP_CONN_SLEEPING) { return -1; } IP_Port ip_port = tcp_con_ip_port(tcp_con->connection); uint8_t relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; memcpy(relay_pk, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); kill_TCP_connection(tcp_con->connection); tcp_con->connection = new_TCP_connection(tcp_c->mono_time, ip_port, relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key, &tcp_c->proxy_info); if (!tcp_con->connection) { kill_tcp_relay_connection(tcp_c, tcp_connections_number); return -1; } unsigned int i; for (i = 0; i < tcp_c->connections_length; ++i) { TCP_Connection_to *con_to = get_connection(tcp_c, i); if (con_to) { set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0); } } if (tcp_con->onion) { --tcp_c->onion_num_conns; tcp_con->onion = 0; } tcp_con->lock_count = 0; tcp_con->sleep_count = 0; tcp_con->connected_time = 0; tcp_con->status = TCP_CONN_VALID; tcp_con->unsleep = 0; return 0; } static int sleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) { TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } if (tcp_con->status != TCP_CONN_CONNECTED) { return -1; } if (tcp_con->lock_count != tcp_con->sleep_count) { return -1; } tcp_con->ip_port = tcp_con_ip_port(tcp_con->connection); memcpy(tcp_con->relay_pk, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); kill_TCP_connection(tcp_con->connection); tcp_con->connection = nullptr; unsigned int i; for (i = 0; i < tcp_c->connections_length; ++i) { TCP_Connection_to *con_to = get_connection(tcp_c, i); if (con_to) { set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0); } } if (tcp_con->onion) { --tcp_c->onion_num_conns; tcp_con->onion = 0; } tcp_con->lock_count = 0; tcp_con->sleep_count = 0; tcp_con->connected_time = 0; tcp_con->status = TCP_CONN_SLEEPING; tcp_con->unsleep = 0; return 0; } static int unsleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number) { TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } if (tcp_con->status != TCP_CONN_SLEEPING) { return -1; } tcp_con->connection = new_TCP_connection(tcp_c->mono_time, tcp_con->ip_port, tcp_con->relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key, &tcp_c->proxy_info); if (!tcp_con->connection) { kill_tcp_relay_connection(tcp_c, tcp_connections_number); return -1; } tcp_con->lock_count = 0; tcp_con->sleep_count = 0; tcp_con->connected_time = 0; tcp_con->status = TCP_CONN_VALID; tcp_con->unsleep = 0; return 0; } /* Send a TCP routing request. * * return 0 on success. * return -1 on failure. */ static int send_tcp_relay_routing_request(TCP_Connections *tcp_c, int tcp_connections_number, uint8_t *public_key) { TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } if (tcp_con->status == TCP_CONN_SLEEPING) { return -1; } if (send_routing_request(tcp_con->connection, public_key) != 1) { return -1; } return 0; } static int tcp_response_callback(void *object, uint8_t connection_id, const uint8_t *public_key) { TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object; TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con); unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con); TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } int connections_number = find_tcp_connection_to(tcp_c, public_key); if (connections_number == -1) { return -1; } TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (con_to == nullptr) { return -1; } if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1) { return -1; } set_tcp_connection_number(tcp_con->connection, connection_id, connections_number); return 0; } static int tcp_status_callback(void *object, uint32_t number, uint8_t connection_id, uint8_t status) { TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object; TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con); unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con); TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); TCP_Connection_to *con_to = get_connection(tcp_c, number); if (!con_to || !tcp_con) { return -1; } if (status == 1) { if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1) { return -1; } --tcp_con->lock_count; if (con_to->status == TCP_CONN_SLEEPING) { --tcp_con->sleep_count; } } else if (status == 2) { if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_ONLINE, connection_id) == -1) { return -1; } ++tcp_con->lock_count; if (con_to->status == TCP_CONN_SLEEPING) { ++tcp_con->sleep_count; } } return 0; } static int tcp_conn_data_callback(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, uint16_t length, void *userdata) { if (length == 0) { return -1; } TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object; TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con); unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con); TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } TCP_Connection_to *con_to = get_connection(tcp_c, number); if (!con_to) { return -1; } if (tcp_c->tcp_data_callback) { tcp_c->tcp_data_callback(tcp_c->tcp_data_callback_object, con_to->id, data, length, userdata); } return 0; } static int tcp_conn_oob_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length, void *userdata) { if (length == 0) { return -1; } TCP_Client_Connection *tcp_client_con = (TCP_Client_Connection *)object; TCP_Connections *tcp_c = (TCP_Connections *)tcp_con_custom_object(tcp_client_con); unsigned int tcp_connections_number = tcp_con_custom_uint(tcp_client_con); TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } /* TODO(irungentoo): optimize */ int connections_number = find_tcp_connection_to(tcp_c, public_key); TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (con_to && tcp_connection_in_conn(con_to, tcp_connections_number)) { return tcp_conn_data_callback(object, connections_number, 0, data, length, userdata); } if (tcp_c->tcp_oob_callback) { tcp_c->tcp_oob_callback(tcp_c->tcp_oob_callback_object, public_key, tcp_connections_number, data, length, userdata); } return 0; } static int tcp_onion_callback(void *object, const uint8_t *data, uint16_t length, void *userdata) { TCP_Connections *tcp_c = (TCP_Connections *)object; if (tcp_c->tcp_onion_callback) { tcp_c->tcp_onion_callback(tcp_c->tcp_onion_callback_object, data, length, userdata); } return 0; } /* Set callbacks for the TCP relay connection. * * return 0 on success. * return -1 on failure. */ static int tcp_relay_set_callbacks(TCP_Connections *tcp_c, int tcp_connections_number) { TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } TCP_Client_Connection *con = tcp_con->connection; tcp_con_set_custom_object(con, tcp_c); tcp_con_set_custom_uint(con, tcp_connections_number); onion_response_handler(con, &tcp_onion_callback, tcp_c); routing_response_handler(con, &tcp_response_callback, con); routing_status_handler(con, &tcp_status_callback, con); routing_data_handler(con, &tcp_conn_data_callback, con); oob_data_handler(con, &tcp_conn_oob_callback, con); return 0; } static int tcp_relay_on_online(TCP_Connections *tcp_c, int tcp_connections_number) { TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } unsigned int sent = 0; for (unsigned int i = 0; i < tcp_c->connections_length; ++i) { TCP_Connection_to *con_to = get_connection(tcp_c, i); if (con_to) { if (tcp_connection_in_conn(con_to, tcp_connections_number)) { if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) { ++sent; } } } } tcp_relay_set_callbacks(tcp_c, tcp_connections_number); tcp_con->status = TCP_CONN_CONNECTED; /* If this connection isn't used by any connection, we don't need to wait for them to come online. */ if (sent) { tcp_con->connected_time = mono_time_get(tcp_c->mono_time); } else { tcp_con->connected_time = 0; } if (tcp_c->onion_status && tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) { tcp_con->onion = 1; ++tcp_c->onion_num_conns; } return 0; } static int add_tcp_relay_instance(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk) { if (net_family_is_tcp_ipv4(ip_port.ip.family)) { ip_port.ip.family = net_family_ipv4; } else if (net_family_is_tcp_ipv6(ip_port.ip.family)) { ip_port.ip.family = net_family_ipv6; } if (!net_family_is_ipv4(ip_port.ip.family) && !net_family_is_ipv6(ip_port.ip.family)) { return -1; } int tcp_connections_number = create_tcp_connection(tcp_c); if (tcp_connections_number == -1) { return -1; } TCP_con *tcp_con = &tcp_c->tcp_connections[tcp_connections_number]; tcp_con->connection = new_TCP_connection(tcp_c->mono_time, ip_port, relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key, &tcp_c->proxy_info); if (!tcp_con->connection) { return -1; } tcp_con->status = TCP_CONN_VALID; return tcp_connections_number; } /* Add a TCP relay to the TCP_Connections instance. * * return 0 on success. * return -1 on failure. */ int add_tcp_relay_global(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk) { int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk); if (tcp_connections_number != -1) { return -1; } if (add_tcp_relay_instance(tcp_c, ip_port, relay_pk) == -1) { return -1; } return 0; } /* Add a TCP relay tied to a connection. * * return 0 on success. * return -1 on failure. */ int add_tcp_number_relay_connection(TCP_Connections *tcp_c, int connections_number, unsigned int tcp_connections_number) { TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (!con_to) { return -1; } TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } if (con_to->status != TCP_CONN_SLEEPING && tcp_con->status == TCP_CONN_SLEEPING) { tcp_con->unsleep = 1; } if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) { return -1; } if (tcp_con->status == TCP_CONN_CONNECTED) { if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) { tcp_con->connected_time = mono_time_get(tcp_c->mono_time); } } return 0; } /* Add a TCP relay tied to a connection. * * This should be called with the same relay by two peers who want to create a TCP connection with each other. * * return 0 on success. * return -1 on failure. */ int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, IP_Port ip_port, const uint8_t *relay_pk) { TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (!con_to) { return -1; } int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk); if (tcp_connections_number != -1) { return add_tcp_number_relay_connection(tcp_c, connections_number, tcp_connections_number); } if (online_tcp_connection_from_conn(con_to) >= RECOMMENDED_FRIEND_TCP_CONNECTIONS) { return -1; } tcp_connections_number = add_tcp_relay_instance(tcp_c, ip_port, relay_pk); TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number); if (!tcp_con) { return -1; } if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) { return -1; } return 0; } /* return number of online tcp relays tied to the connection on success. * return 0 on failure. */ unsigned int tcp_connection_to_online_tcp_relays(TCP_Connections *tcp_c, int connections_number) { TCP_Connection_to *con_to = get_connection(tcp_c, connections_number); if (!con_to) { return 0; } return online_tcp_connection_from_conn(con_to); } /* Copy a maximum of max_num TCP relays we are connected to to tcp_relays. * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. * * return number of relays copied to tcp_relays on success. * return 0 on failure. */ uint32_t tcp_copy_connected_relays(TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num) { const uint32_t r = random_u32(); uint32_t copied = 0; for (uint32_t i = 0; (i < tcp_c->tcp_connections_length) && (copied < max_num); ++i) { TCP_con *tcp_con = get_tcp_connection(tcp_c, (i + r) % tcp_c->tcp_connections_length); if (!tcp_con) { continue; } if (tcp_con->status == TCP_CONN_CONNECTED) { memcpy(tcp_relays[copied].public_key, tcp_con_public_key(tcp_con->connection), CRYPTO_PUBLIC_KEY_SIZE); tcp_relays[copied].ip_port = tcp_con_ip_port(tcp_con->connection); Family *const family = &tcp_relays[copied].ip_port.ip.family; if (net_family_is_ipv4(*family)) { *family = net_family_tcp_ipv4; } else if (net_family_is_ipv6(*family)) { *family = net_family_tcp_ipv6; } ++copied; } } return copied; } /* Set if we want TCP_connection to allocate some connection for onion use. * * If status is 1, allocate some connections. if status is 0, don't. * * return 0 on success. * return -1 on failure. */ int set_tcp_onion_status(TCP_Connections *tcp_c, bool status) { if (tcp_c->onion_status == status) { return -1; } if (status) { for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { TCP_con *tcp_con = get_tcp_connection(tcp_c, i); if (tcp_con) { if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion) { ++tcp_c->onion_num_conns; tcp_con->onion = 1; } } if (tcp_c->onion_num_conns >= NUM_ONION_TCP_CONNECTIONS) { break; } } if (tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) { unsigned int wakeup = NUM_ONION_TCP_CONNECTIONS - tcp_c->onion_num_conns; for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { TCP_con *tcp_con = get_tcp_connection(tcp_c, i); if (tcp_con) { if (tcp_con->status == TCP_CONN_SLEEPING) { tcp_con->unsleep = 1; } } if (!wakeup) { break; } } } tcp_c->onion_status = 1; } else { for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { TCP_con *tcp_con = get_tcp_connection(tcp_c, i); if (tcp_con) { if (tcp_con->onion) { --tcp_c->onion_num_conns; tcp_con->onion = 0; } } } tcp_c->onion_status = 0; } return 0; } /* Returns a new TCP_Connections object associated with the secret_key. * * In order for others to connect to this instance new_tcp_connection_to() must be called with the * public_key associated with secret_key. * * Returns NULL on failure. */ TCP_Connections *new_tcp_connections(Mono_Time *mono_time, const uint8_t *secret_key, TCP_Proxy_Info *proxy_info) { if (secret_key == nullptr) { return nullptr; } TCP_Connections *temp = (TCP_Connections *)calloc(1, sizeof(TCP_Connections)); if (temp == nullptr) { return nullptr; } temp->mono_time = mono_time; memcpy(temp->self_secret_key, secret_key, CRYPTO_SECRET_KEY_SIZE); crypto_derive_public_key(temp->self_public_key, temp->self_secret_key); temp->proxy_info = *proxy_info; return temp; } static void do_tcp_conns(const Logger *logger, TCP_Connections *tcp_c, void *userdata) { for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { TCP_con *tcp_con = get_tcp_connection(tcp_c, i); if (tcp_con == nullptr) { continue; } if (tcp_con->status != TCP_CONN_SLEEPING) { do_TCP_connection(logger, tcp_c->mono_time, tcp_con->connection, userdata); /* callbacks can change TCP connection address. */ tcp_con = get_tcp_connection(tcp_c, i); // Make sure the TCP connection wasn't dropped in any of the callbacks. assert(tcp_con != nullptr); if (tcp_con_status(tcp_con->connection) == TCP_CLIENT_DISCONNECTED) { if (tcp_con->status == TCP_CONN_CONNECTED) { reconnect_tcp_relay_connection(tcp_c, i); } else { kill_tcp_relay_connection(tcp_c, i); } continue; } if (tcp_con->status == TCP_CONN_VALID && tcp_con_status(tcp_con->connection) == TCP_CLIENT_CONFIRMED) { tcp_relay_on_online(tcp_c, i); } if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion && tcp_con->lock_count && tcp_con->lock_count == tcp_con->sleep_count && mono_time_is_timeout(tcp_c->mono_time, tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) { sleep_tcp_relay_connection(tcp_c, i); } } if (tcp_con->status == TCP_CONN_SLEEPING && tcp_con->unsleep) { unsleep_tcp_relay_connection(tcp_c, i); } } } static void kill_nonused_tcp(TCP_Connections *tcp_c) { if (tcp_c->tcp_connections_length == 0) { return; } uint32_t num_online = 0; uint32_t num_kill = 0; VLA(unsigned int, to_kill, tcp_c->tcp_connections_length); for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { TCP_con *tcp_con = get_tcp_connection(tcp_c, i); if (tcp_con) { if (tcp_con->status == TCP_CONN_CONNECTED) { if (!tcp_con->onion && !tcp_con->lock_count && mono_time_is_timeout(tcp_c->mono_time, tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) { to_kill[num_kill] = i; ++num_kill; } ++num_online; } } } if (num_online <= RECOMMENDED_FRIEND_TCP_CONNECTIONS) { return; } uint32_t n = num_online - RECOMMENDED_FRIEND_TCP_CONNECTIONS; if (n < num_kill) { num_kill = n; } for (uint32_t i = 0; i < num_kill; ++i) { kill_tcp_relay_connection(tcp_c, to_kill[i]); } } void do_tcp_connections(const Logger *logger, TCP_Connections *tcp_c, void *userdata) { do_tcp_conns(logger, tcp_c, userdata); kill_nonused_tcp(tcp_c); } void kill_tcp_connections(TCP_Connections *tcp_c) { for (uint32_t i = 0; i < tcp_c->tcp_connections_length; ++i) { kill_TCP_connection(tcp_c->tcp_connections[i].connection); } free(tcp_c->tcp_connections); free(tcp_c->connections); free(tcp_c); } c-toxcore-0.2.13/toxcore/TCP_connection.h000066400000000000000000000157151415350724400202170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2015 Tox project. */ /* * Handles TCP relay connections between two Tox clients. */ #ifndef C_TOXCORE_TOXCORE_TCP_CONNECTION_H #define C_TOXCORE_TOXCORE_TCP_CONNECTION_H #include "TCP_client.h" #define TCP_CONN_NONE 0 #define TCP_CONN_VALID 1 /* NOTE: only used by TCP_con */ #define TCP_CONN_CONNECTED 2 /* Connection is not connected but can be quickly reconnected in case it is needed. */ #define TCP_CONN_SLEEPING 3 #define TCP_CONNECTIONS_STATUS_NONE 0 #define TCP_CONNECTIONS_STATUS_REGISTERED 1 #define TCP_CONNECTIONS_STATUS_ONLINE 2 #define MAX_FRIEND_TCP_CONNECTIONS 6 /* Time until connection to friend gets killed (if it doesn't get locked within that time) */ #define TCP_CONNECTION_ANNOUNCE_TIMEOUT TCP_CONNECTION_TIMEOUT /* The amount of recommended connections for each friend * NOTE: Must be at most (MAX_FRIEND_TCP_CONNECTIONS / 2) */ #define RECOMMENDED_FRIEND_TCP_CONNECTIONS (MAX_FRIEND_TCP_CONNECTIONS / 2) /* Number of TCP connections used for onion purposes. */ #define NUM_ONION_TCP_CONNECTIONS RECOMMENDED_FRIEND_TCP_CONNECTIONS typedef struct TCP_Conn_to { uint32_t tcp_connection; unsigned int status; unsigned int connection_id; } TCP_Conn_to; typedef struct TCP_Connection_to { uint8_t status; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer */ TCP_Conn_to connections[MAX_FRIEND_TCP_CONNECTIONS]; int id; /* id used in callbacks. */ } TCP_Connection_to; typedef struct TCP_con { uint8_t status; TCP_Client_Connection *connection; uint64_t connected_time; uint32_t lock_count; uint32_t sleep_count; bool onion; /* Only used when connection is sleeping. */ IP_Port ip_port; uint8_t relay_pk[CRYPTO_PUBLIC_KEY_SIZE]; bool unsleep; /* set to 1 to unsleep connection. */ } TCP_con; typedef struct TCP_Connections TCP_Connections; const uint8_t *tcp_connections_public_key(const TCP_Connections *tcp_c); /* Send a packet to the TCP connection. * * return -1 on failure. * return 0 on success. */ int send_packet_tcp_connection(TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, uint16_t length); /* Return a random TCP connection number for use in send_tcp_onion_request. * * TODO(irungentoo): This number is just the index of an array that the elements * can change without warning. * * return TCP connection number on success. * return -1 on failure. */ int get_random_tcp_onion_conn_number(TCP_Connections *tcp_c); /* Send an onion packet via the TCP relay corresponding to tcp_connections_number. * * return 0 on success. * return -1 on failure. */ int tcp_send_onion_request(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length); /* Set if we want TCP_connection to allocate some connection for onion use. * * If status is 1, allocate some connections. if status is 0, don't. * * return 0 on success. * return -1 on failure. */ int set_tcp_onion_status(TCP_Connections *tcp_c, bool status); /* Send an oob packet via the TCP relay corresponding to tcp_connections_number. * * return 0 on success. * return -1 on failure. */ int tcp_send_oob_packet(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *public_key, const uint8_t *packet, uint16_t length); typedef int tcp_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); /* Set the callback for TCP data packets. */ void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_data_cb *tcp_data_callback, void *object); typedef int tcp_onion_cb(void *object, const uint8_t *data, uint16_t length, void *userdata); /* Set the callback for TCP onion packets. */ void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_onion_cb *tcp_onion_callback, void *object); typedef int tcp_oob_cb(void *object, const uint8_t *public_key, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length, void *userdata); /* Set the callback for TCP oob data packets. */ void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, tcp_oob_cb *tcp_oob_callback, void *object); /* Create a new TCP connection to public_key. * * public_key must be the counterpart to the secret key that the other peer used with new_tcp_connections(). * * id is the id in the callbacks for that connection. * * return connections_number on success. * return -1 on failure. */ int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id); /* return 0 on success. * return -1 on failure. */ int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number); /* Set connection status. * * status of 1 means we are using the connection. * status of 0 means we are not using it. * * Unused tcp connections will be disconnected from but kept in case they are needed. * * return 0 on success. * return -1 on failure. */ int set_tcp_connection_to_status(TCP_Connections *tcp_c, int connections_number, bool status); /* return number of online tcp relays tied to the connection on success. * return 0 on failure. */ unsigned int tcp_connection_to_online_tcp_relays(TCP_Connections *tcp_c, int connections_number); /* Add a TCP relay tied to a connection. * * NOTE: This can only be used during the tcp_oob_callback. * * return 0 on success. * return -1 on failure. */ int add_tcp_number_relay_connection(TCP_Connections *tcp_c, int connections_number, unsigned int tcp_connections_number); /* Add a TCP relay tied to a connection. * * This should be called with the same relay by two peers who want to create a TCP connection with each other. * * return 0 on success. * return -1 on failure. */ int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, IP_Port ip_port, const uint8_t *relay_pk); /* Add a TCP relay to the instance. * * return 0 on success. * return -1 on failure. */ int add_tcp_relay_global(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk); /* Copy a maximum of max_num TCP relays we are connected to to tcp_relays. * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. * * return number of relays copied to tcp_relays on success. * return 0 on failure. */ uint32_t tcp_copy_connected_relays(TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num); /* Returns a new TCP_Connections object associated with the secret_key. * * In order for others to connect to this instance new_tcp_connection_to() must be called with the * public_key associated with secret_key. * * Returns NULL on failure. */ TCP_Connections *new_tcp_connections(Mono_Time *mono_time, const uint8_t *secret_key, TCP_Proxy_Info *proxy_info); void do_tcp_connections(const Logger *logger, TCP_Connections *tcp_c, void *userdata); void kill_tcp_connections(TCP_Connections *tcp_c); #endif c-toxcore-0.2.13/toxcore/TCP_server.c000066400000000000000000001214061415350724400173540ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Implementation of the TCP relay server part of Tox. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "TCP_server.h" #include #include #if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) #include #endif #ifdef TCP_SERVER_USE_EPOLL #include #include #endif #include "mono_time.h" #include "util.h" #ifdef TCP_SERVER_USE_EPOLL #define TCP_SOCKET_LISTENING 0 #define TCP_SOCKET_INCOMING 1 #define TCP_SOCKET_UNCONFIRMED 2 #define TCP_SOCKET_CONFIRMED 3 #endif typedef struct TCP_Secure_Conn { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint32_t index; // TODO(iphydf): Add an enum for this (same as in TCP_client.c, probably). uint8_t status; /* 0 if not used, 1 if other is offline, 2 if other is online. */ uint8_t other_id; } TCP_Secure_Conn; typedef struct TCP_Secure_Connection { Socket sock; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint16_t next_packet_length; TCP_Secure_Conn connections[NUM_CLIENT_CONNECTIONS]; uint8_t last_packet[2 + MAX_PACKET_SIZE]; uint8_t status; uint16_t last_packet_length; uint16_t last_packet_sent; TCP_Priority_List *priority_queue_start; TCP_Priority_List *priority_queue_end; uint64_t identifier; uint64_t last_pinged; uint64_t ping_id; } TCP_Secure_Connection; struct TCP_Server { const Logger *logger; Onion *onion; #ifdef TCP_SERVER_USE_EPOLL int efd; uint64_t last_run_pinged; #endif Socket *socks_listening; unsigned int num_listening_socks; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t secret_key[CRYPTO_SECRET_KEY_SIZE]; TCP_Secure_Connection incoming_connection_queue[MAX_INCOMING_CONNECTIONS]; uint16_t incoming_connection_queue_index; TCP_Secure_Connection unconfirmed_connection_queue[MAX_INCOMING_CONNECTIONS]; uint16_t unconfirmed_connection_queue_index; TCP_Secure_Connection *accepted_connection_array; uint32_t size_accepted_connections; uint32_t num_accepted_connections; uint64_t counter; BS_List accepted_key_list; }; const uint8_t *tcp_server_public_key(const TCP_Server *tcp_server) { return tcp_server->public_key; } size_t tcp_server_listen_count(const TCP_Server *tcp_server) { return tcp_server->num_listening_socks; } /* This is needed to compile on Android below API 21 */ #ifdef TCP_SERVER_USE_EPOLL #ifndef EPOLLRDHUP #define EPOLLRDHUP 0x2000 #endif #endif /* Increase the size of the connection list * * return -1 on failure * return 0 on success. */ static int alloc_new_connections(TCP_Server *tcp_server, uint32_t num) { const uint32_t new_size = tcp_server->size_accepted_connections + num; if (new_size < tcp_server->size_accepted_connections) { return -1; } TCP_Secure_Connection *new_connections = (TCP_Secure_Connection *)realloc( tcp_server->accepted_connection_array, new_size * sizeof(TCP_Secure_Connection)); if (new_connections == nullptr) { return -1; } const uint32_t old_size = tcp_server->size_accepted_connections; const uint32_t size_new_entries = num * sizeof(TCP_Secure_Connection); memset(new_connections + old_size, 0, size_new_entries); tcp_server->accepted_connection_array = new_connections; tcp_server->size_accepted_connections = new_size; return 0; } void wipe_priority_list(TCP_Priority_List *p) { while (p) { TCP_Priority_List *pp = p; p = p->next; free(pp); } } static void wipe_secure_connection(TCP_Secure_Connection *con) { if (con->status) { wipe_priority_list(con->priority_queue_start); crypto_memzero(con, sizeof(TCP_Secure_Connection)); } } static void move_secure_connection(TCP_Secure_Connection *con_new, TCP_Secure_Connection *con_old) { memcpy(con_new, con_old, sizeof(TCP_Secure_Connection)); crypto_memzero(con_old, sizeof(TCP_Secure_Connection)); } static void free_accepted_connection_array(TCP_Server *tcp_server) { if (tcp_server->accepted_connection_array == nullptr) { return; } for (uint32_t i = 0; i < tcp_server->size_accepted_connections; ++i) { wipe_secure_connection(&tcp_server->accepted_connection_array[i]); } free(tcp_server->accepted_connection_array); tcp_server->accepted_connection_array = nullptr; tcp_server->size_accepted_connections = 0; } /* return index corresponding to connection with peer on success * return -1 on failure. */ static int get_TCP_connection_index(const TCP_Server *tcp_server, const uint8_t *public_key) { return bs_list_find(&tcp_server->accepted_key_list, public_key); } static int kill_accepted(TCP_Server *tcp_server, int index); /* Add accepted TCP connection to the list. * * return index on success * return -1 on failure */ static int add_accepted(TCP_Server *tcp_server, const Mono_Time *mono_time, TCP_Secure_Connection *con) { int index = get_TCP_connection_index(tcp_server, con->public_key); if (index != -1) { /* If an old connection to the same public key exists, kill it. */ kill_accepted(tcp_server, index); index = -1; } if (tcp_server->size_accepted_connections == tcp_server->num_accepted_connections) { if (alloc_new_connections(tcp_server, 4) == -1) { return -1; } index = tcp_server->num_accepted_connections; } else { uint32_t i; for (i = tcp_server->size_accepted_connections; i != 0; --i) { if (tcp_server->accepted_connection_array[i - 1].status == TCP_STATUS_NO_STATUS) { index = i - 1; break; } } } if (index == -1) { LOGGER_ERROR(tcp_server->logger, "FAIL index is -1"); return -1; } if (!bs_list_add(&tcp_server->accepted_key_list, con->public_key, index)) { return -1; } move_secure_connection(&tcp_server->accepted_connection_array[index], con); tcp_server->accepted_connection_array[index].status = TCP_STATUS_CONFIRMED; ++tcp_server->num_accepted_connections; tcp_server->accepted_connection_array[index].identifier = ++tcp_server->counter; tcp_server->accepted_connection_array[index].last_pinged = mono_time_get(mono_time); tcp_server->accepted_connection_array[index].ping_id = 0; return index; } /* Delete accepted connection from list. * * return 0 on success * return -1 on failure */ static int del_accepted(TCP_Server *tcp_server, int index) { if ((uint32_t)index >= tcp_server->size_accepted_connections) { return -1; } if (tcp_server->accepted_connection_array[index].status == TCP_STATUS_NO_STATUS) { return -1; } if (!bs_list_remove(&tcp_server->accepted_key_list, tcp_server->accepted_connection_array[index].public_key, index)) { return -1; } wipe_secure_connection(&tcp_server->accepted_connection_array[index]); --tcp_server->num_accepted_connections; if (tcp_server->num_accepted_connections == 0) { free_accepted_connection_array(tcp_server); } return 0; } /* Read the next two bytes in TCP stream then convert them to * length (host byte order). * * return length on success * return 0 if nothing has been read from socket. * return -1 on failure. */ uint16_t read_TCP_length(const Logger *logger, Socket sock) { const unsigned int count = net_socket_data_recv_buffer(sock); if (count >= sizeof(uint16_t)) { uint16_t length; const int len = net_recv(sock, &length, sizeof(uint16_t)); if (len != sizeof(uint16_t)) { LOGGER_ERROR(logger, "FAIL recv packet"); return 0; } length = net_ntohs(length); if (length > MAX_PACKET_SIZE) { return -1; } return length; } return 0; } /* Read length bytes from socket. * * return length on success * return -1 on failure/no data in buffer. */ int read_TCP_packet(const Logger *logger, Socket sock, uint8_t *data, uint16_t length) { unsigned int count = net_socket_data_recv_buffer(sock); if (count >= length) { const int len = net_recv(sock, data, length); if (len != length) { LOGGER_ERROR(logger, "FAIL recv packet"); return -1; } return len; } return -1; } /* return length of received packet on success. * return 0 if could not read any packet. * return -1 on failure (connection must be killed). */ int read_packet_TCP_secure_connection(const Logger *logger, Socket sock, uint16_t *next_packet_length, const uint8_t *shared_key, uint8_t *recv_nonce, uint8_t *data, uint16_t max_len) { if (*next_packet_length == 0) { uint16_t len = read_TCP_length(logger, sock); if (len == (uint16_t) -1) { return -1; } if (len == 0) { return 0; } *next_packet_length = len; } if (max_len + CRYPTO_MAC_SIZE < *next_packet_length) { return -1; } VLA(uint8_t, data_encrypted, *next_packet_length); int len_packet = read_TCP_packet(logger, sock, data_encrypted, *next_packet_length); if (len_packet != *next_packet_length) { return 0; } *next_packet_length = 0; int len = decrypt_data_symmetric(shared_key, recv_nonce, data_encrypted, len_packet, data); if (len + CRYPTO_MAC_SIZE != len_packet) { return -1; } increment_nonce(recv_nonce); return len; } /* return 0 if pending data was sent completely * return -1 if it wasn't */ static int send_pending_data_nonpriority(TCP_Secure_Connection *con) { if (con->last_packet_length == 0) { return 0; } const uint16_t left = con->last_packet_length - con->last_packet_sent; const int len = net_send(con->sock, con->last_packet + con->last_packet_sent, left); if (len <= 0) { return -1; } if (len == left) { con->last_packet_length = 0; con->last_packet_sent = 0; return 0; } con->last_packet_sent += len; return -1; } /* return 0 if pending data was sent completely * return -1 if it wasn't */ static int send_pending_data(TCP_Secure_Connection *con) { /* finish sending current non-priority packet */ if (send_pending_data_nonpriority(con) == -1) { return -1; } TCP_Priority_List *p = con->priority_queue_start; while (p) { const uint16_t left = p->size - p->sent; const int len = net_send(con->sock, p->data + p->sent, left); if (len != left) { if (len > 0) { p->sent += len; } break; } TCP_Priority_List *pp = p; p = p->next; free(pp); } con->priority_queue_start = p; if (!p) { con->priority_queue_end = nullptr; return 0; } return -1; } /* return 0 on failure (only if malloc fails) * return 1 on success */ static bool add_priority(TCP_Secure_Connection *con, const uint8_t *packet, uint16_t size, uint16_t sent) { TCP_Priority_List *p = con->priority_queue_end; TCP_Priority_List *new_list = (TCP_Priority_List *)malloc(sizeof(TCP_Priority_List) + size); if (!new_list) { return 0; } new_list->next = nullptr; new_list->size = size; new_list->sent = sent; memcpy(new_list->data, packet, size); if (p) { p->next = new_list; } else { con->priority_queue_start = new_list; } con->priority_queue_end = new_list; return 1; } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ static int write_packet_TCP_secure_connection(TCP_Secure_Connection *con, const uint8_t *data, uint16_t length, bool priority) { if (length + CRYPTO_MAC_SIZE > MAX_PACKET_SIZE) { return -1; } bool sendpriority = 1; if (send_pending_data(con) == -1) { if (priority) { sendpriority = 0; } else { return 0; } } VLA(uint8_t, packet, sizeof(uint16_t) + length + CRYPTO_MAC_SIZE); const 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; } if (priority) { len = sendpriority ? net_send(con->sock, packet, SIZEOF_VLA(packet)) : 0; if (len <= 0) { len = 0; } increment_nonce(con->sent_nonce); if ((unsigned int)len == SIZEOF_VLA(packet)) { return 1; } return add_priority(con, packet, SIZEOF_VLA(packet), len); } len = net_send(con->sock, packet, SIZEOF_VLA(packet)); if (len <= 0) { return 0; } increment_nonce(con->sent_nonce); if ((unsigned int)len == SIZEOF_VLA(packet)) { return 1; } memcpy(con->last_packet, packet, SIZEOF_VLA(packet)); con->last_packet_length = SIZEOF_VLA(packet); con->last_packet_sent = len; return 1; } /* Kill a TCP_Secure_Connection */ static void kill_TCP_secure_connection(TCP_Secure_Connection *con) { kill_sock(con->sock); wipe_secure_connection(con); } static int rm_connection_index(TCP_Server *tcp_server, TCP_Secure_Connection *con, uint8_t con_number); /* Kill an accepted TCP_Secure_Connection * * return -1 on failure. * return 0 on success. */ static int kill_accepted(TCP_Server *tcp_server, int index) { if ((uint32_t)index >= tcp_server->size_accepted_connections) { return -1; } uint32_t i; for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { rm_connection_index(tcp_server, &tcp_server->accepted_connection_array[index], i); } Socket sock = tcp_server->accepted_connection_array[index].sock; if (del_accepted(tcp_server, index) != 0) { return -1; } kill_sock(sock); return 0; } /* return 1 if everything went well. * return -1 if the connection must be killed. */ static int handle_TCP_handshake(TCP_Secure_Connection *con, const uint8_t *data, uint16_t length, const uint8_t *self_secret_key) { if (length != TCP_CLIENT_HANDSHAKE_SIZE) { return -1; } if (con->status != TCP_STATUS_CONNECTED) { return -1; } uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; encrypt_precompute(data, self_secret_key, shared_key); uint8_t plain[TCP_HANDSHAKE_PLAIN_SIZE]; int len = decrypt_data_symmetric(shared_key, data + CRYPTO_PUBLIC_KEY_SIZE, data + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE, plain); if (len != TCP_HANDSHAKE_PLAIN_SIZE) { return -1; } memcpy(con->public_key, data, CRYPTO_PUBLIC_KEY_SIZE); uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; uint8_t resp_plain[TCP_HANDSHAKE_PLAIN_SIZE]; crypto_new_keypair(resp_plain, temp_secret_key); random_nonce(con->sent_nonce); memcpy(resp_plain + CRYPTO_PUBLIC_KEY_SIZE, con->sent_nonce, CRYPTO_NONCE_SIZE); memcpy(con->recv_nonce, plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_NONCE_SIZE); uint8_t response[TCP_SERVER_HANDSHAKE_SIZE]; random_nonce(response); len = encrypt_data_symmetric(shared_key, response, resp_plain, TCP_HANDSHAKE_PLAIN_SIZE, response + CRYPTO_NONCE_SIZE); if (len != TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE) { return -1; } if (TCP_SERVER_HANDSHAKE_SIZE != net_send(con->sock, response, TCP_SERVER_HANDSHAKE_SIZE)) { return -1; } encrypt_precompute(plain, temp_secret_key, con->shared_key); con->status = TCP_STATUS_UNCONFIRMED; return 1; } /* return 1 if connection handshake was handled correctly. * return 0 if we didn't get it yet. * return -1 if the connection must be killed. */ static int read_connection_handshake(const Logger *logger, TCP_Secure_Connection *con, const uint8_t *self_secret_key) { uint8_t data[TCP_CLIENT_HANDSHAKE_SIZE]; const int len = read_TCP_packet(logger, con->sock, data, TCP_CLIENT_HANDSHAKE_SIZE); if (len != -1) { return handle_TCP_handshake(con, data, len, self_secret_key); } return 0; } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ static int send_routing_response(TCP_Secure_Connection *con, uint8_t rpid, const uint8_t *public_key) { uint8_t data[1 + 1 + CRYPTO_PUBLIC_KEY_SIZE]; data[0] = TCP_PACKET_ROUTING_RESPONSE; data[1] = rpid; memcpy(data + 2, public_key, CRYPTO_PUBLIC_KEY_SIZE); return write_packet_TCP_secure_connection(con, data, sizeof(data), 1); } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ static int send_connect_notification(TCP_Secure_Connection *con, uint8_t id) { uint8_t data[2] = {TCP_PACKET_CONNECTION_NOTIFICATION, (uint8_t)(id + NUM_RESERVED_PORTS)}; return write_packet_TCP_secure_connection(con, data, sizeof(data), 1); } /* return 1 on success. * return 0 if could not send packet. * return -1 on failure (connection must be killed). */ static int send_disconnect_notification(TCP_Secure_Connection *con, uint8_t id) { uint8_t data[2] = {TCP_PACKET_DISCONNECT_NOTIFICATION, (uint8_t)(id + NUM_RESERVED_PORTS)}; return write_packet_TCP_secure_connection(con, data, sizeof(data), 1); } /* return 0 on success. * return -1 on failure (connection must be killed). */ static int handle_TCP_routing_req(TCP_Server *tcp_server, uint32_t con_id, const uint8_t *public_key) { uint32_t i; uint32_t index = -1; TCP_Secure_Connection *con = &tcp_server->accepted_connection_array[con_id]; /* If person tries to cennect to himself we deny the request*/ if (public_key_cmp(con->public_key, public_key) == 0) { if (send_routing_response(con, 0, public_key) == -1) { return -1; } return 0; } for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { if (con->connections[i].status != 0) { if (public_key_cmp(public_key, con->connections[i].public_key) == 0) { if (send_routing_response(con, i + NUM_RESERVED_PORTS, public_key) == -1) { return -1; } return 0; } } else if (index == (uint32_t) -1) { index = i; } } if (index == (uint32_t) -1) { if (send_routing_response(con, 0, public_key) == -1) { return -1; } return 0; } int ret = send_routing_response(con, index + NUM_RESERVED_PORTS, public_key); if (ret == 0) { return 0; } if (ret == -1) { return -1; } con->connections[index].status = 1; memcpy(con->connections[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); int other_index = get_TCP_connection_index(tcp_server, public_key); if (other_index != -1) { uint32_t other_id = -1; TCP_Secure_Connection *other_conn = &tcp_server->accepted_connection_array[other_index]; for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) { if (other_conn->connections[i].status == 1 && public_key_cmp(other_conn->connections[i].public_key, con->public_key) == 0) { other_id = i; break; } } if (other_id != (uint32_t) -1) { con->connections[index].status = 2; con->connections[index].index = other_index; con->connections[index].other_id = other_id; other_conn->connections[other_id].status = 2; other_conn->connections[other_id].index = con_id; other_conn->connections[other_id].other_id = index; // TODO(irungentoo): return values? send_connect_notification(con, index); send_connect_notification(other_conn, other_id); } } return 0; } /* return 0 on success. * return -1 on failure (connection must be killed). */ static int handle_TCP_oob_send(TCP_Server *tcp_server, uint32_t con_id, const uint8_t *public_key, const uint8_t *data, uint16_t length) { if (length == 0 || length > TCP_MAX_OOB_DATA_LENGTH) { return -1; } TCP_Secure_Connection *con = &tcp_server->accepted_connection_array[con_id]; int other_index = get_TCP_connection_index(tcp_server, public_key); if (other_index != -1) { VLA(uint8_t, resp_packet, 1 + CRYPTO_PUBLIC_KEY_SIZE + length); resp_packet[0] = TCP_PACKET_OOB_RECV; memcpy(resp_packet + 1, con->public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(resp_packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length); write_packet_TCP_secure_connection(&tcp_server->accepted_connection_array[other_index], resp_packet, SIZEOF_VLA(resp_packet), 0); } return 0; } /* Remove connection with con_number from the connections array of con. * * return -1 on failure. * return 0 on success. */ static int rm_connection_index(TCP_Server *tcp_server, TCP_Secure_Connection *con, uint8_t con_number) { if (con_number >= NUM_CLIENT_CONNECTIONS) { return -1; } if (con->connections[con_number].status) { uint32_t index = con->connections[con_number].index; uint8_t other_id = con->connections[con_number].other_id; if (con->connections[con_number].status == 2) { if (index >= tcp_server->size_accepted_connections) { return -1; } tcp_server->accepted_connection_array[index].connections[other_id].other_id = 0; tcp_server->accepted_connection_array[index].connections[other_id].index = 0; tcp_server->accepted_connection_array[index].connections[other_id].status = 1; // TODO(irungentoo): return values? send_disconnect_notification(&tcp_server->accepted_connection_array[index], other_id); } con->connections[con_number].index = 0; con->connections[con_number].other_id = 0; con->connections[con_number].status = 0; return 0; } return -1; } static int handle_onion_recv_1(void *object, IP_Port dest, const uint8_t *data, uint16_t length) { TCP_Server *tcp_server = (TCP_Server *)object; uint32_t index = dest.ip.ip.v6.uint32[0]; if (index >= tcp_server->size_accepted_connections) { return 1; } TCP_Secure_Connection *con = &tcp_server->accepted_connection_array[index]; if (con->identifier != dest.ip.ip.v6.uint64[1]) { return 1; } VLA(uint8_t, packet, 1 + length); memcpy(packet + 1, data, length); packet[0] = TCP_PACKET_ONION_RESPONSE; if (write_packet_TCP_secure_connection(con, packet, SIZEOF_VLA(packet), 0) != 1) { return 1; } return 0; } /* return 0 on success * return -1 on failure */ static int handle_TCP_packet(TCP_Server *tcp_server, uint32_t con_id, const uint8_t *data, uint16_t length) { if (length == 0) { return -1; } TCP_Secure_Connection *con = &tcp_server->accepted_connection_array[con_id]; switch (data[0]) { case TCP_PACKET_ROUTING_REQUEST: { if (length != 1 + CRYPTO_PUBLIC_KEY_SIZE) { return -1; } return handle_TCP_routing_req(tcp_server, con_id, data + 1); } case TCP_PACKET_CONNECTION_NOTIFICATION: { if (length != 2) { return -1; } break; } case TCP_PACKET_DISCONNECT_NOTIFICATION: { if (length != 2) { return -1; } return rm_connection_index(tcp_server, con, data[1] - NUM_RESERVED_PORTS); } case TCP_PACKET_PING: { if (length != 1 + sizeof(uint64_t)) { return -1; } uint8_t response[1 + sizeof(uint64_t)]; response[0] = TCP_PACKET_PONG; memcpy(response + 1, data + 1, sizeof(uint64_t)); write_packet_TCP_secure_connection(con, response, sizeof(response), 1); return 0; } case TCP_PACKET_PONG: { if (length != 1 + sizeof(uint64_t)) { return -1; } uint64_t ping_id; memcpy(&ping_id, data + 1, sizeof(uint64_t)); if (ping_id) { if (ping_id == con->ping_id) { con->ping_id = 0; } return 0; } return -1; } case TCP_PACKET_OOB_SEND: { if (length <= 1 + CRYPTO_PUBLIC_KEY_SIZE) { return -1; } return handle_TCP_oob_send(tcp_server, con_id, data + 1, data + 1 + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_PUBLIC_KEY_SIZE)); } case TCP_PACKET_ONION_REQUEST: { if (tcp_server->onion) { if (length <= 1 + CRYPTO_NONCE_SIZE + ONION_SEND_BASE * 2) { return -1; } IP_Port source; source.port = 0; // dummy initialise source.ip.family = net_family_tcp_onion; source.ip.ip.v6.uint32[0] = con_id; source.ip.ip.v6.uint32[1] = 0; source.ip.ip.v6.uint64[1] = con->identifier; onion_send_1(tcp_server->onion, data + 1 + CRYPTO_NONCE_SIZE, length - (1 + CRYPTO_NONCE_SIZE), source, data + 1); } return 0; } case TCP_PACKET_ONION_RESPONSE: { return -1; } default: { if (data[0] < NUM_RESERVED_PORTS) { return -1; } uint8_t c_id = data[0] - NUM_RESERVED_PORTS; if (c_id >= NUM_CLIENT_CONNECTIONS) { return -1; } if (con->connections[c_id].status == 0) { return -1; } if (con->connections[c_id].status != 2) { return 0; } uint32_t index = con->connections[c_id].index; uint8_t other_c_id = con->connections[c_id].other_id + NUM_RESERVED_PORTS; VLA(uint8_t, new_data, length); memcpy(new_data, data, length); new_data[0] = other_c_id; int ret = write_packet_TCP_secure_connection(&tcp_server->accepted_connection_array[index], new_data, length, 0); if (ret == -1) { return -1; } return 0; } } return 0; } static int confirm_TCP_connection(TCP_Server *tcp_server, const Mono_Time *mono_time, TCP_Secure_Connection *con, const uint8_t *data, uint16_t length) { int index = add_accepted(tcp_server, mono_time, con); if (index == -1) { kill_TCP_secure_connection(con); return -1; } wipe_secure_connection(con); if (handle_TCP_packet(tcp_server, index, data, length) == -1) { kill_accepted(tcp_server, index); return -1; } return index; } /* return index on success * return -1 on failure */ static int accept_connection(TCP_Server *tcp_server, Socket sock) { if (!sock_valid(sock)) { return -1; } if (!set_socket_nonblock(sock)) { kill_sock(sock); return -1; } if (!set_socket_nosigpipe(sock)) { kill_sock(sock); return -1; } uint16_t index = tcp_server->incoming_connection_queue_index % MAX_INCOMING_CONNECTIONS; TCP_Secure_Connection *conn = &tcp_server->incoming_connection_queue[index]; if (conn->status != TCP_STATUS_NO_STATUS) { kill_TCP_secure_connection(conn); } conn->status = TCP_STATUS_CONNECTED; conn->sock = sock; conn->next_packet_length = 0; ++tcp_server->incoming_connection_queue_index; return index; } static Socket new_listening_TCP_socket(Family family, uint16_t port) { Socket sock = net_socket(family, TOX_SOCK_STREAM, TOX_PROTO_TCP); if (!sock_valid(sock)) { return net_invalid_socket; } int ok = set_socket_nonblock(sock); if (ok && net_family_is_ipv6(family)) { ok = set_socket_dualstack(sock); } if (ok) { ok = set_socket_reuseaddr(sock); } ok = ok && bind_to_port(sock, family, port) && (net_listen(sock, TCP_MAX_BACKLOG) == 0); if (!ok) { kill_sock(sock); return net_invalid_socket; } return sock; } TCP_Server *new_TCP_server(const Logger *logger, uint8_t ipv6_enabled, uint16_t num_sockets, const uint16_t *ports, const uint8_t *secret_key, Onion *onion) { if (num_sockets == 0 || ports == nullptr) { return nullptr; } if (networking_at_startup() != 0) { return nullptr; } TCP_Server *temp = (TCP_Server *)calloc(1, sizeof(TCP_Server)); if (temp == nullptr) { return nullptr; } temp->logger = logger; temp->socks_listening = (Socket *)calloc(num_sockets, sizeof(Socket)); if (temp->socks_listening == nullptr) { free(temp); return nullptr; } #ifdef TCP_SERVER_USE_EPOLL temp->efd = epoll_create(8); if (temp->efd == -1) { free(temp->socks_listening); free(temp); return nullptr; } #endif const Family family = ipv6_enabled ? net_family_ipv6 : net_family_ipv4; uint32_t i; #ifdef TCP_SERVER_USE_EPOLL struct epoll_event ev; #endif for (i = 0; i < num_sockets; ++i) { Socket sock = new_listening_TCP_socket(family, ports[i]); if (sock_valid(sock)) { #ifdef TCP_SERVER_USE_EPOLL ev.events = EPOLLIN | EPOLLET; ev.data.u64 = sock.socket | ((uint64_t)TCP_SOCKET_LISTENING << 32); if (epoll_ctl(temp->efd, EPOLL_CTL_ADD, sock.socket, &ev) == -1) { continue; } #endif temp->socks_listening[temp->num_listening_socks] = sock; ++temp->num_listening_socks; } } if (temp->num_listening_socks == 0) { free(temp->socks_listening); free(temp); return nullptr; } if (onion) { temp->onion = onion; set_callback_handle_recv_1(onion, &handle_onion_recv_1, temp); } memcpy(temp->secret_key, secret_key, CRYPTO_SECRET_KEY_SIZE); crypto_derive_public_key(temp->public_key, temp->secret_key); bs_list_init(&temp->accepted_key_list, CRYPTO_PUBLIC_KEY_SIZE, 8); return temp; } #ifndef TCP_SERVER_USE_EPOLL static void do_TCP_accept_new(TCP_Server *tcp_server) { uint32_t i; for (i = 0; i < tcp_server->num_listening_socks; ++i) { Socket sock; do { sock = net_accept(tcp_server->socks_listening[i]); } while (accept_connection(tcp_server, sock) != -1); } } #endif static int do_incoming(TCP_Server *tcp_server, uint32_t i) { if (tcp_server->incoming_connection_queue[i].status != TCP_STATUS_CONNECTED) { return -1; } int ret = read_connection_handshake(tcp_server->logger, &tcp_server->incoming_connection_queue[i], tcp_server->secret_key); if (ret == -1) { kill_TCP_secure_connection(&tcp_server->incoming_connection_queue[i]); } else if (ret == 1) { int index_new = tcp_server->unconfirmed_connection_queue_index % MAX_INCOMING_CONNECTIONS; TCP_Secure_Connection *conn_old = &tcp_server->incoming_connection_queue[i]; TCP_Secure_Connection *conn_new = &tcp_server->unconfirmed_connection_queue[index_new]; if (conn_new->status != TCP_STATUS_NO_STATUS) { kill_TCP_secure_connection(conn_new); } move_secure_connection(conn_new, conn_old); ++tcp_server->unconfirmed_connection_queue_index; return index_new; } return -1; } static int do_unconfirmed(TCP_Server *tcp_server, const Mono_Time *mono_time, uint32_t i) { TCP_Secure_Connection *conn = &tcp_server->unconfirmed_connection_queue[i]; if (conn->status != TCP_STATUS_UNCONFIRMED) { return -1; } uint8_t packet[MAX_PACKET_SIZE]; int len = read_packet_TCP_secure_connection(tcp_server->logger, conn->sock, &conn->next_packet_length, conn->shared_key, conn->recv_nonce, packet, sizeof(packet)); if (len == 0) { return -1; } if (len == -1) { kill_TCP_secure_connection(conn); return -1; } return confirm_TCP_connection(tcp_server, mono_time, conn, packet, len); } static bool tcp_process_secure_packet(TCP_Server *tcp_server, uint32_t i) { TCP_Secure_Connection *const conn = &tcp_server->accepted_connection_array[i]; uint8_t packet[MAX_PACKET_SIZE]; int len = read_packet_TCP_secure_connection(tcp_server->logger, conn->sock, &conn->next_packet_length, conn->shared_key, conn->recv_nonce, packet, sizeof(packet)); if (len == 0) { return false; } if (len == -1) { kill_accepted(tcp_server, i); return false; } if (handle_TCP_packet(tcp_server, i, packet, len) == -1) { kill_accepted(tcp_server, i); return false; } return true; } static void do_confirmed_recv(TCP_Server *tcp_server, uint32_t i) { while (tcp_process_secure_packet(tcp_server, i)) { // Keep reading until an error occurs or there is no more data to read. continue; } } #ifndef TCP_SERVER_USE_EPOLL static void do_TCP_incoming(TCP_Server *tcp_server) { for (uint32_t i = 0; i < MAX_INCOMING_CONNECTIONS; ++i) { do_incoming(tcp_server, i); } } static void do_TCP_unconfirmed(TCP_Server *tcp_server, const Mono_Time *mono_time) { for (uint32_t i = 0; i < MAX_INCOMING_CONNECTIONS; ++i) { do_unconfirmed(tcp_server, mono_time, i); } } #endif static void do_TCP_confirmed(TCP_Server *tcp_server, const Mono_Time *mono_time) { #ifdef TCP_SERVER_USE_EPOLL if (tcp_server->last_run_pinged == mono_time_get(mono_time)) { return; } tcp_server->last_run_pinged = mono_time_get(mono_time); #endif uint32_t i; for (i = 0; i < tcp_server->size_accepted_connections; ++i) { TCP_Secure_Connection *conn = &tcp_server->accepted_connection_array[i]; if (conn->status != TCP_STATUS_CONFIRMED) { continue; } if (mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_FREQUENCY)) { uint8_t ping[1 + sizeof(uint64_t)]; ping[0] = TCP_PACKET_PING; uint64_t ping_id = random_u64(); if (!ping_id) { ++ping_id; } memcpy(ping + 1, &ping_id, sizeof(uint64_t)); int ret = write_packet_TCP_secure_connection(conn, ping, sizeof(ping), 1); if (ret == 1) { conn->last_pinged = mono_time_get(mono_time); conn->ping_id = ping_id; } else { if (mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_FREQUENCY + TCP_PING_TIMEOUT)) { kill_accepted(tcp_server, i); continue; } } } if (conn->ping_id && mono_time_is_timeout(mono_time, conn->last_pinged, TCP_PING_TIMEOUT)) { kill_accepted(tcp_server, i); continue; } send_pending_data(conn); #ifndef TCP_SERVER_USE_EPOLL do_confirmed_recv(tcp_server, i); #endif } } #ifdef TCP_SERVER_USE_EPOLL static bool tcp_epoll_process(TCP_Server *tcp_server, const Mono_Time *mono_time) { #define MAX_EVENTS 16 struct epoll_event events[MAX_EVENTS]; const int nfds = epoll_wait(tcp_server->efd, events, MAX_EVENTS, 0); #undef MAX_EVENTS for (int n = 0; n < nfds; ++n) { const Socket sock = {(int)(events[n].data.u64 & 0xFFFFFFFF)}; const int status = (events[n].data.u64 >> 32) & 0xFF; const int index = events[n].data.u64 >> 40; if ((events[n].events & EPOLLERR) || (events[n].events & EPOLLHUP) || (events[n].events & EPOLLRDHUP)) { switch (status) { case TCP_SOCKET_LISTENING: { // should never happen break; } case TCP_SOCKET_INCOMING: { kill_TCP_secure_connection(&tcp_server->incoming_connection_queue[index]); break; } case TCP_SOCKET_UNCONFIRMED: { kill_TCP_secure_connection(&tcp_server->unconfirmed_connection_queue[index]); break; } case TCP_SOCKET_CONFIRMED: { kill_accepted(tcp_server, index); break; } } continue; } if (!(events[n].events & EPOLLIN)) { continue; } switch (status) { case TCP_SOCKET_LISTENING: { // socket is from socks_listening, accept connection while (1) { Socket sock_new = net_accept(sock); if (!sock_valid(sock_new)) { break; } int index_new = accept_connection(tcp_server, sock_new); if (index_new == -1) { continue; } struct epoll_event ev; ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP; ev.data.u64 = sock_new.socket | ((uint64_t)TCP_SOCKET_INCOMING << 32) | ((uint64_t)index_new << 40); if (epoll_ctl(tcp_server->efd, EPOLL_CTL_ADD, sock_new.socket, &ev) == -1) { kill_TCP_secure_connection(&tcp_server->incoming_connection_queue[index_new]); continue; } } break; } case TCP_SOCKET_INCOMING: { const int index_new = do_incoming(tcp_server, index); if (index_new != -1) { events[n].events = EPOLLIN | EPOLLET | EPOLLRDHUP; events[n].data.u64 = sock.socket | ((uint64_t)TCP_SOCKET_UNCONFIRMED << 32) | ((uint64_t)index_new << 40); if (epoll_ctl(tcp_server->efd, EPOLL_CTL_MOD, sock.socket, &events[n]) == -1) { kill_TCP_secure_connection(&tcp_server->unconfirmed_connection_queue[index_new]); break; } } break; } case TCP_SOCKET_UNCONFIRMED: { const int index_new = do_unconfirmed(tcp_server, mono_time, index); if (index_new != -1) { events[n].events = EPOLLIN | EPOLLET | EPOLLRDHUP; events[n].data.u64 = sock.socket | ((uint64_t)TCP_SOCKET_CONFIRMED << 32) | ((uint64_t)index_new << 40); if (epoll_ctl(tcp_server->efd, EPOLL_CTL_MOD, sock.socket, &events[n]) == -1) { // remove from confirmed connections kill_accepted(tcp_server, index_new); break; } } break; } case TCP_SOCKET_CONFIRMED: { do_confirmed_recv(tcp_server, index); break; } } } return nfds > 0; } static void do_TCP_epoll(TCP_Server *tcp_server, const Mono_Time *mono_time) { while (tcp_epoll_process(tcp_server, mono_time)) { // Keep processing packets until there are no more FDs ready for reading. continue; } } #endif void do_TCP_server(TCP_Server *tcp_server, Mono_Time *mono_time) { #ifdef TCP_SERVER_USE_EPOLL do_TCP_epoll(tcp_server, mono_time); #else do_TCP_accept_new(tcp_server); do_TCP_incoming(tcp_server); do_TCP_unconfirmed(tcp_server, mono_time); #endif do_TCP_confirmed(tcp_server, mono_time); } void kill_TCP_server(TCP_Server *tcp_server) { for (uint32_t i = 0; i < tcp_server->num_listening_socks; ++i) { kill_sock(tcp_server->socks_listening[i]); } if (tcp_server->onion) { set_callback_handle_recv_1(tcp_server->onion, nullptr, nullptr); } bs_list_free(&tcp_server->accepted_key_list); #ifdef TCP_SERVER_USE_EPOLL close(tcp_server->efd); #endif for (uint32_t i = 0; i < MAX_INCOMING_CONNECTIONS; ++i) { wipe_secure_connection(&tcp_server->incoming_connection_queue[i]); wipe_secure_connection(&tcp_server->unconfirmed_connection_queue[i]); } free_accepted_connection_array(tcp_server); free(tcp_server->socks_listening); free(tcp_server); } c-toxcore-0.2.13/toxcore/TCP_server.h000066400000000000000000000060501415350724400173560ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Implementation of the TCP relay server part of Tox. */ #ifndef C_TOXCORE_TOXCORE_TCP_SERVER_H #define C_TOXCORE_TOXCORE_TCP_SERVER_H #include "crypto_core.h" #include "list.h" #include "onion.h" #define MAX_INCOMING_CONNECTIONS 256 #define TCP_MAX_BACKLOG MAX_INCOMING_CONNECTIONS #define MAX_PACKET_SIZE 2048 #define TCP_HANDSHAKE_PLAIN_SIZE (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE) #define TCP_SERVER_HANDSHAKE_SIZE (CRYPTO_NONCE_SIZE + TCP_HANDSHAKE_PLAIN_SIZE + CRYPTO_MAC_SIZE) #define TCP_CLIENT_HANDSHAKE_SIZE (CRYPTO_PUBLIC_KEY_SIZE + TCP_SERVER_HANDSHAKE_SIZE) #define TCP_MAX_OOB_DATA_LENGTH 1024 #define NUM_RESERVED_PORTS 16 #define NUM_CLIENT_CONNECTIONS (256 - NUM_RESERVED_PORTS) #define TCP_PACKET_ROUTING_REQUEST 0 #define TCP_PACKET_ROUTING_RESPONSE 1 #define TCP_PACKET_CONNECTION_NOTIFICATION 2 #define TCP_PACKET_DISCONNECT_NOTIFICATION 3 #define TCP_PACKET_PING 4 #define TCP_PACKET_PONG 5 #define TCP_PACKET_OOB_SEND 6 #define TCP_PACKET_OOB_RECV 7 #define TCP_PACKET_ONION_REQUEST 8 #define TCP_PACKET_ONION_RESPONSE 9 #define ARRAY_ENTRY_SIZE 6 /* frequency to ping connected nodes and timeout in seconds */ #define TCP_PING_FREQUENCY 30 #define TCP_PING_TIMEOUT 10 typedef enum TCP_Status { TCP_STATUS_NO_STATUS, TCP_STATUS_CONNECTED, TCP_STATUS_UNCONFIRMED, TCP_STATUS_CONFIRMED, } TCP_Status; typedef struct TCP_Priority_List TCP_Priority_List; struct TCP_Priority_List { TCP_Priority_List *next; uint16_t size; uint16_t sent; uint8_t data[]; }; void wipe_priority_list(TCP_Priority_List *p); typedef struct TCP_Server TCP_Server; const uint8_t *tcp_server_public_key(const TCP_Server *tcp_server); size_t tcp_server_listen_count(const TCP_Server *tcp_server); /* Create new TCP server instance. */ TCP_Server *new_TCP_server(const Logger *logger, uint8_t ipv6_enabled, uint16_t num_sockets, const uint16_t *ports, const uint8_t *secret_key, Onion *onion); /* Run the TCP_server */ void do_TCP_server(TCP_Server *tcp_server, Mono_Time *mono_time); /* Kill the TCP server */ void kill_TCP_server(TCP_Server *tcp_server); /* Read the next two bytes in TCP stream then convert them to * length (host byte order). * * return length on success * return 0 if nothing has been read from socket. * return -1 on failure. */ uint16_t read_TCP_length(const Logger *logger, Socket sock); /* Read length bytes from socket. * * return length on success * return -1 on failure/no data in buffer. */ int read_TCP_packet(const Logger *logger, Socket sock, uint8_t *data, uint16_t length); /* return length of received packet on success. * return 0 if could not read any packet. * return -1 on failure (connection must be killed). */ int read_packet_TCP_secure_connection(const Logger *logger, Socket sock, uint16_t *next_packet_length, const uint8_t *shared_key, uint8_t *recv_nonce, uint8_t *data, uint16_t max_len); #endif c-toxcore-0.2.13/toxcore/ccompat.h000066400000000000000000000030701415350724400167670ustar00rootroot00000000000000/* * C language compatibility macros for varying compiler support. */ #ifndef C_TOXCORE_TOXCORE_CCOMPAT_H #define C_TOXCORE_TOXCORE_CCOMPAT_H //!TOKSTYLE- // Variable length arrays. // VLA(type, name, size) allocates a variable length array with automatic // storage duration. VLA_SIZE(name) evaluates to the runtime size of that array // in bytes. // // If C99 VLAs are not available, an emulation using alloca (stack allocation // "function") is used. Note the semantic difference: alloca'd memory does not // get freed at the end of the declaration's scope. Do not use VLA() in loops or // you may run out of stack space. #if !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L // C99 VLAs. #define VLA(type, name, size) type name[size] #define SIZEOF_VLA sizeof #else // Emulation using alloca. #ifdef _WIN32 #include #elif defined(__linux__) #include #else #include #if !defined(alloca) && defined(__GNUC__) #define alloca __builtin_alloca #endif #endif #define VLA(type, name, size) \ const size_t name##_size = (size) * sizeof(type); \ type *const name = (type *)alloca(name##_size) #define SIZEOF_VLA(name) name##_size #endif #if !defined(__cplusplus) || __cplusplus < 201103L #define nullptr NULL #ifndef static_assert #define static_assert(cond, msg) extern int unused_for_static_assert #endif #endif #ifdef __GNUC__ #define GNU_PRINTF(f, a) __attribute__((__format__(__printf__, f, a))) #else #define GNU_PRINTF(f, a) #endif //!TOKSTYLE+ #endif // C_TOXCORE_TOXCORE_CCOMPAT_H c-toxcore-0.2.13/toxcore/crypto_core.api.h000066400000000000000000000154231415350724400204460ustar00rootroot00000000000000%{ /* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Functions for the core crypto. */ #ifndef C_TOXCORE_TOXCORE_CRYPTO_CORE_H #define C_TOXCORE_TOXCORE_CRYPTO_CORE_H #include #include #include #ifdef __cplusplus extern "C" { #endif %} /** * The number of bytes in a Tox public key. */ const CRYPTO_PUBLIC_KEY_SIZE = 32; /** * The number of bytes in a Tox secret key. */ const CRYPTO_SECRET_KEY_SIZE = 32; /** * The number of bytes in a shared key computed from public and secret keys. */ const CRYPTO_SHARED_KEY_SIZE = 32; /** * The number of bytes in a symmetric key. */ const CRYPTO_SYMMETRIC_KEY_SIZE = CRYPTO_SHARED_KEY_SIZE; /** * The number of bytes needed for the MAC (message authentication code) in an * encrypted message. */ const CRYPTO_MAC_SIZE = 16; /** * The number of bytes in a nonce used for encryption/decryption. */ const CRYPTO_NONCE_SIZE = 24; /** * The number of bytes in a SHA256 hash. */ const CRYPTO_SHA256_SIZE = 32; /** * The number of bytes in a SHA512 hash. */ const CRYPTO_SHA512_SIZE = 64; /** * A `memcmp`-like function whose running time does not depend on the input * bytes, only on the input length. Useful to compare sensitive data where * timing attacks could reveal that data. * * This means for instance that comparing "aaaa" and "aaaa" takes 4 time, and * "aaaa" and "baaa" also takes 4 time. With a regular `memcmp`, the latter may * take 1 time, because it immediately knows that the two strings are not equal. */ static int32_t crypto_memcmp(const uint8_t *p1, const uint8_t *p2, size_t length); /** * A `bzero`-like function which won't be optimised away by the compiler. Some * compilers will inline `bzero` or `memset` if they can prove that there will * be no reads to the written data. Use this function if you want to be sure the * memory is indeed zeroed. */ static void crypto_memzero(void *data, size_t length); /** * Compute a SHA256 hash (32 bytes). */ static void crypto_sha256(uint8_t[CRYPTO_SHA256_SIZE] hash, const uint8_t[length] data); /** * Compute a SHA512 hash (64 bytes). */ static void crypto_sha512(uint8_t[CRYPTO_SHA512_SIZE] hash, const uint8_t[length] data); /** * Compare 2 public keys of length CRYPTO_PUBLIC_KEY_SIZE, not vulnerable to * timing attacks. * * @return 0 if both mem locations of length are equal, -1 if they are not. */ static int32_t public_key_cmp( const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] pk1, const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] pk2); namespace random { /** * Return a random 8 bit integer. */ static uint8_t u08(); /** * Return a random 16 bit integer. */ static uint16_t u16(); /** * Return a random 32 bit integer. */ static uint32_t u32(); /** * Return a random 64 bit integer. */ static uint64_t u64(); /** * Fill the given nonce with random bytes. */ static void nonce(uint8_t[CRYPTO_NONCE_SIZE] nonce); /** * Fill an array of bytes with random values. */ static void bytes(uint8_t[length] bytes); } /** * Check if a Tox public key CRYPTO_PUBLIC_KEY_SIZE is valid or not. This * should only be used for input validation. * * @return false if it isn't, true if it is. */ static bool public_key_valid(const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key); /** * Generate a new random keypair. Every call to this function is likely to * generate a different keypair. */ static int32_t crypto_new_keypair( uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key); /** * Derive the public key from a given secret key. */ static void crypto_derive_public_key( uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, const uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key); /** * Encrypt plain text of the given length to encrypted of length + * $CRYPTO_MAC_SIZE using the public key ($CRYPTO_PUBLIC_KEY_SIZE bytes) of the * receiver and the secret key of the sender and a $CRYPTO_NONCE_SIZE byte * nonce. * * @return -1 if there was a problem, length of encrypted data if everything * was fine. */ static int32_t encrypt_data( const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, const uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key, const uint8_t[CRYPTO_NONCE_SIZE] nonce, const uint8_t[length] plain, uint8_t *encrypted); /** * Decrypt encrypted text of the given length to plain text of the given length * - $CRYPTO_MAC_SIZE using the public key ($CRYPTO_PUBLIC_KEY_SIZE bytes) of * the sender, the secret key of the receiver and a $CRYPTO_NONCE_SIZE byte * nonce. * * @return -1 if there was a problem (decryption failed), length of plain text * data if everything was fine. */ static int32_t decrypt_data( const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, const uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key, const uint8_t[CRYPTO_NONCE_SIZE] nonce, const uint8_t[length] encrypted, uint8_t *plain); /** * Fast encrypt/decrypt operations. Use if this is not a one-time communication. * $encrypt_precompute does the shared-key generation once so it does not have * to be performed on every encrypt/decrypt. */ static int32_t encrypt_precompute( const uint8_t[CRYPTO_PUBLIC_KEY_SIZE] public_key, const uint8_t[CRYPTO_SECRET_KEY_SIZE] secret_key, uint8_t[CRYPTO_SHARED_KEY_SIZE] shared_key); /** * Encrypts plain of length length to encrypted of length + $CRYPTO_MAC_SIZE * using a shared key $CRYPTO_SYMMETRIC_KEY_SIZE big and a $CRYPTO_NONCE_SIZE * byte nonce. * * @return -1 if there was a problem, length of encrypted data if everything * was fine. */ static int32_t encrypt_data_symmetric( const uint8_t[CRYPTO_SHARED_KEY_SIZE] shared_key, const uint8_t[CRYPTO_NONCE_SIZE] nonce, const uint8_t[length] plain, uint8_t *encrypted); /** * Decrypts encrypted of length length to plain of length length - * $CRYPTO_MAC_SIZE using a shared key CRYPTO_SHARED_KEY_SIZE big and a * $CRYPTO_NONCE_SIZE byte nonce. * * @return -1 if there was a problem (decryption failed), length of plain data * if everything was fine. */ static int32_t decrypt_data_symmetric( const uint8_t[CRYPTO_SHARED_KEY_SIZE] shared_key, const uint8_t[CRYPTO_NONCE_SIZE] nonce, const uint8_t[length] encrypted, uint8_t *plain); /** * Increment the given nonce by 1 in big endian (rightmost byte incremented * first). */ static void increment_nonce(uint8_t[CRYPTO_NONCE_SIZE] nonce); /** * Increment the given nonce by a given number. The number should be in host * byte order. */ static void increment_nonce_number(uint8_t[CRYPTO_NONCE_SIZE] nonce, uint32_t host_order_num); /** * Fill a key CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes. */ static void new_symmetric_key(uint8_t[CRYPTO_SYMMETRIC_KEY_SIZE] key); %{ #ifdef __cplusplus } // extern "C" #endif #endif // C_TOXCORE_TOXCORE_CRYPTO_CORE_H %} c-toxcore-0.2.13/toxcore/crypto_core.c000066400000000000000000000241501415350724400176660ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Functions for the core crypto. * * NOTE: This code has to be perfect. We don't mess around with encryption. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "crypto_core.h" #include #include #include "ccompat.h" #ifndef VANILLA_NACL /* We use libsodium by default. */ #include #else #include #include #include #include #include #include #include #define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES) #endif //!TOKSTYLE- static_assert(CRYPTO_PUBLIC_KEY_SIZE == crypto_box_PUBLICKEYBYTES, "CRYPTO_PUBLIC_KEY_SIZE should be equal to crypto_box_PUBLICKEYBYTES"); static_assert(CRYPTO_SECRET_KEY_SIZE == crypto_box_SECRETKEYBYTES, "CRYPTO_SECRET_KEY_SIZE should be equal to crypto_box_SECRETKEYBYTES"); static_assert(CRYPTO_SHARED_KEY_SIZE == crypto_box_BEFORENMBYTES, "CRYPTO_SHARED_KEY_SIZE should be equal to crypto_box_BEFORENMBYTES"); static_assert(CRYPTO_SYMMETRIC_KEY_SIZE == crypto_box_BEFORENMBYTES, "CRYPTO_SYMMETRIC_KEY_SIZE should be equal to crypto_box_BEFORENMBYTES"); static_assert(CRYPTO_MAC_SIZE == crypto_box_MACBYTES, "CRYPTO_MAC_SIZE should be equal to crypto_box_MACBYTES"); static_assert(CRYPTO_NONCE_SIZE == crypto_box_NONCEBYTES, "CRYPTO_NONCE_SIZE should be equal to crypto_box_NONCEBYTES"); static_assert(CRYPTO_SHA256_SIZE == crypto_hash_sha256_BYTES, "CRYPTO_SHA256_SIZE should be equal to crypto_hash_sha256_BYTES"); static_assert(CRYPTO_SHA512_SIZE == crypto_hash_sha512_BYTES, "CRYPTO_SHA512_SIZE should be equal to crypto_hash_sha512_BYTES"); static_assert(CRYPTO_PUBLIC_KEY_SIZE == 32, "CRYPTO_PUBLIC_KEY_SIZE is required to be 32 bytes for public_key_cmp to work"); //!TOKSTYLE+ static uint8_t *crypto_malloc(size_t bytes) { return (uint8_t *)malloc(bytes); } static void crypto_free(uint8_t *ptr, size_t bytes) { if (ptr != nullptr) { crypto_memzero(ptr, bytes); } free(ptr); } int32_t public_key_cmp(const uint8_t *pk1, const uint8_t *pk2) { return crypto_verify_32(pk1, pk2); } uint8_t random_u08(void) { uint8_t randnum; random_bytes(&randnum, 1); return randnum; } uint16_t random_u16(void) { uint16_t randnum; random_bytes((uint8_t *)&randnum, sizeof(randnum)); return randnum; } uint32_t random_u32(void) { uint32_t randnum; random_bytes((uint8_t *)&randnum, sizeof(randnum)); return randnum; } uint64_t random_u64(void) { uint64_t randnum; random_bytes((uint8_t *)&randnum, sizeof(randnum)); return randnum; } bool public_key_valid(const uint8_t *public_key) { if (public_key[31] >= 128) { /* Last bit of key is always zero. */ return 0; } return 1; } /* Precomputes the shared key from their public_key and our secret_key. * This way we can avoid an expensive elliptic curve scalar multiply for each * encrypt/decrypt operation. * shared_key has to be crypto_box_BEFORENMBYTES bytes long. */ int32_t encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *shared_key) { return crypto_box_beforenm(shared_key, public_key, secret_key); } int32_t encrypt_data_symmetric(const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, size_t length, uint8_t *encrypted) { if (length == 0 || !secret_key || !nonce || !plain || !encrypted) { return -1; } const size_t size_temp_plain = length + crypto_box_ZEROBYTES; const size_t size_temp_encrypted = length + crypto_box_MACBYTES + crypto_box_BOXZEROBYTES; uint8_t *temp_plain = crypto_malloc(size_temp_plain); uint8_t *temp_encrypted = crypto_malloc(size_temp_encrypted); if (temp_plain == nullptr || temp_encrypted == nullptr) { crypto_free(temp_plain, size_temp_plain); crypto_free(temp_encrypted, size_temp_encrypted); return -1; } memset(temp_plain, 0, crypto_box_ZEROBYTES); // Pad the message with 32 0 bytes. memcpy(temp_plain + crypto_box_ZEROBYTES, plain, length); if (crypto_box_afternm(temp_encrypted, temp_plain, length + crypto_box_ZEROBYTES, nonce, secret_key) != 0) { crypto_free(temp_plain, size_temp_plain); crypto_free(temp_encrypted, size_temp_encrypted); return -1; } // Unpad the encrypted message. memcpy(encrypted, temp_encrypted + crypto_box_BOXZEROBYTES, length + crypto_box_MACBYTES); crypto_free(temp_plain, size_temp_plain); crypto_free(temp_encrypted, size_temp_encrypted); return length + crypto_box_MACBYTES; } int32_t decrypt_data_symmetric(const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *encrypted, size_t length, uint8_t *plain) { if (length <= crypto_box_BOXZEROBYTES || !secret_key || !nonce || !encrypted || !plain) { return -1; } const size_t size_temp_plain = length + crypto_box_ZEROBYTES; const size_t size_temp_encrypted = length + crypto_box_BOXZEROBYTES; uint8_t *temp_plain = crypto_malloc(size_temp_plain); uint8_t *temp_encrypted = crypto_malloc(size_temp_encrypted); if (temp_plain == nullptr || temp_encrypted == nullptr) { crypto_free(temp_plain, size_temp_plain); crypto_free(temp_encrypted, size_temp_encrypted); return -1; } memset(temp_encrypted, 0, crypto_box_BOXZEROBYTES); // Pad the message with 16 0 bytes. memcpy(temp_encrypted + crypto_box_BOXZEROBYTES, encrypted, length); if (crypto_box_open_afternm(temp_plain, temp_encrypted, length + crypto_box_BOXZEROBYTES, nonce, secret_key) != 0) { crypto_free(temp_plain, size_temp_plain); crypto_free(temp_encrypted, size_temp_encrypted); return -1; } memcpy(plain, temp_plain + crypto_box_ZEROBYTES, length - crypto_box_MACBYTES); crypto_free(temp_plain, size_temp_plain); crypto_free(temp_encrypted, size_temp_encrypted); return length - crypto_box_MACBYTES; } int32_t encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, size_t length, uint8_t *encrypted) { if (!public_key || !secret_key) { return -1; } uint8_t k[crypto_box_BEFORENMBYTES]; encrypt_precompute(public_key, secret_key, k); int ret = encrypt_data_symmetric(k, nonce, plain, length, encrypted); crypto_memzero(k, sizeof(k)); return ret; } int32_t decrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *encrypted, size_t length, uint8_t *plain) { if (!public_key || !secret_key) { return -1; } uint8_t k[crypto_box_BEFORENMBYTES]; encrypt_precompute(public_key, secret_key, k); int ret = decrypt_data_symmetric(k, nonce, encrypted, length, plain); crypto_memzero(k, sizeof(k)); return ret; } /* Increment the given nonce by 1. */ void increment_nonce(uint8_t *nonce) { /* TODO(irungentoo): use `increment_nonce_number(nonce, 1)` or * sodium_increment (change to little endian). * * NOTE don't use breaks inside this loop. * In particular, make sure, as far as possible, * that loop bounds and their potential underflow or overflow * are independent of user-controlled input (you may have heard of the Heartbleed bug). */ uint32_t i = crypto_box_NONCEBYTES; uint_fast16_t carry = 1U; for (; i != 0; --i) { carry += (uint_fast16_t)nonce[i - 1]; nonce[i - 1] = (uint8_t)carry; carry >>= 8; } } static uint32_t host_to_network(uint32_t x) { #if !defined(BYTE_ORDER) || BYTE_ORDER == LITTLE_ENDIAN return ((x >> 24) & 0x000000FF) | // move byte 3 to byte 0 ((x >> 8) & 0x0000FF00) | // move byte 2 to byte 1 ((x << 8) & 0x00FF0000) | // move byte 1 to byte 2 ((x << 24) & 0xFF000000); // move byte 0 to byte 3 #else return x; #endif } /* increment the given nonce by num */ void increment_nonce_number(uint8_t *nonce, uint32_t host_order_num) { /* NOTE don't use breaks inside this loop * In particular, make sure, as far as possible, * that loop bounds and their potential underflow or overflow * are independent of user-controlled input (you may have heard of the Heartbleed bug). */ const uint32_t big_endian_num = host_to_network(host_order_num); const uint8_t *const num_vec = (const uint8_t *)&big_endian_num; uint8_t num_as_nonce[crypto_box_NONCEBYTES] = {0}; num_as_nonce[crypto_box_NONCEBYTES - 4] = num_vec[0]; num_as_nonce[crypto_box_NONCEBYTES - 3] = num_vec[1]; num_as_nonce[crypto_box_NONCEBYTES - 2] = num_vec[2]; num_as_nonce[crypto_box_NONCEBYTES - 1] = num_vec[3]; uint32_t i = crypto_box_NONCEBYTES; uint_fast16_t carry = 0U; for (; i != 0; --i) { carry += (uint_fast16_t)nonce[i - 1] + (uint_fast16_t)num_as_nonce[i - 1]; nonce[i - 1] = (uint8_t)carry; carry >>= 8; } } /* Fill the given nonce with random bytes. */ void random_nonce(uint8_t *nonce) { random_bytes(nonce, crypto_box_NONCEBYTES); } /* Fill a key CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes */ void new_symmetric_key(uint8_t *key) { random_bytes(key, CRYPTO_SYMMETRIC_KEY_SIZE); } int32_t crypto_new_keypair(uint8_t *public_key, uint8_t *secret_key) { return crypto_box_keypair(public_key, secret_key); } void crypto_derive_public_key(uint8_t *public_key, const uint8_t *secret_key) { crypto_scalarmult_curve25519_base(public_key, secret_key); } void crypto_sha256(uint8_t *hash, const uint8_t *data, size_t length) { crypto_hash_sha256(hash, data, length); } void crypto_sha512(uint8_t *hash, const uint8_t *data, size_t length) { crypto_hash_sha512(hash, data, length); } void random_bytes(uint8_t *data, size_t length) { randombytes(data, length); } c-toxcore-0.2.13/toxcore/crypto_core.h000066400000000000000000000147711415350724400177030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Functions for the core crypto. */ #ifndef C_TOXCORE_TOXCORE_CRYPTO_CORE_H #define C_TOXCORE_TOXCORE_CRYPTO_CORE_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * The number of bytes in a Tox public key. */ #define CRYPTO_PUBLIC_KEY_SIZE 32 uint32_t crypto_public_key_size(void); /** * The number of bytes in a Tox secret key. */ #define CRYPTO_SECRET_KEY_SIZE 32 uint32_t crypto_secret_key_size(void); /** * The number of bytes in a shared key computed from public and secret keys. */ #define CRYPTO_SHARED_KEY_SIZE 32 uint32_t crypto_shared_key_size(void); /** * The number of bytes in a symmetric key. */ #define CRYPTO_SYMMETRIC_KEY_SIZE CRYPTO_SHARED_KEY_SIZE uint32_t crypto_symmetric_key_size(void); /** * The number of bytes needed for the MAC (message authentication code) in an * encrypted message. */ #define CRYPTO_MAC_SIZE 16 uint32_t crypto_mac_size(void); /** * The number of bytes in a nonce used for encryption/decryption. */ #define CRYPTO_NONCE_SIZE 24 uint32_t crypto_nonce_size(void); /** * The number of bytes in a SHA256 hash. */ #define CRYPTO_SHA256_SIZE 32 uint32_t crypto_sha256_size(void); /** * The number of bytes in a SHA512 hash. */ #define CRYPTO_SHA512_SIZE 64 uint32_t crypto_sha512_size(void); /** * A `memcmp`-like function whose running time does not depend on the input * bytes, only on the input length. Useful to compare sensitive data where * timing attacks could reveal that data. * * This means for instance that comparing "aaaa" and "aaaa" takes 4 time, and * "aaaa" and "baaa" also takes 4 time. With a regular `memcmp`, the latter may * take 1 time, because it immediately knows that the two strings are not equal. */ int32_t crypto_memcmp(const uint8_t *p1, const uint8_t *p2, size_t length); /** * A `bzero`-like function which won't be optimised away by the compiler. Some * compilers will inline `bzero` or `memset` if they can prove that there will * be no reads to the written data. Use this function if you want to be sure the * memory is indeed zeroed. */ void crypto_memzero(void *data, size_t length); /** * Compute a SHA256 hash (32 bytes). */ void crypto_sha256(uint8_t *hash, const uint8_t *data, size_t length); /** * Compute a SHA512 hash (64 bytes). */ void crypto_sha512(uint8_t *hash, const uint8_t *data, size_t length); /** * Compare 2 public keys of length CRYPTO_PUBLIC_KEY_SIZE, not vulnerable to * timing attacks. * * @return 0 if both mem locations of length are equal, -1 if they are not. */ int32_t public_key_cmp(const uint8_t *pk1, const uint8_t *pk2); /** * Return a random 8 bit integer. */ uint8_t random_u08(void); /** * Return a random 16 bit integer. */ uint16_t random_u16(void); /** * Return a random 32 bit integer. */ uint32_t random_u32(void); /** * Return a random 64 bit integer. */ uint64_t random_u64(void); /** * Fill the given nonce with random bytes. */ void random_nonce(uint8_t *nonce); /** * Fill an array of bytes with random values. */ void random_bytes(uint8_t *bytes, size_t length); /** * Check if a Tox public key CRYPTO_PUBLIC_KEY_SIZE is valid or not. This * should only be used for input validation. * * @return false if it isn't, true if it is. */ bool public_key_valid(const uint8_t *public_key); /** * Generate a new random keypair. Every call to this function is likely to * generate a different keypair. */ int32_t crypto_new_keypair(uint8_t *public_key, uint8_t *secret_key); /** * Derive the public key from a given secret key. */ void crypto_derive_public_key(uint8_t *public_key, const uint8_t *secret_key); /** * Encrypt plain text of the given length to encrypted of length + * CRYPTO_MAC_SIZE using the public key (CRYPTO_PUBLIC_KEY_SIZE bytes) of the * receiver and the secret key of the sender and a CRYPTO_NONCE_SIZE byte * nonce. * * @return -1 if there was a problem, length of encrypted data if everything * was fine. */ int32_t encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, size_t length, uint8_t *encrypted); /** * Decrypt encrypted text of the given length to plain text of the given length * - CRYPTO_MAC_SIZE using the public key (CRYPTO_PUBLIC_KEY_SIZE bytes) of * the sender, the secret key of the receiver and a CRYPTO_NONCE_SIZE byte * nonce. * * @return -1 if there was a problem (decryption failed), length of plain text * data if everything was fine. */ int32_t decrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *encrypted, size_t length, uint8_t *plain); /** * Fast encrypt/decrypt operations. Use if this is not a one-time communication. * encrypt_precompute does the shared-key generation once so it does not have * to be performed on every encrypt/decrypt. */ int32_t encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *shared_key); /** * Encrypts plain of length length to encrypted of length + CRYPTO_MAC_SIZE * using a shared key CRYPTO_SYMMETRIC_KEY_SIZE big and a CRYPTO_NONCE_SIZE * byte nonce. * * @return -1 if there was a problem, length of encrypted data if everything * was fine. */ int32_t encrypt_data_symmetric(const uint8_t *shared_key, const uint8_t *nonce, const uint8_t *plain, size_t length, uint8_t *encrypted); /** * Decrypts encrypted of length length to plain of length length - * CRYPTO_MAC_SIZE using a shared key CRYPTO_SHARED_KEY_SIZE big and a * CRYPTO_NONCE_SIZE byte nonce. * * @return -1 if there was a problem (decryption failed), length of plain data * if everything was fine. */ int32_t decrypt_data_symmetric(const uint8_t *shared_key, const uint8_t *nonce, const uint8_t *encrypted, size_t length, uint8_t *plain); /** * Increment the given nonce by 1 in big endian (rightmost byte incremented * first). */ void increment_nonce(uint8_t *nonce); /** * Increment the given nonce by a given number. The number should be in host * byte order. */ void increment_nonce_number(uint8_t *nonce, uint32_t host_order_num); /** * Fill a key CRYPTO_SYMMETRIC_KEY_SIZE big with random bytes. */ void new_symmetric_key(uint8_t *key); #ifdef __cplusplus } // extern "C" #endif #endif // C_TOXCORE_TOXCORE_CRYPTO_CORE_H c-toxcore-0.2.13/toxcore/crypto_core_mem.c000066400000000000000000000041711415350724400205250ustar00rootroot00000000000000/* * ISC License * * Copyright (c) 2013-2016 * Frank Denis * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "crypto_core.h" #ifndef VANILLA_NACL /* We use libsodium by default. */ #include #else #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) #include #include #endif #endif void crypto_memzero(void *data, size_t length) { #ifndef VANILLA_NACL sodium_memzero(data, length); #elif defined(_WIN32) SecureZeroMemory(data, length); #elif defined(HAVE_MEMSET_S) if (length > 0U) { errno_t code = memset_s(data, (rsize_t) length, 0, (rsize_t) length); if (code != 0) { abort(); /* LCOV_EXCL_LINE */ } } #elif defined(HAVE_EXPLICIT_BZERO) explicit_bzero(data, length); #else //!TOKSTYLE- volatile uint8_t *volatile pnt = data; //!TOKSTYLE+ size_t i = (size_t) 0U; while (i < length) { pnt[i] = 0U; ++i; } #endif } int32_t crypto_memcmp(const uint8_t *p1, const uint8_t *p2, size_t length) { #ifndef VANILLA_NACL return sodium_memcmp(p1, p2, length); #else //!TOKSTYLE- const volatile uint8_t *volatile b1 = p1; const volatile uint8_t *volatile b2 = p2; //!TOKSTYLE+ size_t i; uint8_t d = (uint8_t) 0U; for (i = 0U; i < length; ++i) { d |= b1[i] ^ b2[i]; } return (1 & ((d - 1) >> 8)) - 1; #endif } c-toxcore-0.2.13/toxcore/crypto_core_test.cc000066400000000000000000000060531415350724400210720ustar00rootroot00000000000000#include "crypto_core.h" #include #include #include namespace { enum { /** * The size of the arrays to compare. This was chosen to take around 2000 * CPU clocks on x86_64. * * This is 1MiB. */ CRYPTO_TEST_MEMCMP_SIZE = 1024 * 1024, /** * The number of times we run memcmp in the test. * * We compute the median time taken to reduce error margins. */ CRYPTO_TEST_MEMCMP_ITERATIONS = 500, /** * The margin of error (in clocks) we allow for this test. * * Should be within 0.5% of ~2000 CPU clocks. In reality, the code is much * more precise and is usually within 1 CPU clock. */ CRYPTO_TEST_MEMCMP_EPS = 10, }; clock_t memcmp_time(uint8_t const *a, uint8_t const *b, size_t len) { clock_t start = clock(); volatile int result = crypto_memcmp(a, b, len); (void)result; return clock() - start; } /** * This function performs the actual timing. It interleaves comparison of * equal and non-equal arrays to reduce the influence of external effects * such as the machine being a little more busy 1 second later. */ std::pair memcmp_median(uint8_t const *src, uint8_t const *same, uint8_t const *not_same, size_t len) { clock_t same_results[CRYPTO_TEST_MEMCMP_ITERATIONS]; clock_t not_same_results[CRYPTO_TEST_MEMCMP_ITERATIONS]; for (size_t i = 0; i < CRYPTO_TEST_MEMCMP_ITERATIONS; i++) { same_results[i] = memcmp_time(src, same, len); not_same_results[i] = memcmp_time(src, not_same, len); } std::sort(same_results, same_results + CRYPTO_TEST_MEMCMP_ITERATIONS); clock_t const same_median = same_results[CRYPTO_TEST_MEMCMP_ITERATIONS / 2]; std::sort(not_same_results, not_same_results + CRYPTO_TEST_MEMCMP_ITERATIONS); clock_t const not_same_median = not_same_results[CRYPTO_TEST_MEMCMP_ITERATIONS / 2]; return {same_median, not_same_median}; } /** * This test checks whether crypto_memcmp takes the same time for equal and * non-equal chunks of memory. */ TEST(CryptoCore, MemcmpTimingIsDataIndependent) { // A random piece of memory. std::vector src(CRYPTO_TEST_MEMCMP_SIZE); random_bytes(src.data(), CRYPTO_TEST_MEMCMP_SIZE); // A separate piece of memory containing the same data. std::vector same = src; // Another piece of memory containing different data. std::vector not_same(CRYPTO_TEST_MEMCMP_SIZE); random_bytes(not_same.data(), CRYPTO_TEST_MEMCMP_SIZE); // Once we have C++17: // auto const [same_median, not_same_median] = auto const result = memcmp_median(src.data(), same.data(), not_same.data(), CRYPTO_TEST_MEMCMP_SIZE); clock_t const delta = std::max(result.first, result.second) - std::min(result.first, result.second); EXPECT_LT(delta, CRYPTO_TEST_MEMCMP_EPS) << "Delta time is too long (" << delta << " >= " << CRYPTO_TEST_MEMCMP_EPS << ")\n" << "Time of the same data comparison: " << result.first << " clocks\n" << "Time of the different data comparison: " << result.second << " clocks"; } } // namespace c-toxcore-0.2.13/toxcore/friend_connection.c000066400000000000000000000704511415350724400210310ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Connection to friends. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "friend_connection.h" #include #include #include "mono_time.h" #include "util.h" #define PORTS_PER_DISCOVERY 10 typedef struct Friend_Conn_Callbacks { fc_status_cb *status_callback; fc_data_cb *data_callback; fc_lossy_data_cb *lossy_data_callback; void *callback_object; int callback_id; } Friend_Conn_Callbacks; typedef struct Friend_Conn { uint8_t status; uint8_t real_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t dht_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint16_t dht_lock; IP_Port dht_ip_port; uint64_t dht_pk_lastrecv; uint64_t dht_ip_port_lastrecv; int onion_friendnum; int crypt_connection_id; uint64_t ping_lastrecv; uint64_t ping_lastsent; uint64_t share_relays_lastsent; Friend_Conn_Callbacks callbacks[MAX_FRIEND_CONNECTION_CALLBACKS]; uint16_t lock_count; Node_format tcp_relays[FRIEND_MAX_STORED_TCP_RELAYS]; uint16_t tcp_relay_counter; bool hosting_tcp_relay; } Friend_Conn; struct Friend_Connections { const Mono_Time *mono_time; const Logger *logger; Net_Crypto *net_crypto; DHT *dht; Onion_Client *onion_c; Friend_Conn *conns; uint32_t num_cons; fr_request_cb *fr_request_callback; void *fr_request_object; global_status_cb *global_status_callback; void *global_status_callback_object; uint64_t last_lan_discovery; uint16_t next_lan_port; bool local_discovery_enabled; }; Net_Crypto *friendconn_net_crypto(const Friend_Connections *fr_c) { return fr_c->net_crypto; } /* return true if the friendcon_id is valid. * return false if the friendcon_id is not valid. */ static bool friendconn_id_valid(const Friend_Connections *fr_c, int friendcon_id) { return (unsigned int)friendcon_id < fr_c->num_cons && fr_c->conns != nullptr && fr_c->conns[friendcon_id].status != FRIENDCONN_STATUS_NONE; } /* Set the size of the friend connections list to num. * * return false if realloc fails. * return true if it succeeds. */ static bool realloc_friendconns(Friend_Connections *fr_c, uint32_t num) { if (num == 0) { free(fr_c->conns); fr_c->conns = nullptr; return true; } Friend_Conn *newgroup_cons = (Friend_Conn *)realloc(fr_c->conns, num * sizeof(Friend_Conn)); if (newgroup_cons == nullptr) { return false; } fr_c->conns = newgroup_cons; return true; } /* Create a new empty friend connection. * * return -1 on failure. * return friendcon_id on success. */ static int create_friend_conn(Friend_Connections *fr_c) { for (uint32_t i = 0; i < fr_c->num_cons; ++i) { if (fr_c->conns[i].status == FRIENDCONN_STATUS_NONE) { return i; } } if (!realloc_friendconns(fr_c, fr_c->num_cons + 1)) { return -1; } const int id = fr_c->num_cons; ++fr_c->num_cons; memset(&fr_c->conns[id], 0, sizeof(Friend_Conn)); return id; } /* Wipe a friend connection. * * return -1 on failure. * return 0 on success. */ static int wipe_friend_conn(Friend_Connections *fr_c, int friendcon_id) { if (!friendconn_id_valid(fr_c, friendcon_id)) { return -1; } memset(&fr_c->conns[friendcon_id], 0, sizeof(Friend_Conn)); uint32_t i; for (i = fr_c->num_cons; i != 0; --i) { if (fr_c->conns[i - 1].status != FRIENDCONN_STATUS_NONE) { break; } } if (fr_c->num_cons != i) { fr_c->num_cons = i; realloc_friendconns(fr_c, fr_c->num_cons); } return 0; } static Friend_Conn *get_conn(const Friend_Connections *fr_c, int friendcon_id) { if (!friendconn_id_valid(fr_c, friendcon_id)) { return nullptr; } return &fr_c->conns[friendcon_id]; } /* return friendcon_id corresponding to the real public key on success. * return -1 on failure. */ int getfriend_conn_id_pk(Friend_Connections *fr_c, const uint8_t *real_pk) { for (uint32_t i = 0; i < fr_c->num_cons; ++i) { Friend_Conn *friend_con = get_conn(fr_c, i); if (friend_con) { if (public_key_cmp(friend_con->real_public_key, real_pk) == 0) { return i; } } } return -1; } /* Add a TCP relay associated to the friend. * * return -1 on failure. * return 0 on success. */ int friend_add_tcp_relay(Friend_Connections *fr_c, int friendcon_id, IP_Port ip_port, const uint8_t *public_key) { Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } /* Local ip and same pk means that they are hosting a TCP relay. */ if (ip_is_local(ip_port.ip) && public_key_cmp(friend_con->dht_temp_pk, public_key) == 0) { if (!net_family_is_unspec(friend_con->dht_ip_port.ip.family)) { ip_port.ip = friend_con->dht_ip_port.ip; } else { friend_con->hosting_tcp_relay = 0; } } const uint16_t index = friend_con->tcp_relay_counter % FRIEND_MAX_STORED_TCP_RELAYS; for (unsigned i = 0; i < FRIEND_MAX_STORED_TCP_RELAYS; ++i) { if (!net_family_is_unspec(friend_con->tcp_relays[i].ip_port.ip.family) && public_key_cmp(friend_con->tcp_relays[i].public_key, public_key) == 0) { memset(&friend_con->tcp_relays[i], 0, sizeof(Node_format)); } } friend_con->tcp_relays[index].ip_port = ip_port; memcpy(friend_con->tcp_relays[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); ++friend_con->tcp_relay_counter; return add_tcp_relay_peer(fr_c->net_crypto, friend_con->crypt_connection_id, ip_port, public_key); } /* Connect to number saved relays for friend. */ static void connect_to_saved_tcp_relays(Friend_Connections *fr_c, int friendcon_id, unsigned int number) { const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return; } for (unsigned i = 0; (i < FRIEND_MAX_STORED_TCP_RELAYS) && (number != 0); ++i) { const uint16_t index = (friend_con->tcp_relay_counter - (i + 1)) % FRIEND_MAX_STORED_TCP_RELAYS; if (!net_family_is_unspec(friend_con->tcp_relays[index].ip_port.ip.family)) { if (add_tcp_relay_peer(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->tcp_relays[index].ip_port, friend_con->tcp_relays[index].public_key) == 0) { --number; } } } } static unsigned int send_relays(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return 0; } Node_format nodes[MAX_SHARED_RELAYS]; uint8_t data[1024]; const int n = copy_connected_tcp_relays(fr_c->net_crypto, nodes, MAX_SHARED_RELAYS); for (int i = 0; i < n; ++i) { /* Associated the relays being sent with this connection. * On receiving the peer will do the same which will establish the connection. */ friend_add_tcp_relay(fr_c, friendcon_id, nodes[i].ip_port, nodes[i].public_key); } int length = pack_nodes(data + 1, sizeof(data) - 1, nodes, n); if (length <= 0) { return 0; } data[0] = PACKET_ID_SHARE_RELAYS; ++length; if (write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, data, length, 0) != -1) { friend_con->share_relays_lastsent = mono_time_get(fr_c->mono_time); return 1; } return 0; } /* callback for recv TCP relay nodes. */ static int tcp_relay_node_callback(void *object, uint32_t number, IP_Port ip_port, const uint8_t *public_key) { Friend_Connections *fr_c = (Friend_Connections *)object; const Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) { return -1; } if (friend_con->crypt_connection_id != -1) { return friend_add_tcp_relay(fr_c, number, ip_port, public_key); } return add_tcp_relay(fr_c->net_crypto, ip_port, public_key); } static int friend_new_connection(Friend_Connections *fr_c, int friendcon_id); /* Callback for DHT ip_port changes. */ static void dht_ip_callback(void *object, int32_t number, IP_Port ip_port) { Friend_Connections *const fr_c = (Friend_Connections *)object; Friend_Conn *const friend_con = get_conn(fr_c, number); if (!friend_con) { return; } if (friend_con->crypt_connection_id == -1) { friend_new_connection(fr_c, number); } set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, ip_port, 1); friend_con->dht_ip_port = ip_port; friend_con->dht_ip_port_lastrecv = mono_time_get(fr_c->mono_time); if (friend_con->hosting_tcp_relay) { friend_add_tcp_relay(fr_c, number, ip_port, friend_con->dht_temp_pk); friend_con->hosting_tcp_relay = 0; } } static void change_dht_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_public_key) { Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return; } friend_con->dht_pk_lastrecv = mono_time_get(fr_c->mono_time); if (friend_con->dht_lock) { if (dht_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock) != 0) { LOGGER_ERROR(fr_c->logger, "a. Could not delete dht peer. Please report this."); return; } friend_con->dht_lock = 0; } dht_addfriend(fr_c->dht, dht_public_key, dht_ip_callback, fr_c, friendcon_id, &friend_con->dht_lock); memcpy(friend_con->dht_temp_pk, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); } static int handle_status(void *object, int number, uint8_t status, void *userdata) { Friend_Connections *const fr_c = (Friend_Connections *)object; Friend_Conn *const friend_con = get_conn(fr_c, number); if (!friend_con) { return -1; } bool status_changed = 0; if (status) { /* Went online. */ status_changed = 1; friend_con->status = FRIENDCONN_STATUS_CONNECTED; friend_con->ping_lastrecv = mono_time_get(fr_c->mono_time); friend_con->share_relays_lastsent = 0; onion_set_friend_online(fr_c->onion_c, friend_con->onion_friendnum, status); } else { /* Went offline. */ if (friend_con->status != FRIENDCONN_STATUS_CONNECTING) { status_changed = 1; friend_con->dht_pk_lastrecv = mono_time_get(fr_c->mono_time); onion_set_friend_online(fr_c->onion_c, friend_con->onion_friendnum, status); } friend_con->status = FRIENDCONN_STATUS_CONNECTING; friend_con->crypt_connection_id = -1; friend_con->hosting_tcp_relay = 0; } if (status_changed) { if (fr_c->global_status_callback) { fr_c->global_status_callback(fr_c->global_status_callback_object, number, status, userdata); } for (unsigned i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { if (friend_con->callbacks[i].status_callback) { friend_con->callbacks[i].status_callback( friend_con->callbacks[i].callback_object, friend_con->callbacks[i].callback_id, status, userdata); } } } return 0; } /* Callback for dht public key changes. */ static void dht_pk_callback(void *object, int32_t number, const uint8_t *dht_public_key, void *userdata) { Friend_Connections *const fr_c = (Friend_Connections *)object; Friend_Conn *const friend_con = get_conn(fr_c, number); if (!friend_con) { return; } if (public_key_cmp(friend_con->dht_temp_pk, dht_public_key) == 0) { return; } change_dht_pk(fr_c, number, dht_public_key); /* if pk changed, create a new connection.*/ if (friend_con->crypt_connection_id != -1) { crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); friend_con->crypt_connection_id = -1; handle_status(object, number, 0, userdata); /* Going offline. */ } friend_new_connection(fr_c, number); onion_set_friend_DHT_pubkey(fr_c->onion_c, friend_con->onion_friendnum, dht_public_key); } static int handle_packet(void *object, int number, const uint8_t *data, uint16_t length, void *userdata) { if (length == 0) { return -1; } Friend_Connections *const fr_c = (Friend_Connections *)object; Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) { return -1; } if (data[0] == PACKET_ID_FRIEND_REQUESTS) { if (fr_c->fr_request_callback) { fr_c->fr_request_callback(fr_c->fr_request_object, friend_con->real_public_key, data, length, userdata); } return 0; } if (data[0] == PACKET_ID_ALIVE) { friend_con->ping_lastrecv = mono_time_get(fr_c->mono_time); return 0; } if (data[0] == PACKET_ID_SHARE_RELAYS) { Node_format nodes[MAX_SHARED_RELAYS]; const int n = unpack_nodes(nodes, MAX_SHARED_RELAYS, nullptr, data + 1, length - 1, 1); if (n == -1) { return -1; } for (int j = 0; j < n; ++j) { friend_add_tcp_relay(fr_c, number, nodes[j].ip_port, nodes[j].public_key); } return 0; } for (unsigned i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { if (friend_con->callbacks[i].data_callback) { friend_con->callbacks[i].data_callback( friend_con->callbacks[i].callback_object, friend_con->callbacks[i].callback_id, data, length, userdata); } friend_con = get_conn(fr_c, number); if (!friend_con) { return -1; } } return 0; } static int handle_lossy_packet(void *object, int number, const uint8_t *data, uint16_t length, void *userdata) { if (length == 0) { return -1; } Friend_Connections *const fr_c = (Friend_Connections *)object; const Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) { return -1; } for (unsigned i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { if (friend_con->callbacks[i].lossy_data_callback) { friend_con->callbacks[i].lossy_data_callback( friend_con->callbacks[i].callback_object, friend_con->callbacks[i].callback_id, data, length, userdata); } friend_con = get_conn(fr_c, number); if (!friend_con) { return -1; } } return 0; } static int handle_new_connections(void *object, New_Connection *n_c) { Friend_Connections *const fr_c = (Friend_Connections *)object; const int friendcon_id = getfriend_conn_id_pk(fr_c, n_c->public_key); Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } if (friend_con->crypt_connection_id != -1) { return -1; } const int id = accept_crypto_connection(fr_c->net_crypto, n_c); if (id == -1) { return -1; } connection_status_handler(fr_c->net_crypto, id, &handle_status, fr_c, friendcon_id); connection_data_handler(fr_c->net_crypto, id, &handle_packet, fr_c, friendcon_id); connection_lossy_data_handler(fr_c->net_crypto, id, &handle_lossy_packet, fr_c, friendcon_id); friend_con->crypt_connection_id = id; if (!net_family_is_ipv4(n_c->source.ip.family) && !net_family_is_ipv6(n_c->source.ip.family)) { set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->dht_ip_port, 0); } else { friend_con->dht_ip_port = n_c->source; friend_con->dht_ip_port_lastrecv = mono_time_get(fr_c->mono_time); } if (public_key_cmp(friend_con->dht_temp_pk, n_c->dht_public_key) != 0) { change_dht_pk(fr_c, friendcon_id, n_c->dht_public_key); } nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); return 0; } static int friend_new_connection(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } if (friend_con->crypt_connection_id != -1) { return -1; } /* If dht_temp_pk does not contains a pk. */ if (!friend_con->dht_lock) { return -1; } const int id = new_crypto_connection(fr_c->net_crypto, friend_con->real_public_key, friend_con->dht_temp_pk); if (id == -1) { return -1; } friend_con->crypt_connection_id = id; connection_status_handler(fr_c->net_crypto, id, &handle_status, fr_c, friendcon_id); connection_data_handler(fr_c->net_crypto, id, &handle_packet, fr_c, friendcon_id); connection_lossy_data_handler(fr_c->net_crypto, id, &handle_lossy_packet, fr_c, friendcon_id); nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); return 0; } static int send_ping(const Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } const uint8_t ping = PACKET_ID_ALIVE; const int64_t ret = write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, &ping, sizeof(ping), 0); if (ret != -1) { friend_con->ping_lastsent = mono_time_get(fr_c->mono_time); return 0; } return -1; } /* Increases lock_count for the connection with friendcon_id by 1. * * return 0 on success. * return -1 on failure. */ int friend_connection_lock(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } ++friend_con->lock_count; return 0; } /* return FRIENDCONN_STATUS_CONNECTED if the friend is connected. * return FRIENDCONN_STATUS_CONNECTING if the friend isn't connected. * return FRIENDCONN_STATUS_NONE on failure. */ unsigned int friend_con_connected(Friend_Connections *fr_c, int friendcon_id) { const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return 0; } return friend_con->status; } /* Copy public keys associated to friendcon_id. * * return 0 on success. * return -1 on failure. */ int get_friendcon_public_keys(uint8_t *real_pk, uint8_t *dht_temp_pk, Friend_Connections *fr_c, int friendcon_id) { const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } if (real_pk) { memcpy(real_pk, friend_con->real_public_key, CRYPTO_PUBLIC_KEY_SIZE); } if (dht_temp_pk) { memcpy(dht_temp_pk, friend_con->dht_temp_pk, CRYPTO_PUBLIC_KEY_SIZE); } return 0; } /* Set temp dht key for connection. */ void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_temp_pk, void *userdata) { dht_pk_callback(fr_c, friendcon_id, dht_temp_pk, userdata); } /* Set the callbacks for the friend connection. * index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we want the callback to set in the array. * * return 0 on success. * return -1 on failure */ int friend_connection_callbacks(Friend_Connections *fr_c, int friendcon_id, unsigned int index, fc_status_cb *status_callback, fc_data_cb *data_callback, fc_lossy_data_cb *lossy_data_callback, void *object, int number) { Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } if (index >= MAX_FRIEND_CONNECTION_CALLBACKS) { return -1; } friend_con->callbacks[index].status_callback = status_callback; friend_con->callbacks[index].data_callback = data_callback; friend_con->callbacks[index].lossy_data_callback = lossy_data_callback; friend_con->callbacks[index].callback_object = object; friend_con->callbacks[index].callback_id = number; return 0; } /* Set global status callback for friend connections. */ void set_global_status_callback(Friend_Connections *fr_c, global_status_cb *global_status_callback, void *object) { fr_c->global_status_callback = global_status_callback; fr_c->global_status_callback_object = object; } /* return the crypt_connection_id for the connection. * * return crypt_connection_id on success. * return -1 on failure. */ int friend_connection_crypt_connection_id(Friend_Connections *fr_c, int friendcon_id) { const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } return friend_con->crypt_connection_id; } /* Create a new friend connection. * If one to that real public key already exists, increase lock count and return it. * * return -1 on failure. * return connection id on success. */ int new_friend_connection(Friend_Connections *fr_c, const uint8_t *real_public_key) { int friendcon_id = getfriend_conn_id_pk(fr_c, real_public_key); if (friendcon_id != -1) { ++fr_c->conns[friendcon_id].lock_count; return friendcon_id; } friendcon_id = create_friend_conn(fr_c); if (friendcon_id == -1) { return -1; } const int32_t onion_friendnum = onion_addfriend(fr_c->onion_c, real_public_key); if (onion_friendnum == -1) { return -1; } Friend_Conn *const friend_con = &fr_c->conns[friendcon_id]; friend_con->crypt_connection_id = -1; friend_con->status = FRIENDCONN_STATUS_CONNECTING; memcpy(friend_con->real_public_key, real_public_key, CRYPTO_PUBLIC_KEY_SIZE); friend_con->onion_friendnum = onion_friendnum; recv_tcp_relay_handler(fr_c->onion_c, onion_friendnum, &tcp_relay_node_callback, fr_c, friendcon_id); onion_dht_pk_callback(fr_c->onion_c, onion_friendnum, &dht_pk_callback, fr_c, friendcon_id); return friendcon_id; } /* Kill a friend connection. * * return -1 on failure. * return 0 on success. */ int kill_friend_connection(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } if (friend_con->lock_count) { --friend_con->lock_count; return 0; } onion_delfriend(fr_c->onion_c, friend_con->onion_friendnum); crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); if (friend_con->dht_lock) { dht_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock); } return wipe_friend_conn(fr_c, friendcon_id); } /* Set friend request callback. * * This function will be called every time a friend request packet is received. */ void set_friend_request_callback(Friend_Connections *fr_c, fr_request_cb *fr_request_callback, void *object) { fr_c->fr_request_callback = fr_request_callback; fr_c->fr_request_object = object; oniondata_registerhandler(fr_c->onion_c, CRYPTO_PACKET_FRIEND_REQ, fr_request_callback, object); } /* Send a Friend request packet. * * return -1 if failure. * return 0 if it sent the friend request directly to the friend. * return the number of peers it was routed through if it did not send it directly. */ int send_friend_request_packet(Friend_Connections *fr_c, int friendcon_id, uint32_t nospam_num, const uint8_t *data, uint16_t length) { if (1 + sizeof(nospam_num) + length > ONION_CLIENT_MAX_DATA_SIZE || length == 0) { return -1; } const Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) { return -1; } VLA(uint8_t, packet, 1 + sizeof(nospam_num) + length); memcpy(packet + 1, &nospam_num, sizeof(nospam_num)); memcpy(packet + 1 + sizeof(nospam_num), data, length); if (friend_con->status == FRIENDCONN_STATUS_CONNECTED) { packet[0] = PACKET_ID_FRIEND_REQUESTS; return write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, packet, SIZEOF_VLA(packet), 0) != -1; } packet[0] = CRYPTO_PACKET_FRIEND_REQ; const int num = send_onion_data(fr_c->onion_c, friend_con->onion_friendnum, packet, SIZEOF_VLA(packet)); if (num <= 0) { return -1; } return num; } /* Create new friend_connections instance. */ Friend_Connections *new_friend_connections(const Logger *logger, const Mono_Time *mono_time, Onion_Client *onion_c, bool local_discovery_enabled) { if (onion_c == nullptr) { return nullptr; } Friend_Connections *const temp = (Friend_Connections *)calloc(1, sizeof(Friend_Connections)); if (temp == nullptr) { return nullptr; } temp->mono_time = mono_time; temp->logger = logger; temp->dht = onion_get_dht(onion_c); temp->net_crypto = onion_get_net_crypto(onion_c); temp->onion_c = onion_c; temp->local_discovery_enabled = local_discovery_enabled; // Don't include default port in port range temp->next_lan_port = TOX_PORTRANGE_FROM + 1; new_connection_handler(temp->net_crypto, &handle_new_connections, temp); if (temp->local_discovery_enabled) { lan_discovery_init(temp->dht); } return temp; } /* Send a LAN discovery packet every LAN_DISCOVERY_INTERVAL seconds. */ static void lan_discovery(Friend_Connections *fr_c) { if (fr_c->last_lan_discovery + LAN_DISCOVERY_INTERVAL < mono_time_get(fr_c->mono_time)) { const uint16_t first = fr_c->next_lan_port; uint16_t last = first + PORTS_PER_DISCOVERY; last = last > TOX_PORTRANGE_TO ? TOX_PORTRANGE_TO : last; // Always send to default port lan_discovery_send(net_htons(TOX_PORT_DEFAULT), fr_c->dht); // And check some extra ports for (uint16_t port = first; port < last; ++port) { lan_discovery_send(net_htons(port), fr_c->dht); } // Don't include default port in port range fr_c->next_lan_port = last != TOX_PORTRANGE_TO ? last : TOX_PORTRANGE_FROM + 1; fr_c->last_lan_discovery = mono_time_get(fr_c->mono_time); } } /* main friend_connections loop. */ void do_friend_connections(Friend_Connections *fr_c, void *userdata) { const uint64_t temp_time = mono_time_get(fr_c->mono_time); for (uint32_t i = 0; i < fr_c->num_cons; ++i) { Friend_Conn *const friend_con = get_conn(fr_c, i); if (friend_con) { if (friend_con->status == FRIENDCONN_STATUS_CONNECTING) { if (friend_con->dht_pk_lastrecv + FRIEND_DHT_TIMEOUT < temp_time) { if (friend_con->dht_lock) { dht_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock); friend_con->dht_lock = 0; memset(friend_con->dht_temp_pk, 0, CRYPTO_PUBLIC_KEY_SIZE); } } if (friend_con->dht_ip_port_lastrecv + FRIEND_DHT_TIMEOUT < temp_time) { friend_con->dht_ip_port.ip.family = net_family_unspec; } if (friend_con->dht_lock) { if (friend_new_connection(fr_c, i) == 0) { set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->dht_ip_port, 0); connect_to_saved_tcp_relays(fr_c, i, (MAX_FRIEND_TCP_CONNECTIONS / 2)); /* Only fill it half up. */ } } } else if (friend_con->status == FRIENDCONN_STATUS_CONNECTED) { if (friend_con->ping_lastsent + FRIEND_PING_INTERVAL < temp_time) { send_ping(fr_c, i); } if (friend_con->share_relays_lastsent + SHARE_RELAYS_INTERVAL < temp_time) { send_relays(fr_c, i); } if (friend_con->ping_lastrecv + FRIEND_CONNECTION_TIMEOUT < temp_time) { /* If we stopped receiving ping packets, kill it. */ crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); friend_con->crypt_connection_id = -1; handle_status(fr_c, i, 0, userdata); /* Going offline. */ } } } } if (fr_c->local_discovery_enabled) { lan_discovery(fr_c); } } /* Free everything related with friend_connections. */ void kill_friend_connections(Friend_Connections *fr_c) { if (!fr_c) { return; } for (uint32_t i = 0; i < fr_c->num_cons; ++i) { kill_friend_connection(fr_c, i); } if (fr_c->local_discovery_enabled) { lan_discovery_kill(fr_c->dht); } free(fr_c); } c-toxcore-0.2.13/toxcore/friend_connection.h000066400000000000000000000130071415350724400210300ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Connection to friends. */ #ifndef C_TOXCORE_TOXCORE_FRIEND_CONNECTION_H #define C_TOXCORE_TOXCORE_FRIEND_CONNECTION_H #include "DHT.h" #include "LAN_discovery.h" #include "net_crypto.h" #include "onion_client.h" #define MAX_FRIEND_CONNECTION_CALLBACKS 2 #define MESSENGER_CALLBACK_INDEX 0 #define GROUPCHAT_CALLBACK_INDEX 1 #define PACKET_ID_ALIVE 16 #define PACKET_ID_SHARE_RELAYS 17 #define PACKET_ID_FRIEND_REQUESTS 18 /* Interval between the sending of ping packets. */ #define FRIEND_PING_INTERVAL 8 /* If no packets are received from friend in this time interval, kill the connection. */ #define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 4) /* Time before friend is removed from the DHT after last hearing about him. */ #define FRIEND_DHT_TIMEOUT BAD_NODE_TIMEOUT #define FRIEND_MAX_STORED_TCP_RELAYS (MAX_FRIEND_TCP_CONNECTIONS * 4) /* Max number of tcp relays sent to friends */ #define MAX_SHARED_RELAYS RECOMMENDED_FRIEND_TCP_CONNECTIONS /* Interval between the sending of tcp relay information */ #define SHARE_RELAYS_INTERVAL (5 * 60) typedef enum Friendconn_Status { FRIENDCONN_STATUS_NONE, FRIENDCONN_STATUS_CONNECTING, FRIENDCONN_STATUS_CONNECTED, } Friendconn_Status; typedef struct Friend_Connections Friend_Connections; Net_Crypto *friendconn_net_crypto(const Friend_Connections *fr_c); /* return friendcon_id corresponding to the real public key on success. * return -1 on failure. */ int getfriend_conn_id_pk(Friend_Connections *fr_c, const uint8_t *real_pk); /* Increases lock_count for the connection with friendcon_id by 1. * * return 0 on success. * return -1 on failure. */ int friend_connection_lock(Friend_Connections *fr_c, int friendcon_id); /* return FRIENDCONN_STATUS_CONNECTED if the friend is connected. * return FRIENDCONN_STATUS_CONNECTING if the friend isn't connected. * return FRIENDCONN_STATUS_NONE on failure. */ unsigned int friend_con_connected(Friend_Connections *fr_c, int friendcon_id); /* Copy public keys associated to friendcon_id. * * return 0 on success. * return -1 on failure. */ int get_friendcon_public_keys(uint8_t *real_pk, uint8_t *dht_temp_pk, Friend_Connections *fr_c, int friendcon_id); /* Set temp dht key for connection. */ void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_temp_pk, void *userdata); /* Add a TCP relay associated to the friend. * * return -1 on failure. * return 0 on success. */ int friend_add_tcp_relay(Friend_Connections *fr_c, int friendcon_id, IP_Port ip_port, const uint8_t *public_key); typedef int global_status_cb(void *object, int id, uint8_t status, void *userdata); typedef int fc_status_cb(void *object, int id, uint8_t status, void *userdata); typedef int fc_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); typedef int fc_lossy_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); /* Set global status callback for friend connections. */ void set_global_status_callback(Friend_Connections *fr_c, global_status_cb *global_status_callback, void *object); /* Set the callbacks for the friend connection. * index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we want the callback to set in the array. * * return 0 on success. * return -1 on failure */ int friend_connection_callbacks(Friend_Connections *fr_c, int friendcon_id, unsigned int index, fc_status_cb *status_callback, fc_data_cb *data_callback, fc_lossy_data_cb *lossy_data_callback, void *object, int number); /* return the crypt_connection_id for the connection. * * return crypt_connection_id on success. * return -1 on failure. */ int friend_connection_crypt_connection_id(Friend_Connections *fr_c, int friendcon_id); /* Create a new friend connection. * If one to that real public key already exists, increase lock count and return it. * * return -1 on failure. * return connection id on success. */ int new_friend_connection(Friend_Connections *fr_c, const uint8_t *real_public_key); /* Kill a friend connection. * * return -1 on failure. * return 0 on success. */ int kill_friend_connection(Friend_Connections *fr_c, int friendcon_id); /* Send a Friend request packet. * * return -1 if failure. * return 0 if it sent the friend request directly to the friend. * return the number of peers it was routed through if it did not send it directly. */ int send_friend_request_packet(Friend_Connections *fr_c, int friendcon_id, uint32_t nospam_num, const uint8_t *data, uint16_t length); typedef int fr_request_cb(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t len, void *userdata); /* Set friend request callback. * * This function will be called every time a friend request is received. */ void set_friend_request_callback(Friend_Connections *fr_c, fr_request_cb *fr_request_callback, void *object); /* Create new friend_connections instance. */ Friend_Connections *new_friend_connections(const Logger *logger, const Mono_Time *mono_time, Onion_Client *onion_c, bool local_discovery_enabled); /* main friend_connections loop. */ void do_friend_connections(Friend_Connections *fr_c, void *userdata); /* Free everything related with friend_connections. */ void kill_friend_connections(Friend_Connections *fr_c); #endif c-toxcore-0.2.13/toxcore/friend_requests.c000066400000000000000000000105741415350724400205450ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Handle friend requests. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "friend_requests.h" #include #include #include "util.h" /* NOTE: The following is just a temporary fix for the multiple friend requests received at the same time problem. * TODO(irungentoo): Make this better (This will most likely tie in with the way we will handle spam.) */ #define MAX_RECEIVED_STORED 32 struct Received_Requests { uint8_t requests[MAX_RECEIVED_STORED][CRYPTO_PUBLIC_KEY_SIZE]; uint16_t requests_index; }; struct Friend_Requests { uint32_t nospam; fr_friend_request_cb *handle_friendrequest; uint8_t handle_friendrequest_isset; void *handle_friendrequest_object; filter_function_cb *filter_function; void *filter_function_userdata; struct Received_Requests received; }; /* Set and get the nospam variable used to prevent one type of friend request spam. */ void set_nospam(Friend_Requests *fr, uint32_t num) { fr->nospam = num; } uint32_t get_nospam(const Friend_Requests *fr) { return fr->nospam; } /* Set the function that will be executed when a friend request is received. */ void callback_friendrequest(Friend_Requests *fr, fr_friend_request_cb *function, void *object) { fr->handle_friendrequest = function; fr->handle_friendrequest_isset = 1; fr->handle_friendrequest_object = object; } /* Set the function used to check if a friend request should be displayed to the user or not. */ void set_filter_function(Friend_Requests *fr, filter_function_cb *function, void *userdata) { fr->filter_function = function; fr->filter_function_userdata = userdata; } /* Add to list of received friend requests. */ static void addto_receivedlist(Friend_Requests *fr, const uint8_t *real_pk) { if (fr->received.requests_index >= MAX_RECEIVED_STORED) { fr->received.requests_index = 0; } id_copy(fr->received.requests[fr->received.requests_index], real_pk); ++fr->received.requests_index; } /* Check if a friend request was already received. * * return false if it did not. * return true if it did. */ static bool request_received(const Friend_Requests *fr, const uint8_t *real_pk) { for (uint32_t i = 0; i < MAX_RECEIVED_STORED; ++i) { if (id_equal(fr->received.requests[i], real_pk)) { return true; } } return false; } /* Remove real pk from received.requests list. * * return 0 if it removed it successfully. * return -1 if it didn't find it. */ int remove_request_received(Friend_Requests *fr, const uint8_t *real_pk) { for (uint32_t i = 0; i < MAX_RECEIVED_STORED; ++i) { if (id_equal(fr->received.requests[i], real_pk)) { crypto_memzero(fr->received.requests[i], CRYPTO_PUBLIC_KEY_SIZE); return 0; } } return -1; } static int friendreq_handlepacket(void *object, const uint8_t *source_pubkey, const uint8_t *packet, uint16_t length, void *userdata) { Friend_Requests *const fr = (Friend_Requests *)object; if (length <= 1 + sizeof(fr->nospam) || length > ONION_CLIENT_MAX_DATA_SIZE) { return 1; } ++packet; --length; if (fr->handle_friendrequest_isset == 0) { return 1; } if (request_received(fr, source_pubkey)) { return 1; } if (memcmp(packet, &fr->nospam, sizeof(fr->nospam)) != 0) { return 1; } if (fr->filter_function) { if (fr->filter_function(source_pubkey, fr->filter_function_userdata) != 0) { return 1; } } addto_receivedlist(fr, source_pubkey); const uint32_t message_len = length - sizeof(fr->nospam); VLA(uint8_t, message, message_len + 1); memcpy(message, packet + sizeof(fr->nospam), message_len); message[SIZEOF_VLA(message) - 1] = 0; /* Be sure the message is null terminated. */ fr->handle_friendrequest(fr->handle_friendrequest_object, source_pubkey, message, message_len, userdata); return 0; } void friendreq_init(Friend_Requests *fr, Friend_Connections *fr_c) { set_friend_request_callback(fr_c, &friendreq_handlepacket, fr); } Friend_Requests *friendreq_new(void) { return (Friend_Requests *)calloc(1, sizeof(Friend_Requests)); } void friendreq_kill(Friend_Requests *fr) { free(fr); } c-toxcore-0.2.13/toxcore/friend_requests.h000066400000000000000000000032561415350724400205510ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Handle friend requests. */ #ifndef C_TOXCORE_TOXCORE_FRIEND_REQUESTS_H #define C_TOXCORE_TOXCORE_FRIEND_REQUESTS_H #include "friend_connection.h" #define MAX_FRIEND_REQUEST_DATA_SIZE (ONION_CLIENT_MAX_DATA_SIZE - (1 + sizeof(uint32_t))) typedef struct Friend_Requests Friend_Requests; /* Set and get the nospam variable used to prevent one type of friend request spam. */ void set_nospam(Friend_Requests *fr, uint32_t num); uint32_t get_nospam(const Friend_Requests *fr); /* Remove real_pk from received_requests list. * * return 0 if it removed it successfully. * return -1 if it didn't find it. */ int remove_request_received(Friend_Requests *fr, const uint8_t *real_pk); typedef void fr_friend_request_cb(void *object, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data); /* Set the function that will be executed when a friend request for us is received. */ void callback_friendrequest(Friend_Requests *fr, fr_friend_request_cb *function, void *object); typedef int filter_function_cb(const uint8_t *public_key, void *user_data); /* Set the function used to check if a friend request should be displayed to the user or not. * It must return 0 if the request is ok (anything else if it is bad.) */ void set_filter_function(Friend_Requests *fr, filter_function_cb *function, void *userdata); /* Sets up friendreq packet handlers. */ void friendreq_init(Friend_Requests *fr, Friend_Connections *fr_c); Friend_Requests *friendreq_new(void); void friendreq_kill(Friend_Requests *fr); #endif c-toxcore-0.2.13/toxcore/group.c000066400000000000000000002773461415350724400165130ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Slightly better groupchats implementation. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "group.h" #include #include #include #include "mono_time.h" #include "state.h" #include "util.h" /** * Packet type IDs as per the protocol specification. */ typedef enum Group_Message_Id { GROUP_MESSAGE_PING_ID = 0, GROUP_MESSAGE_NEW_PEER_ID = 16, GROUP_MESSAGE_KILL_PEER_ID = 17, GROUP_MESSAGE_FREEZE_PEER_ID = 18, GROUP_MESSAGE_NAME_ID = 48, GROUP_MESSAGE_TITLE_ID = 49, } Group_Message_Id; #define GROUP_MESSAGE_NEW_PEER_LENGTH (sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2) #define GROUP_MESSAGE_KILL_PEER_LENGTH (sizeof(uint16_t)) #define MAX_GROUP_MESSAGE_DATA_LEN (MAX_CRYPTO_DATA_SIZE - (1 + MIN_MESSAGE_PACKET_LEN)) typedef enum Invite_Id { INVITE_ID = 0, INVITE_ACCEPT_ID = 1, INVITE_MEMBER_ID = 2, } Invite_Id; #define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) #define INVITE_ACCEPT_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH) #define INVITE_MEMBER_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH + sizeof(uint16_t)) #define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) typedef enum Peer_Id { PEER_INTRODUCED_ID = 1, PEER_QUERY_ID = 8, PEER_RESPONSE_ID = 9, PEER_TITLE_ID = 10, } Peer_Id; #define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1) /* return false if the groupnumber is not valid. * return true if the groupnumber is valid. */ static bool is_groupnumber_valid(const Group_Chats *g_c, uint32_t groupnumber) { return groupnumber < g_c->num_chats && g_c->chats != nullptr && g_c->chats[groupnumber].status != GROUPCHAT_STATUS_NONE; } /* Set the size of the groupchat list to num. * * return false if realloc fails. * return true if it succeeds. */ static bool realloc_conferences(Group_Chats *g_c, uint16_t num) { if (num == 0) { free(g_c->chats); g_c->chats = nullptr; return true; } Group_c *newgroup_chats = (Group_c *)realloc(g_c->chats, num * sizeof(Group_c)); if (newgroup_chats == nullptr) { return false; } g_c->chats = newgroup_chats; return true; } static void setup_conference(Group_c *g) { memset(g, 0, sizeof(Group_c)); g->maxfrozen = MAX_FROZEN_DEFAULT; } /* Create a new empty groupchat connection. * * return -1 on failure. * return groupnumber on success. */ static int32_t create_group_chat(Group_Chats *g_c) { for (uint16_t i = 0; i < g_c->num_chats; ++i) { if (g_c->chats[i].status == GROUPCHAT_STATUS_NONE) { return i; } } if (realloc_conferences(g_c, g_c->num_chats + 1)) { uint16_t id = g_c->num_chats; ++g_c->num_chats; setup_conference(&g_c->chats[id]); return id; } return -1; } /* Wipe a groupchat. * * return true on success. */ static bool wipe_group_chat(Group_Chats *g_c, uint32_t groupnumber) { if (!is_groupnumber_valid(g_c, groupnumber)) { return false; } uint16_t i; crypto_memzero(&g_c->chats[groupnumber], sizeof(Group_c)); for (i = g_c->num_chats; i != 0; --i) { if (g_c->chats[i - 1].status != GROUPCHAT_STATUS_NONE) { break; } } if (g_c->num_chats != i) { g_c->num_chats = i; realloc_conferences(g_c, g_c->num_chats); } return true; } static Group_c *get_group_c(const Group_Chats *g_c, uint32_t groupnumber) { if (!is_groupnumber_valid(g_c, groupnumber)) { return nullptr; } return &g_c->chats[groupnumber]; } /* * check if peer with real_pk is in peer array. * * return peer index if peer is in group. * return -1 if peer is not in group. * * TODO(irungentoo): make this more efficient. */ static int peer_in_group(const Group_c *g, const uint8_t *real_pk) { for (uint32_t i = 0; i < g->numpeers; ++i) { if (id_equal(g->group[i].real_pk, real_pk)) { return i; } } return -1; } static int frozen_in_group(const Group_c *g, const uint8_t *real_pk) { for (uint32_t i = 0; i < g->numfrozen; ++i) { if (id_equal(g->frozen[i].real_pk, real_pk)) { return i; } } return -1; } /* * check if group with the given type and id is in group array. * * return group number if peer is in list. * return -1 if group is not in list. * * TODO(irungentoo): make this more efficient and maybe use constant time comparisons? */ static int32_t get_group_num(const Group_Chats *g_c, const uint8_t type, const uint8_t *id) { for (uint16_t i = 0; i < g_c->num_chats; ++i) { if (g_c->chats[i].type == type && crypto_memcmp(g_c->chats[i].id, id, GROUP_ID_LENGTH) == 0) { return i; } } return -1; } int32_t conference_by_id(const Group_Chats *g_c, const uint8_t *id) { for (uint16_t i = 0; i < g_c->num_chats; ++i) { if (crypto_memcmp(g_c->chats[i].id, id, GROUP_ID_LENGTH) == 0) { return i; } } return -1; } /* * check if peer with peer_number is in peer array. * * return peer index if peer is in chat. * return -1 if peer is not in chat. * * TODO(irungentoo): make this more efficient. */ static int get_peer_index(const Group_c *g, uint16_t peer_number) { for (uint32_t i = 0; i < g->numpeers; ++i) { if (g->group[i].peer_number == peer_number) { return i; } } return -1; } static uint64_t calculate_comp_value(const uint8_t *pk1, const uint8_t *pk2) { uint64_t cmp1 = 0; uint64_t cmp2 = 0; for (size_t i = 0; i < sizeof(uint64_t); ++i) { cmp1 = (cmp1 << 8) + (uint64_t)pk1[i]; cmp2 = (cmp2 << 8) + (uint64_t)pk2[i]; } return cmp1 - cmp2; } typedef enum Groupchat_Closest_Change { GROUPCHAT_CLOSEST_CHANGE_NONE, GROUPCHAT_CLOSEST_CHANGE_ADDED, GROUPCHAT_CLOSEST_CHANGE_REMOVED, } Groupchat_Closest_Change; static bool add_to_closest(Group_c *g, const uint8_t *real_pk, const uint8_t *temp_pk) { if (public_key_cmp(g->real_pk, real_pk) == 0) { return false; } unsigned int index = DESIRED_CLOSEST; for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) { if (g->closest_peers[i].entry && public_key_cmp(real_pk, g->closest_peers[i].real_pk) == 0) { return true; } } for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) { if (g->closest_peers[i].entry == 0) { index = i; break; } } if (index == DESIRED_CLOSEST) { uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); uint64_t comp_d = 0; for (unsigned int i = 0; i < (DESIRED_CLOSEST / 2); ++i) { uint64_t comp = calculate_comp_value(g->real_pk, g->closest_peers[i].real_pk); if (comp > comp_val && comp > comp_d) { index = i; comp_d = comp; } } comp_val = calculate_comp_value(real_pk, g->real_pk); for (unsigned int i = (DESIRED_CLOSEST / 2); i < DESIRED_CLOSEST; ++i) { uint64_t comp = calculate_comp_value(g->closest_peers[i].real_pk, g->real_pk); if (comp > comp_val && comp > comp_d) { index = i; comp_d = comp; } } } if (index == DESIRED_CLOSEST) { return false; } uint8_t old_real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t old_temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t old = 0; if (g->closest_peers[index].entry) { memcpy(old_real_pk, g->closest_peers[index].real_pk, CRYPTO_PUBLIC_KEY_SIZE); memcpy(old_temp_pk, g->closest_peers[index].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); old = 1; } g->closest_peers[index].entry = 1; memcpy(g->closest_peers[index].real_pk, real_pk, CRYPTO_PUBLIC_KEY_SIZE); memcpy(g->closest_peers[index].temp_pk, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); if (old) { add_to_closest(g, old_real_pk, old_temp_pk); } if (!g->changed) { g->changed = GROUPCHAT_CLOSEST_CHANGE_ADDED; } return true; } static bool pk_in_closest_peers(const Group_c *g, uint8_t *real_pk) { for (unsigned int i = 0; i < DESIRED_CLOSEST; ++i) { if (!g->closest_peers[i].entry) { continue; } if (public_key_cmp(g->closest_peers[i].real_pk, real_pk) == 0) { return true; } } return false; } static void remove_connection_reason(Group_Chats *g_c, Group_c *g, uint16_t i, uint8_t reason); static void purge_closest(Group_Chats *g_c, uint32_t groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return; } for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { continue; } if (!(g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST)) { continue; } uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[i].number); if (!pk_in_closest_peers(g, real_pk)) { remove_connection_reason(g_c, g, i, GROUPCHAT_CONNECTION_REASON_CLOSEST); } } } static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t type, const uint8_t *id); static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, Group_c *g, uint8_t reason, uint8_t lock); static void add_closest_connections(Group_Chats *g_c, uint32_t groupnumber, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return; } for (uint32_t i = 0; i < DESIRED_CLOSEST; ++i) { if (!g->closest_peers[i].entry) { continue; } int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->closest_peers[i].real_pk); uint8_t fresh = 0; if (friendcon_id == -1) { friendcon_id = new_friend_connection(g_c->fr_c, g->closest_peers[i].real_pk); fresh = 1; if (friendcon_id == -1) { continue; } set_dht_temp_pk(g_c->fr_c, friendcon_id, g->closest_peers[i].temp_pk, userdata); } const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_CLOSEST, !fresh); if (connection_index == -1) { if (fresh) { kill_friend_connection(g_c->fr_c, friendcon_id); } continue; } if (friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED && g->connections[connection_index].type == GROUPCHAT_CONNECTION_CONNECTING) { send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); } } } static bool connect_to_closest(Group_Chats *g_c, uint32_t groupnumber, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } if (!g->changed) { return true; } if (g->changed == GROUPCHAT_CLOSEST_CHANGE_REMOVED) { for (uint32_t i = 0; i < g->numpeers; ++i) { add_to_closest(g, g->group[i].real_pk, g->group[i].temp_pk); } } purge_closest(g_c, groupnumber); add_closest_connections(g_c, groupnumber, userdata); g->changed = GROUPCHAT_CLOSEST_CHANGE_NONE; return true; } static int get_frozen_index(const Group_c *g, uint16_t peer_number) { for (uint32_t i = 0; i < g->numfrozen; ++i) { if (g->frozen[i].peer_number == peer_number) { return i; } } return -1; } static bool delete_frozen(Group_c *g, uint32_t frozen_index) { if (frozen_index >= g->numfrozen) { return false; } --g->numfrozen; if (g->numfrozen == 0) { free(g->frozen); g->frozen = nullptr; } else { if (g->numfrozen != frozen_index) { g->frozen[frozen_index] = g->frozen[g->numfrozen]; } Group_Peer *const frozen_temp = (Group_Peer *)realloc(g->frozen, sizeof(Group_Peer) * (g->numfrozen)); if (frozen_temp == nullptr) { return false; } g->frozen = frozen_temp; } return true; } /* Update last_active timestamp on peer, and thaw the peer if it is frozen. * * return peer index if peer is in the conference. * return -1 otherwise, and on error. */ static int note_peer_active(Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_number, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } const int peer_index = get_peer_index(g, peer_number); if (peer_index != -1) { g->group[peer_index].last_active = mono_time_get(g_c->mono_time); return peer_index; } const int frozen_index = get_frozen_index(g, peer_number); if (frozen_index == -1) { return -1; } /* Now thaw the peer */ Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); if (temp == nullptr) { return -1; } const uint32_t thawed_index = g->numpeers; g->group = temp; g->group[thawed_index] = g->frozen[frozen_index]; g->group[thawed_index].temp_pk_updated = false; g->group[thawed_index].last_active = mono_time_get(g_c->mono_time); add_to_closest(g, g->group[thawed_index].real_pk, g->group[thawed_index].temp_pk); ++g->numpeers; delete_frozen(g, frozen_index); if (g_c->peer_list_changed_callback) { g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); } if (g->peer_on_join) { g->peer_on_join(g->object, groupnumber, thawed_index); } g->need_send_name = true; return thawed_index; } static bool delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata); static void delete_any_peer_with_pk(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return; } const int peer_index = peer_in_group(g, real_pk); if (peer_index >= 0) { delpeer(g_c, groupnumber, peer_index, userdata); } const int frozen_index = frozen_in_group(g, real_pk); if (frozen_index >= 0) { delete_frozen(g, frozen_index); } } /* Add a peer to the group chat, or update an existing peer. * * fresh indicates whether we should consider this information on the peer to * be current, and so should update temp_pk and consider the peer active. * * do_gc_callback indicates whether we want to trigger callbacks set by the client * via the public API. This should be set to false if this function is called * from outside of the tox_iterate() loop. * * return peer_index if success or peer already in chat. * return -1 if error. */ static int addpeer(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk, uint16_t peer_number, void *userdata, bool fresh, bool do_gc_callback) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } const int peer_index = fresh ? note_peer_active(g_c, groupnumber, peer_number, userdata) : get_peer_index(g, peer_number); if (peer_index != -1) { if (!id_equal(g->group[peer_index].real_pk, real_pk)) { return -1; } if (fresh || !g->group[peer_index].temp_pk_updated) { id_copy(g->group[peer_index].temp_pk, temp_pk); g->group[peer_index].temp_pk_updated = true; } return peer_index; } if (!fresh) { const int frozen_index = get_frozen_index(g, peer_number); if (frozen_index != -1) { if (!id_equal(g->frozen[frozen_index].real_pk, real_pk)) { return -1; } id_copy(g->frozen[frozen_index].temp_pk, temp_pk); return -1; } } delete_any_peer_with_pk(g_c, groupnumber, real_pk, userdata); Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); if (temp == nullptr) { return -1; } memset(&temp[g->numpeers], 0, sizeof(Group_Peer)); g->group = temp; const uint32_t new_index = g->numpeers; id_copy(g->group[new_index].real_pk, real_pk); id_copy(g->group[new_index].temp_pk, temp_pk); g->group[new_index].temp_pk_updated = true; g->group[new_index].peer_number = peer_number; g->group[new_index].last_active = mono_time_get(g_c->mono_time); g->group[new_index].is_friend = (getfriend_id(g_c->m, real_pk) != -1); ++g->numpeers; add_to_closest(g, real_pk, temp_pk); if (do_gc_callback && g_c->peer_list_changed_callback) { g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); } if (g->peer_on_join) { g->peer_on_join(g->object, groupnumber, new_index); } return new_index; } static void remove_connection(Group_Chats *g_c, Group_c *g, uint16_t i) { if (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER) { --g->num_introducer_connections; } kill_friend_connection(g_c->fr_c, g->connections[i].number); g->connections[i].type = GROUPCHAT_CONNECTION_NONE; } static void remove_from_closest(Group_c *g, int peer_index) { for (uint32_t i = 0; i < DESIRED_CLOSEST; ++i) { if (g->closest_peers[i].entry && id_equal(g->closest_peers[i].real_pk, g->group[peer_index].real_pk)) { g->closest_peers[i].entry = 0; g->changed = GROUPCHAT_CLOSEST_CHANGE_REMOVED; break; } } } /* * Delete a peer from the group chat. * * return true on success */ static bool delpeer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } remove_from_closest(g, peer_index); const int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->group[peer_index].real_pk); if (friendcon_id != -1) { for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { continue; } if (g->connections[i].number == (unsigned int)friendcon_id) { remove_connection(g_c, g, i); break; } } } --g->numpeers; void *peer_object = g->group[peer_index].object; if (g->numpeers == 0) { free(g->group); g->group = nullptr; } else { if (g->numpeers != (uint32_t)peer_index) { g->group[peer_index] = g->group[g->numpeers]; } Group_Peer *temp = (Group_Peer *)realloc(g->group, sizeof(Group_Peer) * (g->numpeers)); if (temp == nullptr) { return false; } g->group = temp; } if (g_c->peer_list_changed_callback) { g_c->peer_list_changed_callback(g_c->m, groupnumber, userdata); } if (g->peer_on_leave) { g->peer_on_leave(g->object, groupnumber, peer_object); } return true; } static int cmp_u64(uint64_t a, uint64_t b) { return (a > b) - (a < b); } /* Order peers with friends first and with more recently active earlier */ static int cmp_frozen(const void *a, const void *b) { const Group_Peer *pa = (const Group_Peer *) a; const Group_Peer *pb = (const Group_Peer *) b; if (pa->is_friend ^ pb->is_friend) { return pa->is_friend ? -1 : 1; } return cmp_u64(pb->last_active, pa->last_active); } /* Delete frozen peers as necessary to ensure at most g->maxfrozen remain. * * return true if any frozen peers are removed. */ static bool delete_old_frozen(Group_c *g) { if (g->numfrozen <= g->maxfrozen) { return false; } if (g->maxfrozen == 0) { free(g->frozen); g->frozen = nullptr; g->numfrozen = 0; return true; } qsort(g->frozen, g->numfrozen, sizeof(Group_Peer), cmp_frozen); Group_Peer *temp = (Group_Peer *)realloc(g->frozen, sizeof(Group_Peer) * g->maxfrozen); if (temp == nullptr) { return false; } g->frozen = temp; g->numfrozen = g->maxfrozen; return true; } static bool try_send_rejoin(Group_Chats *g_c, Group_c *g, const uint8_t *real_pk); static bool freeze_peer(Group_Chats *g_c, uint32_t groupnumber, int peer_index, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } Group_Peer *temp = (Group_Peer *)realloc(g->frozen, sizeof(Group_Peer) * (g->numfrozen + 1)); if (temp == nullptr) { return false; } g->frozen = temp; g->frozen[g->numfrozen] = g->group[peer_index]; g->frozen[g->numfrozen].object = nullptr; if (!delpeer(g_c, groupnumber, peer_index, userdata)) { return false; } try_send_rejoin(g_c, g, g->frozen[g->numfrozen].real_pk); ++g->numfrozen; delete_old_frozen(g); return true; } /* Set the nick for a peer. * * do_gc_callback indicates whether we want to trigger callbacks set by the client * via the public API. This should be set to false if this function is called * from outside of the tox_iterate() loop. * * return true on success. */ static bool setnick(Group_Chats *g_c, uint32_t groupnumber, int peer_index, const uint8_t *nick, uint16_t nick_len, void *userdata, bool do_gc_callback) { if (nick_len > MAX_NAME_LENGTH) { return false; } Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } g->group[peer_index].nick_updated = true; if (g->group[peer_index].nick_len == nick_len && (nick_len == 0 || !memcmp(g->group[peer_index].nick, nick, nick_len))) { /* same name as already stored */ return true; } if (nick_len) { memcpy(g->group[peer_index].nick, nick, nick_len); } g->group[peer_index].nick_len = nick_len; if (do_gc_callback && g_c->peer_name_callback) { g_c->peer_name_callback(g_c->m, groupnumber, peer_index, nick, nick_len, userdata); } return true; } /* Set the title for a group. * * return true on success. */ static bool settitle(Group_Chats *g_c, uint32_t groupnumber, int peer_index, const uint8_t *title, uint8_t title_len, void *userdata) { if (title_len > MAX_NAME_LENGTH || title_len == 0) { return false; } Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } if (g->title_len == title_len && !memcmp(g->title, title, title_len)) { /* same title as already set */ return true; } memcpy(g->title, title, title_len); g->title_len = title_len; g->title_fresh = true; if (g_c->title_callback) { g_c->title_callback(g_c->m, groupnumber, peer_index, title, title_len, userdata); } return true; } /* Check if the group has no online connection, and freeze all peers if so */ static void check_disconnected(Group_Chats *g_c, uint32_t groupnumber, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return; } for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) { return; } } for (uint32_t i = 0; i < g->numpeers; ++i) { while (i < g->numpeers && !id_equal(g->group[i].real_pk, g->real_pk)) { freeze_peer(g_c, groupnumber, i, userdata); } } } static void set_conns_type_connections(Group_Chats *g_c, uint32_t groupnumber, int friendcon_id, uint8_t type, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return; } for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { continue; } if (g->connections[i].number != (unsigned int)friendcon_id) { continue; } if (type == GROUPCHAT_CONNECTION_ONLINE) { send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); } else { g->connections[i].type = type; check_disconnected(g_c, groupnumber, userdata); } } } /* Set the type for all connections with friendcon_id */ static void set_conns_status_groups(Group_Chats *g_c, int friendcon_id, uint8_t type, void *userdata) { for (uint16_t i = 0; i < g_c->num_chats; ++i) { set_conns_type_connections(g_c, i, friendcon_id, type, userdata); } } static void rejoin_frozen_friend(Group_Chats *g_c, int friendcon_id) { uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, friendcon_id); for (uint16_t i = 0; i < g_c->num_chats; ++i) { Group_c *g = get_group_c(g_c, i); if (!g) { continue; } for (uint32_t j = 0; j < g->numfrozen; ++j) { if (id_equal(g->frozen[j].real_pk, real_pk)) { try_send_rejoin(g_c, g, real_pk); break; } } } } static int g_handle_any_status(void *object, int friendcon_id, uint8_t status, void *userdata) { Group_Chats *g_c = (Group_Chats *)object; if (status) { rejoin_frozen_friend(g_c, friendcon_id); } return 0; } static int g_handle_status(void *object, int friendcon_id, uint8_t status, void *userdata) { Group_Chats *g_c = (Group_Chats *)object; if (status) { /* Went online */ set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CONNECTION_ONLINE, userdata); } else { /* Went offline */ set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CONNECTION_CONNECTING, userdata); // TODO(irungentoo): remove timedout connections? } return 0; } static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata); /* Add friend to group chat. * * return connections index on success * return -1 on failure. */ static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, Group_c *g, uint8_t reason, uint8_t lock) { uint16_t empty = MAX_GROUP_CONNECTIONS; uint16_t ind = MAX_GROUP_CONNECTIONS; for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { empty = i; continue; } if (g->connections[i].number == (uint32_t)friendcon_id) { ind = i; /* Already in list. */ break; } } if (ind == MAX_GROUP_CONNECTIONS) { if (empty == MAX_GROUP_CONNECTIONS) { return -1; } if (lock) { friend_connection_lock(g_c->fr_c, friendcon_id); } g->connections[empty].type = GROUPCHAT_CONNECTION_CONNECTING; g->connections[empty].number = friendcon_id; g->connections[empty].reasons = 0; // TODO(irungentoo): friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &g_handle_status, &g_handle_packet, &handle_lossy, g_c, friendcon_id); ind = empty; } if (!(g->connections[ind].reasons & reason)) { g->connections[ind].reasons |= reason; if (reason == GROUPCHAT_CONNECTION_REASON_INTRODUCER) { ++g->num_introducer_connections; } } return ind; } static unsigned int send_peer_introduced(Group_Chats *g_c, int friendcon_id, uint16_t group_num); /* Removes reason for keeping connection. * * Kills connection if this was the last reason. */ static void remove_connection_reason(Group_Chats *g_c, Group_c *g, uint16_t i, uint8_t reason) { if (!(g->connections[i].reasons & reason)) { return; } g->connections[i].reasons &= ~reason; if (reason == GROUPCHAT_CONNECTION_REASON_INTRODUCER) { --g->num_introducer_connections; if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) { send_peer_introduced(g_c, g->connections[i].number, g->connections[i].group_number); } } if (g->connections[i].reasons == 0) { kill_friend_connection(g_c->fr_c, g->connections[i].number); g->connections[i].type = GROUPCHAT_CONNECTION_NONE; } } /* Creates a new groupchat and puts it in the chats array. * * type is one of `GROUPCHAT_TYPE_*` * * return group number on success. * return -1 on failure. */ int add_groupchat(Group_Chats *g_c, uint8_t type) { const int32_t groupnumber = create_group_chat(g_c); if (groupnumber == -1) { return -1; } Group_c *g = &g_c->chats[groupnumber]; g->status = GROUPCHAT_STATUS_CONNECTED; g->type = type; new_symmetric_key(g->id); g->peer_number = 0; /* Founder is peer 0. */ memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), 0, nullptr, true, false); if (peer_index == -1) { return -1; } setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, nullptr, false); return groupnumber; } static bool group_leave(const Group_Chats *g_c, uint32_t groupnumber, bool permanent); /* Delete a groupchat from the chats array, informing the group first as * appropriate. * * return 0 on success. * return -1 if groupnumber is invalid. */ int del_groupchat(Group_Chats *g_c, uint32_t groupnumber, bool leave_permanently) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } group_leave(g_c, groupnumber, leave_permanently); for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { continue; } g->connections[i].type = GROUPCHAT_CONNECTION_NONE; kill_friend_connection(g_c->fr_c, g->connections[i].number); } for (uint32_t i = 0; i < g->numpeers; ++i) { if (g->peer_on_leave) { g->peer_on_leave(g->object, groupnumber, g->group[i].object); } } free(g->group); free(g->frozen); if (g->group_on_delete) { g->group_on_delete(g->object, groupnumber); } return wipe_group_chat(g_c, groupnumber); } static const Group_Peer *peer_in_list(const Group_c *g, uint32_t peernumber, bool frozen) { const Group_Peer *list = frozen ? g->frozen : g->group; const uint32_t num = frozen ? g->numfrozen : g->numpeers; if (peernumber >= num) { return nullptr; } return &list[peernumber]; } /* Copy the public key of (frozen, if frozen is true) peernumber who is in * groupnumber to pk. pk must be CRYPTO_PUBLIC_KEY_SIZE long. * * return 0 on success * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. */ int group_peer_pubkey(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *pk, bool frozen) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } const Group_Peer *peer = peer_in_list(g, peernumber, frozen); if (peer == nullptr) { return -2; } memcpy(pk, peer->real_pk, CRYPTO_PUBLIC_KEY_SIZE); return 0; } /* * Return the size of (frozen, if frozen is true) peernumber's name. * * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. */ int group_peername_size(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, bool frozen) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } const Group_Peer *peer = peer_in_list(g, peernumber, frozen); if (peer == nullptr) { return -2; } return peer->nick_len; } /* Copy the name of (frozen, if frozen is true) peernumber who is in * groupnumber to name. name must be at least MAX_NAME_LENGTH long. * * return length of name if success * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. */ int group_peername(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *name, bool frozen) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } const Group_Peer *peer = peer_in_list(g, peernumber, frozen); if (peer == nullptr) { return -2; } if (peer->nick_len > 0) { memcpy(name, peer->nick, peer->nick_len); } return peer->nick_len; } /* Copy last active timestamp of frozennumber who is in groupnumber to * last_active. * * return 0 on success. * return -1 if groupnumber is invalid. * return -2 if frozennumber is invalid. */ int group_frozen_last_active(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint64_t *last_active) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (peernumber >= g->numfrozen) { return -2; } *last_active = g->frozen[peernumber].last_active; return 0; } /* Set maximum number of frozen peers. * * return 0 on success. * return -1 if groupnumber is invalid. */ int group_set_max_frozen(const Group_Chats *g_c, uint32_t groupnumber, uint32_t maxfrozen) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } g->maxfrozen = maxfrozen; delete_old_frozen(g); return 0; } /* Return the number of (frozen, if frozen is true) peers in the group chat on * success. * return -1 if groupnumber is invalid. */ int group_number_peers(const Group_Chats *g_c, uint32_t groupnumber, bool frozen) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } return frozen ? g->numfrozen : g->numpeers; } /* return 1 if the peernumber corresponds to ours. * return 0 if the peernumber is not ours. * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. * return -3 if we are not connected to the group chat. */ int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (peernumber >= g->numpeers) { return -2; } if (g->status != GROUPCHAT_STATUS_CONNECTED) { return -3; } return g->peer_number == g->group[peernumber].peer_number; } /* return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. * * return -1 on failure. * return type on success. */ int group_get_type(const Group_Chats *g_c, uint32_t groupnumber) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } return g->type; } /* Copies the unique id of `group_chat[groupnumber]` into `id`. * * return false on failure. * return true on success. */ bool conference_get_id(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *id) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } if (id != nullptr) { memcpy(id, g->id, sizeof(g->id)); } return true; } /* Send a group packet to friendcon_id. * * return 1 on success * return 0 on failure */ static unsigned int send_packet_group_peer(Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, uint16_t group_num, const uint8_t *data, uint16_t length) { if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) { return 0; } group_num = net_htons(group_num); VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length); packet[0] = packet_id; memcpy(packet + 1, &group_num, sizeof(uint16_t)); memcpy(packet + 1 + sizeof(uint16_t), data, length); return write_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, SIZEOF_VLA(packet), 0) != -1; } /* Send a group lossy packet to friendcon_id. * * return 1 on success * return 0 on failure */ static unsigned int send_lossy_group_peer(Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, uint16_t group_num, const uint8_t *data, uint16_t length) { if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) { return 0; } group_num = net_htons(group_num); VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length); packet[0] = packet_id; memcpy(packet + 1, &group_num, sizeof(uint16_t)); memcpy(packet + 1 + sizeof(uint16_t), data, length); return send_lossy_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, SIZEOF_VLA(packet)) != -1; } /* invite friendnumber to groupnumber. * * return 0 on success. * return -1 if groupnumber is invalid. * return -2 if invite packet failed to send. * return -3 if we are not connected to the group chat. */ int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (g->status != GROUPCHAT_STATUS_CONNECTED) { return -3; } uint8_t invite[INVITE_PACKET_SIZE]; invite[0] = INVITE_ID; const uint16_t groupchat_num = net_htons((uint16_t)groupnumber); memcpy(invite + 1, &groupchat_num, sizeof(groupchat_num)); invite[1 + sizeof(groupchat_num)] = g->type; memcpy(invite + 1 + sizeof(groupchat_num) + 1, g->id, GROUP_ID_LENGTH); if (send_conference_invite_packet(g_c->m, friendnumber, invite, sizeof(invite))) { return 0; } return -2; } /* Send a rejoin packet to a peer if we have a friend connection to the peer. * return true if a packet was sent. * return false otherwise. */ static bool try_send_rejoin(Group_Chats *g_c, Group_c *g, const uint8_t *real_pk) { const int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, real_pk); if (friendcon_id == -1) { return false; } uint8_t packet[1 + 1 + GROUP_ID_LENGTH]; packet[0] = PACKET_ID_REJOIN_CONFERENCE; packet[1] = g->type; memcpy(packet + 2, g->id, GROUP_ID_LENGTH); if (write_cryptpacket(friendconn_net_crypto(g_c->fr_c), friend_connection_crypt_connection_id(g_c->fr_c, friendcon_id), packet, sizeof(packet), 0) == -1) { return false; } add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, 1); return true; } static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num); static bool send_invite_response(Group_Chats *g_c, int groupnumber, uint32_t friendnumber, const uint8_t *data, uint16_t length); /* Join a group (we need to have been invited first.) * * expected_type is the groupchat type we expect the chat we are joining to * have. * * return group number on success. * return -1 if data length is invalid. * return -2 if group is not the expected type. * return -3 if friendnumber is invalid. * return -4 if client is already in this group. * return -5 if group instance failed to initialize. * return -6 if join packet fails to send. */ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length) { if (length != sizeof(uint16_t) + 1 + GROUP_ID_LENGTH) { return -1; } if (data[sizeof(uint16_t)] != expected_type) { return -2; } const int friendcon_id = getfriendcon_id(g_c->m, friendnumber); if (friendcon_id == -1) { return -3; } if (get_group_num(g_c, data[sizeof(uint16_t)], data + sizeof(uint16_t) + 1) != -1) { return -4; } const int groupnumber = create_group_chat(g_c); if (groupnumber == -1) { return -5; } Group_c *g = &g_c->chats[groupnumber]; g->status = GROUPCHAT_STATUS_VALID; memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); if (!send_invite_response(g_c, groupnumber, friendnumber, data, length)) { g->status = GROUPCHAT_STATUS_NONE; return -6; } return groupnumber; } static bool send_invite_response(Group_Chats *g_c, int groupnumber, uint32_t friendnumber, const uint8_t *data, uint16_t length) { Group_c *g = get_group_c(g_c, groupnumber); if (g == nullptr) { return false; } const bool member = (g->status == GROUPCHAT_STATUS_CONNECTED); VLA(uint8_t, response, member ? INVITE_MEMBER_PACKET_SIZE : INVITE_ACCEPT_PACKET_SIZE); response[0] = member ? INVITE_MEMBER_ID : INVITE_ACCEPT_ID; net_pack_u16(response + 1, groupnumber); memcpy(response + 1 + sizeof(uint16_t), data, length); if (member) { net_pack_u16(response + 1 + sizeof(uint16_t) + length, g->peer_number); } if (!send_conference_invite_packet(g_c->m, friendnumber, response, SIZEOF_VLA(response))) { return false; } if (!member) { g->type = data[sizeof(uint16_t)]; memcpy(g->id, data + sizeof(uint16_t) + 1, GROUP_ID_LENGTH); } uint16_t other_groupnum; net_unpack_u16(data, &other_groupnum); const int friendcon_id = getfriendcon_id(g_c->m, friendnumber); if (friendcon_id == -1) { return false; } const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, 1); if (member) { add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCING, 0); } if (connection_index != -1) { g->connections[connection_index].group_number = other_groupnum; g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE; } send_peer_query(g_c, friendcon_id, other_groupnum); return true; } /* Set handlers for custom lossy packets. */ void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, lossy_packet_cb *function) { g_c->lossy_packethandlers[byte].function = function; } /* Set the callback for group invites. */ void g_callback_group_invite(Group_Chats *g_c, g_conference_invite_cb *function) { g_c->invite_callback = function; } /* Set the callback for group connection. */ void g_callback_group_connected(Group_Chats *g_c, g_conference_connected_cb *function) { g_c->connected_callback = function; } /* Set the callback for group messages. */ void g_callback_group_message(Group_Chats *g_c, g_conference_message_cb *function) { g_c->message_callback = function; } /* Set callback function for peer nickname changes. * * It gets called every time a peer changes their nickname. */ void g_callback_peer_name(Group_Chats *g_c, peer_name_cb *function) { g_c->peer_name_callback = function; } /* Set callback function for peer list changes. * * It gets called every time the name list changes(new peer, deleted peer) */ void g_callback_peer_list_changed(Group_Chats *g_c, peer_list_changed_cb *function) { g_c->peer_list_changed_callback = function; } /* Set callback function for title changes. */ void g_callback_group_title(Group_Chats *g_c, title_cb *function) { g_c->title_callback = function; } /* Set a function to be called when a new peer joins a group chat. * * return 0 on success. * return -1 on failure. */ int callback_groupchat_peer_new(const Group_Chats *g_c, uint32_t groupnumber, peer_on_join_cb *function) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } g->peer_on_join = function; return 0; } /* Set a function to be called when a peer leaves a group chat. * * return 0 on success. * return -1 on failure. */ int callback_groupchat_peer_delete(Group_Chats *g_c, uint32_t groupnumber, peer_on_leave_cb *function) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } g->peer_on_leave = function; return 0; } /* Set a function to be called when the group chat is deleted. * * return 0 on success. * return -1 on failure. */ int callback_groupchat_delete(Group_Chats *g_c, uint32_t groupnumber, group_on_delete_cb *function) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } g->group_on_delete = function; return 0; } static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint8_t message_id, const uint8_t *data, uint16_t len); /* send a ping message * return true on success */ static bool group_ping_send(const Group_Chats *g_c, uint32_t groupnumber) { if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_PING_ID, nullptr, 0) > 0) { return true; } return false; } /* send a new_peer message * return true on success */ static bool group_new_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num, const uint8_t *real_pk, uint8_t *temp_pk) { uint8_t packet[GROUP_MESSAGE_NEW_PEER_LENGTH]; peer_num = net_htons(peer_num); memcpy(packet, &peer_num, sizeof(uint16_t)); memcpy(packet + sizeof(uint16_t), real_pk, CRYPTO_PUBLIC_KEY_SIZE); memcpy(packet + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE, temp_pk, CRYPTO_PUBLIC_KEY_SIZE); if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NEW_PEER_ID, packet, sizeof(packet)) > 0) { return true; } return false; } /* send a kill_peer message * return true on success */ static bool group_kill_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num) { uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH]; peer_num = net_htons(peer_num); memcpy(packet, &peer_num, sizeof(uint16_t)); if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_KILL_PEER_ID, packet, sizeof(packet)) > 0) { return true; } return false; } /* send a freeze_peer message * return true on success */ static bool group_freeze_peer_send(const Group_Chats *g_c, uint32_t groupnumber, uint16_t peer_num) { uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH]; peer_num = net_htons(peer_num); memcpy(packet, &peer_num, sizeof(uint16_t)); if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_FREEZE_PEER_ID, packet, sizeof(packet)) > 0) { return true; } return false; } /* send a name message * return true on success */ static bool group_name_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *nick, uint16_t nick_len) { if (nick_len > MAX_NAME_LENGTH) { return false; } if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NAME_ID, nick, nick_len) > 0) { return true; } return false; } /* send message to announce leaving group * return true on success */ static bool group_leave(const Group_Chats *g_c, uint32_t groupnumber, bool permanent) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } if (permanent) { return group_kill_peer_send(g_c, groupnumber, g->peer_number); } else { return group_freeze_peer_send(g_c, groupnumber, g->peer_number); } } /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success * return -1 if groupnumber is invalid. * return -2 if title is too long or empty. * return -3 if packet fails to send. */ int group_title_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *title, uint8_t title_len) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (title_len > MAX_NAME_LENGTH || title_len == 0) { return -2; } /* same as already set? */ if (g->title_len == title_len && !memcmp(g->title, title, title_len)) { return 0; } memcpy(g->title, title, title_len); g->title_len = title_len; if (g->numpeers == 1) { return 0; } if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_TITLE_ID, title, title_len) > 0) { return 0; } return -3; } /* return the group's title size. * return -1 of groupnumber is invalid. * return -2 if title is too long or empty. */ int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (g->title_len > MAX_NAME_LENGTH || g->title_len == 0) { return -2; } return g->title_len; } /* Get group title from groupnumber and put it in title. * Title needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. * * return length of copied title if success. * return -1 if groupnumber is invalid. * return -2 if title is too long or empty. */ int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (g->title_len > MAX_NAME_LENGTH || g->title_len == 0) { return -2; } memcpy(title, g->title, g->title_len); return g->title_len; } static bool get_peer_number(const Group_c *g, const uint8_t *real_pk, uint16_t *peer_number) { const int peer_index = peer_in_group(g, real_pk); if (peer_index >= 0) { *peer_number = g->group[peer_index].peer_number; return true; } const int frozen_index = frozen_in_group(g, real_pk); if (frozen_index >= 0) { *peer_number = g->frozen[frozen_index].peer_number; return true; } return false; } static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *userdata) { Group_Chats *g_c = m->conferences_object; if (length <= 1) { return; } const uint8_t *invite_data = data + 1; const uint16_t invite_length = length - 1; switch (data[0]) { case INVITE_ID: { if (length != INVITE_PACKET_SIZE) { return; } const int groupnumber = get_group_num(g_c, data[1 + sizeof(uint16_t)], data + 1 + sizeof(uint16_t) + 1); if (groupnumber == -1) { if (g_c->invite_callback) { g_c->invite_callback(m, friendnumber, invite_data[sizeof(uint16_t)], invite_data, invite_length, userdata); } return; } else { Group_c *g = get_group_c(g_c, groupnumber); if (g && g->status == GROUPCHAT_STATUS_CONNECTED) { send_invite_response(g_c, groupnumber, friendnumber, invite_data, invite_length); } } break; } case INVITE_ACCEPT_ID: case INVITE_MEMBER_ID: { const bool member = (data[0] == INVITE_MEMBER_ID); if (length != (member ? INVITE_MEMBER_PACKET_SIZE : INVITE_ACCEPT_PACKET_SIZE)) { return; } uint16_t other_groupnum; uint16_t groupnum; net_unpack_u16(data + 1, &other_groupnum); net_unpack_u16(data + 1 + sizeof(uint16_t), &groupnum); Group_c *g = get_group_c(g_c, groupnum); if (!g) { return; } if (data[1 + sizeof(uint16_t) * 2] != g->type) { return; } if (crypto_memcmp(data + 1 + sizeof(uint16_t) * 2 + 1, g->id, GROUP_ID_LENGTH) != 0) { return; } uint16_t peer_number; if (member) { net_unpack_u16(data + 1 + sizeof(uint16_t) * 2 + 1 + GROUP_ID_LENGTH, &peer_number); } else { /* TODO(irungentoo): what if two people enter the group at the * same time and are given the same peer_number by different * nodes? */ peer_number = random_u16(); unsigned int tries = 0; while (get_peer_index(g, peer_number) != -1 || get_frozen_index(g, peer_number) != -1) { peer_number = random_u16(); ++tries; if (tries > 32) { return; } } } const int friendcon_id = getfriendcon_id(m, friendnumber); if (friendcon_id == -1) { // TODO(iphydf): Log something? return; } uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true, true); const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCING, 1); if (member) { add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCER, 0); send_peer_query(g_c, friendcon_id, other_groupnum); } if (connection_index != -1) { g->connections[connection_index].group_number = other_groupnum; g->connections[connection_index].type = GROUPCHAT_CONNECTION_ONLINE; } group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); break; } default: return; } } /* Find index of friend in the connections list. * * return index on success * return -1 on failure. */ static int friend_in_connections(const Group_c *g, int friendcon_id) { for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { continue; } if (g->connections[i].number == (uint32_t)friendcon_id) { return i; } } return -1; } /* return number of connections. */ static unsigned int count_connected(const Group_c *g) { unsigned int count = 0; for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_ONLINE) { ++count; } } return count; } static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t type, const uint8_t *id) { uint8_t packet[1 + ONLINE_PACKET_DATA_SIZE]; group_num = net_htons(group_num); packet[0] = PACKET_ID_ONLINE_PACKET; memcpy(packet + 1, &group_num, sizeof(uint16_t)); packet[1 + sizeof(uint16_t)] = type; memcpy(packet + 1 + sizeof(uint16_t) + 1, id, GROUP_ID_LENGTH); return write_cryptpacket(friendconn_net_crypto(fr_c), friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, sizeof(packet), 0) != -1; } static bool ping_groupchat(Group_Chats *g_c, uint32_t groupnumber); static int handle_packet_online(Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length) { if (length != ONLINE_PACKET_DATA_SIZE) { return -1; } const int groupnumber = get_group_num(g_c, data[sizeof(uint16_t)], data + sizeof(uint16_t) + 1); if (groupnumber == -1) { return -1; } uint16_t other_groupnum; memcpy(&other_groupnum, data, sizeof(uint16_t)); other_groupnum = net_ntohs(other_groupnum); Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } const int index = friend_in_connections(g, friendcon_id); if (index == -1) { return -1; } if (g->connections[index].type == GROUPCHAT_CONNECTION_ONLINE) { return -1; } if (count_connected(g) == 0 || (g->connections[index].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER)) { send_peer_query(g_c, friendcon_id, other_groupnum); } g->connections[index].group_number = other_groupnum; g->connections[index].type = GROUPCHAT_CONNECTION_ONLINE; send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->type, g->id); if (g->connections[index].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCING) { uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); const int peer_index = peer_in_group(g, real_pk); if (peer_index != -1) { group_new_peer_send(g_c, groupnumber, g->group[peer_index].peer_number, real_pk, temp_pk); } g->need_send_name = true; } ping_groupchat(g_c, groupnumber); return 0; } static int handle_packet_rejoin(Group_Chats *g_c, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) { if (length < 1 + GROUP_ID_LENGTH) { return -1; } const int32_t groupnum = get_group_num(g_c, *data, data + 1); Group_c *g = get_group_c(g_c, groupnum); if (!g) { return -1; } uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); uint16_t peer_number; if (!get_peer_number(g, real_pk, &peer_number)) { return -1; } addpeer(g_c, groupnum, real_pk, temp_pk, peer_number, userdata, true, true); const int connection_index = add_conn_to_groupchat(g_c, friendcon_id, g, GROUPCHAT_CONNECTION_REASON_INTRODUCING, 1); if (connection_index != -1) { send_packet_online(g_c->fr_c, friendcon_id, groupnum, g->type, g->id); } return 0; } // we could send title with invite, but then if it changes between sending and accepting inv, joinee won't see it /* return 1 on success. * return 0 on failure */ static unsigned int send_peer_introduced(Group_Chats *g_c, int friendcon_id, uint16_t group_num) { uint8_t packet[1]; packet[0] = PEER_INTRODUCED_ID; return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); } /* return 1 on success. * return 0 on failure */ static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num) { uint8_t packet[1]; packet[0] = PEER_QUERY_ID; return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, packet, sizeof(packet)); } /* return number of peers sent on success. * return 0 on failure. */ static unsigned int send_peers(Group_Chats *g_c, const Group_c *g, int friendcon_id, uint16_t group_num) { uint8_t response_packet[MAX_CRYPTO_DATA_SIZE - (1 + sizeof(uint16_t))]; response_packet[0] = PEER_RESPONSE_ID; uint8_t *p = response_packet + 1; uint16_t sent = 0; for (uint32_t i = 0;; ++i) { if (i == g->numpeers || (p - response_packet) + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1 + g->group[i].nick_len > sizeof(response_packet)) { if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, response_packet, (p - response_packet))) { sent = i; } else { return sent; } if (i == g->numpeers) { break; } p = response_packet + 1; } const uint16_t peer_num = net_htons(g->group[i].peer_number); memcpy(p, &peer_num, sizeof(peer_num)); p += sizeof(peer_num); memcpy(p, g->group[i].real_pk, CRYPTO_PUBLIC_KEY_SIZE); p += CRYPTO_PUBLIC_KEY_SIZE; memcpy(p, g->group[i].temp_pk, CRYPTO_PUBLIC_KEY_SIZE); p += CRYPTO_PUBLIC_KEY_SIZE; *p = g->group[i].nick_len; p += 1; memcpy(p, g->group[i].nick, g->group[i].nick_len); p += g->group[i].nick_len; } if (g->title_len) { VLA(uint8_t, title_packet, 1 + g->title_len); title_packet[0] = PEER_TITLE_ID; memcpy(title_packet + 1, g->title, g->title_len); send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_CONFERENCE, group_num, title_packet, SIZEOF_VLA(title_packet)); } return sent; } static int handle_send_peers(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, void *userdata) { if (length == 0) { return -1; } Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } const uint8_t *d = data; while ((unsigned int)(length - (d - data)) >= sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE * 2 + 1) { uint16_t peer_num; memcpy(&peer_num, d, sizeof(peer_num)); peer_num = net_ntohs(peer_num); d += sizeof(uint16_t); if (g->status == GROUPCHAT_STATUS_VALID && public_key_cmp(d, nc_get_self_public_key(g_c->m->net_crypto)) == 0) { g->peer_number = peer_num; g->status = GROUPCHAT_STATUS_CONNECTED; if (g_c->connected_callback) { g_c->connected_callback(g_c->m, groupnumber, userdata); } g->need_send_name = true; } const int peer_index = addpeer(g_c, groupnumber, d, d + CRYPTO_PUBLIC_KEY_SIZE, peer_num, userdata, false, true); if (peer_index == -1) { return -1; } d += CRYPTO_PUBLIC_KEY_SIZE * 2; const uint8_t name_length = *d; d += 1; if (name_length > (length - (d - data)) || name_length > MAX_NAME_LENGTH) { return -1; } if (!g->group[peer_index].nick_updated) { setnick(g_c, groupnumber, peer_index, d, name_length, userdata, true); } d += name_length; } return 0; } static void handle_direct_packet(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, int connection_index, void *userdata) { if (length == 0) { return; } Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return; } switch (data[0]) { case PEER_INTRODUCED_ID: { remove_connection_reason(g_c, g, connection_index, GROUPCHAT_CONNECTION_REASON_INTRODUCING); } break; case PEER_QUERY_ID: { if (g->connections[connection_index].type != GROUPCHAT_CONNECTION_ONLINE) { return; } send_peers(g_c, g, g->connections[connection_index].number, g->connections[connection_index].group_number); } break; case PEER_RESPONSE_ID: { handle_send_peers(g_c, groupnumber, data + 1, length - 1, userdata); } break; case PEER_TITLE_ID: { if (!g->title_fresh) { settitle(g_c, groupnumber, -1, data + 1, length - 1, userdata); } } break; } } /* Send message to all connections except receiver (if receiver isn't -1) * NOTE: this function appends the group chat number to the data passed to it. * * return number of messages sent. */ static unsigned int send_message_all_connections(const Group_Chats *g_c, const Group_c *g, const uint8_t *data, uint16_t length, int receiver) { uint16_t sent = 0; for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE) { continue; } if ((int)i == receiver) { continue; } if (send_packet_group_peer(g_c->fr_c, g->connections[i].number, PACKET_ID_MESSAGE_CONFERENCE, g->connections[i].group_number, data, length)) { ++sent; } } return sent; } /* Send lossy message to all connections except receiver (if receiver isn't -1) * NOTE: this function appends the group chat number to the data passed to it. * * return number of messages sent. */ static unsigned int send_lossy_all_connections(const Group_Chats *g_c, const Group_c *g, const uint8_t *data, uint16_t length, int receiver) { unsigned int sent = 0; unsigned int num_connected_closest = 0; unsigned int connected_closest[DESIRED_CLOSEST]; for (unsigned int i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE) { continue; } if ((int)i == receiver) { continue; } if (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST) { connected_closest[num_connected_closest] = i; ++num_connected_closest; continue; } if (send_lossy_group_peer(g_c->fr_c, g->connections[i].number, PACKET_ID_LOSSY_CONFERENCE, g->connections[i].group_number, data, length)) { ++sent; } } if (!num_connected_closest) { return sent; } unsigned int to_send[2] = {0, 0}; uint64_t comp_val_old[2] = {(uint64_t) -1, (uint64_t) -1}; for (unsigned int i = 0; i < num_connected_closest; ++i) { uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE] = {0}; get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[connected_closest[i]].number); const uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); for (uint8_t j = 0; j < 2; ++j) { if (j ? (comp_val > comp_val_old[j]) : (comp_val < comp_val_old[j])) { to_send[j] = connected_closest[i]; comp_val_old[j] = comp_val; } } } for (uint8_t j = 0; j < 2; ++j) { if (j && to_send[1] == to_send[0]) { break; } if (send_lossy_group_peer(g_c->fr_c, g->connections[to_send[j]].number, PACKET_ID_LOSSY_CONFERENCE, g->connections[to_send[j]].group_number, data, length)) { ++sent; } } return sent; } /* Send data of len with message_id to groupnumber. * * return number of peers it was sent to on success. * return -1 if groupnumber is invalid. * return -2 if message is too long. * return -3 if we are not connected to the group. * return -4 if message failed to send. */ static int send_message_group(const Group_Chats *g_c, uint32_t groupnumber, uint8_t message_id, const uint8_t *data, uint16_t len) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (len > MAX_GROUP_MESSAGE_DATA_LEN) { return -2; } if (g->status != GROUPCHAT_STATUS_CONNECTED || count_connected(g) == 0) { return -3; } VLA(uint8_t, packet, sizeof(uint16_t) + sizeof(uint32_t) + 1 + len); const uint16_t peer_num = net_htons(g->peer_number); memcpy(packet, &peer_num, sizeof(peer_num)); ++g->message_number; if (!g->message_number) { ++g->message_number; } const uint32_t message_num = net_htonl(g->message_number); memcpy(packet + sizeof(uint16_t), &message_num, sizeof(message_num)); packet[sizeof(uint16_t) + sizeof(uint32_t)] = message_id; if (len) { memcpy(packet + sizeof(uint16_t) + sizeof(uint32_t) + 1, data, len); } unsigned int ret = send_message_all_connections(g_c, g, packet, SIZEOF_VLA(packet), -1); if (ret == 0) { return -4; } return ret; } /* send a group message * return 0 on success * see: send_message_group() for error codes. */ int group_message_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *message, uint16_t length) { const int ret = send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length); if (ret > 0) { return 0; } return ret; } /* send a group action * return 0 on success * see: send_message_group() for error codes. */ int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *action, uint16_t length) { const int ret = send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length); if (ret > 0) { return 0; } return ret; } /* High level function to send custom lossy packets. * * return -1 on failure. * return 0 on success. */ int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length) { // TODO(irungentoo): length check here? Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } VLA(uint8_t, packet, sizeof(uint16_t) * 2 + length); const uint16_t peer_number = net_htons(g->peer_number); memcpy(packet, &peer_number, sizeof(uint16_t)); const uint16_t message_num = net_htons(g->lossy_message_number); memcpy(packet + sizeof(uint16_t), &message_num, sizeof(uint16_t)); memcpy(packet + sizeof(uint16_t) * 2, data, length); if (send_lossy_all_connections(g_c, g, packet, SIZEOF_VLA(packet), -1) == 0) { return -1; } ++g->lossy_message_number; return 0; } static Message_Info *find_message_slot_or_reject(uint32_t message_number, uint8_t message_id, Group_Peer *peer) { const bool ignore_older = (message_id == GROUP_MESSAGE_NAME_ID || message_id == GROUP_MESSAGE_TITLE_ID); Message_Info *i; for (i = peer->last_message_infos; i < peer->last_message_infos + peer->num_last_message_infos; ++i) { if (message_number - (i->message_number + 1) <= ((uint32_t)1 << 31)) { break; } if (message_number == i->message_number) { return nullptr; } if (ignore_older && message_id == i->message_id) { return nullptr; } } return i; } /* Stores message info in peer->last_message_infos. * * return true if message should be processed. * return false otherwise. */ static bool check_message_info(uint32_t message_number, uint8_t message_id, Group_Peer *peer) { Message_Info *const i = find_message_slot_or_reject(message_number, message_id, peer); if (i == nullptr) { return false; } if (i == peer->last_message_infos + MAX_LAST_MESSAGE_INFOS) { return false; } if (peer->num_last_message_infos < MAX_LAST_MESSAGE_INFOS) { ++peer->num_last_message_infos; } memmove(i + 1, i, ((peer->last_message_infos + peer->num_last_message_infos - 1) - i) * sizeof(Message_Info)); i->message_number = message_number; i->message_id = message_id; return true; } static void handle_message_packet_group(Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length, int connection_index, void *userdata) { if (length < sizeof(uint16_t) + sizeof(uint32_t) + 1) { return; } Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return; } uint16_t peer_number; memcpy(&peer_number, data, sizeof(uint16_t)); peer_number = net_ntohs(peer_number); uint32_t message_number; memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); message_number = net_ntohl(message_number); const uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)]; const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(message_number) + 1; const uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1); const bool ignore_frozen = message_id == GROUP_MESSAGE_FREEZE_PEER_ID; const int index = ignore_frozen ? get_peer_index(g, peer_number) : note_peer_active(g_c, groupnumber, peer_number, userdata); if (index == -1) { if (ignore_frozen) { return; } if (g->connections[connection_index].type != GROUPCHAT_CONNECTION_ONLINE) { return; } /* If we don't know the peer this packet came from, then we query the * list of peers from the relaying peer. * (They wouldn't have relayed it if they didn't know the peer.) */ send_peer_query(g_c, g->connections[connection_index].number, g->connections[connection_index].group_number); return; } if (g->num_introducer_connections > 0 && count_connected(g) > DESIRED_CLOSEST) { for (uint32_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE || !(g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_INTRODUCER) || i == connection_index) { continue; } uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[i].number); if (id_equal(g->group[index].real_pk, real_pk)) { /* Received message from peer relayed via another peer, so * the introduction was successful */ remove_connection_reason(g_c, g, i, GROUPCHAT_CONNECTION_REASON_INTRODUCER); } } } if (!check_message_info(message_number, message_id, &g->group[index])) { return; } uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; get_friendcon_public_keys(real_pk, nullptr, g_c->fr_c, g->connections[connection_index].number); const bool direct_from_sender = id_equal(g->group[index].real_pk, real_pk); switch (message_id) { case GROUP_MESSAGE_PING_ID: break; case GROUP_MESSAGE_NEW_PEER_ID: { if (msg_data_len != GROUP_MESSAGE_NEW_PEER_LENGTH) { return; } uint16_t new_peer_number; memcpy(&new_peer_number, msg_data, sizeof(uint16_t)); new_peer_number = net_ntohs(new_peer_number); addpeer(g_c, groupnumber, msg_data + sizeof(uint16_t), msg_data + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE, new_peer_number, userdata, true, true); } break; case GROUP_MESSAGE_KILL_PEER_ID: case GROUP_MESSAGE_FREEZE_PEER_ID: { if (msg_data_len != GROUP_MESSAGE_KILL_PEER_LENGTH) { return; } uint16_t kill_peer_number; memcpy(&kill_peer_number, msg_data, sizeof(uint16_t)); kill_peer_number = net_ntohs(kill_peer_number); if (peer_number == kill_peer_number) { if (message_id == GROUP_MESSAGE_KILL_PEER_ID) { delpeer(g_c, groupnumber, index, userdata); } else { freeze_peer(g_c, groupnumber, index, userdata); } } else { return; // TODO(irungentoo): } } break; case GROUP_MESSAGE_NAME_ID: { if (!setnick(g_c, groupnumber, index, msg_data, msg_data_len, userdata, true)) { return; } } break; case GROUP_MESSAGE_TITLE_ID: { if (!settitle(g_c, groupnumber, index, msg_data, msg_data_len, userdata)) { return; } } break; case PACKET_ID_MESSAGE: { if (msg_data_len == 0) { return; } VLA(uint8_t, newmsg, msg_data_len + 1); memcpy(newmsg, msg_data, msg_data_len); newmsg[msg_data_len] = 0; // TODO(irungentoo): if (g_c->message_callback) { g_c->message_callback(g_c->m, groupnumber, index, 0, newmsg, msg_data_len, userdata); } break; } case PACKET_ID_ACTION: { if (msg_data_len == 0) { return; } VLA(uint8_t, newmsg, msg_data_len + 1); memcpy(newmsg, msg_data, msg_data_len); newmsg[msg_data_len] = 0; // TODO(irungentoo): if (g_c->message_callback) { g_c->message_callback(g_c->m, groupnumber, index, 1, newmsg, msg_data_len, userdata); } break; } default: return; } /* If the packet was received from the peer who sent the message, relay it * back. When the sender only has one group connection (e.g. because there * are only two peers in the group), this is the only way for them to * receive their own message. */ send_message_all_connections(g_c, g, data, length, direct_from_sender ? -1 : connection_index); } static int g_handle_packet(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) { Group_Chats *g_c = (Group_Chats *)object; if (length < 1 + sizeof(uint16_t) + 1) { return -1; } if (data[0] == PACKET_ID_ONLINE_PACKET) { return handle_packet_online(g_c, friendcon_id, data + 1, length - 1); } if (data[0] == PACKET_ID_REJOIN_CONFERENCE) { return handle_packet_rejoin(g_c, friendcon_id, data + 1, length - 1, userdata); } uint16_t groupnumber; memcpy(&groupnumber, data + 1, sizeof(uint16_t)); groupnumber = net_ntohs(groupnumber); const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } const int index = friend_in_connections(g, friendcon_id); if (index == -1) { return -1; } if (data[0] == PACKET_ID_DIRECT_CONFERENCE) { handle_direct_packet(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index, userdata); return 0; } if (data[0] == PACKET_ID_MESSAGE_CONFERENCE) { handle_message_packet_group(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index, userdata); return 0; } return -1; } /* Did we already receive the lossy packet or not. * * return -1 on failure. * return 0 if packet was not received. * return 1 if packet was received. * * TODO(irungentoo): test this */ static int lossy_packet_not_received(const Group_c *g, int peer_index, uint16_t message_number) { if (peer_index == -1) { return -1; } if (g->group[peer_index].bottom_lossy_number == g->group[peer_index].top_lossy_number) { g->group[peer_index].top_lossy_number = message_number; g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) < MAX_LOSSY_COUNT) { if (g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT]) { return 1; } g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) > (1 << 15)) { return -1; } const uint16_t top_distance = message_number - g->group[peer_index].top_lossy_number; if (top_distance >= MAX_LOSSY_COUNT) { crypto_memzero(g->group[peer_index].recv_lossy, sizeof(g->group[peer_index].recv_lossy)); } else { // top_distance < MAX_LOSSY_COUNT for (unsigned int i = g->group[peer_index].bottom_lossy_number; i != g->group[peer_index].bottom_lossy_number + top_distance; ++i) { g->group[peer_index].recv_lossy[i % MAX_LOSSY_COUNT] = 0; } } g->group[peer_index].top_lossy_number = message_number; g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } /* Does this group type make use of lossy packets? */ static bool type_uses_lossy(uint8_t type) { return (type == GROUPCHAT_TYPE_AV); } static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length, void *userdata) { Group_Chats *g_c = (Group_Chats *)object; if (data[0] != PACKET_ID_LOSSY_CONFERENCE) { return -1; } if (length < 1 + sizeof(uint16_t) * 3 + 1) { return -1; } uint16_t groupnumber; uint16_t peer_number; uint16_t message_number; memcpy(&groupnumber, data + 1, sizeof(uint16_t)); memcpy(&peer_number, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); memcpy(&message_number, data + 1 + sizeof(uint16_t) * 2, sizeof(uint16_t)); groupnumber = net_ntohs(groupnumber); peer_number = net_ntohs(peer_number); message_number = net_ntohs(message_number); const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (!type_uses_lossy(g->type)) { return -1; } const int index = friend_in_connections(g, friendcon_id); if (index == -1) { return -1; } if (peer_number == g->peer_number) { return -1; } const int peer_index = get_peer_index(g, peer_number); if (peer_index == -1) { return -1; } if (lossy_packet_not_received(g, peer_index, message_number) != 0) { return -1; } const uint8_t *lossy_data = data + 1 + sizeof(uint16_t) * 3; uint16_t lossy_length = length - (1 + sizeof(uint16_t) * 3); const uint8_t message_id = lossy_data[0]; ++lossy_data; --lossy_length; send_lossy_all_connections(g_c, g, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); if (!g_c->lossy_packethandlers[message_id].function) { return -1; } if (g_c->lossy_packethandlers[message_id].function(g->object, groupnumber, peer_index, g->group[peer_index].object, lossy_data, lossy_length) == -1) { return -1; } return 0; } /* Set the object that is tied to the group chat. * * return 0 on success. * return -1 on failure */ int group_set_object(const Group_Chats *g_c, uint32_t groupnumber, void *object) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } g->object = object; return 0; } /* Set the object that is tied to the group peer. * * return 0 on success. * return -1 on failure */ int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, void *object) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return -1; } if (peernumber >= g->numpeers) { return -1; } g->group[peernumber].object = object; return 0; } /* Return the object tied to the group chat previously set by group_set_object. * * return NULL on failure. * return object on success. */ void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return nullptr; } return g->object; } /* Return the object tied to the group chat peer previously set by group_peer_set_object. * * return NULL on failure. * return object on success. */ void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber) { const Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return nullptr; } if (peernumber >= g->numpeers) { return nullptr; } return g->group[peernumber].object; } /* Interval in seconds to send ping messages */ #define GROUP_PING_INTERVAL 20 static bool ping_groupchat(Group_Chats *g_c, uint32_t groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } if (mono_time_is_timeout(g_c->mono_time, g->last_sent_ping, GROUP_PING_INTERVAL)) { if (group_ping_send(g_c, groupnumber)) { g->last_sent_ping = mono_time_get(g_c->mono_time); } } return true; } /* Seconds of inactivity after which to freeze a peer */ #define FREEZE_TIMEOUT (GROUP_PING_INTERVAL * 3) static bool groupchat_freeze_timedout(Group_Chats *g_c, uint32_t groupnumber, void *userdata) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) { return false; } for (uint32_t i = 0; i < g->numpeers; ++i) { if (g->group[i].peer_number == g->peer_number) { continue; } if (mono_time_is_timeout(g_c->mono_time, g->group[i].last_active, FREEZE_TIMEOUT)) { freeze_peer(g_c, groupnumber, i, userdata); } } if (g->numpeers <= 1) { g->title_fresh = false; } return true; } /* Push non-empty slots to start. */ static void squash_connections(Group_c *g) { uint16_t i = 0; for (uint16_t j = 0; j < MAX_GROUP_CONNECTIONS; ++j) { if (g->connections[j].type != GROUPCHAT_CONNECTION_NONE) { g->connections[i] = g->connections[j]; ++i; } } for (; i < MAX_GROUP_CONNECTIONS; ++i) { g->connections[i].type = GROUPCHAT_CONNECTION_NONE; } } #define MIN_EMPTY_CONNECTIONS (1 + MAX_GROUP_CONNECTIONS / 10) /* Remove old connections as necessary to ensure we have space for new * connections. This invalidates connections array indices (which is * why we do this periodically rather than on adding a connection). */ static void clean_connections(Group_Chats *g_c, Group_c *g) { uint16_t to_clear = MIN_EMPTY_CONNECTIONS; for (uint16_t i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->connections[i].type == GROUPCHAT_CONNECTION_NONE) { --to_clear; if (to_clear == 0) { break; } } } for (; to_clear > 0; --to_clear) { // Remove a connection. Prefer non-closest connections, and given // that prefer non-online connections, and given that prefer earlier // slots. uint16_t i = 0; while (i < MAX_GROUP_CONNECTIONS && (g->connections[i].type != GROUPCHAT_CONNECTION_CONNECTING || (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST))) { ++i; } if (i == MAX_GROUP_CONNECTIONS) { i = 0; while (i < MAX_GROUP_CONNECTIONS - to_clear && (g->connections[i].type != GROUPCHAT_CONNECTION_ONLINE || (g->connections[i].reasons & GROUPCHAT_CONNECTION_REASON_CLOSEST))) { ++i; } } if (g->connections[i].type != GROUPCHAT_CONNECTION_NONE) { remove_connection(g_c, g, i); } } squash_connections(g); } /* Send current name (set in messenger) to all online groups. */ void send_name_all_groups(Group_Chats *g_c) { for (uint16_t i = 0; i < g_c->num_chats; ++i) { Group_c *g = get_group_c(g_c, i); if (!g) { continue; } if (g->status == GROUPCHAT_STATUS_CONNECTED) { group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); g->need_send_name = false; } } } #define SAVED_PEER_SIZE_CONSTANT (2 * CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint16_t) + sizeof(uint64_t) + 1) static uint32_t saved_peer_size(const Group_Peer *peer) { return SAVED_PEER_SIZE_CONSTANT + peer->nick_len; } static uint8_t *save_peer(const Group_Peer *peer, uint8_t *data) { memcpy(data, peer->real_pk, CRYPTO_PUBLIC_KEY_SIZE); data += CRYPTO_PUBLIC_KEY_SIZE; memcpy(data, peer->temp_pk, CRYPTO_PUBLIC_KEY_SIZE); data += CRYPTO_PUBLIC_KEY_SIZE; host_to_lendian_bytes16(data, peer->peer_number); data += sizeof(uint16_t); host_to_lendian_bytes64(data, peer->last_active); data += sizeof(uint64_t); *data = peer->nick_len; ++data; memcpy(data, peer->nick, peer->nick_len); data += peer->nick_len; return data; } #define SAVED_CONF_SIZE_CONSTANT (1 + GROUP_ID_LENGTH + sizeof(uint32_t) \ + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + 1) static uint32_t saved_conf_size(const Group_c *g) { uint32_t len = SAVED_CONF_SIZE_CONSTANT + g->title_len; for (uint32_t j = 0; j < g->numpeers + g->numfrozen; ++j) { const Group_Peer *peer = (j < g->numpeers) ? &g->group[j] : &g->frozen[j - g->numpeers]; if (id_equal(peer->real_pk, g->real_pk)) { continue; } len += saved_peer_size(peer); } return len; } /* Save a future message number. The save will remain valid until we have sent * this many more messages. */ #define SAVE_OFFSET_MESSAGE_NUMBER (1 << 16) #define SAVE_OFFSET_LOSSY_MESSAGE_NUMBER (1 << 13) static uint8_t *save_conf(const Group_c *g, uint8_t *data) { *data = g->type; ++data; memcpy(data, g->id, GROUP_ID_LENGTH); data += GROUP_ID_LENGTH; host_to_lendian_bytes32(data, g->message_number + SAVE_OFFSET_MESSAGE_NUMBER); data += sizeof(uint32_t); host_to_lendian_bytes16(data, g->lossy_message_number + SAVE_OFFSET_LOSSY_MESSAGE_NUMBER); data += sizeof(uint16_t); host_to_lendian_bytes16(data, g->peer_number); data += sizeof(uint16_t); uint8_t *const numsaved_location = data; data += sizeof(uint32_t); *data = g->title_len; ++data; memcpy(data, g->title, g->title_len); data += g->title_len; uint32_t numsaved = 0; for (uint32_t j = 0; j < g->numpeers + g->numfrozen; ++j) { const Group_Peer *peer = (j < g->numpeers) ? &g->group[j] : &g->frozen[j - g->numpeers]; if (id_equal(peer->real_pk, g->real_pk)) { continue; } data = save_peer(peer, data); ++numsaved; } host_to_lendian_bytes32(numsaved_location, numsaved); return data; } static uint32_t conferences_section_size(const Group_Chats *g_c) { uint32_t len = 0; for (uint16_t i = 0; i < g_c->num_chats; ++i) { const Group_c *g = get_group_c(g_c, i); if (!g || g->status != GROUPCHAT_STATUS_CONNECTED) { continue; } len += saved_conf_size(g); } return len; } uint32_t conferences_size(const Group_Chats *g_c) { return 2 * sizeof(uint32_t) + conferences_section_size(g_c); } uint8_t *conferences_save(const Group_Chats *g_c, uint8_t *data) { const uint32_t len = conferences_section_size(g_c); data = state_write_section_header(data, STATE_COOKIE_TYPE, len, STATE_TYPE_CONFERENCES); for (uint16_t i = 0; i < g_c->num_chats; ++i) { const Group_c *g = get_group_c(g_c, i); if (!g || g->status != GROUPCHAT_STATUS_CONNECTED) { continue; } data = save_conf(g, data); } return data; } static State_Load_Status load_conferences(Group_Chats *g_c, const uint8_t *data, uint32_t length) { const uint8_t *init_data = data; while (length >= (uint32_t)(data - init_data) + SAVED_CONF_SIZE_CONSTANT) { const int groupnumber = create_group_chat(g_c); if (groupnumber == -1) { return STATE_LOAD_STATUS_ERROR; } Group_c *g = &g_c->chats[groupnumber]; g->type = *data; ++data; memcpy(g->id, data, GROUP_ID_LENGTH); data += GROUP_ID_LENGTH; lendian_bytes_to_host32(&g->message_number, data); data += sizeof(uint32_t); lendian_bytes_to_host16(&g->lossy_message_number, data); data += sizeof(uint16_t); lendian_bytes_to_host16(&g->peer_number, data); data += sizeof(uint16_t); lendian_bytes_to_host32(&g->numfrozen, data); data += sizeof(uint32_t); if (g->numfrozen > 0) { g->frozen = (Group_Peer *)malloc(sizeof(Group_Peer) * g->numfrozen); if (g->frozen == nullptr) { return STATE_LOAD_STATUS_ERROR; } } g->title_len = *data; if (g->title_len > MAX_NAME_LENGTH) { return STATE_LOAD_STATUS_ERROR; } ++data; if (length < (uint32_t)(data - init_data) + g->title_len) { return STATE_LOAD_STATUS_ERROR; } memcpy(g->title, data, g->title_len); data += g->title_len; for (uint32_t j = 0; j < g->numfrozen; ++j) { if (length < (uint32_t)(data - init_data) + SAVED_PEER_SIZE_CONSTANT) { return STATE_LOAD_STATUS_ERROR; } Group_Peer *peer = &g->frozen[j]; memset(peer, 0, sizeof(Group_Peer)); id_copy(peer->real_pk, data); data += CRYPTO_PUBLIC_KEY_SIZE; id_copy(peer->temp_pk, data); data += CRYPTO_PUBLIC_KEY_SIZE; lendian_bytes_to_host16(&peer->peer_number, data); data += sizeof(uint16_t); lendian_bytes_to_host64(&peer->last_active, data); data += sizeof(uint64_t); peer->nick_len = *data; if (peer->nick_len > MAX_NAME_LENGTH) { return STATE_LOAD_STATUS_ERROR; } ++data; if (length < (uint32_t)(data - init_data) + peer->nick_len) { return STATE_LOAD_STATUS_ERROR; } memcpy(peer->nick, data, peer->nick_len); data += peer->nick_len; // NOTE: this relies on friends being loaded before conferences. peer->is_friend = (getfriend_id(g_c->m, peer->real_pk) != -1); } if (g->numfrozen > g->maxfrozen) { g->maxfrozen = g->numfrozen; } g->status = GROUPCHAT_STATUS_CONNECTED; memcpy(g->real_pk, nc_get_self_public_key(g_c->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); const int peer_index = addpeer(g_c, groupnumber, g->real_pk, dht_get_self_public_key(g_c->m->dht), g->peer_number, nullptr, true, false); if (peer_index == -1) { return STATE_LOAD_STATUS_ERROR; } setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length, nullptr, false); } return STATE_LOAD_STATUS_CONTINUE; } bool conferences_load_state_section(Group_Chats *g_c, const uint8_t *data, uint32_t length, uint16_t type, State_Load_Status *status) { if (type != STATE_TYPE_CONFERENCES) { return false; } *status = load_conferences(g_c, data, length); return true; } /* Create new groupchat instance. */ Group_Chats *new_groupchats(Mono_Time *mono_time, Messenger *m) { if (!m) { return nullptr; } Group_Chats *temp = (Group_Chats *)calloc(1, sizeof(Group_Chats)); if (temp == nullptr) { return nullptr; } temp->mono_time = mono_time; temp->m = m; temp->fr_c = m->fr_c; m->conferences_object = temp; m_callback_conference_invite(m, &handle_friend_invite_packet); set_global_status_callback(m->fr_c, &g_handle_any_status, temp); return temp; } /* main groupchats loop. */ void do_groupchats(Group_Chats *g_c, void *userdata) { for (uint16_t i = 0; i < g_c->num_chats; ++i) { Group_c *g = get_group_c(g_c, i); if (!g) { continue; } if (g->status == GROUPCHAT_STATUS_CONNECTED) { connect_to_closest(g_c, i, userdata); ping_groupchat(g_c, i); groupchat_freeze_timedout(g_c, i, userdata); clean_connections(g_c, g); if (g->need_send_name) { group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); g->need_send_name = false; } } } // TODO(irungentoo): } /* Free everything related with group chats. */ void kill_groupchats(Group_Chats *g_c) { for (uint16_t i = 0; i < g_c->num_chats; ++i) { del_groupchat(g_c, i, false); } m_callback_conference_invite(g_c->m, nullptr); set_global_status_callback(g_c->m->fr_c, nullptr, nullptr); g_c->m->conferences_object = nullptr; free(g_c); } /* Return the number of chats in the instance m. * You should use this to determine how much memory to allocate * for copy_chatlist. */ uint32_t count_chatlist(const Group_Chats *g_c) { uint32_t ret = 0; for (uint16_t i = 0; i < g_c->num_chats; ++i) { if (g_c->chats[i].status != GROUPCHAT_STATUS_NONE) { ++ret; } } return ret; } /* Copy a list of valid chat IDs into the array out_list. * If out_list is NULL, returns 0. * Otherwise, returns the number of elements copied. * If the array was too small, the contents * of out_list will be truncated to list_size. */ uint32_t copy_chatlist(const Group_Chats *g_c, uint32_t *out_list, uint32_t list_size) { if (!out_list) { return 0; } if (g_c->num_chats == 0) { return 0; } uint32_t ret = 0; for (uint16_t i = 0; i < g_c->num_chats; ++i) { if (ret >= list_size) { break; /* Abandon ship */ } if (g_c->chats[i].status > GROUPCHAT_STATUS_NONE) { out_list[ret] = i; ++ret; } } return ret; } c-toxcore-0.2.13/toxcore/group.h000066400000000000000000000360561415350724400165070ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Slightly better groupchats implementation. */ #ifndef C_TOXCORE_TOXCORE_GROUP_H #define C_TOXCORE_TOXCORE_GROUP_H #include "Messenger.h" typedef enum Groupchat_Status { GROUPCHAT_STATUS_NONE, GROUPCHAT_STATUS_VALID, GROUPCHAT_STATUS_CONNECTED, } Groupchat_Status; typedef enum Groupchat_Type { GROUPCHAT_TYPE_TEXT, GROUPCHAT_TYPE_AV, } Groupchat_Type; #define MAX_LOSSY_COUNT 256 typedef struct Message_Info { uint32_t message_number; uint8_t message_id; } Message_Info; #define MAX_LAST_MESSAGE_INFOS 8 typedef struct Group_Peer { uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; bool temp_pk_updated; bool is_friend; uint64_t last_active; Message_Info last_message_infos[MAX_LAST_MESSAGE_INFOS]; /* received messages, strictly decreasing in message_number */ uint8_t num_last_message_infos; uint8_t nick[MAX_NAME_LENGTH]; uint8_t nick_len; bool nick_updated; uint16_t peer_number; uint8_t recv_lossy[MAX_LOSSY_COUNT]; uint16_t bottom_lossy_number; uint16_t top_lossy_number; void *object; } Group_Peer; #define DESIRED_CLOSEST 4 #define MAX_GROUP_CONNECTIONS 16 #define GROUP_ID_LENGTH CRYPTO_SYMMETRIC_KEY_SIZE typedef enum Groupchat_Connection_Type { GROUPCHAT_CONNECTION_NONE, GROUPCHAT_CONNECTION_CONNECTING, GROUPCHAT_CONNECTION_ONLINE, } Groupchat_Connection_Type; /* Connection is to one of the closest DESIRED_CLOSEST peers */ #define GROUPCHAT_CONNECTION_REASON_CLOSEST (1 << 0) /* Connection is to a peer we are introducing to the conference */ #define GROUPCHAT_CONNECTION_REASON_INTRODUCING (1 << 1) /* Connection is to a peer who is introducing us to the conference */ #define GROUPCHAT_CONNECTION_REASON_INTRODUCER (1 << 2) typedef struct Groupchat_Connection { uint8_t type; /* `GROUPCHAT_CONNECTION_*` */ uint8_t reasons; /* bit field with flags `GROUPCHAT_CONNECTION_REASON_*` */ uint32_t number; uint16_t group_number; } Groupchat_Connection; typedef struct Groupchat_Closest { uint8_t entry; uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_pk[CRYPTO_PUBLIC_KEY_SIZE]; } Groupchat_Closest; typedef void peer_on_join_cb(void *object, uint32_t conference_number, uint32_t peer_number); typedef void peer_on_leave_cb(void *object, uint32_t conference_number, void *peer_object); typedef void group_on_delete_cb(void *object, uint32_t conference_number); // maximum number of frozen peers to store; group_set_max_frozen() overrides. #define MAX_FROZEN_DEFAULT 128 typedef struct Group_c { uint8_t status; bool need_send_name; bool title_fresh; Group_Peer *group; uint32_t numpeers; Group_Peer *frozen; uint32_t numfrozen; uint32_t maxfrozen; Groupchat_Connection connections[MAX_GROUP_CONNECTIONS]; uint8_t real_pk[CRYPTO_PUBLIC_KEY_SIZE]; Groupchat_Closest closest_peers[DESIRED_CLOSEST]; uint8_t changed; uint8_t type; uint8_t id[GROUP_ID_LENGTH]; uint8_t title[MAX_NAME_LENGTH]; uint8_t title_len; uint32_t message_number; uint16_t lossy_message_number; uint16_t peer_number; uint64_t last_sent_ping; uint32_t num_introducer_connections; void *object; peer_on_join_cb *peer_on_join; peer_on_leave_cb *peer_on_leave; group_on_delete_cb *group_on_delete; } Group_c; /* Callback for group invites. * * data of length is what needs to be passed to join_groupchat(). */ typedef void g_conference_invite_cb(Messenger *m, uint32_t friend_number, int type, const uint8_t *cookie, size_t length, void *user_data); /* Callback for group connection. */ typedef void g_conference_connected_cb(Messenger *m, uint32_t conference_number, void *user_data); /* Callback for group messages. */ typedef void g_conference_message_cb(Messenger *m, uint32_t conference_number, uint32_t peer_number, int type, const uint8_t *message, size_t length, void *user_data); /* Callback for peer nickname changes. */ typedef void peer_name_cb(Messenger *m, uint32_t conference_number, uint32_t peer_number, const uint8_t *name, size_t length, void *user_data); /* Set callback function for peer list changes. */ typedef void peer_list_changed_cb(Messenger *m, uint32_t conference_number, void *user_data); /* Callback for title changes. * * If peer_number == -1, then author is unknown (e.g. initial joining the group). */ typedef void title_cb(Messenger *m, uint32_t conference_number, uint32_t peer_number, const uint8_t *title, size_t length, void *user_data); /* Callback for lossy packets. * * NOTE: Handler must return 0 if packet is to be relayed, -1 if the packet should not be relayed. */ typedef int lossy_packet_cb(void *object, uint32_t conference_number, uint32_t peer_number, void *peer_object, const uint8_t *packet, uint16_t length); typedef struct Group_Lossy_Handler { lossy_packet_cb *function; } Group_Lossy_Handler; typedef struct Group_Chats { const Mono_Time *mono_time; Messenger *m; Friend_Connections *fr_c; Group_c *chats; uint16_t num_chats; g_conference_invite_cb *invite_callback; g_conference_connected_cb *connected_callback; g_conference_message_cb *message_callback; peer_name_cb *peer_name_callback; peer_list_changed_cb *peer_list_changed_callback; title_cb *title_callback; Group_Lossy_Handler lossy_packethandlers[256]; } Group_Chats; /* Set the callback for group invites. */ void g_callback_group_invite(Group_Chats *g_c, g_conference_invite_cb *function); /* Set the callback for group connection. */ void g_callback_group_connected(Group_Chats *g_c, g_conference_connected_cb *function); /* Set the callback for group messages. */ void g_callback_group_message(Group_Chats *g_c, g_conference_message_cb *function); /* Set callback function for title changes. */ void g_callback_group_title(Group_Chats *g_c, title_cb *function); /* Set callback function for peer nickname changes. * * It gets called every time a peer changes their nickname. */ void g_callback_peer_name(Group_Chats *g_c, peer_name_cb *function); /* Set callback function for peer list changes. * * It gets called every time the name list changes(new peer, deleted peer) */ void g_callback_peer_list_changed(Group_Chats *g_c, peer_list_changed_cb *function); /* Creates a new groupchat and puts it in the chats array. * * type is one of `GROUPCHAT_TYPE_*` * * return group number on success. * return -1 on failure. */ int add_groupchat(Group_Chats *g_c, uint8_t type); /* Delete a groupchat from the chats array, informing the group first as * appropriate. * * return 0 on success. * return -1 if groupnumber is invalid. */ int del_groupchat(Group_Chats *g_c, uint32_t groupnumber, bool leave_permanently); /* Copy the public key of (frozen, if frozen is true) peernumber who is in * groupnumber to pk. * pk must be CRYPTO_PUBLIC_KEY_SIZE long. * * return 0 on success * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. */ int group_peer_pubkey(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *pk, bool frozen); /* * Return the size of (frozen, if frozen is true) peernumber's name. * * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. */ int group_peername_size(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, bool frozen); /* Copy the name of (frozen, if frozen is true) peernumber who is in * groupnumber to name. * name must be at least MAX_NAME_LENGTH long. * * return length of name if success * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. */ int group_peername(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint8_t *name, bool frozen); /* Copy last active timestamp of frozen peernumber who is in groupnumber to * last_active. * * return 0 on success. * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. */ int group_frozen_last_active(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, uint64_t *last_active); /* Set maximum number of frozen peers. * * return 0 on success. * return -1 if groupnumber is invalid. */ int group_set_max_frozen(const Group_Chats *g_c, uint32_t groupnumber, uint32_t maxfrozen); /* invite friendnumber to groupnumber * * return 0 on success. * return -1 if groupnumber is invalid. * return -2 if invite packet failed to send. */ int invite_friend(Group_Chats *g_c, uint32_t friendnumber, uint32_t groupnumber); /* Join a group (you need to have been invited first.) * * expected_type is the groupchat type we expect the chat we are joining to * have. * * return group number on success. * return -1 if data length is invalid. * return -2 if group is not the expected type. * return -3 if friendnumber is invalid. * return -4 if client is already in this group. * return -5 if group instance failed to initialize. * return -6 if join packet fails to send. */ int join_groupchat(Group_Chats *g_c, uint32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length); /* send a group message * return 0 on success * see: send_message_group() for error codes. */ int group_message_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *message, uint16_t length); /* send a group action * return 0 on success * see: send_message_group() for error codes. */ int group_action_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *action, uint16_t length); /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success * return -1 if groupnumber is invalid. * return -2 if title is too long or empty. * return -3 if packet fails to send. */ int group_title_send(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *title, uint8_t title_len); /* return the group's title size. * return -1 of groupnumber is invalid. * return -2 if title is too long or empty. */ int group_title_get_size(const Group_Chats *g_c, uint32_t groupnumber); /* Get group title from groupnumber and put it in title. * Title needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes. * * return length of copied title if success. * return -1 if groupnumber is invalid. * return -2 if title is too long or empty. */ int group_title_get(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *title); /* Return the number of (frozen, if frozen is true) peers in the group chat on * success. * return -1 if groupnumber is invalid. */ int group_number_peers(const Group_Chats *g_c, uint32_t groupnumber, bool frozen); /* return 1 if the peernumber corresponds to ours. * return 0 if the peernumber is not ours. * return -1 if groupnumber is invalid. * return -2 if peernumber is invalid. * return -3 if we are not connected to the group chat. */ int group_peernumber_is_ours(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber); /* Set handlers for custom lossy packets. */ void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, lossy_packet_cb *function); /* High level function to send custom lossy packets. * * return -1 on failure. * return 0 on success. */ int send_group_lossy_packet(const Group_Chats *g_c, uint32_t groupnumber, const uint8_t *data, uint16_t length); /* Return the number of chats in the instance m. * You should use this to determine how much memory to allocate * for copy_chatlist. */ uint32_t count_chatlist(const Group_Chats *g_c); /* Copy a list of valid chat IDs into the array out_list. * If out_list is NULL, returns 0. * Otherwise, returns the number of elements copied. * If the array was too small, the contents * of out_list will be truncated to list_size. */ uint32_t copy_chatlist(const Group_Chats *g_c, uint32_t *out_list, uint32_t list_size); /* return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. * * return -1 on failure. * return type on success. */ int group_get_type(const Group_Chats *g_c, uint32_t groupnumber); /* Copies the unique id of `group_chat[groupnumber]` into id. * * return false on failure. * return true on success. */ bool conference_get_id(const Group_Chats *g_c, uint32_t groupnumber, uint8_t *id); int32_t conference_by_id(const Group_Chats *g_c, const uint8_t *id); /* Send current name (set in messenger) to all online groups. */ void send_name_all_groups(Group_Chats *g_c); /* Set the object that is tied to the group chat. * * return 0 on success. * return -1 on failure */ int group_set_object(const Group_Chats *g_c, uint32_t groupnumber, void *object); /* Set the object that is tied to the group peer. * * return 0 on success. * return -1 on failure */ int group_peer_set_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber, void *object); /* Return the object tied to the group chat previously set by group_set_object. * * return NULL on failure. * return object on success. */ void *group_get_object(const Group_Chats *g_c, uint32_t groupnumber); /* Return the object tied to the group chat peer previously set by group_peer_set_object. * * return NULL on failure. * return object on success. */ void *group_peer_get_object(const Group_Chats *g_c, uint32_t groupnumber, uint32_t peernumber); /* Set a function to be called when a new peer joins a group chat. * * return 0 on success. * return -1 on failure. */ int callback_groupchat_peer_new(const Group_Chats *g_c, uint32_t groupnumber, peer_on_join_cb *function); /* Set a function to be called when a peer leaves a group chat. * * return 0 on success. * return -1 on failure. */ int callback_groupchat_peer_delete(Group_Chats *g_c, uint32_t groupnumber, peer_on_leave_cb *function); /* Set a function to be called when the group chat is deleted. * * return 0 on success. * return -1 on failure. */ int callback_groupchat_delete(Group_Chats *g_c, uint32_t groupnumber, group_on_delete_cb *function); /* Return size of the conferences data (for saving). */ uint32_t conferences_size(const Group_Chats *g_c); /* Save the conferences in data (must be allocated memory of size at least conferences_size()) */ uint8_t *conferences_save(const Group_Chats *g_c, uint8_t *data); /** * Load a state section. * * @param data Data to load * @param length Length of data * @param type Type of section (`STATE_TYPE_*`) * @param status Result of loading section is stored here if the section is handled. * @return true iff section handled. */ bool conferences_load_state_section(Group_Chats *g_c, const uint8_t *data, uint32_t length, uint16_t type, State_Load_Status *status); /* Create new groupchat instance. */ Group_Chats *new_groupchats(Mono_Time *mono_time, Messenger *m); /* main groupchats loop. */ void do_groupchats(Group_Chats *g_c, void *userdata); /* Free everything related with group chats. */ void kill_groupchats(Group_Chats *g_c); #endif c-toxcore-0.2.13/toxcore/list.c000066400000000000000000000134611415350724400163140ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Simple struct with functions to create a list which associates ids with data * -Allows for finding ids associated with data such as IPs or public keys in a short time * -Should only be used if there are relatively few add/remove calls to the list */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "list.h" #include #include #include #include "ccompat.h" /* Basically, the elements in the list are placed in order so that they can be searched for easily * -each element is seen as a big-endian integer when ordering them * -the ids array is maintained so that each id always matches * -the search algorithm cuts down the time to find the id associated with a piece of data * at the cost of slow add/remove functions for large lists * -Starts at 1/2 of the array, compares the element in the array with the data, * then moves +/- 1/4 of the array depending on whether the value is greater or lower, * then +- 1/8, etc, until the value is matched or its position where it should be in the array is found * -some considerations since the array size is never perfect */ static int32_t list_index(uint32_t i) { return ~i; } /* Find data in list * * return value: * >= 0 : index of data in array * < 0 : no match, returns index (return value is list_index(index)) where * the data should be inserted */ static int find(const BS_List *list, const uint8_t *data) { // should work well, but could be improved if (list->n == 0) { return list_index(0); } uint32_t i = list->n / 2; // current position in the array uint32_t delta = i / 2; // how much we move in the array if (!delta) { delta = 1; } int d = -1; // used to determine if closest match is found // closest match is found if we move back to where we have already been while (1) { int r = memcmp(data, list->data + list->element_size * i, list->element_size); if (r == 0) { return i; } if (r > 0) { // data is greater // move down i += delta; if (d == 0 || i == list->n) { // reached bottom of list, or closest match return list_index(i); } delta = (delta) / 2; if (delta == 0) { delta = 1; d = 1; } } else { // data is smaller if (d == 1 || i == 0) { // reached top or list or closest match return list_index(i); } // move up i -= delta; delta = (delta) / 2; if (delta == 0) { delta = 1; d = 0; } } } } /** * Resizes the list. * * @return true on success. */ static bool resize(BS_List *list, uint32_t new_size) { if (new_size == 0) { bs_list_free(list); return true; } uint8_t *data = (uint8_t *)realloc(list->data, list->element_size * new_size); if (!data) { return false; } list->data = data; int *ids = (int *)realloc(list->ids, sizeof(int) * new_size); if (!ids) { return false; } list->ids = ids; return true; } int bs_list_init(BS_List *list, uint32_t element_size, uint32_t initial_capacity) { // set initial values list->n = 0; list->element_size = element_size; list->capacity = 0; list->data = nullptr; list->ids = nullptr; if (initial_capacity != 0) { if (!resize(list, initial_capacity)) { return 0; } } list->capacity = initial_capacity; return 1; } void bs_list_free(BS_List *list) { // free both arrays free(list->data); list->data = nullptr; free(list->ids); list->ids = nullptr; } int bs_list_find(const BS_List *list, const uint8_t *data) { int r = find(list, data); // return only -1 and positive values if (r < 0) { return -1; } return list->ids[r]; } int bs_list_add(BS_List *list, const uint8_t *data, int id) { // find where the new element should be inserted // see: return value of find() int i = find(list, data); if (i >= 0) { // already in list return 0; } i = ~i; // increase the size of the arrays if needed if (list->n == list->capacity) { // 1.5 * n + 1 const uint32_t new_capacity = list->n + list->n / 2 + 1; if (!resize(list, new_capacity)) { return 0; } list->capacity = new_capacity; } // insert data to element array memmove(list->data + (i + 1) * list->element_size, list->data + i * list->element_size, (list->n - i) * list->element_size); memcpy(list->data + i * list->element_size, data, list->element_size); // insert id to id array memmove(&list->ids[i + 1], &list->ids[i], (list->n - i) * sizeof(int)); list->ids[i] = id; // increase n ++list->n; return 1; } int bs_list_remove(BS_List *list, const uint8_t *data, int id) { int i = find(list, data); if (i < 0) { return 0; } if (list->ids[i] != id) { // this should never happen return 0; } // decrease the size of the arrays if needed if (list->n < list->capacity / 2) { const uint32_t new_capacity = list->capacity / 2; if (resize(list, new_capacity)) { list->capacity = new_capacity; } } --list->n; memmove(list->data + i * list->element_size, list->data + (i + 1) * list->element_size, (list->n - i) * list->element_size); memmove(&list->ids[i], &list->ids[i + 1], (list->n - i) * sizeof(int)); return 1; } c-toxcore-0.2.13/toxcore/list.h000066400000000000000000000032541415350724400163200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Simple struct with functions to create a list which associates ids with data * -Allows for finding ids associated with data such as IPs or public keys in a short time * -Should only be used if there are relatively few add/remove calls to the list */ #ifndef C_TOXCORE_TOXCORE_LIST_H #define C_TOXCORE_TOXCORE_LIST_H #include typedef struct BS_List { uint32_t n; // number of elements uint32_t capacity; // number of elements memory is allocated for uint32_t element_size; // size of the elements uint8_t *data; // array of elements int *ids; // array of element ids } BS_List; /* Initialize a list, element_size is the size of the elements in the list and * initial_capacity is the number of elements the memory will be initially allocated for * * return value: * 1 : success * 0 : failure */ int bs_list_init(BS_List *list, uint32_t element_size, uint32_t initial_capacity); /* Free a list initiated with list_init */ void bs_list_free(BS_List *list); /* Retrieve the id of an element in the list * * return value: * >= 0 : id associated with data * -1 : failure */ int bs_list_find(const BS_List *list, const uint8_t *data); /* Add an element with associated id to the list * * return value: * 1 : success * 0 : failure (data already in list) */ int bs_list_add(BS_List *list, const uint8_t *data, int id); /* Remove element from the list * * return value: * 1 : success * 0 : failure (element not found or id does not match) */ int bs_list_remove(BS_List *list, const uint8_t *data, int id); #endif c-toxcore-0.2.13/toxcore/logger.c000066400000000000000000000053431415350724400166200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013,2015 Tox project. */ /* * Text logging abstraction. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "logger.h" #include #include #include #include #include struct Logger { logger_cb *callback; void *context; void *userdata; }; #ifdef USE_STDERR_LOGGER static const char *logger_level_name(Logger_Level level) { switch (level) { case LOGGER_LEVEL_TRACE: return "TRACE"; case LOGGER_LEVEL_DEBUG: return "DEBUG"; case LOGGER_LEVEL_INFO: return "INFO"; case LOGGER_LEVEL_WARNING: return "WARNING"; case LOGGER_LEVEL_ERROR: return "ERROR"; } return ""; } static void logger_stderr_handler(void *context, Logger_Level level, const char *file, int line, const char *func, const char *message, void *userdata) { // GL stands for "global logger". fprintf(stderr, "[GL] %s %s:%d(%s): %s\n", logger_level_name(level), file, line, func, message); } static const Logger logger_stderr = { logger_stderr_handler, nullptr, nullptr, }; #endif /** * Public Functions */ Logger *logger_new(void) { return (Logger *)calloc(1, sizeof(Logger)); } void logger_kill(Logger *log) { free(log); } void logger_callback_log(Logger *log, logger_cb *function, void *context, void *userdata) { log->callback = function; log->context = context; log->userdata = userdata; } void logger_write(const Logger *log, Logger_Level level, const char *file, int line, const char *func, const char *format, ...) { if (!log) { #ifdef USE_STDERR_LOGGER log = &logger_stderr; #else fprintf(stderr, "NULL logger not permitted.\n"); abort(); #endif } if (!log->callback) { return; } // Only pass the file name, not the entire file path, for privacy reasons. // The full path may contain PII of the person compiling toxcore (their // username and directory layout). const char *filename = strrchr(file, '/'); file = filename ? filename + 1 : file; #if defined(_WIN32) || defined(__CYGWIN__) // On Windows, the path separator *may* be a backslash, so we look for that // one too. const char *windows_filename = strrchr(file, '\\'); file = windows_filename ? windows_filename + 1 : file; #endif // Format message char msg[1024]; va_list args; va_start(args, format); vsnprintf(msg, sizeof(msg), format, args); va_end(args); log->callback(log->context, level, file, line, func, msg, log->userdata); } c-toxcore-0.2.13/toxcore/logger.h000066400000000000000000000055461415350724400166320ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Logger abstraction backed by callbacks for writing. */ #ifndef C_TOXCORE_TOXCORE_LOGGER_H #define C_TOXCORE_TOXCORE_LOGGER_H #include #include "ccompat.h" #ifndef MIN_LOGGER_LEVEL #define MIN_LOGGER_LEVEL LOGGER_LEVEL_INFO #endif // NOTE: Don't forget to update build system files after modifying the enum. typedef enum Logger_Level { LOGGER_LEVEL_TRACE, LOGGER_LEVEL_DEBUG, LOGGER_LEVEL_INFO, LOGGER_LEVEL_WARNING, LOGGER_LEVEL_ERROR, } Logger_Level; typedef struct Logger Logger; typedef void logger_cb(void *context, Logger_Level level, const char *file, int line, const char *func, const char *message, void *userdata); /** * Creates a new logger with logging disabled (callback is NULL) by default. */ Logger *logger_new(void); /** * Frees all resources associated with the logger. */ void logger_kill(Logger *log); /** * Sets the logger callback. Disables logging if set to NULL. * The context parameter is passed to the callback as first argument. */ void logger_callback_log(Logger *log, logger_cb *function, void *context, void *userdata); /** * Main write function. If logging is disabled, this does nothing. * * If the logger is NULL, this writes to stderr. This behaviour should not be * used in production code, but can be useful for temporarily debugging a * function that does not have a logger available. It's essentially * fprintf(stderr, ...), but with timestamps and source location. Toxcore must * be built with -DUSE_STDERR_LOGGER for this to work. It will cause an * assertion failure otherwise. */ void logger_write( const Logger *log, Logger_Level level, const char *file, int line, const char *func, const char *format, ...) GNU_PRINTF(6, 7); #define LOGGER_WRITE(log, level, ...) \ do { \ if (level >= MIN_LOGGER_LEVEL) { \ logger_write(log, level, __FILE__, __LINE__, __func__, __VA_ARGS__); \ } \ } while (0) /* To log with an logger */ #define LOGGER_TRACE(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_TRACE , __VA_ARGS__) #define LOGGER_DEBUG(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_DEBUG , __VA_ARGS__) #define LOGGER_INFO(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_INFO , __VA_ARGS__) #define LOGGER_WARNING(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_WARNING, __VA_ARGS__) #define LOGGER_ERROR(log, ...) LOGGER_WRITE(log, LOGGER_LEVEL_ERROR , __VA_ARGS__) #define LOGGER_FATAL(log, ...) \ do { \ LOGGER_ERROR(log, __VA_ARGS__); \ abort(); \ } while(0) #define LOGGER_ASSERT(log, cond, ...) \ do { \ if (!(cond)) { \ LOGGER_ERROR(log, "Assertion failed"); \ LOGGER_FATAL(log, __VA_ARGS__); \ } \ } while(0) #endif // C_TOXCORE_TOXCORE_LOGGER_H c-toxcore-0.2.13/toxcore/mono_time.c000066400000000000000000000133671415350724400173340ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2020 The TokTok team. * Copyright © 2014 Tox project. */ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #if !defined(OS_WIN32) && (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) #define OS_WIN32 #define WIN32_LEAN_AND_MEAN #include #endif #ifdef __APPLE__ #include #include #endif #ifndef OS_WIN32 #include #endif #include "mono_time.h" #include #include #include #include "ccompat.h" /* don't call into system billions of times for no reason */ struct Mono_Time { uint64_t time; uint64_t base_time; #ifdef OS_WIN32 /* protect `last_clock_update` and `last_clock_mono` from concurrent access */ pthread_mutex_t last_clock_lock; uint32_t last_clock_mono; bool last_clock_update; #endif /* protect `time` from concurrent access */ pthread_rwlock_t *time_update_lock; mono_time_current_time_cb *current_time_callback; void *user_data; }; static uint64_t current_time_monotonic_default(Mono_Time *mono_time, void *user_data) { uint64_t time = 0; #ifdef OS_WIN32 /* Must hold mono_time->last_clock_lock here */ /* GetTickCount provides only a 32 bit counter, but we can't use * GetTickCount64 for backwards compatibility, so we handle wraparound * ourselves. */ uint32_t ticks = GetTickCount(); /* the higher 32 bits count the number of wrap arounds */ uint64_t old_ovf = mono_time->time & ~((uint64_t)UINT32_MAX); /* Check if time has decreased because of 32 bit wrap from GetTickCount() */ if (ticks < mono_time->last_clock_mono) { /* account for overflow */ old_ovf += UINT32_MAX + UINT64_C(1); } if (mono_time->last_clock_update) { mono_time->last_clock_mono = ticks; mono_time->last_clock_update = false; } /* splice the low and high bits back together */ time = old_ovf + ticks; #else struct timespec clock_mono; #if defined(__APPLE__) clock_serv_t muhclock; mach_timespec_t machtime; host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &muhclock); clock_get_time(muhclock, &machtime); mach_port_deallocate(mach_task_self(), muhclock); clock_mono.tv_sec = machtime.tv_sec; clock_mono.tv_nsec = machtime.tv_nsec; #else clock_gettime(CLOCK_MONOTONIC, &clock_mono); #endif time = 1000ULL * clock_mono.tv_sec + (clock_mono.tv_nsec / 1000000ULL); #endif return time; } Mono_Time *mono_time_new(void) { Mono_Time *mono_time = (Mono_Time *)malloc(sizeof(Mono_Time)); if (mono_time == nullptr) { return nullptr; } mono_time->time_update_lock = (pthread_rwlock_t *)malloc(sizeof(pthread_rwlock_t)); if (mono_time->time_update_lock == nullptr) { free(mono_time); return nullptr; } if (pthread_rwlock_init(mono_time->time_update_lock, nullptr) < 0) { free(mono_time->time_update_lock); free(mono_time); return nullptr; } mono_time->current_time_callback = current_time_monotonic_default; mono_time->user_data = nullptr; #ifdef OS_WIN32 mono_time->last_clock_mono = 0; mono_time->last_clock_update = false; if (pthread_mutex_init(&mono_time->last_clock_lock, nullptr) < 0) { free(mono_time->time_update_lock); free(mono_time); return nullptr; } #endif mono_time->time = 0; mono_time->base_time = (uint64_t)time(nullptr) - (current_time_monotonic(mono_time) / 1000ULL); mono_time_update(mono_time); return mono_time; } void mono_time_free(Mono_Time *mono_time) { #ifdef OS_WIN32 pthread_mutex_destroy(&mono_time->last_clock_lock); #endif pthread_rwlock_destroy(mono_time->time_update_lock); free(mono_time->time_update_lock); free(mono_time); } void mono_time_update(Mono_Time *mono_time) { uint64_t time = 0; #ifdef OS_WIN32 /* we actually want to update the overflow state of mono_time here */ pthread_mutex_lock(&mono_time->last_clock_lock); mono_time->last_clock_update = true; #endif time = mono_time->current_time_callback(mono_time, mono_time->user_data) / 1000ULL; time += mono_time->base_time; #ifdef OS_WIN32 pthread_mutex_unlock(&mono_time->last_clock_lock); #endif pthread_rwlock_wrlock(mono_time->time_update_lock); mono_time->time = time; pthread_rwlock_unlock(mono_time->time_update_lock); } uint64_t mono_time_get(const Mono_Time *mono_time) { uint64_t time = 0; pthread_rwlock_rdlock(mono_time->time_update_lock); time = mono_time->time; pthread_rwlock_unlock(mono_time->time_update_lock); return time; } bool mono_time_is_timeout(const Mono_Time *mono_time, uint64_t timestamp, uint64_t timeout) { return timestamp + timeout <= mono_time_get(mono_time); } void mono_time_set_current_time_callback(Mono_Time *mono_time, mono_time_current_time_cb *current_time_callback, void *user_data) { if (current_time_callback == nullptr) { mono_time->current_time_callback = current_time_monotonic_default; mono_time->user_data = nullptr; } else { mono_time->current_time_callback = current_time_callback; mono_time->user_data = user_data; } } /* return current monotonic time in milliseconds (ms). */ uint64_t current_time_monotonic(Mono_Time *mono_time) { /* For WIN32 we don't want to change overflow state of mono_time here */ #ifdef OS_WIN32 /* We don't want to update the overflow state of mono_time here, * but must protect against other threads */ pthread_mutex_lock(&mono_time->last_clock_lock); #endif uint64_t time = mono_time->current_time_callback(mono_time, mono_time->user_data); #ifdef OS_WIN32 pthread_mutex_unlock(&mono_time->last_clock_lock); #endif return time; } c-toxcore-0.2.13/toxcore/mono_time.h000066400000000000000000000062571415350724400173410ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2020 The TokTok team. * Copyright © 2014 Tox project. */ #ifndef C_TOXCORE_TOXCORE_MONO_TIME_H #define C_TOXCORE_TOXCORE_MONO_TIME_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef MONO_TIME_DEFINED #define MONO_TIME_DEFINED /** * The timer portion of the toxcore event loop. * * We update the time exactly once per tox_iterate call. Programs built on lower * level APIs such as the DHT bootstrap node must update the time manually in * each iteration. * * Time is kept per Tox instance, not globally, even though "time" as a concept * is global. This is because by definition `mono_time` represents the time at * the start of an iteration, and also by definition the time when all network * events for the current iteration occurred. This affects mainly two situations: * * 1. Two timers started in the same iteration: e.g. two timers set to expire in * 10 seconds will both expire at the same time, i.e. about 10 seconds later. * If the time were global, `mono_time` would be a random number that is * either the time at the start of an iteration, or 1 second later (since the * timer resolution is 1 second). This can happen when one update happens at * e.g. 10:00:00.995 and a few milliseconds later a concurrently running * instance updates the time at 10:00:01.005, making one timer expire a * second after the other. * 2. One timer based on an event: if we want to encode a behaviour of a timer * expiring e.g. 10 seconds after a network event occurred, we simply start a * timer in the event handler. If a concurrent instance updates the time * underneath us, it may instead expire 9 seconds after the event. * * Both these situations cause incorrect behaviour randomly. In practice, * toxcore is somewhat robust against strange timer behaviour, but the * implementation should at least theoretically match the specification. */ typedef struct Mono_Time Mono_Time; #endif /* MONO_TIME_DEFINED */ Mono_Time *mono_time_new(void); void mono_time_free(Mono_Time *mono_time); /** * Update mono_time; subsequent calls to mono_time_get or mono_time_is_timeout * will use the time at the call to mono_time_update. */ void mono_time_update(Mono_Time *mono_time); /** * Return unix time since epoch in seconds. */ uint64_t mono_time_get(const Mono_Time *mono_time); /** * Return true iff timestamp is at least timeout seconds in the past. */ bool mono_time_is_timeout(const Mono_Time *mono_time, uint64_t timestamp, uint64_t timeout); /** * Return current monotonic time in milliseconds (ms). The starting point is * unspecified. */ uint64_t current_time_monotonic(Mono_Time *mono_time); typedef uint64_t mono_time_current_time_cb(Mono_Time *mono_time, void *user_data); /* Override implementation of current_time_monotonic() (for tests). * * The caller is obligated to ensure that current_time_monotonic() continues * to increase monotonically. */ void mono_time_set_current_time_callback(Mono_Time *mono_time, mono_time_current_time_cb *current_time_callback, void *user_data); #ifdef __cplusplus } #endif #endif // C_TOXCORE_TOXCORE_MONO_TIME_H c-toxcore-0.2.13/toxcore/mono_time_test.cc000066400000000000000000000027241415350724400205310ustar00rootroot00000000000000#include "mono_time.h" #include namespace { TEST(MonoTime, UnixTimeIncreasesOverTime) { Mono_Time *mono_time = mono_time_new(); mono_time_update(mono_time); uint64_t const start = mono_time_get(mono_time); while (start == mono_time_get(mono_time)) { mono_time_update(mono_time); } uint64_t const end = mono_time_get(mono_time); EXPECT_GT(end, start); mono_time_free(mono_time); } TEST(MonoTime, IsTimeout) { Mono_Time *mono_time = mono_time_new(); uint64_t const start = mono_time_get(mono_time); EXPECT_FALSE(mono_time_is_timeout(mono_time, start, 1)); while (start == mono_time_get(mono_time)) { mono_time_update(mono_time); } EXPECT_TRUE(mono_time_is_timeout(mono_time, start, 1)); mono_time_free(mono_time); } uint64_t test_current_time_callback(Mono_Time *mono_time, void *user_data) { return *static_cast(user_data); } TEST(MonoTime, CustomTime) { Mono_Time *mono_time = mono_time_new(); uint64_t test_time = current_time_monotonic(mono_time) + 42137; mono_time_set_current_time_callback(mono_time, test_current_time_callback, &test_time); mono_time_update(mono_time); EXPECT_EQ(current_time_monotonic(mono_time), test_time); uint64_t const start = mono_time_get(mono_time); test_time += 7000; mono_time_update(mono_time); EXPECT_EQ(mono_time_get(mono_time) - start, 7); EXPECT_EQ(current_time_monotonic(mono_time), test_time); mono_time_free(mono_time); } } // namespace c-toxcore-0.2.13/toxcore/net_crypto.c000066400000000000000000002770221415350724400175340ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Functions for the core network crypto. * * NOTE: This code has to be perfect. We don't mess around with encryption. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "net_crypto.h" #include #include #include #include "mono_time.h" #include "util.h" typedef struct Packet_Data { uint64_t sent_time; uint16_t length; uint8_t data[MAX_CRYPTO_DATA_SIZE]; } Packet_Data; typedef struct Packets_Array { Packet_Data *buffer[CRYPTO_PACKET_BUFFER_SIZE]; uint32_t buffer_start; uint32_t buffer_end; /* packet numbers in array: `{buffer_start, buffer_end)` */ } Packets_Array; typedef enum Crypto_Conn_State { CRYPTO_CONN_FREE = 0, /* the connection slot is free. This value is 0 so it is valid after * `crypto_memzero(...)` of the parent struct */ CRYPTO_CONN_NO_CONNECTION, /* the connection is allocated, but not yet used */ CRYPTO_CONN_COOKIE_REQUESTING, /* we are sending cookie request packets */ CRYPTO_CONN_HANDSHAKE_SENT, /* we are sending handshake packets */ CRYPTO_CONN_NOT_CONFIRMED, /* we are sending handshake packets. * we have received one from the other, but no data */ CRYPTO_CONN_ESTABLISHED, /* the connection is established */ } Crypto_Conn_State; typedef struct Crypto_Connection { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ uint8_t sessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* Our public key for this session. */ uint8_t sessionsecret_key[CRYPTO_SECRET_KEY_SIZE]; /* Our private key for this session. */ uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; /* The precomputed shared key from encrypt_precompute. */ Crypto_Conn_State status; /* See Crypto_Conn_State documentation */ uint64_t cookie_request_number; /* number used in the cookie request packets for this connection */ uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer */ uint8_t *temp_packet; /* Where the cookie request/handshake packet is stored while it is being sent. */ uint16_t temp_packet_length; uint64_t temp_packet_sent_time; /* The time at which the last temp_packet was sent in ms. */ uint32_t temp_packet_num_sent; IP_Port ip_portv4; /* The ip and port to contact this guy directly.*/ IP_Port ip_portv6; uint64_t direct_lastrecv_timev4; /* The Time at which we last received a direct packet in ms. */ uint64_t direct_lastrecv_timev6; uint64_t last_tcp_sent; /* Time the last TCP packet was sent. */ Packets_Array send_array; Packets_Array recv_array; connection_status_cb *connection_status_callback; void *connection_status_callback_object; int connection_status_callback_id; connection_data_cb *connection_data_callback; void *connection_data_callback_object; int connection_data_callback_id; connection_lossy_data_cb *connection_lossy_data_callback; void *connection_lossy_data_callback_object; int connection_lossy_data_callback_id; uint64_t last_request_packet_sent; uint64_t direct_send_attempt_time; uint32_t packet_counter; double packet_recv_rate; uint64_t packet_counter_set; double packet_send_rate; uint32_t packets_left; uint64_t last_packets_left_set; double last_packets_left_rem; double packet_send_rate_requested; uint32_t packets_left_requested; uint64_t last_packets_left_requested_set; double last_packets_left_requested_rem; uint32_t last_sendqueue_size[CONGESTION_QUEUE_ARRAY_SIZE]; uint32_t last_sendqueue_counter; long signed int last_num_packets_sent[CONGESTION_LAST_SENT_ARRAY_SIZE]; long signed int last_num_packets_resent[CONGESTION_LAST_SENT_ARRAY_SIZE]; uint32_t packets_sent; uint32_t packets_resent; uint64_t last_congestion_event; uint64_t rtt_time; /* TCP_connection connection_number */ unsigned int connection_number_tcp; uint8_t maximum_speed_reached; /* Must be a pointer, because the struct is moved in memory */ pthread_mutex_t *mutex; dht_pk_cb *dht_pk_callback; void *dht_pk_callback_object; uint32_t dht_pk_callback_number; } Crypto_Connection; struct Net_Crypto { const Logger *log; Mono_Time *mono_time; DHT *dht; TCP_Connections *tcp_c; Crypto_Connection *crypto_connections; pthread_mutex_t tcp_mutex; pthread_mutex_t connections_mutex; unsigned int connection_use_counter; uint32_t crypto_connections_length; /* Length of connections array. */ /* Our public and secret keys. */ uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; /* The secret key used for cookies */ uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; new_connection_cb *new_connection_callback; void *new_connection_callback_object; /* The current optimal sleep time */ uint32_t current_sleep_time; BS_List ip_port_list; }; const uint8_t *nc_get_self_public_key(const Net_Crypto *c) { return c->self_public_key; } const uint8_t *nc_get_self_secret_key(const Net_Crypto *c) { return c->self_secret_key; } TCP_Connections *nc_get_tcp_c(const Net_Crypto *c) { return c->tcp_c; } DHT *nc_get_dht(const Net_Crypto *c) { return c->dht; } static bool crypt_connection_id_is_valid(const Net_Crypto *c, int crypt_connection_id) { if ((uint32_t)crypt_connection_id >= c->crypto_connections_length) { return false; } if (c->crypto_connections == nullptr) { return false; } const Crypto_Conn_State status = c->crypto_connections[crypt_connection_id].status; if (status == CRYPTO_CONN_NO_CONNECTION || status == CRYPTO_CONN_FREE) { return false; } return true; } /* cookie timeout in seconds */ #define COOKIE_TIMEOUT 15 #define COOKIE_DATA_LENGTH (uint16_t)(CRYPTO_PUBLIC_KEY_SIZE * 2) #define COOKIE_CONTENTS_LENGTH (uint16_t)(sizeof(uint64_t) + COOKIE_DATA_LENGTH) #define COOKIE_LENGTH (uint16_t)(CRYPTO_NONCE_SIZE + COOKIE_CONTENTS_LENGTH + CRYPTO_MAC_SIZE) #define COOKIE_REQUEST_PLAIN_LENGTH (uint16_t)(COOKIE_DATA_LENGTH + sizeof(uint64_t)) #define COOKIE_REQUEST_LENGTH (uint16_t)(1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE) #define COOKIE_RESPONSE_LENGTH (uint16_t)(1 + CRYPTO_NONCE_SIZE + COOKIE_LENGTH + sizeof(uint64_t) + CRYPTO_MAC_SIZE) /* Create a cookie request packet and put it in packet. * dht_public_key is the dht public key of the other * * packet must be of size COOKIE_REQUEST_LENGTH or bigger. * * return -1 on failure. * return COOKIE_REQUEST_LENGTH on success. */ static int create_cookie_request(const Net_Crypto *c, uint8_t *packet, uint8_t *dht_public_key, uint64_t number, uint8_t *shared_key) { uint8_t plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t padding[CRYPTO_PUBLIC_KEY_SIZE] = {0}; memcpy(plain, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(plain + CRYPTO_PUBLIC_KEY_SIZE, padding, CRYPTO_PUBLIC_KEY_SIZE); memcpy(plain + (CRYPTO_PUBLIC_KEY_SIZE * 2), &number, sizeof(uint64_t)); dht_get_shared_key_sent(c->dht, shared_key, dht_public_key); uint8_t nonce[CRYPTO_NONCE_SIZE]; random_nonce(nonce); packet[0] = NET_PACKET_COOKIE_REQUEST; memcpy(packet + 1, dht_get_self_public_key(c->dht), CRYPTO_PUBLIC_KEY_SIZE); memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); int len = encrypt_data_symmetric(shared_key, nonce, plain, sizeof(plain), packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); if (len != COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE) { return -1; } return (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + len); } /* Create cookie of length COOKIE_LENGTH from bytes of length COOKIE_DATA_LENGTH using encryption_key * * return -1 on failure. * return 0 on success. */ static int create_cookie(const Logger *log, const Mono_Time *mono_time, uint8_t *cookie, const uint8_t *bytes, const uint8_t *encryption_key) { uint8_t contents[COOKIE_CONTENTS_LENGTH]; const uint64_t temp_time = mono_time_get(mono_time); memcpy(contents, &temp_time, sizeof(temp_time)); memcpy(contents + sizeof(temp_time), bytes, COOKIE_DATA_LENGTH); random_nonce(cookie); int len = encrypt_data_symmetric(encryption_key, cookie, contents, sizeof(contents), cookie + CRYPTO_NONCE_SIZE); if (len != COOKIE_LENGTH - CRYPTO_NONCE_SIZE) { return -1; } return 0; } /* Open cookie of length COOKIE_LENGTH to bytes of length COOKIE_DATA_LENGTH using encryption_key * * return -1 on failure. * return 0 on success. */ static int open_cookie(const Logger *log, const Mono_Time *mono_time, uint8_t *bytes, const uint8_t *cookie, const uint8_t *encryption_key) { uint8_t contents[COOKIE_CONTENTS_LENGTH]; const int len = decrypt_data_symmetric(encryption_key, cookie, cookie + CRYPTO_NONCE_SIZE, COOKIE_LENGTH - CRYPTO_NONCE_SIZE, contents); if (len != sizeof(contents)) { return -1; } uint64_t cookie_time; memcpy(&cookie_time, contents, sizeof(cookie_time)); const uint64_t temp_time = mono_time_get(mono_time); if (cookie_time + COOKIE_TIMEOUT < temp_time || temp_time < cookie_time) { return -1; } memcpy(bytes, contents + sizeof(cookie_time), COOKIE_DATA_LENGTH); return 0; } /* Create a cookie response packet and put it in packet. * request_plain must be COOKIE_REQUEST_PLAIN_LENGTH bytes. * packet must be of size COOKIE_RESPONSE_LENGTH or bigger. * * return -1 on failure. * return COOKIE_RESPONSE_LENGTH on success. */ static int create_cookie_response(const Net_Crypto *c, uint8_t *packet, const uint8_t *request_plain, const uint8_t *shared_key, const uint8_t *dht_public_key) { uint8_t cookie_plain[COOKIE_DATA_LENGTH]; memcpy(cookie_plain, request_plain, CRYPTO_PUBLIC_KEY_SIZE); memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; if (create_cookie(c->log, c->mono_time, plain, cookie_plain, c->secret_symmetric_key) != 0) { return -1; } memcpy(plain + COOKIE_LENGTH, request_plain + COOKIE_DATA_LENGTH, sizeof(uint64_t)); packet[0] = NET_PACKET_COOKIE_RESPONSE; random_nonce(packet + 1); int len = encrypt_data_symmetric(shared_key, packet + 1, plain, sizeof(plain), packet + 1 + CRYPTO_NONCE_SIZE); if (len != COOKIE_RESPONSE_LENGTH - (1 + CRYPTO_NONCE_SIZE)) { return -1; } return COOKIE_RESPONSE_LENGTH; } /* Handle the cookie request packet of length length. * Put what was in the request in request_plain (must be of size COOKIE_REQUEST_PLAIN_LENGTH) * Put the key used to decrypt the request into shared_key (of size CRYPTO_SHARED_KEY_SIZE) for use in the response. * * return -1 on failure. * return 0 on success. */ static int handle_cookie_request(const Net_Crypto *c, uint8_t *request_plain, uint8_t *shared_key, uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) { if (length != COOKIE_REQUEST_LENGTH) { return -1; } memcpy(dht_public_key, packet + 1, CRYPTO_PUBLIC_KEY_SIZE); dht_get_shared_key_sent(c->dht, shared_key, dht_public_key); int len = decrypt_data_symmetric(shared_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, COOKIE_REQUEST_PLAIN_LENGTH + CRYPTO_MAC_SIZE, request_plain); if (len != COOKIE_REQUEST_PLAIN_LENGTH) { return -1; } return 0; } /* Handle the cookie request packet (for raw UDP) */ static int udp_handle_cookie_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Net_Crypto *c = (Net_Crypto *)object; uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; if (handle_cookie_request(c, request_plain, shared_key, dht_public_key, packet, length) != 0) { return 1; } uint8_t data[COOKIE_RESPONSE_LENGTH]; if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { return 1; } if ((uint32_t)sendpacket(dht_get_net(c->dht), source, data, sizeof(data)) != sizeof(data)) { return 1; } return 0; } /* Handle the cookie request packet (for TCP) */ static int tcp_handle_cookie_request(Net_Crypto *c, int connections_number, const uint8_t *packet, uint16_t length) { uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; if (handle_cookie_request(c, request_plain, shared_key, dht_public_key, packet, length) != 0) { return -1; } uint8_t data[COOKIE_RESPONSE_LENGTH]; if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { return -1; } int ret = send_packet_tcp_connection(c->tcp_c, connections_number, data, sizeof(data)); return ret; } /* Handle the cookie request packet (for TCP oob packets) */ static int tcp_oob_handle_cookie_request(const Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) { uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint8_t dht_public_key_temp[CRYPTO_PUBLIC_KEY_SIZE]; if (handle_cookie_request(c, request_plain, shared_key, dht_public_key_temp, packet, length) != 0) { return -1; } if (public_key_cmp(dht_public_key, dht_public_key_temp) != 0) { return -1; } uint8_t data[COOKIE_RESPONSE_LENGTH]; if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) { return -1; } int ret = tcp_send_oob_packet(c->tcp_c, tcp_connections_number, dht_public_key, data, sizeof(data)); return ret; } /* Handle a cookie response packet of length encrypted with shared_key. * put the cookie in the response in cookie * * cookie must be of length COOKIE_LENGTH. * * return -1 on failure. * return COOKIE_LENGTH on success. */ static int handle_cookie_response(const Logger *log, uint8_t *cookie, uint64_t *number, const uint8_t *packet, uint16_t length, const uint8_t *shared_key) { if (length != COOKIE_RESPONSE_LENGTH) { return -1; } uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; const int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, length - (1 + CRYPTO_NONCE_SIZE), plain); if (len != sizeof(plain)) { return -1; } memcpy(cookie, plain, COOKIE_LENGTH); memcpy(number, plain + COOKIE_LENGTH, sizeof(uint64_t)); return COOKIE_LENGTH; } #define HANDSHAKE_PACKET_LENGTH (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH + CRYPTO_MAC_SIZE) /* Create a handshake packet and put it in packet. * cookie must be COOKIE_LENGTH bytes. * packet must be of size HANDSHAKE_PACKET_LENGTH or bigger. * * return -1 on failure. * return HANDSHAKE_PACKET_LENGTH on success. */ static int create_crypto_handshake(const Net_Crypto *c, uint8_t *packet, const uint8_t *cookie, const uint8_t *nonce, const uint8_t *session_pk, const uint8_t *peer_real_pk, const uint8_t *peer_dht_pubkey) { uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; memcpy(plain, nonce, CRYPTO_NONCE_SIZE); memcpy(plain + CRYPTO_NONCE_SIZE, session_pk, CRYPTO_PUBLIC_KEY_SIZE); crypto_sha512(plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, cookie, COOKIE_LENGTH); uint8_t cookie_plain[COOKIE_DATA_LENGTH]; memcpy(cookie_plain, peer_real_pk, CRYPTO_PUBLIC_KEY_SIZE); memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, peer_dht_pubkey, CRYPTO_PUBLIC_KEY_SIZE); if (create_cookie(c->log, c->mono_time, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, cookie_plain, c->secret_symmetric_key) != 0) { return -1; } random_nonce(packet + 1 + COOKIE_LENGTH); int len = encrypt_data(peer_real_pk, c->self_secret_key, packet + 1 + COOKIE_LENGTH, plain, sizeof(plain), packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE); if (len != HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE)) { return -1; } packet[0] = NET_PACKET_CRYPTO_HS; memcpy(packet + 1, cookie, COOKIE_LENGTH); return HANDSHAKE_PACKET_LENGTH; } /* Handle a crypto handshake packet of length. * put the nonce contained in the packet in nonce, * the session public key in session_pk * the real public key of the peer in peer_real_pk * the dht public key of the peer in dht_public_key and * the cookie inside the encrypted part of the packet in cookie. * * if expected_real_pk isn't NULL it denotes the real public key * the packet should be from. * * nonce must be at least CRYPTO_NONCE_SIZE * session_pk must be at least CRYPTO_PUBLIC_KEY_SIZE * peer_real_pk must be at least CRYPTO_PUBLIC_KEY_SIZE * cookie must be at least COOKIE_LENGTH * * return -1 on failure. * return 0 on success. */ static int handle_crypto_handshake(const Net_Crypto *c, uint8_t *nonce, uint8_t *session_pk, uint8_t *peer_real_pk, uint8_t *dht_public_key, uint8_t *cookie, const uint8_t *packet, uint16_t length, const uint8_t *expected_real_pk) { if (length != HANDSHAKE_PACKET_LENGTH) { return -1; } uint8_t cookie_plain[COOKIE_DATA_LENGTH]; if (open_cookie(c->log, c->mono_time, cookie_plain, packet + 1, c->secret_symmetric_key) != 0) { return -1; } if (expected_real_pk) { if (public_key_cmp(cookie_plain, expected_real_pk) != 0) { return -1; } } uint8_t cookie_hash[CRYPTO_SHA512_SIZE]; crypto_sha512(cookie_hash, packet + 1, COOKIE_LENGTH); uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; int len = decrypt_data(cookie_plain, c->self_secret_key, packet + 1 + COOKIE_LENGTH, packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE, HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE), plain); if (len != sizeof(plain)) { return -1; } if (crypto_memcmp(cookie_hash, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_SHA512_SIZE) != 0) { return -1; } memcpy(nonce, plain, CRYPTO_NONCE_SIZE); memcpy(session_pk, plain + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE); memcpy(cookie, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, COOKIE_LENGTH); memcpy(peer_real_pk, cookie_plain, CRYPTO_PUBLIC_KEY_SIZE); memcpy(dht_public_key, cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); return 0; } static Crypto_Connection *get_crypto_connection(const Net_Crypto *c, int crypt_connection_id) { if (!crypt_connection_id_is_valid(c, crypt_connection_id)) { return nullptr; } return &c->crypto_connections[crypt_connection_id]; } /* Associate an ip_port to a connection. * * return -1 on failure. * return 0 on success. */ static int add_ip_port_connection(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } if (net_family_is_ipv4(ip_port.ip.family)) { if (!ipport_equal(&ip_port, &conn->ip_portv4) && !ip_is_lan(conn->ip_portv4.ip)) { if (!bs_list_add(&c->ip_port_list, (uint8_t *)&ip_port, crypt_connection_id)) { return -1; } bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv4, crypt_connection_id); conn->ip_portv4 = ip_port; return 0; } } else if (net_family_is_ipv6(ip_port.ip.family)) { if (!ipport_equal(&ip_port, &conn->ip_portv6)) { if (!bs_list_add(&c->ip_port_list, (uint8_t *)&ip_port, crypt_connection_id)) { return -1; } bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv6, crypt_connection_id); conn->ip_portv6 = ip_port; return 0; } } return -1; } /* Return the IP_Port that should be used to send packets to the other peer. * * return IP_Port with family 0 on failure. * return IP_Port on success. */ static IP_Port return_ip_port_connection(Net_Crypto *c, int crypt_connection_id) { const IP_Port empty = {{{0}}}; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return empty; } const uint64_t current_time = mono_time_get(c->mono_time); bool v6 = 0; bool v4 = 0; if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev4) > current_time) { v4 = 1; } if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev6) > current_time) { v6 = 1; } /* Prefer IP_Ports which haven't timed out to those which have. * To break ties, prefer ipv4 lan, then ipv6, then non-lan ipv4. */ if (v4 && ip_is_lan(conn->ip_portv4.ip)) { return conn->ip_portv4; } if (v6 && net_family_is_ipv6(conn->ip_portv6.ip.family)) { return conn->ip_portv6; } if (v4 && net_family_is_ipv4(conn->ip_portv4.ip.family)) { return conn->ip_portv4; } if (ip_is_lan(conn->ip_portv4.ip)) { return conn->ip_portv4; } if (net_family_is_ipv6(conn->ip_portv6.ip.family)) { return conn->ip_portv6; } if (net_family_is_ipv4(conn->ip_portv4.ip.family)) { return conn->ip_portv4; } return empty; } /* Sends a packet to the peer using the fastest route. * * return -1 on failure. * return 0 on success. */ static int send_packet_to(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) { // TODO(irungentoo): TCP, etc... Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } int direct_send_attempt = 0; pthread_mutex_lock(conn->mutex); IP_Port ip_port = return_ip_port_connection(c, crypt_connection_id); // TODO(irungentoo): on bad networks, direct connections might not last indefinitely. if (!net_family_is_unspec(ip_port.ip.family)) { bool direct_connected = 0; // FIXME(sudden6): handle return value crypto_connection_status(c, crypt_connection_id, &direct_connected, nullptr); if (direct_connected) { if ((uint32_t)sendpacket(dht_get_net(c->dht), ip_port, data, length) == length) { pthread_mutex_unlock(conn->mutex); return 0; } pthread_mutex_unlock(conn->mutex); return -1; } // TODO(irungentoo): a better way of sending packets directly to confirm the others ip. const uint64_t current_time = mono_time_get(c->mono_time); if ((((UDP_DIRECT_TIMEOUT / 2) + conn->direct_send_attempt_time) < current_time && length < 96) || data[0] == NET_PACKET_COOKIE_REQUEST || data[0] == NET_PACKET_CRYPTO_HS) { if ((uint32_t)sendpacket(dht_get_net(c->dht), ip_port, data, length) == length) { direct_send_attempt = 1; conn->direct_send_attempt_time = mono_time_get(c->mono_time); } } } pthread_mutex_unlock(conn->mutex); pthread_mutex_lock(&c->tcp_mutex); int ret = send_packet_tcp_connection(c->tcp_c, conn->connection_number_tcp, data, length); pthread_mutex_unlock(&c->tcp_mutex); pthread_mutex_lock(conn->mutex); if (ret == 0) { conn->last_tcp_sent = current_time_monotonic(c->mono_time); } pthread_mutex_unlock(conn->mutex); if (ret == 0 || direct_send_attempt) { return 0; } return -1; } /** START: Array Related functions */ /* Return number of packets in array * Note that holes are counted too. */ static uint32_t num_packets_array(const Packets_Array *array) { return array->buffer_end - array->buffer_start; } /* Add data with packet number to array. * * return -1 on failure. * return 0 on success. */ static int add_data_to_buffer(const Logger *log, Packets_Array *array, uint32_t number, const Packet_Data *data) { if (number - array->buffer_start >= CRYPTO_PACKET_BUFFER_SIZE) { return -1; } uint32_t num = number % CRYPTO_PACKET_BUFFER_SIZE; if (array->buffer[num]) { return -1; } Packet_Data *new_d = (Packet_Data *)malloc(sizeof(Packet_Data)); if (new_d == nullptr) { return -1; } memcpy(new_d, data, sizeof(Packet_Data)); array->buffer[num] = new_d; if (number - array->buffer_start >= num_packets_array(array)) { array->buffer_end = number + 1; } return 0; } /* Get pointer of data with packet number. * * return -1 on failure. * return 0 if data at number is empty. * return 1 if data pointer was put in data. */ static int get_data_pointer(const Logger *log, const Packets_Array *array, Packet_Data **data, uint32_t number) { const uint32_t num_spots = num_packets_array(array); if (array->buffer_end - number > num_spots || number - array->buffer_start >= num_spots) { return -1; } uint32_t num = number % CRYPTO_PACKET_BUFFER_SIZE; if (!array->buffer[num]) { return 0; } *data = array->buffer[num]; return 1; } /* Add data to end of array. * * return -1 on failure. * return packet number on success. */ static int64_t add_data_end_of_buffer(const Logger *log, Packets_Array *array, const Packet_Data *data) { const uint32_t num_spots = num_packets_array(array); if (num_spots >= CRYPTO_PACKET_BUFFER_SIZE) { return -1; } Packet_Data *new_d = (Packet_Data *)malloc(sizeof(Packet_Data)); if (new_d == nullptr) { return -1; } memcpy(new_d, data, sizeof(Packet_Data)); uint32_t id = array->buffer_end; array->buffer[id % CRYPTO_PACKET_BUFFER_SIZE] = new_d; ++array->buffer_end; return id; } /* Read data from beginning of array. * * return -1 on failure. * return packet number on success. */ static int64_t read_data_beg_buffer(const Logger *log, Packets_Array *array, Packet_Data *data) { if (array->buffer_end == array->buffer_start) { return -1; } const uint32_t num = array->buffer_start % CRYPTO_PACKET_BUFFER_SIZE; if (!array->buffer[num]) { return -1; } memcpy(data, array->buffer[num], sizeof(Packet_Data)); uint32_t id = array->buffer_start; ++array->buffer_start; free(array->buffer[num]); array->buffer[num] = nullptr; return id; } /* Delete all packets in array before number (but not number) * * return -1 on failure. * return 0 on success */ static int clear_buffer_until(const Logger *log, Packets_Array *array, uint32_t number) { const uint32_t num_spots = num_packets_array(array); if (array->buffer_end - number >= num_spots || number - array->buffer_start > num_spots) { return -1; } uint32_t i; for (i = array->buffer_start; i != number; ++i) { uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; if (array->buffer[num]) { free(array->buffer[num]); array->buffer[num] = nullptr; } } array->buffer_start = i; return 0; } static int clear_buffer(Packets_Array *array) { uint32_t i; for (i = array->buffer_start; i != array->buffer_end; ++i) { uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; if (array->buffer[num]) { free(array->buffer[num]); array->buffer[num] = nullptr; } } array->buffer_start = i; return 0; } /* Set array buffer end to number. * * return -1 on failure. * return 0 on success. */ static int set_buffer_end(const Logger *log, Packets_Array *array, uint32_t number) { if (number - array->buffer_start > CRYPTO_PACKET_BUFFER_SIZE) { return -1; } if (number - array->buffer_end > CRYPTO_PACKET_BUFFER_SIZE) { return -1; } array->buffer_end = number; return 0; } /* Create a packet request packet from recv_array and send_buffer_end into * data of length. * * return -1 on failure. * return length of packet on success. */ static int generate_request_packet(const Logger *log, uint8_t *data, uint16_t length, const Packets_Array *recv_array) { if (length == 0) { return -1; } data[0] = PACKET_ID_REQUEST; uint16_t cur_len = 1; if (recv_array->buffer_start == recv_array->buffer_end) { return cur_len; } if (length <= cur_len) { return cur_len; } uint32_t n = 1; for (uint32_t i = recv_array->buffer_start; i != recv_array->buffer_end; ++i) { uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; if (!recv_array->buffer[num]) { data[cur_len] = n; n = 0; ++cur_len; if (length <= cur_len) { return cur_len; } } else if (n == 255) { data[cur_len] = 0; n = 0; ++cur_len; if (length <= cur_len) { return cur_len; } } ++n; } return cur_len; } /* Handle a request data packet. * Remove all the packets the other received from the array. * * return -1 on failure. * return number of requested packets on success. */ static int handle_request_packet(Mono_Time *mono_time, const Logger *log, Packets_Array *send_array, const uint8_t *data, uint16_t length, uint64_t *latest_send_time, uint64_t rtt_time) { if (length == 0) { return -1; } if (data[0] != PACKET_ID_REQUEST) { return -1; } if (length == 1) { return 0; } ++data; --length; uint32_t n = 1; uint32_t requested = 0; const uint64_t temp_time = current_time_monotonic(mono_time); uint64_t l_sent_time = -1; for (uint32_t i = send_array->buffer_start; i != send_array->buffer_end; ++i) { if (length == 0) { break; } uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; if (n == data[0]) { if (send_array->buffer[num]) { uint64_t sent_time = send_array->buffer[num]->sent_time; if ((sent_time + rtt_time) < temp_time) { send_array->buffer[num]->sent_time = 0; } } ++data; --length; n = 0; ++requested; } else { if (send_array->buffer[num]) { uint64_t sent_time = send_array->buffer[num]->sent_time; if (l_sent_time < sent_time) { l_sent_time = sent_time; } free(send_array->buffer[num]); send_array->buffer[num] = nullptr; } } if (n == 255) { n = 1; if (data[0] != 0) { return -1; } ++data; --length; } else { ++n; } } if (*latest_send_time < l_sent_time) { *latest_send_time = l_sent_time; } return requested; } /** END: Array Related functions */ #define MAX_DATA_DATA_PACKET_SIZE (MAX_CRYPTO_PACKET_SIZE - (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE)) /* Creates and sends a data packet to the peer using the fastest route. * * return -1 on failure. * return 0 on success. */ static int send_data_packet(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) { const uint16_t max_length = MAX_CRYPTO_PACKET_SIZE - (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE); if (length == 0 || length > max_length) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } pthread_mutex_lock(conn->mutex); VLA(uint8_t, packet, 1 + sizeof(uint16_t) + length + CRYPTO_MAC_SIZE); packet[0] = NET_PACKET_CRYPTO_DATA; memcpy(packet + 1, conn->sent_nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); const int len = encrypt_data_symmetric(conn->shared_key, conn->sent_nonce, data, length, packet + 1 + sizeof(uint16_t)); if (len + 1 + sizeof(uint16_t) != SIZEOF_VLA(packet)) { pthread_mutex_unlock(conn->mutex); return -1; } increment_nonce(conn->sent_nonce); pthread_mutex_unlock(conn->mutex); return send_packet_to(c, crypt_connection_id, packet, SIZEOF_VLA(packet)); } /* Creates and sends a data packet with buffer_start and num to the peer using the fastest route. * * return -1 on failure. * return 0 on success. */ static int send_data_packet_helper(Net_Crypto *c, int crypt_connection_id, uint32_t buffer_start, uint32_t num, const uint8_t *data, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { return -1; } num = net_htonl(num); buffer_start = net_htonl(buffer_start); uint16_t padding_length = (MAX_CRYPTO_DATA_SIZE - length) % CRYPTO_MAX_PADDING; VLA(uint8_t, packet, sizeof(uint32_t) + sizeof(uint32_t) + padding_length + length); memcpy(packet, &buffer_start, sizeof(uint32_t)); memcpy(packet + sizeof(uint32_t), &num, sizeof(uint32_t)); memset(packet + (sizeof(uint32_t) * 2), PACKET_ID_PADDING, padding_length); memcpy(packet + (sizeof(uint32_t) * 2) + padding_length, data, length); return send_data_packet(c, crypt_connection_id, packet, SIZEOF_VLA(packet)); } static int reset_max_speed_reached(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } /* If last packet send failed, try to send packet again. * If sending it fails we won't be able to send the new packet. */ if (conn->maximum_speed_reached) { Packet_Data *dt = nullptr; const uint32_t packet_num = conn->send_array.buffer_end - 1; const int ret = get_data_pointer(c->log, &conn->send_array, &dt, packet_num); if (ret == 1 && dt->sent_time == 0) { if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, dt->length) != 0) { return -1; } dt->sent_time = current_time_monotonic(c->mono_time); } conn->maximum_speed_reached = 0; } return 0; } /* return -1 if data could not be put in packet queue. * return positive packet number if data was put into the queue. */ static int64_t send_lossless_packet(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, uint8_t congestion_control) { if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } /* If last packet send failed, try to send packet again. * If sending it fails we won't be able to send the new packet. */ reset_max_speed_reached(c, crypt_connection_id); if (conn->maximum_speed_reached && congestion_control) { return -1; } Packet_Data dt; dt.sent_time = 0; dt.length = length; memcpy(dt.data, data, length); pthread_mutex_lock(conn->mutex); int64_t packet_num = add_data_end_of_buffer(c->log, &conn->send_array, &dt); pthread_mutex_unlock(conn->mutex); if (packet_num == -1) { return -1; } if (!congestion_control && conn->maximum_speed_reached) { return packet_num; } if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, data, length) == 0) { Packet_Data *dt1 = nullptr; if (get_data_pointer(c->log, &conn->send_array, &dt1, packet_num) == 1) { dt1->sent_time = current_time_monotonic(c->mono_time); } } else { conn->maximum_speed_reached = 1; LOGGER_DEBUG(c->log, "send_data_packet failed"); } return packet_num; } /* Get the lowest 2 bytes from the nonce and convert * them to host byte format before returning them. */ static uint16_t get_nonce_uint16(const uint8_t *nonce) { uint16_t num; memcpy(&num, nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); return net_ntohs(num); } #define DATA_NUM_THRESHOLD 21845 /* Handle a data packet. * Decrypt packet of length and put it into data. * data must be at least MAX_DATA_DATA_PACKET_SIZE big. * * return -1 on failure. * return length of data on success. */ static int handle_data_packet(const Net_Crypto *c, int crypt_connection_id, uint8_t *data, const uint8_t *packet, uint16_t length) { const uint16_t crypto_packet_overhead = 1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE; if (length <= crypto_packet_overhead || length > MAX_CRYPTO_PACKET_SIZE) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } uint8_t nonce[CRYPTO_NONCE_SIZE]; memcpy(nonce, conn->recv_nonce, CRYPTO_NONCE_SIZE); uint16_t num_cur_nonce = get_nonce_uint16(nonce); uint16_t num; net_unpack_u16(packet + 1, &num); uint16_t diff = num - num_cur_nonce; increment_nonce_number(nonce, diff); int len = decrypt_data_symmetric(conn->shared_key, nonce, packet + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), data); if ((unsigned int)len != length - crypto_packet_overhead) { return -1; } if (diff > DATA_NUM_THRESHOLD * 2) { increment_nonce_number(conn->recv_nonce, DATA_NUM_THRESHOLD); } return len; } /* Send a request packet. * * return -1 on failure. * return 0 on success. */ static int send_request_packet(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } uint8_t data[MAX_CRYPTO_DATA_SIZE]; int len = generate_request_packet(c->log, data, sizeof(data), &conn->recv_array); if (len == -1) { return -1; } return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, data, len); } /* Send up to max num previously requested data packets. * * return -1 on failure. * return number of packets sent on success. */ static int send_requested_packets(Net_Crypto *c, int crypt_connection_id, uint32_t max_num) { if (max_num == 0) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } const uint64_t temp_time = current_time_monotonic(c->mono_time); const uint32_t array_size = num_packets_array(&conn->send_array); uint32_t num_sent = 0; for (uint32_t i = 0; i < array_size; ++i) { Packet_Data *dt; const uint32_t packet_num = i + conn->send_array.buffer_start; const int ret = get_data_pointer(c->log, &conn->send_array, &dt, packet_num); if (ret == -1) { return -1; } if (ret == 0) { continue; } if (dt->sent_time) { continue; } if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, dt->length) == 0) { dt->sent_time = temp_time; ++num_sent; } if (num_sent >= max_num) { break; } } return num_sent; } /* Add a new temp packet to send repeatedly. * * return -1 on failure. * return 0 on success. */ static int new_temp_packet(const Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } uint8_t *temp_packet = (uint8_t *)malloc(length); if (temp_packet == nullptr) { return -1; } if (conn->temp_packet) { free(conn->temp_packet); } memcpy(temp_packet, packet, length); conn->temp_packet = temp_packet; conn->temp_packet_length = length; conn->temp_packet_sent_time = 0; conn->temp_packet_num_sent = 0; return 0; } /* Clear the temp packet. * * return -1 on failure. * return 0 on success. */ static int clear_temp_packet(const Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } if (conn->temp_packet) { free(conn->temp_packet); } conn->temp_packet = nullptr; conn->temp_packet_length = 0; conn->temp_packet_sent_time = 0; conn->temp_packet_num_sent = 0; return 0; } /* Send the temp packet. * * return -1 on failure. * return 0 on success. */ static int send_temp_packet(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } if (!conn->temp_packet) { return -1; } if (send_packet_to(c, crypt_connection_id, conn->temp_packet, conn->temp_packet_length) != 0) { return -1; } conn->temp_packet_sent_time = current_time_monotonic(c->mono_time); ++conn->temp_packet_num_sent; return 0; } /* Create a handshake packet and set it as a temp packet. * cookie must be COOKIE_LENGTH. * * return -1 on failure. * return 0 on success. */ static int create_send_handshake(Net_Crypto *c, int crypt_connection_id, const uint8_t *cookie, const uint8_t *dht_public_key) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } uint8_t handshake_packet[HANDSHAKE_PACKET_LENGTH]; if (create_crypto_handshake(c, handshake_packet, cookie, conn->sent_nonce, conn->sessionpublic_key, conn->public_key, dht_public_key) != sizeof(handshake_packet)) { return -1; } if (new_temp_packet(c, crypt_connection_id, handshake_packet, sizeof(handshake_packet)) != 0) { return -1; } send_temp_packet(c, crypt_connection_id); return 0; } /* Send a kill packet. * * return -1 on failure. * return 0 on success. */ static int send_kill_packet(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } uint8_t kill_packet = PACKET_ID_KILL; return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, &kill_packet, sizeof(kill_packet)); } static void connection_kill(Net_Crypto *c, int crypt_connection_id, void *userdata) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return; } if (conn->connection_status_callback) { conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, 0, userdata); } while (1) { /* TODO(irungentoo): is this really the best way to do this? */ pthread_mutex_lock(&c->connections_mutex); if (!c->connection_use_counter) { break; } pthread_mutex_unlock(&c->connections_mutex); } crypto_kill(c, crypt_connection_id); pthread_mutex_unlock(&c->connections_mutex); } /* Handle a received data packet. * * return -1 on failure. * return 0 on success. */ static int handle_data_packet_core(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, bool udp, void *userdata) { if (length > MAX_CRYPTO_PACKET_SIZE || length <= CRYPTO_DATA_PACKET_MIN_SIZE) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } uint8_t data[MAX_DATA_DATA_PACKET_SIZE]; int len = handle_data_packet(c, crypt_connection_id, data, packet, length); if (len <= (int)(sizeof(uint32_t) * 2)) { return -1; } uint32_t buffer_start; uint32_t num; memcpy(&buffer_start, data, sizeof(uint32_t)); memcpy(&num, data + sizeof(uint32_t), sizeof(uint32_t)); buffer_start = net_ntohl(buffer_start); num = net_ntohl(num); uint64_t rtt_calc_time = 0; if (buffer_start != conn->send_array.buffer_start) { Packet_Data *packet_time; if (get_data_pointer(c->log, &conn->send_array, &packet_time, conn->send_array.buffer_start) == 1) { rtt_calc_time = packet_time->sent_time; } if (clear_buffer_until(c->log, &conn->send_array, buffer_start) != 0) { return -1; } } uint8_t *real_data = data + (sizeof(uint32_t) * 2); uint16_t real_length = len - (sizeof(uint32_t) * 2); while (real_data[0] == PACKET_ID_PADDING) { /* Remove Padding */ ++real_data; --real_length; if (real_length == 0) { return -1; } } if (real_data[0] == PACKET_ID_KILL) { connection_kill(c, crypt_connection_id, userdata); return 0; } if (conn->status == CRYPTO_CONN_NOT_CONFIRMED) { clear_temp_packet(c, crypt_connection_id); conn->status = CRYPTO_CONN_ESTABLISHED; if (conn->connection_status_callback) { conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, 1, userdata); } } if (real_data[0] == PACKET_ID_REQUEST) { uint64_t rtt_time; if (udp) { rtt_time = conn->rtt_time; } else { rtt_time = DEFAULT_TCP_PING_CONNECTION; } int requested = handle_request_packet(c->mono_time, c->log, &conn->send_array, real_data, real_length, &rtt_calc_time, rtt_time); if (requested == -1) { return -1; } set_buffer_end(c->log, &conn->recv_array, num); } else if (real_data[0] >= PACKET_ID_RANGE_LOSSLESS_START && real_data[0] <= PACKET_ID_RANGE_LOSSLESS_END) { Packet_Data dt = {0}; dt.length = real_length; memcpy(dt.data, real_data, real_length); if (add_data_to_buffer(c->log, &conn->recv_array, num, &dt) != 0) { return -1; } while (1) { pthread_mutex_lock(conn->mutex); int ret = read_data_beg_buffer(c->log, &conn->recv_array, &dt); pthread_mutex_unlock(conn->mutex); if (ret == -1) { break; } if (conn->connection_data_callback) { conn->connection_data_callback(conn->connection_data_callback_object, conn->connection_data_callback_id, dt.data, dt.length, userdata); } /* conn might get killed in callback. */ conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } } /* Packet counter. */ ++conn->packet_counter; } else if (real_data[0] >= PACKET_ID_RANGE_LOSSY_START && real_data[0] <= PACKET_ID_RANGE_LOSSY_END) { set_buffer_end(c->log, &conn->recv_array, num); if (conn->connection_lossy_data_callback) { conn->connection_lossy_data_callback(conn->connection_lossy_data_callback_object, conn->connection_lossy_data_callback_id, real_data, real_length, userdata); } } else { return -1; } if (rtt_calc_time != 0) { uint64_t rtt_time = current_time_monotonic(c->mono_time) - rtt_calc_time; if (rtt_time < conn->rtt_time) { conn->rtt_time = rtt_time; } } return 0; } /* Handle a packet that was received for the connection. * * return -1 on failure. * return 0 on success. */ static int handle_packet_connection(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, bool udp, void *userdata) { if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } switch (packet[0]) { case NET_PACKET_COOKIE_RESPONSE: { if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING) { return -1; } uint8_t cookie[COOKIE_LENGTH]; uint64_t number; if (handle_cookie_response(c->log, cookie, &number, packet, length, conn->shared_key) != sizeof(cookie)) { return -1; } if (number != conn->cookie_request_number) { return -1; } if (create_send_handshake(c, crypt_connection_id, cookie, conn->dht_public_key) != 0) { return -1; } conn->status = CRYPTO_CONN_HANDSHAKE_SENT; return 0; } case NET_PACKET_CRYPTO_HS: { if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING && conn->status != CRYPTO_CONN_HANDSHAKE_SENT && conn->status != CRYPTO_CONN_NOT_CONFIRMED) { return -1; } uint8_t peer_real_pk[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t cookie[COOKIE_LENGTH]; if (handle_crypto_handshake(c, conn->recv_nonce, conn->peersessionpublic_key, peer_real_pk, dht_public_key, cookie, packet, length, conn->public_key) != 0) { return -1; } if (public_key_cmp(dht_public_key, conn->dht_public_key) == 0) { encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING) { if (create_send_handshake(c, crypt_connection_id, cookie, dht_public_key) != 0) { return -1; } } conn->status = CRYPTO_CONN_NOT_CONFIRMED; } else { if (conn->dht_pk_callback) { conn->dht_pk_callback(conn->dht_pk_callback_object, conn->dht_pk_callback_number, dht_public_key, userdata); } } return 0; } case NET_PACKET_CRYPTO_DATA: { if (conn->status != CRYPTO_CONN_NOT_CONFIRMED && conn->status != CRYPTO_CONN_ESTABLISHED) { return -1; } return handle_data_packet_core(c, crypt_connection_id, packet, length, udp, userdata); } default: { return -1; } } } /* Set the size of the friend list to numfriends. * * return -1 if realloc fails. * return 0 if it succeeds. */ static int realloc_cryptoconnection(Net_Crypto *c, uint32_t num) { if (num == 0) { free(c->crypto_connections); c->crypto_connections = nullptr; return 0; } Crypto_Connection *newcrypto_connections = (Crypto_Connection *)realloc(c->crypto_connections, num * sizeof(Crypto_Connection)); if (newcrypto_connections == nullptr) { return -1; } c->crypto_connections = newcrypto_connections; return 0; } /* Create a new empty crypto connection. * * return -1 on failure. * return connection id on success. */ static int create_crypto_connection(Net_Crypto *c) { while (1) { /* TODO(irungentoo): is this really the best way to do this? */ pthread_mutex_lock(&c->connections_mutex); if (!c->connection_use_counter) { break; } pthread_mutex_unlock(&c->connections_mutex); } int id = -1; for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { if (c->crypto_connections[i].status == CRYPTO_CONN_FREE) { id = i; break; } } if (id == -1) { if (realloc_cryptoconnection(c, c->crypto_connections_length + 1) == 0) { id = c->crypto_connections_length; ++c->crypto_connections_length; memset(&c->crypto_connections[id], 0, sizeof(Crypto_Connection)); } } if (id != -1) { // Memsetting float/double to 0 is non-portable, so we explicitly set them to 0 c->crypto_connections[id].packet_recv_rate = 0; c->crypto_connections[id].packet_send_rate = 0; c->crypto_connections[id].last_packets_left_rem = 0; c->crypto_connections[id].packet_send_rate_requested = 0; c->crypto_connections[id].last_packets_left_requested_rem = 0; c->crypto_connections[id].mutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t)); if (c->crypto_connections[id].mutex == nullptr) { pthread_mutex_unlock(&c->connections_mutex); return -1; } if (pthread_mutex_init(c->crypto_connections[id].mutex, nullptr) != 0) { free(c->crypto_connections[id].mutex); pthread_mutex_unlock(&c->connections_mutex); return -1; } c->crypto_connections[id].status = CRYPTO_CONN_NO_CONNECTION; } pthread_mutex_unlock(&c->connections_mutex); return id; } /* Wipe a crypto connection. * * return -1 on failure. * return 0 on success. */ static int wipe_crypto_connection(Net_Crypto *c, int crypt_connection_id) { if ((uint32_t)crypt_connection_id >= c->crypto_connections_length) { return -1; } if (c->crypto_connections == nullptr) { return -1; } const Crypto_Conn_State status = c->crypto_connections[crypt_connection_id].status; if (status == CRYPTO_CONN_FREE) { return -1; } uint32_t i; pthread_mutex_destroy(c->crypto_connections[crypt_connection_id].mutex); free(c->crypto_connections[crypt_connection_id].mutex); crypto_memzero(&c->crypto_connections[crypt_connection_id], sizeof(Crypto_Connection)); /* check if we can resize the connections array */ for (i = c->crypto_connections_length; i != 0; --i) { if (c->crypto_connections[i - 1].status != CRYPTO_CONN_FREE) { break; } } if (c->crypto_connections_length != i) { c->crypto_connections_length = i; realloc_cryptoconnection(c, c->crypto_connections_length); } return 0; } /* Get crypto connection id from public key of peer. * * return -1 if there are no connections like we are looking for. * return id if it found it. */ static int getcryptconnection_id(const Net_Crypto *c, const uint8_t *public_key) { for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { if (!crypt_connection_id_is_valid(c, i)) { continue; } if (public_key_cmp(public_key, c->crypto_connections[i].public_key) == 0) { return i; } } return -1; } /* Add a source to the crypto connection. * This is to be used only when we have received a packet from that source. * * return -1 on failure. * return positive number on success. * 0 if source was a direct UDP connection. */ static int crypto_connection_add_source(Net_Crypto *c, int crypt_connection_id, IP_Port source) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } if (net_family_is_ipv4(source.ip.family) || net_family_is_ipv6(source.ip.family)) { if (add_ip_port_connection(c, crypt_connection_id, source) != 0) { return -1; } if (net_family_is_ipv4(source.ip.family)) { conn->direct_lastrecv_timev4 = mono_time_get(c->mono_time); } else { conn->direct_lastrecv_timev6 = mono_time_get(c->mono_time); } return 0; } if (net_family_is_tcp_family(source.ip.family)) { if (add_tcp_number_relay_connection(c->tcp_c, conn->connection_number_tcp, source.ip.ip.v6.uint32[0]) == 0) { return 1; } } return -1; } /* Set function to be called when someone requests a new connection to us. * * The set function should return -1 on failure and 0 on success. * * n_c is only valid for the duration of the function call. */ void new_connection_handler(Net_Crypto *c, new_connection_cb *new_connection_callback, void *object) { c->new_connection_callback = new_connection_callback; c->new_connection_callback_object = object; } /* Handle a handshake packet by someone who wants to initiate a new connection with us. * This calls the callback set by new_connection_handler() if the handshake is ok. * * return -1 on failure. * return 0 on success. */ static int handle_new_connection_handshake(Net_Crypto *c, IP_Port source, const uint8_t *data, uint16_t length, void *userdata) { New_Connection n_c; n_c.cookie = (uint8_t *)malloc(COOKIE_LENGTH); if (n_c.cookie == nullptr) { return -1; } n_c.source = source; n_c.cookie_length = COOKIE_LENGTH; if (handle_crypto_handshake(c, n_c.recv_nonce, n_c.peersessionpublic_key, n_c.public_key, n_c.dht_public_key, n_c.cookie, data, length, nullptr) != 0) { free(n_c.cookie); return -1; } const int crypt_connection_id = getcryptconnection_id(c, n_c.public_key); if (crypt_connection_id != -1) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } if (public_key_cmp(n_c.dht_public_key, conn->dht_public_key) != 0) { connection_kill(c, crypt_connection_id, userdata); } else { if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING && conn->status != CRYPTO_CONN_HANDSHAKE_SENT) { free(n_c.cookie); return -1; } memcpy(conn->recv_nonce, n_c.recv_nonce, CRYPTO_NONCE_SIZE); memcpy(conn->peersessionpublic_key, n_c.peersessionpublic_key, CRYPTO_PUBLIC_KEY_SIZE); encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); crypto_connection_add_source(c, crypt_connection_id, source); if (create_send_handshake(c, crypt_connection_id, n_c.cookie, n_c.dht_public_key) != 0) { free(n_c.cookie); return -1; } conn->status = CRYPTO_CONN_NOT_CONFIRMED; free(n_c.cookie); return 0; } } int ret = c->new_connection_callback(c->new_connection_callback_object, &n_c); free(n_c.cookie); return ret; } /* Accept a crypto connection. * * return -1 on failure. * return connection id on success. */ int accept_crypto_connection(Net_Crypto *c, New_Connection *n_c) { if (getcryptconnection_id(c, n_c->public_key) != -1) { return -1; } const int crypt_connection_id = create_crypto_connection(c); if (crypt_connection_id == -1) { LOGGER_ERROR(c->log, "Could not create new crypto connection"); return -1; } Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; if (n_c->cookie_length != COOKIE_LENGTH) { wipe_crypto_connection(c, crypt_connection_id); return -1; } pthread_mutex_lock(&c->tcp_mutex); const int connection_number_tcp = new_tcp_connection_to(c->tcp_c, n_c->dht_public_key, crypt_connection_id); pthread_mutex_unlock(&c->tcp_mutex); if (connection_number_tcp == -1) { wipe_crypto_connection(c, crypt_connection_id); return -1; } conn->connection_number_tcp = connection_number_tcp; memcpy(conn->public_key, n_c->public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(conn->recv_nonce, n_c->recv_nonce, CRYPTO_NONCE_SIZE); memcpy(conn->peersessionpublic_key, n_c->peersessionpublic_key, CRYPTO_PUBLIC_KEY_SIZE); random_nonce(conn->sent_nonce); crypto_new_keypair(conn->sessionpublic_key, conn->sessionsecret_key); encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); conn->status = CRYPTO_CONN_NOT_CONFIRMED; if (create_send_handshake(c, crypt_connection_id, n_c->cookie, n_c->dht_public_key) != 0) { pthread_mutex_lock(&c->tcp_mutex); kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); pthread_mutex_unlock(&c->tcp_mutex); wipe_crypto_connection(c, crypt_connection_id); return -1; } memcpy(conn->dht_public_key, n_c->dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; conn->rtt_time = DEFAULT_PING_CONNECTION; crypto_connection_add_source(c, crypt_connection_id, n_c->source); return crypt_connection_id; } /* Create a crypto connection. * If one to that real public key already exists, return it. * * return -1 on failure. * return connection id on success. */ int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const uint8_t *dht_public_key) { int crypt_connection_id = getcryptconnection_id(c, real_public_key); if (crypt_connection_id != -1) { return crypt_connection_id; } crypt_connection_id = create_crypto_connection(c); if (crypt_connection_id == -1) { return -1; } Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; pthread_mutex_lock(&c->tcp_mutex); const int connection_number_tcp = new_tcp_connection_to(c->tcp_c, dht_public_key, crypt_connection_id); pthread_mutex_unlock(&c->tcp_mutex); if (connection_number_tcp == -1) { wipe_crypto_connection(c, crypt_connection_id); return -1; } conn->connection_number_tcp = connection_number_tcp; memcpy(conn->public_key, real_public_key, CRYPTO_PUBLIC_KEY_SIZE); random_nonce(conn->sent_nonce); crypto_new_keypair(conn->sessionpublic_key, conn->sessionsecret_key); conn->status = CRYPTO_CONN_COOKIE_REQUESTING; conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; conn->rtt_time = DEFAULT_PING_CONNECTION; memcpy(conn->dht_public_key, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); conn->cookie_request_number = random_u64(); uint8_t cookie_request[COOKIE_REQUEST_LENGTH]; if (create_cookie_request(c, cookie_request, conn->dht_public_key, conn->cookie_request_number, conn->shared_key) != sizeof(cookie_request) || new_temp_packet(c, crypt_connection_id, cookie_request, sizeof(cookie_request)) != 0) { pthread_mutex_lock(&c->tcp_mutex); kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); pthread_mutex_unlock(&c->tcp_mutex); wipe_crypto_connection(c, crypt_connection_id); return -1; } return crypt_connection_id; } /* Set the direct ip of the crypto connection. * * Connected is 0 if we are not sure we are connected to that person, 1 if we are sure. * * return -1 on failure. * return 0 on success. */ int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, bool connected) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } if (add_ip_port_connection(c, crypt_connection_id, ip_port) != 0) { return -1; } const uint64_t direct_lastrecv_time = connected ? mono_time_get(c->mono_time) : 0; if (net_family_is_ipv4(ip_port.ip.family)) { conn->direct_lastrecv_timev4 = direct_lastrecv_time; } else { conn->direct_lastrecv_timev6 = direct_lastrecv_time; } return 0; } static int tcp_data_callback(void *object, int crypt_connection_id, const uint8_t *data, uint16_t length, void *userdata) { Net_Crypto *c = (Net_Crypto *)object; if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } if (data[0] == NET_PACKET_COOKIE_REQUEST) { return tcp_handle_cookie_request(c, conn->connection_number_tcp, data, length); } // This unlocks the mutex that at this point is locked by do_tcp before // calling do_tcp_connections. pthread_mutex_unlock(&c->tcp_mutex); int ret = handle_packet_connection(c, crypt_connection_id, data, length, 0, userdata); pthread_mutex_lock(&c->tcp_mutex); if (ret != 0) { return -1; } // TODO(irungentoo): detect and kill bad TCP connections. return 0; } static int tcp_oob_callback(void *object, const uint8_t *public_key, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length, void *userdata) { Net_Crypto *c = (Net_Crypto *)object; if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { return -1; } if (data[0] == NET_PACKET_COOKIE_REQUEST) { return tcp_oob_handle_cookie_request(c, tcp_connections_number, public_key, data, length); } if (data[0] == NET_PACKET_CRYPTO_HS) { IP_Port source; source.port = 0; source.ip.family = net_family_tcp_family; source.ip.ip.v6.uint32[0] = tcp_connections_number; if (handle_new_connection_handshake(c, source, data, length, userdata) != 0) { return -1; } return 0; } return -1; } /* Add a tcp relay, associating it to a crypt_connection_id. * * return 0 if it was added. * return -1 if it wasn't. */ int add_tcp_relay_peer(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, const uint8_t *public_key) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } pthread_mutex_lock(&c->tcp_mutex); int ret = add_tcp_relay_connection(c->tcp_c, conn->connection_number_tcp, ip_port, public_key); pthread_mutex_unlock(&c->tcp_mutex); return ret; } /* Add a tcp relay to the array. * * return 0 if it was added. * return -1 if it wasn't. */ int add_tcp_relay(Net_Crypto *c, IP_Port ip_port, const uint8_t *public_key) { pthread_mutex_lock(&c->tcp_mutex); int ret = add_tcp_relay_global(c->tcp_c, ip_port, public_key); pthread_mutex_unlock(&c->tcp_mutex); return ret; } /* Return a random TCP connection number for use in send_tcp_onion_request. * * TODO(irungentoo): This number is just the index of an array that the elements can * change without warning. * * return TCP connection number on success. * return -1 on failure. */ int get_random_tcp_con_number(Net_Crypto *c) { pthread_mutex_lock(&c->tcp_mutex); int ret = get_random_tcp_onion_conn_number(c->tcp_c); pthread_mutex_unlock(&c->tcp_mutex); return ret; } /* Send an onion packet via the TCP relay corresponding to tcp_connections_number. * * return 0 on success. * return -1 on failure. */ int send_tcp_onion_request(Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length) { pthread_mutex_lock(&c->tcp_mutex); int ret = tcp_send_onion_request(c->tcp_c, tcp_connections_number, data, length); pthread_mutex_unlock(&c->tcp_mutex); return ret; } /* Copy a maximum of num TCP relays we are connected to to tcp_relays. * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. * * return number of relays copied to tcp_relays on success. * return 0 on failure. */ unsigned int copy_connected_tcp_relays(Net_Crypto *c, Node_format *tcp_relays, uint16_t num) { if (num == 0) { return 0; } pthread_mutex_lock(&c->tcp_mutex); unsigned int ret = tcp_copy_connected_relays(c->tcp_c, tcp_relays, num); pthread_mutex_unlock(&c->tcp_mutex); return ret; } static void do_tcp(Net_Crypto *c, void *userdata) { pthread_mutex_lock(&c->tcp_mutex); do_tcp_connections(c->log, c->tcp_c, userdata); pthread_mutex_unlock(&c->tcp_mutex); for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { Crypto_Connection *conn = get_crypto_connection(c, i); if (conn == nullptr) { continue; } if (conn->status != CRYPTO_CONN_ESTABLISHED) { continue; } bool direct_connected = false; if (!crypto_connection_status(c, i, &direct_connected, nullptr)) { continue; } pthread_mutex_lock(&c->tcp_mutex); set_tcp_connection_to_status(c->tcp_c, conn->connection_number_tcp, !direct_connected); pthread_mutex_unlock(&c->tcp_mutex); } } /* Set function to be called when connection with crypt_connection_id goes connects/disconnects. * * The set function should return -1 on failure and 0 on success. * Note that if this function is set, the connection will clear itself on disconnect. * Object and id will be passed to this function untouched. * status is 1 if the connection is going online, 0 if it is going offline. * * return -1 on failure. * return 0 on success. */ int connection_status_handler(const Net_Crypto *c, int crypt_connection_id, connection_status_cb *connection_status_callback, void *object, int id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } conn->connection_status_callback = connection_status_callback; conn->connection_status_callback_object = object; conn->connection_status_callback_id = id; return 0; } /* Set function to be called when connection with crypt_connection_id receives a data packet of length. * * The set function should return -1 on failure and 0 on success. * Object and id will be passed to this function untouched. * * return -1 on failure. * return 0 on success. */ int connection_data_handler(const Net_Crypto *c, int crypt_connection_id, connection_data_cb *connection_data_callback, void *object, int id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } conn->connection_data_callback = connection_data_callback; conn->connection_data_callback_object = object; conn->connection_data_callback_id = id; return 0; } /* Set function to be called when connection with crypt_connection_id receives a lossy data packet of length. * * The set function should return -1 on failure and 0 on success. * Object and id will be passed to this function untouched. * * return -1 on failure. * return 0 on success. */ int connection_lossy_data_handler(Net_Crypto *c, int crypt_connection_id, connection_lossy_data_cb *connection_lossy_data_callback, void *object, int id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } conn->connection_lossy_data_callback = connection_lossy_data_callback; conn->connection_lossy_data_callback_object = object; conn->connection_lossy_data_callback_id = id; return 0; } /* Set the function for this friend that will be callbacked with object and number if * the friend sends us a different dht public key than we have associated to him. * * If this function is called, the connection should be recreated with the new public key. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int nc_dht_pk_callback(Net_Crypto *c, int crypt_connection_id, dht_pk_cb *function, void *object, uint32_t number) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } conn->dht_pk_callback = function; conn->dht_pk_callback_object = object; conn->dht_pk_callback_number = number; return 0; } /* Get the crypto connection id from the ip_port. * * return -1 on failure. * return connection id on success. */ static int crypto_id_ip_port(const Net_Crypto *c, IP_Port ip_port) { return bs_list_find(&c->ip_port_list, (uint8_t *)&ip_port); } #define CRYPTO_MIN_PACKET_SIZE (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE) /* Handle raw UDP packets coming directly from the socket. * * Handles: * Cookie response packets. * Crypto handshake packets. * Crypto data packets. * */ static int udp_handle_packet(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Net_Crypto *c = (Net_Crypto *)object; if (length <= CRYPTO_MIN_PACKET_SIZE || length > MAX_CRYPTO_PACKET_SIZE) { return 1; } const int crypt_connection_id = crypto_id_ip_port(c, source); if (crypt_connection_id == -1) { if (packet[0] != NET_PACKET_CRYPTO_HS) { return 1; } if (handle_new_connection_handshake(c, source, packet, length, userdata) != 0) { return 1; } return 0; } if (handle_packet_connection(c, crypt_connection_id, packet, length, 1, userdata) != 0) { return 1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } pthread_mutex_lock(conn->mutex); if (net_family_is_ipv4(source.ip.family)) { conn->direct_lastrecv_timev4 = mono_time_get(c->mono_time); } else { conn->direct_lastrecv_timev6 = mono_time_get(c->mono_time); } pthread_mutex_unlock(conn->mutex); return 0; } /* The dT for the average packet receiving rate calculations. * Also used as the */ #define PACKET_COUNTER_AVERAGE_INTERVAL 50 /* Ratio of recv queue size / recv packet rate (in seconds) times * the number of ms between request packets to send at that ratio */ #define REQUEST_PACKETS_COMPARE_CONSTANT (0.125 * 100.0) /* Timeout for increasing speed after congestion event (in ms). */ #define CONGESTION_EVENT_TIMEOUT 1000 /* If the send queue is SEND_QUEUE_RATIO times larger than the * calculated link speed the packet send speed will be reduced * by a value depending on this number. */ #define SEND_QUEUE_RATIO 2.0 static void send_crypto_packets(Net_Crypto *c) { const uint64_t temp_time = current_time_monotonic(c->mono_time); double total_send_rate = 0; uint32_t peak_request_packet_interval = -1; for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { Crypto_Connection *conn = get_crypto_connection(c, i); if (conn == nullptr) { continue; } if ((CRYPTO_SEND_PACKET_INTERVAL + conn->temp_packet_sent_time) < temp_time) { send_temp_packet(c, i); } if ((conn->status == CRYPTO_CONN_NOT_CONFIRMED || conn->status == CRYPTO_CONN_ESTABLISHED) && (CRYPTO_SEND_PACKET_INTERVAL + conn->last_request_packet_sent) < temp_time) { if (send_request_packet(c, i) == 0) { conn->last_request_packet_sent = temp_time; } } if (conn->status == CRYPTO_CONN_ESTABLISHED) { if (conn->packet_recv_rate > CRYPTO_PACKET_MIN_RATE) { double request_packet_interval = (REQUEST_PACKETS_COMPARE_CONSTANT / ((num_packets_array( &conn->recv_array) + 1.0) / (conn->packet_recv_rate + 1.0))); double request_packet_interval2 = ((CRYPTO_PACKET_MIN_RATE / conn->packet_recv_rate) * (double)CRYPTO_SEND_PACKET_INTERVAL) + (double)PACKET_COUNTER_AVERAGE_INTERVAL; if (request_packet_interval2 < request_packet_interval) { request_packet_interval = request_packet_interval2; } if (request_packet_interval < PACKET_COUNTER_AVERAGE_INTERVAL) { request_packet_interval = PACKET_COUNTER_AVERAGE_INTERVAL; } if (request_packet_interval > CRYPTO_SEND_PACKET_INTERVAL) { request_packet_interval = CRYPTO_SEND_PACKET_INTERVAL; } if (temp_time - conn->last_request_packet_sent > (uint64_t)request_packet_interval) { if (send_request_packet(c, i) == 0) { conn->last_request_packet_sent = temp_time; } } if (request_packet_interval < peak_request_packet_interval) { peak_request_packet_interval = request_packet_interval; } } if ((PACKET_COUNTER_AVERAGE_INTERVAL + conn->packet_counter_set) < temp_time) { const double dt = temp_time - conn->packet_counter_set; conn->packet_recv_rate = (double)conn->packet_counter / (dt / 1000.0); conn->packet_counter = 0; conn->packet_counter_set = temp_time; uint32_t packets_sent = conn->packets_sent; conn->packets_sent = 0; uint32_t packets_resent = conn->packets_resent; conn->packets_resent = 0; /* conjestion control * calculate a new value of conn->packet_send_rate based on some data */ unsigned int pos = conn->last_sendqueue_counter % CONGESTION_QUEUE_ARRAY_SIZE; conn->last_sendqueue_size[pos] = num_packets_array(&conn->send_array); long signed int sum = 0; sum = (long signed int)conn->last_sendqueue_size[pos] - (long signed int)conn->last_sendqueue_size[(pos + 1) % CONGESTION_QUEUE_ARRAY_SIZE]; unsigned int n_p_pos = conn->last_sendqueue_counter % CONGESTION_LAST_SENT_ARRAY_SIZE; conn->last_num_packets_sent[n_p_pos] = packets_sent; conn->last_num_packets_resent[n_p_pos] = packets_resent; conn->last_sendqueue_counter = (conn->last_sendqueue_counter + 1) % (CONGESTION_QUEUE_ARRAY_SIZE * CONGESTION_LAST_SENT_ARRAY_SIZE); bool direct_connected = 0; /* return value can be ignored since the `if` above ensures the connection is established */ crypto_connection_status(c, i, &direct_connected, nullptr); /* When switching from TCP to UDP, don't change the packet send rate for CONGESTION_EVENT_TIMEOUT ms. */ if (!(direct_connected && conn->last_tcp_sent + CONGESTION_EVENT_TIMEOUT > temp_time)) { long signed int total_sent = 0; long signed int total_resent = 0; // TODO(irungentoo): use real delay unsigned int delay = (unsigned int)((conn->rtt_time / PACKET_COUNTER_AVERAGE_INTERVAL) + 0.5); unsigned int packets_set_rem_array = (CONGESTION_LAST_SENT_ARRAY_SIZE - CONGESTION_QUEUE_ARRAY_SIZE); if (delay > packets_set_rem_array) { delay = packets_set_rem_array; } for (unsigned j = 0; j < CONGESTION_QUEUE_ARRAY_SIZE; ++j) { unsigned int ind = (j + (packets_set_rem_array - delay) + n_p_pos) % CONGESTION_LAST_SENT_ARRAY_SIZE; total_sent += conn->last_num_packets_sent[ind]; total_resent += conn->last_num_packets_resent[ind]; } if (sum > 0) { total_sent -= sum; } else { if (total_resent > -sum) { total_resent = -sum; } } /* if queue is too big only allow resending packets. */ uint32_t npackets = num_packets_array(&conn->send_array); double min_speed = 1000.0 * (((double)(total_sent)) / ((double)(CONGESTION_QUEUE_ARRAY_SIZE) * PACKET_COUNTER_AVERAGE_INTERVAL)); double min_speed_request = 1000.0 * (((double)(total_sent + total_resent)) / ((double)( CONGESTION_QUEUE_ARRAY_SIZE) * PACKET_COUNTER_AVERAGE_INTERVAL)); if (min_speed < CRYPTO_PACKET_MIN_RATE) { min_speed = CRYPTO_PACKET_MIN_RATE; } double send_array_ratio = (((double)npackets) / min_speed); // TODO(irungentoo): Improve formula? if (send_array_ratio > SEND_QUEUE_RATIO && CRYPTO_MIN_QUEUE_LENGTH < npackets) { conn->packet_send_rate = min_speed * (1.0 / (send_array_ratio / SEND_QUEUE_RATIO)); } else if (conn->last_congestion_event + CONGESTION_EVENT_TIMEOUT < temp_time) { conn->packet_send_rate = min_speed * 1.2; } else { conn->packet_send_rate = min_speed * 0.9; } conn->packet_send_rate_requested = min_speed_request * 1.2; if (conn->packet_send_rate < CRYPTO_PACKET_MIN_RATE) { conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; } if (conn->packet_send_rate_requested < conn->packet_send_rate) { conn->packet_send_rate_requested = conn->packet_send_rate; } } } if (conn->last_packets_left_set == 0 || conn->last_packets_left_requested_set == 0) { conn->last_packets_left_requested_set = temp_time; conn->last_packets_left_set = temp_time; conn->packets_left_requested = CRYPTO_MIN_QUEUE_LENGTH; conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; } else { if (((uint64_t)((1000.0 / conn->packet_send_rate) + 0.5) + conn->last_packets_left_set) <= temp_time) { double n_packets = conn->packet_send_rate * (((double)(temp_time - conn->last_packets_left_set)) / 1000.0); n_packets += conn->last_packets_left_rem; uint32_t num_packets = n_packets; double rem = n_packets - (double)num_packets; if (conn->packets_left > num_packets * 4 + CRYPTO_MIN_QUEUE_LENGTH) { conn->packets_left = num_packets * 4 + CRYPTO_MIN_QUEUE_LENGTH; } else { conn->packets_left += num_packets; } conn->last_packets_left_set = temp_time; conn->last_packets_left_rem = rem; } if (((uint64_t)((1000.0 / conn->packet_send_rate_requested) + 0.5) + conn->last_packets_left_requested_set) <= temp_time) { double n_packets = conn->packet_send_rate_requested * (((double)(temp_time - conn->last_packets_left_requested_set)) / 1000.0); n_packets += conn->last_packets_left_requested_rem; uint32_t num_packets = n_packets; double rem = n_packets - (double)num_packets; conn->packets_left_requested = num_packets; conn->last_packets_left_requested_set = temp_time; conn->last_packets_left_requested_rem = rem; } if (conn->packets_left > conn->packets_left_requested) { conn->packets_left_requested = conn->packets_left; } } int ret = send_requested_packets(c, i, conn->packets_left_requested); if (ret != -1) { conn->packets_left_requested -= ret; conn->packets_resent += ret; if ((unsigned int)ret < conn->packets_left) { conn->packets_left -= ret; } else { conn->last_congestion_event = temp_time; conn->packets_left = 0; } } if (conn->packet_send_rate > CRYPTO_PACKET_MIN_RATE * 1.5) { total_send_rate += conn->packet_send_rate; } } } c->current_sleep_time = -1; uint32_t sleep_time = peak_request_packet_interval; if (c->current_sleep_time > sleep_time) { c->current_sleep_time = sleep_time; } if (total_send_rate > CRYPTO_PACKET_MIN_RATE) { sleep_time = (1000.0 / total_send_rate); if (c->current_sleep_time > sleep_time) { c->current_sleep_time = sleep_time + 1; } } sleep_time = CRYPTO_SEND_PACKET_INTERVAL; if (c->current_sleep_time > sleep_time) { c->current_sleep_time = sleep_time; } } /* Return 1 if max speed was reached for this connection (no more data can be physically through the pipe). * Return 0 if it wasn't reached. */ bool max_speed_reached(Net_Crypto *c, int crypt_connection_id) { return reset_max_speed_reached(c, crypt_connection_id) != 0; } /* returns the number of packet slots left in the sendbuffer. * return 0 if failure. */ uint32_t crypto_num_free_sendqueue_slots(const Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return 0; } uint32_t max_packets = CRYPTO_PACKET_BUFFER_SIZE - num_packets_array(&conn->send_array); if (conn->packets_left < max_packets) { return conn->packets_left; } return max_packets; } /* Sends a lossless cryptopacket. * * return -1 if data could not be put in packet queue. * return positive packet number if data was put into the queue. * * The first byte of data must in the PACKET_ID_RANGE_LOSSLESS. * * congestion_control: should congestion control apply to this packet? */ int64_t write_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, uint8_t congestion_control) { if (length == 0) { return -1; } if (data[0] < PACKET_ID_RANGE_LOSSLESS_START || data[0] > PACKET_ID_RANGE_LOSSLESS_END) { return -1; } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } if (conn->status != CRYPTO_CONN_ESTABLISHED) { return -1; } if (congestion_control && conn->packets_left == 0) { return -1; } int64_t ret = send_lossless_packet(c, crypt_connection_id, data, length, congestion_control); if (ret == -1) { return -1; } if (congestion_control) { --conn->packets_left; --conn->packets_left_requested; ++conn->packets_sent; } return ret; } /* Check if packet_number was received by the other side. * * packet_number must be a valid packet number of a packet sent on this connection. * * return -1 on failure. * return 0 on success. * * Note: The condition `buffer_end - buffer_start < packet_number - buffer_start` is * a trick which handles situations `buffer_end >= buffer_start` and * `buffer_end < buffer_start` (when buffer_end overflowed) both correctly. * * It CANNOT be simplified to `packet_number < buffer_start`, as it will fail * when `buffer_end < buffer_start`. */ int cryptpacket_received(Net_Crypto *c, int crypt_connection_id, uint32_t packet_number) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } uint32_t num = num_packets_array(&conn->send_array); uint32_t num1 = packet_number - conn->send_array.buffer_start; if (num >= num1) { return -1; } return 0; } /* Sends a lossy cryptopacket. * * return -1 on failure. * return 0 on success. * * The first byte of data must in the PACKET_ID_RANGE_LOSSY. */ int send_lossy_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) { return -1; } if (data[0] < PACKET_ID_RANGE_LOSSY_START || data[0] > PACKET_ID_RANGE_LOSSY_END) { return -1; } pthread_mutex_lock(&c->connections_mutex); ++c->connection_use_counter; pthread_mutex_unlock(&c->connections_mutex); Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); int ret = -1; if (conn) { pthread_mutex_lock(conn->mutex); uint32_t buffer_start = conn->recv_array.buffer_start; uint32_t buffer_end = conn->send_array.buffer_end; pthread_mutex_unlock(conn->mutex); ret = send_data_packet_helper(c, crypt_connection_id, buffer_start, buffer_end, data, length); } pthread_mutex_lock(&c->connections_mutex); --c->connection_use_counter; pthread_mutex_unlock(&c->connections_mutex); return ret; } /* Kill a crypto connection. * * return -1 on failure. * return 0 on success. */ int crypto_kill(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); int ret = -1; if (conn) { if (conn->status == CRYPTO_CONN_ESTABLISHED) { send_kill_packet(c, crypt_connection_id); } pthread_mutex_lock(&c->tcp_mutex); kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); pthread_mutex_unlock(&c->tcp_mutex); bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv4, crypt_connection_id); bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv6, crypt_connection_id); clear_temp_packet(c, crypt_connection_id); clear_buffer(&conn->send_array); clear_buffer(&conn->recv_array); ret = wipe_crypto_connection(c, crypt_connection_id); } return ret; } bool crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, bool *direct_connected, unsigned int *online_tcp_relays) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return false; } if (direct_connected) { *direct_connected = 0; const uint64_t current_time = mono_time_get(c->mono_time); if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev4) > current_time) { *direct_connected = 1; } else if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev6) > current_time) { *direct_connected = 1; } } if (online_tcp_relays) { *online_tcp_relays = tcp_connection_to_online_tcp_relays(c->tcp_c, conn->connection_number_tcp); } return true; } void new_keys(Net_Crypto *c) { crypto_new_keypair(c->self_public_key, c->self_secret_key); } /* Save the public and private keys to the keys array. * Length must be CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE. * * TODO(irungentoo): Save only secret key. */ void save_keys(const Net_Crypto *c, uint8_t *keys) { memcpy(keys, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(keys + CRYPTO_PUBLIC_KEY_SIZE, c->self_secret_key, CRYPTO_SECRET_KEY_SIZE); } /* Load the secret key. * Length must be CRYPTO_SECRET_KEY_SIZE. */ void load_secret_key(Net_Crypto *c, const uint8_t *sk) { memcpy(c->self_secret_key, sk, CRYPTO_SECRET_KEY_SIZE); crypto_derive_public_key(c->self_public_key, c->self_secret_key); } /* Run this to (re)initialize net_crypto. * Sets all the global connection variables to their default values. */ Net_Crypto *new_net_crypto(const Logger *log, Mono_Time *mono_time, DHT *dht, TCP_Proxy_Info *proxy_info) { if (dht == nullptr) { return nullptr; } Net_Crypto *temp = (Net_Crypto *)calloc(1, sizeof(Net_Crypto)); if (temp == nullptr) { return nullptr; } temp->log = log; temp->mono_time = mono_time; temp->tcp_c = new_tcp_connections(mono_time, dht_get_self_secret_key(dht), proxy_info); if (temp->tcp_c == nullptr) { free(temp); return nullptr; } set_packet_tcp_connection_callback(temp->tcp_c, &tcp_data_callback, temp); set_oob_packet_tcp_connection_callback(temp->tcp_c, &tcp_oob_callback, temp); if (create_recursive_mutex(&temp->tcp_mutex) != 0 || pthread_mutex_init(&temp->connections_mutex, nullptr) != 0) { kill_tcp_connections(temp->tcp_c); free(temp); return nullptr; } temp->dht = dht; new_keys(temp); new_symmetric_key(temp->secret_symmetric_key); temp->current_sleep_time = CRYPTO_SEND_PACKET_INTERVAL; networking_registerhandler(dht_get_net(dht), NET_PACKET_COOKIE_REQUEST, &udp_handle_cookie_request, temp); networking_registerhandler(dht_get_net(dht), NET_PACKET_COOKIE_RESPONSE, &udp_handle_packet, temp); networking_registerhandler(dht_get_net(dht), NET_PACKET_CRYPTO_HS, &udp_handle_packet, temp); networking_registerhandler(dht_get_net(dht), NET_PACKET_CRYPTO_DATA, &udp_handle_packet, temp); bs_list_init(&temp->ip_port_list, sizeof(IP_Port), 8); return temp; } static void kill_timedout(Net_Crypto *c, void *userdata) { for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { Crypto_Connection *conn = get_crypto_connection(c, i); if (conn == nullptr) { continue; } if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING || conn->status == CRYPTO_CONN_HANDSHAKE_SENT || conn->status == CRYPTO_CONN_NOT_CONFIRMED) { if (conn->temp_packet_num_sent < MAX_NUM_SENDPACKET_TRIES) { continue; } connection_kill(c, i, userdata); } #if 0 if (conn->status == CRYPTO_CONN_ESTABLISHED) { // TODO(irungentoo): add a timeout here? do_timeout_here(); } #endif } } /* return the optimal interval in ms for running do_net_crypto. */ uint32_t crypto_run_interval(const Net_Crypto *c) { return c->current_sleep_time; } /* Main loop. */ void do_net_crypto(Net_Crypto *c, void *userdata) { kill_timedout(c, userdata); do_tcp(c, userdata); send_crypto_packets(c); } void kill_net_crypto(Net_Crypto *c) { uint32_t i; for (i = 0; i < c->crypto_connections_length; ++i) { crypto_kill(c, i); } pthread_mutex_destroy(&c->tcp_mutex); pthread_mutex_destroy(&c->connections_mutex); kill_tcp_connections(c->tcp_c); bs_list_free(&c->ip_port_list); networking_registerhandler(dht_get_net(c->dht), NET_PACKET_COOKIE_REQUEST, nullptr, nullptr); networking_registerhandler(dht_get_net(c->dht), NET_PACKET_COOKIE_RESPONSE, nullptr, nullptr); networking_registerhandler(dht_get_net(c->dht), NET_PACKET_CRYPTO_HS, nullptr, nullptr); networking_registerhandler(dht_get_net(c->dht), NET_PACKET_CRYPTO_DATA, nullptr, nullptr); crypto_memzero(c, sizeof(Net_Crypto)); free(c); } c-toxcore-0.2.13/toxcore/net_crypto.h000066400000000000000000000275371415350724400175450ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Functions for the core network crypto. */ #ifndef C_TOXCORE_TOXCORE_NET_CRYPTO_H #define C_TOXCORE_TOXCORE_NET_CRYPTO_H #include "DHT.h" #include "LAN_discovery.h" #include "TCP_connection.h" #include "logger.h" #include /** Crypto payloads. */ /** Ranges. */ /* Packets in this range are reserved for net_crypto internal use. */ #define PACKET_ID_RANGE_RESERVED_START 0 #define PACKET_ID_RANGE_RESERVED_END 15 /* Packets in this range are reserved for Messenger use. */ #define PACKET_ID_RANGE_LOSSLESS_START 16 #define PACKET_ID_RANGE_LOSSLESS_NORMAL_START 16 #define PACKET_ID_RANGE_LOSSLESS_NORMAL_END 159 /* Packets in this range can be used for anything. */ #define PACKET_ID_RANGE_LOSSLESS_CUSTOM_START 160 #define PACKET_ID_RANGE_LOSSLESS_CUSTOM_END 191 #define PACKET_ID_RANGE_LOSSLESS_END 191 /* Packets in this range are reserved for AV use. */ #define PACKET_ID_RANGE_LOSSY_START 192 #define PACKET_ID_RANGE_LOSSY_AV_START 192 #define PACKET_ID_RANGE_LOSSY_AV_SIZE 8 #define PACKET_ID_RANGE_LOSSY_AV_END 199 /* Packets in this range can be used for anything. */ #define PACKET_ID_RANGE_LOSSY_CUSTOM_START 200 #define PACKET_ID_RANGE_LOSSY_CUSTOM_END 254 #define PACKET_ID_RANGE_LOSSY_END 254 /** Messages. */ #define PACKET_ID_PADDING 0 // Denotes padding #define PACKET_ID_REQUEST 1 // Used to request unreceived packets #define PACKET_ID_KILL 2 // Used to kill connection #define PACKET_ID_ONLINE 24 #define PACKET_ID_OFFLINE 25 #define PACKET_ID_NICKNAME 48 #define PACKET_ID_STATUSMESSAGE 49 #define PACKET_ID_USERSTATUS 50 #define PACKET_ID_TYPING 51 #define PACKET_ID_MESSAGE 64 #define PACKET_ID_ACTION 65 // PACKET_ID_MESSAGE + MESSAGE_ACTION #define PACKET_ID_MSI 69 // Used by AV to setup calls and etc #define PACKET_ID_FILE_SENDREQUEST 80 #define PACKET_ID_FILE_CONTROL 81 #define PACKET_ID_FILE_DATA 82 #define PACKET_ID_INVITE_CONFERENCE 96 #define PACKET_ID_ONLINE_PACKET 97 #define PACKET_ID_DIRECT_CONFERENCE 98 #define PACKET_ID_MESSAGE_CONFERENCE 99 #define PACKET_ID_REJOIN_CONFERENCE 100 #define PACKET_ID_LOSSY_CONFERENCE 199 /* Maximum size of receiving and sending packet buffers. */ #define CRYPTO_PACKET_BUFFER_SIZE 32768 // Must be a power of 2 /* Minimum packet rate per second. */ #define CRYPTO_PACKET_MIN_RATE 4.0 /* Minimum packet queue max length. */ #define CRYPTO_MIN_QUEUE_LENGTH 64 /* Maximum total size of packets that net_crypto sends. */ #define MAX_CRYPTO_PACKET_SIZE (uint16_t)1400 #define CRYPTO_DATA_PACKET_MIN_SIZE (uint16_t)(1 + sizeof(uint16_t) + (sizeof(uint32_t) + sizeof(uint32_t)) + CRYPTO_MAC_SIZE) /* Max size of data in packets */ #define MAX_CRYPTO_DATA_SIZE (uint16_t)(MAX_CRYPTO_PACKET_SIZE - CRYPTO_DATA_PACKET_MIN_SIZE) /* Interval in ms between sending cookie request/handshake packets. */ #define CRYPTO_SEND_PACKET_INTERVAL 1000 /* The maximum number of times we try to send the cookie request and handshake * before giving up. */ #define MAX_NUM_SENDPACKET_TRIES 8 /* The timeout of no received UDP packets before the direct UDP connection is considered dead. */ #define UDP_DIRECT_TIMEOUT 8 #define MAX_TCP_CONNECTIONS 64 #define MAX_TCP_RELAYS_PEER 4 /* All packets will be padded a number of bytes based on this number. */ #define CRYPTO_MAX_PADDING 8 /* Base current transfer speed on last CONGESTION_QUEUE_ARRAY_SIZE number of points taken * at the dT defined in net_crypto.c */ #define CONGESTION_QUEUE_ARRAY_SIZE 12 #define CONGESTION_LAST_SENT_ARRAY_SIZE (CONGESTION_QUEUE_ARRAY_SIZE * 2) /* Default connection ping in ms. */ #define DEFAULT_PING_CONNECTION 1000 #define DEFAULT_TCP_PING_CONNECTION 500 typedef struct Net_Crypto Net_Crypto; const uint8_t *nc_get_self_public_key(const Net_Crypto *c); const uint8_t *nc_get_self_secret_key(const Net_Crypto *c); TCP_Connections *nc_get_tcp_c(const Net_Crypto *c); DHT *nc_get_dht(const Net_Crypto *c); typedef struct New_Connection { IP_Port source; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer. */ uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ uint8_t *cookie; uint8_t cookie_length; } New_Connection; typedef int connection_status_cb(void *object, int id, uint8_t status, void *userdata); typedef int connection_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); typedef int connection_lossy_data_cb(void *object, int id, const uint8_t *data, uint16_t length, void *userdata); typedef void dht_pk_cb(void *data, int32_t number, const uint8_t *dht_public_key, void *userdata); typedef int new_connection_cb(void *object, New_Connection *n_c); /* Set function to be called when someone requests a new connection to us. * * The set function should return -1 on failure and 0 on success. * * n_c is only valid for the duration of the function call. */ void new_connection_handler(Net_Crypto *c, new_connection_cb *new_connection_callback, void *object); /* Accept a crypto connection. * * return -1 on failure. * return connection id on success. */ int accept_crypto_connection(Net_Crypto *c, New_Connection *n_c); /* Create a crypto connection. * If one to that real public key already exists, return it. * * return -1 on failure. * return connection id on success. */ int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const uint8_t *dht_public_key); /* Set the direct ip of the crypto connection. * * Connected is 0 if we are not sure we are connected to that person, 1 if we are sure. * * return -1 on failure. * return 0 on success. */ int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, bool connected); /* Set function to be called when connection with crypt_connection_id goes connects/disconnects. * * The set function should return -1 on failure and 0 on success. * Note that if this function is set, the connection will clear itself on disconnect. * Object and id will be passed to this function untouched. * status is 1 if the connection is going online, 0 if it is going offline. * * return -1 on failure. * return 0 on success. */ int connection_status_handler(const Net_Crypto *c, int crypt_connection_id, connection_status_cb *connection_status_callback, void *object, int id); /* Set function to be called when connection with crypt_connection_id receives a lossless data packet of length. * * The set function should return -1 on failure and 0 on success. * Object and id will be passed to this function untouched. * * return -1 on failure. * return 0 on success. */ int connection_data_handler(const Net_Crypto *c, int crypt_connection_id, connection_data_cb *connection_data_callback, void *object, int id); /* Set function to be called when connection with crypt_connection_id receives a lossy data packet of length. * * The set function should return -1 on failure and 0 on success. * Object and id will be passed to this function untouched. * * return -1 on failure. * return 0 on success. */ int connection_lossy_data_handler(Net_Crypto *c, int crypt_connection_id, connection_lossy_data_cb *connection_lossy_data_callback, void *object, int id); /* Set the function for this friend that will be callbacked with object and number if * the friend sends us a different dht public key than we have associated to him. * * If this function is called, the connection should be recreated with the new public key. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int nc_dht_pk_callback(Net_Crypto *c, int crypt_connection_id, dht_pk_cb *function, void *object, uint32_t number); /* returns the number of packet slots left in the sendbuffer. * return 0 if failure. */ uint32_t crypto_num_free_sendqueue_slots(const Net_Crypto *c, int crypt_connection_id); /* Return 1 if max speed was reached for this connection (no more data can be physically through the pipe). * Return 0 if it wasn't reached. */ bool max_speed_reached(Net_Crypto *c, int crypt_connection_id); /* Sends a lossless cryptopacket. * * return -1 if data could not be put in packet queue. * return positive packet number if data was put into the queue. * * The first byte of data must be in the PACKET_ID_RANGE_LOSSLESS. * * congestion_control: should congestion control apply to this packet? */ int64_t write_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, uint8_t congestion_control); /* Check if packet_number was received by the other side. * * packet_number must be a valid packet number of a packet sent on this connection. * * return -1 on failure. * return 0 on success. */ int cryptpacket_received(Net_Crypto *c, int crypt_connection_id, uint32_t packet_number); /* Sends a lossy cryptopacket. * * return -1 on failure. * return 0 on success. * * The first byte of data must be in the PACKET_ID_RANGE_LOSSY. */ int send_lossy_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length); /* Add a tcp relay, associating it to a crypt_connection_id. * * return 0 if it was added. * return -1 if it wasn't. */ int add_tcp_relay_peer(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, const uint8_t *public_key); /* Add a tcp relay to the array. * * return 0 if it was added. * return -1 if it wasn't. */ int add_tcp_relay(Net_Crypto *c, IP_Port ip_port, const uint8_t *public_key); /* Return a random TCP connection number for use in send_tcp_onion_request. * * return TCP connection number on success. * return -1 on failure. */ int get_random_tcp_con_number(Net_Crypto *c); /* Send an onion packet via the TCP relay corresponding to TCP_conn_number. * * return 0 on success. * return -1 on failure. */ int send_tcp_onion_request(Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length); /* Copy a maximum of num TCP relays we are connected to to tcp_relays. * NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6. * * return number of relays copied to tcp_relays on success. * return 0 on failure. */ unsigned int copy_connected_tcp_relays(Net_Crypto *c, Node_format *tcp_relays, uint16_t num); /* Kill a crypto connection. * * return -1 on failure. * return 0 on success. */ int crypto_kill(Net_Crypto *c, int crypt_connection_id); /* return true if connection is valid, false otherwise * * sets direct_connected to 1 if connection connects directly to other, 0 if it isn't. * sets online_tcp_relays to the number of connected tcp relays this connection has. */ bool crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, bool *direct_connected, unsigned int *online_tcp_relays); /* Generate our public and private keys. * Only call this function the first time the program starts. */ void new_keys(Net_Crypto *c); /* Save the public and private keys to the keys array. * Length must be CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SECRET_KEY_SIZE. */ void save_keys(const Net_Crypto *c, uint8_t *keys); /* Load the secret key. * Length must be CRYPTO_SECRET_KEY_SIZE. */ void load_secret_key(Net_Crypto *c, const uint8_t *sk); /* Create new instance of Net_Crypto. * Sets all the global connection variables to their default values. */ Net_Crypto *new_net_crypto(const Logger *log, Mono_Time *mono_time, DHT *dht, TCP_Proxy_Info *proxy_info); /* return the optimal interval in ms for running do_net_crypto. */ uint32_t crypto_run_interval(const Net_Crypto *c); /* Main loop. */ void do_net_crypto(Net_Crypto *c, void *userdata); void kill_net_crypto(Net_Crypto *c); #endif c-toxcore-0.2.13/toxcore/network.c000066400000000000000000001212441415350724400170310ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Functions for the core networking. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef __APPLE__ #define _DARWIN_C_SOURCE #endif // For Solaris. #ifdef __sun #define __EXTENSIONS__ 1 #endif // For Linux (and some BSDs). #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 #endif #if defined(_WIN32) && _WIN32_WINNT >= _WIN32_WINNT_WINXP #undef _WIN32_WINNT #define _WIN32_WINNT 0x501 #endif #if !defined(OS_WIN32) && (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) #define OS_WIN32 #endif #ifdef OS_WIN32 #ifndef WINVER // Windows XP #define WINVER 0x0501 #endif #endif #ifdef PLAN9 #include // Plan 9 requires this is imported first // Comment line here to avoid reordering by source code formatters. #include #endif #ifdef OS_WIN32 /* Put win32 includes here */ // The mingw32/64 Windows library warns about including winsock2.h after // windows.h even though with the above it's a valid thing to do. So, to make // mingw32 headers happy, we include winsock2.h first. #include // Comment line here to avoid reordering by source code formatters. #include #include #endif #include "network.h" #ifdef __APPLE__ #include #include #endif #if !defined(OS_WIN32) #include #include #include #include #include #include #include #include #include #include #ifdef __sun #include #include #endif #define TOX_EWOULDBLOCK EWOULDBLOCK static const char *inet_ntop4(const struct in_addr *addr, char *buf, size_t bufsize) { return inet_ntop(AF_INET, addr, buf, bufsize); } static const char *inet_ntop6(const struct in6_addr *addr, char *buf, size_t bufsize) { return inet_ntop(AF_INET6, addr, buf, bufsize); } static int inet_pton4(const char *addrString, struct in_addr *addrbuf) { return inet_pton(AF_INET, addrString, addrbuf); } static int inet_pton6(const char *addrString, struct in6_addr *addrbuf) { return inet_pton(AF_INET6, addrString, addrbuf); } #else #ifndef IPV6_V6ONLY #define IPV6_V6ONLY 27 #endif #define TOX_EWOULDBLOCK WSAEWOULDBLOCK static const char *inet_ntop4(const struct in_addr *addr, char *buf, size_t bufsize) { struct sockaddr_in saddr = {0}; saddr.sin_family = AF_INET; saddr.sin_addr = *addr; DWORD len = bufsize; if (WSAAddressToString((LPSOCKADDR)&saddr, sizeof(saddr), nullptr, buf, &len)) { return nullptr; } return buf; } static const char *inet_ntop6(const struct in6_addr *addr, char *buf, size_t bufsize) { struct sockaddr_in6 saddr = {0}; saddr.sin6_family = AF_INET6; saddr.sin6_addr = *addr; DWORD len = bufsize; if (WSAAddressToString((LPSOCKADDR)&saddr, sizeof(saddr), nullptr, buf, &len)) { return nullptr; } return buf; } static int inet_pton4(const char *addrString, struct in_addr *addrbuf) { struct sockaddr_in saddr = {0}; INT len = sizeof(saddr); if (WSAStringToAddress((LPTSTR)addrString, AF_INET, nullptr, (LPSOCKADDR)&saddr, &len)) { return 0; } *addrbuf = saddr.sin_addr; return 1; } static int inet_pton6(const char *addrString, struct in6_addr *addrbuf) { struct sockaddr_in6 saddr = {0}; INT len = sizeof(saddr); if (WSAStringToAddress((LPTSTR)addrString, AF_INET6, nullptr, (LPSOCKADDR)&saddr, &len)) { return 0; } *addrbuf = saddr.sin6_addr; return 1; } #endif #include #include #include #include #include "logger.h" #include "mono_time.h" #include "util.h" // Disable MSG_NOSIGNAL on systems not supporting it, e.g. Windows, FreeBSD #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL 0 #endif #ifndef IPV6_ADD_MEMBERSHIP #ifdef IPV6_JOIN_GROUP #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP #endif #endif #if TOX_INET6_ADDRSTRLEN < INET6_ADDRSTRLEN #error "TOX_INET6_ADDRSTRLEN should be greater or equal to INET6_ADDRSTRLEN (#INET6_ADDRSTRLEN)" #endif #if TOX_INET_ADDRSTRLEN < INET_ADDRSTRLEN #error "TOX_INET_ADDRSTRLEN should be greater or equal to INET_ADDRSTRLEN (#INET_ADDRSTRLEN)" #endif static int make_proto(int proto) { switch (proto) { case TOX_PROTO_TCP: return IPPROTO_TCP; case TOX_PROTO_UDP: return IPPROTO_UDP; default: return proto; } } static int make_socktype(int type) { switch (type) { case TOX_SOCK_STREAM: return SOCK_STREAM; case TOX_SOCK_DGRAM: return SOCK_DGRAM; default: return type; } } static int make_family(Family tox_family) { switch (tox_family.value) { case TOX_AF_INET: return AF_INET; case TOX_AF_INET6: return AF_INET6; case TOX_AF_UNSPEC: return AF_UNSPEC; default: return tox_family.value; } } static const Family *make_tox_family(int family) { switch (family) { case AF_INET: return &net_family_ipv4; case AF_INET6: return &net_family_ipv6; case AF_UNSPEC: return &net_family_unspec; default: return nullptr; } } static void get_ip4(IP4 *result, const struct in_addr *addr) { result->uint32 = addr->s_addr; } static void get_ip6(IP6 *result, const struct in6_addr *addr) { assert(sizeof(result->uint8) == sizeof(addr->s6_addr)); memcpy(result->uint8, addr->s6_addr, sizeof(result->uint8)); } static void fill_addr4(IP4 ip, struct in_addr *addr) { addr->s_addr = ip.uint32; } static void fill_addr6(IP6 ip, struct in6_addr *addr) { assert(sizeof(ip.uint8) == sizeof(addr->s6_addr)); memcpy(addr->s6_addr, ip.uint8, sizeof(ip.uint8)); } #if !defined(INADDR_LOOPBACK) #define INADDR_LOOPBACK 0x7f000001 #endif const IP4 ip4_broadcast = { INADDR_BROADCAST }; const IP6 ip6_broadcast = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } }; IP4 get_ip4_loopback(void) { IP4 loopback; loopback.uint32 = htonl(INADDR_LOOPBACK); return loopback; } IP6 get_ip6_loopback(void) { IP6 loopback; get_ip6(&loopback, &in6addr_loopback); return loopback; } #ifndef OS_WIN32 #define INVALID_SOCKET (-1) #endif const Socket net_invalid_socket = { (int)INVALID_SOCKET }; const Family net_family_unspec = {TOX_AF_UNSPEC}; const Family net_family_ipv4 = {TOX_AF_INET}; const Family net_family_ipv6 = {TOX_AF_INET6}; const Family net_family_tcp_family = {TCP_FAMILY}; const Family net_family_tcp_onion = {TCP_ONION_FAMILY}; const Family net_family_tcp_ipv4 = {TCP_INET}; const Family net_family_tcp_ipv6 = {TCP_INET6}; const Family net_family_tox_tcp_ipv4 = {TOX_TCP_INET}; const Family net_family_tox_tcp_ipv6 = {TOX_TCP_INET6}; bool net_family_is_unspec(Family family) { return family.value == net_family_unspec.value; } bool net_family_is_ipv4(Family family) { return family.value == net_family_ipv4.value; } bool net_family_is_ipv6(Family family) { return family.value == net_family_ipv6.value; } bool net_family_is_tcp_family(Family family) { return family.value == net_family_tcp_family.value; } bool net_family_is_tcp_onion(Family family) { return family.value == net_family_tcp_onion.value; } bool net_family_is_tcp_ipv4(Family family) { return family.value == net_family_tcp_ipv4.value; } bool net_family_is_tcp_ipv6(Family family) { return family.value == net_family_tcp_ipv6.value; } bool net_family_is_tox_tcp_ipv4(Family family) { return family.value == net_family_tox_tcp_ipv4.value; } bool net_family_is_tox_tcp_ipv6(Family family) { return family.value == net_family_tox_tcp_ipv6.value; } bool sock_valid(Socket sock) { return sock.socket != net_invalid_socket.socket; } /* Close the socket. */ void kill_sock(Socket sock) { #ifdef OS_WIN32 closesocket(sock.socket); #else close(sock.socket); #endif } bool set_socket_nonblock(Socket sock) { #ifdef OS_WIN32 u_long mode = 1; return ioctlsocket(sock.socket, FIONBIO, &mode) == 0; #else return fcntl(sock.socket, F_SETFL, O_NONBLOCK, 1) == 0; #endif } bool set_socket_nosigpipe(Socket sock) { #if defined(__APPLE__) int set = 1; return setsockopt(sock.socket, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&set, sizeof(int)) == 0; #else return true; #endif } bool set_socket_reuseaddr(Socket sock) { int set = 1; return setsockopt(sock.socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&set, sizeof(set)) == 0; } bool set_socket_dualstack(Socket sock) { int ipv6only = 0; socklen_t optsize = sizeof(ipv6only); int res = getsockopt(sock.socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ipv6only, &optsize); if ((res == 0) && (ipv6only == 0)) { return true; } ipv6only = 0; return setsockopt(sock.socket, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&ipv6only, sizeof(ipv6only)) == 0; } static uint32_t data_0(uint16_t buflen, const uint8_t *buffer) { uint32_t data = 0; if (buflen > 4) { net_unpack_u32(buffer + 1, &data); } return data; } static uint32_t data_1(uint16_t buflen, const uint8_t *buffer) { uint32_t data = 0; if (buflen > 7) { net_unpack_u32(buffer + 5, &data); } return data; } static void loglogdata(const Logger *log, const char *message, const uint8_t *buffer, uint16_t buflen, IP_Port ip_port, int res) { char ip_str[IP_NTOA_LEN]; if (res < 0) { /* Windows doesn't necessarily know `%zu` */ int error = net_error(); const char *strerror = net_new_strerror(error); LOGGER_TRACE(log, "[%2u] %s %3u%c %s:%u (%u: %s) | %04x%04x", buffer[0], message, min_u16(buflen, 999), 'E', ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(ip_port.port), error, strerror, data_0(buflen, buffer), data_1(buflen, buffer)); net_kill_strerror(strerror); } else if ((res > 0) && ((size_t)res <= buflen)) { LOGGER_TRACE(log, "[%2u] %s %3u%c %s:%u (%u: %s) | %04x%04x", buffer[0], message, min_u16(res, 999), ((size_t)res < buflen ? '<' : '='), ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(ip_port.port), 0, "OK", data_0(buflen, buffer), data_1(buflen, buffer)); } else { /* empty or overwrite */ LOGGER_TRACE(log, "[%2u] %s %u%c%u %s:%u (%u: %s) | %04x%04x", buffer[0], message, res, (!res ? '!' : '>'), buflen, ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), net_ntohs(ip_port.port), 0, "OK", data_0(buflen, buffer), data_1(buflen, buffer)); } } typedef struct Packet_Handler { packet_handler_cb *function; void *object; } Packet_Handler; struct Networking_Core { const Logger *log; Packet_Handler packethandlers[256]; Family family; uint16_t port; /* Our UDP socket. */ Socket sock; }; Family net_family(const Networking_Core *net) { return net->family; } uint16_t net_port(const Networking_Core *net) { return net->port; } /* Basic network functions: * Function to send packet(data) of length length to ip_port. */ int sendpacket(Networking_Core *net, IP_Port ip_port, const uint8_t *data, uint16_t length) { if (net_family_is_unspec(net->family)) { /* Socket not initialized */ LOGGER_ERROR(net->log, "attempted to send message of length %u on uninitialised socket", (unsigned)length); return -1; } /* socket TOX_AF_INET, but target IP NOT: can't send */ if (net_family_is_ipv4(net->family) && !net_family_is_ipv4(ip_port.ip.family)) { LOGGER_ERROR(net->log, "attempted to send message with network family %d (probably IPv6) on IPv4 socket", ip_port.ip.family.value); return -1; } if (net_family_is_ipv4(ip_port.ip.family) && net_family_is_ipv6(net->family)) { /* must convert to IPV4-in-IPV6 address */ IP6 ip6; /* there should be a macro for this in a standards compliant * environment, not found */ ip6.uint32[0] = 0; ip6.uint32[1] = 0; ip6.uint32[2] = net_htonl(0xFFFF); ip6.uint32[3] = ip_port.ip.ip.v4.uint32; ip_port.ip.family = net_family_ipv6; ip_port.ip.ip.v6 = ip6; } struct sockaddr_storage addr; size_t addrsize; if (net_family_is_ipv4(ip_port.ip.family)) { struct sockaddr_in *const addr4 = (struct sockaddr_in *)&addr; addrsize = sizeof(struct sockaddr_in); addr4->sin_family = AF_INET; addr4->sin_port = ip_port.port; fill_addr4(ip_port.ip.ip.v4, &addr4->sin_addr); } else if (net_family_is_ipv6(ip_port.ip.family)) { struct sockaddr_in6 *const addr6 = (struct sockaddr_in6 *)&addr; addrsize = sizeof(struct sockaddr_in6); addr6->sin6_family = AF_INET6; addr6->sin6_port = ip_port.port; fill_addr6(ip_port.ip.ip.v6, &addr6->sin6_addr); addr6->sin6_flowinfo = 0; addr6->sin6_scope_id = 0; } else { LOGGER_WARNING(net->log, "unknown address type: %d", ip_port.ip.family.value); return -1; } const int res = sendto(net->sock.socket, (const char *)data, length, 0, (struct sockaddr *)&addr, addrsize); loglogdata(net->log, "O=>", data, length, ip_port, res); return res; } /* Function to receive data * ip and port of sender is put into ip_port. * Packet data is put into data. * Packet length is put into length. */ static int receivepacket(const Logger *log, Socket sock, IP_Port *ip_port, uint8_t *data, uint32_t *length) { memset(ip_port, 0, sizeof(IP_Port)); struct sockaddr_storage addr; #ifdef OS_WIN32 int addrlen = sizeof(addr); #else socklen_t addrlen = sizeof(addr); #endif *length = 0; int fail_or_len = recvfrom(sock.socket, (char *) data, MAX_UDP_PACKET_SIZE, 0, (struct sockaddr *)&addr, &addrlen); if (fail_or_len < 0) { int error = net_error(); if (fail_or_len < 0 && error != TOX_EWOULDBLOCK) { const char *strerror = net_new_strerror(error); LOGGER_ERROR(log, "Unexpected error reading from socket: %u, %s", error, strerror); net_kill_strerror(strerror); } return -1; /* Nothing received. */ } *length = (uint32_t)fail_or_len; if (addr.ss_family == AF_INET) { struct sockaddr_in *addr_in = (struct sockaddr_in *)&addr; const Family *const family = make_tox_family(addr_in->sin_family); assert(family != nullptr); if (family == nullptr) { return -1; } ip_port->ip.family = *family; get_ip4(&ip_port->ip.ip.v4, &addr_in->sin_addr); ip_port->port = addr_in->sin_port; } else if (addr.ss_family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&addr; const Family *const family = make_tox_family(addr_in6->sin6_family); assert(family != nullptr); if (family == nullptr) { return -1; } ip_port->ip.family = *family; get_ip6(&ip_port->ip.ip.v6, &addr_in6->sin6_addr); ip_port->port = addr_in6->sin6_port; if (ipv6_ipv4_in_v6(ip_port->ip.ip.v6)) { ip_port->ip.family = net_family_ipv4; ip_port->ip.ip.v4.uint32 = ip_port->ip.ip.v6.uint32[3]; } } else { return -1; } loglogdata(log, "=>O", data, MAX_UDP_PACKET_SIZE, *ip_port, *length); return 0; } void networking_registerhandler(Networking_Core *net, uint8_t byte, packet_handler_cb *cb, void *object) { net->packethandlers[byte].function = cb; net->packethandlers[byte].object = object; } void networking_poll(Networking_Core *net, void *userdata) { if (net_family_is_unspec(net->family)) { /* Socket not initialized */ return; } IP_Port ip_port; uint8_t data[MAX_UDP_PACKET_SIZE]; uint32_t length; while (receivepacket(net->log, net->sock, &ip_port, data, &length) != -1) { if (length < 1) { continue; } if (!(net->packethandlers[data[0]].function)) { LOGGER_WARNING(net->log, "[%02u] -- Packet has no handler", data[0]); continue; } net->packethandlers[data[0]].function(net->packethandlers[data[0]].object, ip_port, data, length, userdata); } } #ifndef VANILLA_NACL /* Used for sodium_init() */ #include #endif //!TOKSTYLE- // Global mutable state is not allowed in Tokstyle. static uint8_t at_startup_ran = 0; //!TOKSTYLE+ int networking_at_startup(void) { if (at_startup_ran != 0) { return 0; } #ifndef VANILLA_NACL #ifdef USE_RANDOMBYTES_STIR randombytes_stir(); #else if (sodium_init() == -1) { return -1; } #endif /*USE_RANDOMBYTES_STIR*/ #endif/*VANILLA_NACL*/ #ifdef OS_WIN32 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) { return -1; } #endif at_startup_ran = 1; return 0; } /* TODO(irungentoo): Put this somewhere */ #if 0 static void at_shutdown(void) { #ifdef OS_WIN32 WSACleanup(); #endif } #endif /* Initialize networking. * Added for reverse compatibility with old new_networking calls. */ Networking_Core *new_networking(const Logger *log, IP ip, uint16_t port) { return new_networking_ex(log, ip, port, port + (TOX_PORTRANGE_TO - TOX_PORTRANGE_FROM), nullptr); } /* Initialize networking. * Bind to ip and port. * ip must be in network order EX: 127.0.0.1 = (7F000001). * port is in host byte order (this means don't worry about it). * * return Networking_Core object if no problems * return NULL if there are problems. * * If error is non NULL it is set to 0 if no issues, 1 if socket related error, 2 if other. */ Networking_Core *new_networking_ex(const Logger *log, IP ip, uint16_t port_from, uint16_t port_to, unsigned int *error) { /* If both from and to are 0, use default port range * If one is 0 and the other is non-0, use the non-0 value as only port * If from > to, swap */ if (port_from == 0 && port_to == 0) { port_from = TOX_PORTRANGE_FROM; port_to = TOX_PORTRANGE_TO; } else if (port_from == 0 && port_to != 0) { port_from = port_to; } else if (port_from != 0 && port_to == 0) { port_to = port_from; } else if (port_from > port_to) { uint16_t temp = port_from; port_from = port_to; port_to = temp; } if (error) { *error = 2; } /* maybe check for invalid IPs like 224+.x.y.z? if there is any IP set ever */ if (!net_family_is_ipv4(ip.family) && !net_family_is_ipv6(ip.family)) { LOGGER_ERROR(log, "invalid address family: %u", ip.family.value); return nullptr; } if (networking_at_startup() != 0) { return nullptr; } Networking_Core *temp = (Networking_Core *)calloc(1, sizeof(Networking_Core)); if (temp == nullptr) { return nullptr; } temp->log = log; temp->family = ip.family; temp->port = 0; /* Initialize our socket. */ /* add log message what we're creating */ temp->sock = net_socket(temp->family, TOX_SOCK_DGRAM, TOX_PROTO_UDP); /* Check for socket error. */ if (!sock_valid(temp->sock)) { int neterror = net_error(); const char *strerror = net_new_strerror(neterror); LOGGER_ERROR(log, "Failed to get a socket?! %d, %s", neterror, strerror); net_kill_strerror(strerror); free(temp); if (error) { *error = 1; } return nullptr; } /* Functions to increase the size of the send and receive UDP buffers. */ int n = 1024 * 1024 * 2; setsockopt(temp->sock.socket, SOL_SOCKET, SO_RCVBUF, (const char *)&n, sizeof(n)); setsockopt(temp->sock.socket, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof(n)); /* Enable broadcast on socket */ int broadcast = 1; setsockopt(temp->sock.socket, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcast, sizeof(broadcast)); /* iOS UDP sockets are weird and apparently can SIGPIPE */ if (!set_socket_nosigpipe(temp->sock)) { kill_networking(temp); if (error) { *error = 1; } return nullptr; } /* Set socket nonblocking. */ if (!set_socket_nonblock(temp->sock)) { kill_networking(temp); if (error) { *error = 1; } return nullptr; } /* Bind our socket to port PORT and the given IP address (usually 0.0.0.0 or ::) */ uint16_t *portptr = nullptr; struct sockaddr_storage addr; size_t addrsize; memset(&addr, 0, sizeof(struct sockaddr_storage)); if (net_family_is_ipv4(temp->family)) { struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; addrsize = sizeof(struct sockaddr_in); addr4->sin_family = AF_INET; addr4->sin_port = 0; fill_addr4(ip.ip.v4, &addr4->sin_addr); portptr = &addr4->sin_port; } else if (net_family_is_ipv6(temp->family)) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; addrsize = sizeof(struct sockaddr_in6); addr6->sin6_family = AF_INET6; addr6->sin6_port = 0; fill_addr6(ip.ip.v6, &addr6->sin6_addr); addr6->sin6_flowinfo = 0; addr6->sin6_scope_id = 0; portptr = &addr6->sin6_port; } else { free(temp); return nullptr; } if (net_family_is_ipv6(ip.family)) { const int is_dualstack = set_socket_dualstack(temp->sock); LOGGER_DEBUG(log, "Dual-stack socket: %s", is_dualstack ? "enabled" : "Failed to enable, won't be able to receive from/send to IPv4 addresses"); /* multicast local nodes */ struct ipv6_mreq mreq; memset(&mreq, 0, sizeof(mreq)); mreq.ipv6mr_multiaddr.s6_addr[ 0] = 0xFF; mreq.ipv6mr_multiaddr.s6_addr[ 1] = 0x02; mreq.ipv6mr_multiaddr.s6_addr[15] = 0x01; mreq.ipv6mr_interface = 0; const int res = setsockopt(temp->sock.socket, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (const char *)&mreq, sizeof(mreq)); int neterror = net_error(); const char *strerror = net_new_strerror(neterror); if (res < 0) { LOGGER_DEBUG(log, "Failed to activate local multicast membership. (%d, %s)", neterror, strerror); } else { LOGGER_DEBUG(log, "Local multicast group FF02::1 joined successfully. (%d, %s)", neterror, strerror); } net_kill_strerror(strerror); } /* A hanging program or a different user might block the standard port. * As long as it isn't a parameter coming from the commandline, * try a few ports after it, to see if we can find a "free" one. * * If we go on without binding, the first sendto() automatically binds to * a free port chosen by the system (i.e. anything from 1024 to 65535). * * Returning NULL after bind fails has both advantages and disadvantages: * advantage: * we can rely on getting the port in the range 33445..33450, which * enables us to tell joe user to open their firewall to a small range * * disadvantage: * some clients might not test return of tox_new(), blindly assuming that * it worked ok (which it did previously without a successful bind) */ uint16_t port_to_try = port_from; *portptr = net_htons(port_to_try); int tries; for (tries = port_from; tries <= port_to; ++tries) { int res = bind(temp->sock.socket, (struct sockaddr *)&addr, addrsize); if (!res) { temp->port = *portptr; char ip_str[IP_NTOA_LEN]; LOGGER_DEBUG(log, "Bound successfully to %s:%u", ip_ntoa(&ip, ip_str, sizeof(ip_str)), net_ntohs(temp->port)); /* errno isn't reset on success, only set on failure, the failed * binds with parallel clients yield a -EPERM to the outside if * errno isn't cleared here */ if (tries > 0) { errno = 0; } if (error) { *error = 0; } return temp; } ++port_to_try; if (port_to_try > port_to) { port_to_try = port_from; } *portptr = net_htons(port_to_try); } char ip_str[IP_NTOA_LEN]; int neterror = net_error(); const char *strerror = net_new_strerror(neterror); LOGGER_ERROR(log, "Failed to bind socket: %d, %s IP: %s port_from: %u port_to: %u", neterror, strerror, ip_ntoa(&ip, ip_str, sizeof(ip_str)), port_from, port_to); net_kill_strerror(strerror); kill_networking(temp); if (error) { *error = 1; } return nullptr; } Networking_Core *new_networking_no_udp(const Logger *log) { if (networking_at_startup() != 0) { return nullptr; } /* this is the easiest way to completely disable UDP without changing too much code. */ Networking_Core *net = (Networking_Core *)calloc(1, sizeof(Networking_Core)); if (net == nullptr) { return nullptr; } net->log = log; return net; } /* Function to cleanup networking stuff. */ void kill_networking(Networking_Core *net) { if (!net) { return; } if (!net_family_is_unspec(net->family)) { /* Socket is initialized, so we close it. */ kill_sock(net->sock); } free(net); } bool ip_equal(const IP *a, const IP *b) { if (!a || !b) { return false; } /* same family */ if (a->family.value == b->family.value) { if (net_family_is_ipv4(a->family) || net_family_is_tcp_ipv4(a->family)) { struct in_addr addr_a; struct in_addr addr_b; fill_addr4(a->ip.v4, &addr_a); fill_addr4(b->ip.v4, &addr_b); return addr_a.s_addr == addr_b.s_addr; } if (net_family_is_ipv6(a->family) || net_family_is_tcp_ipv6(a->family)) { return a->ip.v6.uint64[0] == b->ip.v6.uint64[0] && a->ip.v6.uint64[1] == b->ip.v6.uint64[1]; } return false; } /* different family: check on the IPv6 one if it is the IPv4 one embedded */ if (net_family_is_ipv4(a->family) && net_family_is_ipv6(b->family)) { if (ipv6_ipv4_in_v6(b->ip.v6)) { struct in_addr addr_a; fill_addr4(a->ip.v4, &addr_a); return addr_a.s_addr == b->ip.v6.uint32[3]; } } else if (net_family_is_ipv6(a->family) && net_family_is_ipv4(b->family)) { if (ipv6_ipv4_in_v6(a->ip.v6)) { struct in_addr addr_b; fill_addr4(b->ip.v4, &addr_b); return a->ip.v6.uint32[3] == addr_b.s_addr; } } return false; } bool ipport_equal(const IP_Port *a, const IP_Port *b) { if (!a || !b) { return false; } if (!a->port || (a->port != b->port)) { return false; } return ip_equal(&a->ip, &b->ip); } /* nulls out ip */ void ip_reset(IP *ip) { if (!ip) { return; } memset(ip, 0, sizeof(IP)); } /* nulls out ip, sets family according to flag */ void ip_init(IP *ip, bool ipv6enabled) { if (!ip) { return; } memset(ip, 0, sizeof(IP)); ip->family = ipv6enabled ? net_family_ipv6 : net_family_ipv4; } /* checks if ip is valid */ bool ip_isset(const IP *ip) { if (!ip) { return false; } return !net_family_is_unspec(ip->family); } /* checks if ip is valid */ bool ipport_isset(const IP_Port *ipport) { if (!ipport) { return false; } if (!ipport->port) { return false; } return ip_isset(&ipport->ip); } /* copies an ip structure (careful about direction!) */ void ip_copy(IP *target, const IP *source) { if (!source || !target) { return; } *target = *source; } /* copies an ip_port structure (careful about direction!) */ void ipport_copy(IP_Port *target, const IP_Port *source) { if (!source || !target) { return; } *target = *source; } /* ip_ntoa * converts ip into a string * ip_str must be of length at least IP_NTOA_LEN * * IPv6 addresses are enclosed into square brackets, i.e. "[IPv6]" * writes error message into the buffer on error * * returns ip_str */ const char *ip_ntoa(const IP *ip, char *ip_str, size_t length) { if (length < IP_NTOA_LEN) { snprintf(ip_str, length, "Bad buf length"); return ip_str; } if (ip) { if (net_family_is_ipv4(ip->family)) { /* returns standard quad-dotted notation */ struct in_addr addr; fill_addr4(ip->ip.v4, &addr); ip_str[0] = 0; assert(make_family(ip->family) == AF_INET); inet_ntop4(&addr, ip_str, length); } else if (net_family_is_ipv6(ip->family)) { /* returns hex-groups enclosed into square brackets */ struct in6_addr addr; fill_addr6(ip->ip.v6, &addr); ip_str[0] = '['; assert(make_family(ip->family) == AF_INET6); inet_ntop6(&addr, &ip_str[1], length - 3); const size_t len = strlen(ip_str); ip_str[len] = ']'; ip_str[len + 1] = 0; } else { snprintf(ip_str, length, "(IP invalid, family %u)", ip->family.value); } } else { snprintf(ip_str, length, "(IP invalid: NULL)"); } /* brute force protection against lacking termination */ ip_str[length - 1] = 0; return ip_str; } bool ip_parse_addr(const IP *ip, char *address, size_t length) { if (!address || !ip) { return false; } if (net_family_is_ipv4(ip->family)) { const struct in_addr *addr = (const struct in_addr *)&ip->ip.v4; assert(make_family(ip->family) == AF_INET); return inet_ntop4(addr, address, length) != nullptr; } if (net_family_is_ipv6(ip->family)) { const struct in6_addr *addr = (const struct in6_addr *)&ip->ip.v6; assert(make_family(ip->family) == AF_INET6); return inet_ntop6(addr, address, length) != nullptr; } return false; } bool addr_parse_ip(const char *address, IP *to) { if (!address || !to) { return false; } struct in_addr addr4; if (inet_pton4(address, &addr4) == 1) { to->family = net_family_ipv4; get_ip4(&to->ip.v4, &addr4); return true; } struct in6_addr addr6; if (inet_pton6(address, &addr6) == 1) { to->family = net_family_ipv6; get_ip6(&to->ip.v6, &addr6); return true; } return false; } int addr_resolve(const char *address, IP *to, IP *extra) { if (!address || !to) { return 0; } Family tox_family = to->family; int family = make_family(tox_family); struct addrinfo *server = nullptr; struct addrinfo *walker = nullptr; struct addrinfo hints; int rc; int result = 0; int done = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_DGRAM; // type of socket Tox uses. if (networking_at_startup() != 0) { return 0; } rc = getaddrinfo(address, nullptr, &hints, &server); // Lookup failed. if (rc != 0) { return 0; } IP ip4; ip_init(&ip4, 0); // ipv6enabled = 0 IP ip6; ip_init(&ip6, 1); // ipv6enabled = 1 for (walker = server; (walker != nullptr) && !done; walker = walker->ai_next) { switch (walker->ai_family) { case AF_INET: if (walker->ai_family == family) { /* AF_INET requested, done */ struct sockaddr_in *addr = (struct sockaddr_in *)(void *)walker->ai_addr; get_ip4(&to->ip.v4, &addr->sin_addr); result = TOX_ADDR_RESOLVE_INET; done = 1; } else if (!(result & TOX_ADDR_RESOLVE_INET)) { /* AF_UNSPEC requested, store away */ struct sockaddr_in *addr = (struct sockaddr_in *)(void *)walker->ai_addr; get_ip4(&ip4.ip.v4, &addr->sin_addr); result |= TOX_ADDR_RESOLVE_INET; } break; /* switch */ case AF_INET6: if (walker->ai_family == family) { /* AF_INET6 requested, done */ if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *)(void *)walker->ai_addr; get_ip6(&to->ip.v6, &addr->sin6_addr); result = TOX_ADDR_RESOLVE_INET6; done = 1; } } else if (!(result & TOX_ADDR_RESOLVE_INET6)) { /* AF_UNSPEC requested, store away */ if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *)(void *)walker->ai_addr; get_ip6(&ip6.ip.v6, &addr->sin6_addr); result |= TOX_ADDR_RESOLVE_INET6; } } break; /* switch */ } } if (family == AF_UNSPEC) { if (result & TOX_ADDR_RESOLVE_INET6) { ip_copy(to, &ip6); if ((result & TOX_ADDR_RESOLVE_INET) && (extra != nullptr)) { ip_copy(extra, &ip4); } } else if (result & TOX_ADDR_RESOLVE_INET) { ip_copy(to, &ip4); } else { result = 0; } } freeaddrinfo(server); return result; } bool addr_resolve_or_parse_ip(const char *address, IP *to, IP *extra) { if (!addr_resolve(address, to, extra)) { if (!addr_parse_ip(address, to)) { return false; } } return true; } int net_connect(Socket sock, IP_Port ip_port) { struct sockaddr_storage addr = {0}; size_t addrsize; if (net_family_is_ipv4(ip_port.ip.family)) { struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; addrsize = sizeof(struct sockaddr_in); addr4->sin_family = AF_INET; fill_addr4(ip_port.ip.ip.v4, &addr4->sin_addr); addr4->sin_port = ip_port.port; } else if (net_family_is_ipv6(ip_port.ip.family)) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; addrsize = sizeof(struct sockaddr_in6); addr6->sin6_family = AF_INET6; fill_addr6(ip_port.ip.ip.v6, &addr6->sin6_addr); addr6->sin6_port = ip_port.port; } else { return 0; } return connect(sock.socket, (struct sockaddr *)&addr, addrsize); } int32_t net_getipport(const char *node, IP_Port **res, int tox_type) { struct addrinfo *infos; int ret = getaddrinfo(node, nullptr, nullptr, &infos); *res = nullptr; if (ret != 0) { return -1; } // Used to avoid malloc parameter overflow const size_t max_count = min_u64(SIZE_MAX, INT32_MAX) / sizeof(IP_Port); int type = make_socktype(tox_type); struct addrinfo *cur; size_t count = 0; for (cur = infos; count < max_count && cur != nullptr; cur = cur->ai_next) { if (cur->ai_socktype && type > 0 && cur->ai_socktype != type) { continue; } if (cur->ai_family != AF_INET && cur->ai_family != AF_INET6) { continue; } ++count; } assert(count <= max_count); if (count == 0) { freeaddrinfo(infos); return 0; } *res = (IP_Port *)malloc(sizeof(IP_Port) * count); if (*res == nullptr) { freeaddrinfo(infos); return -1; } IP_Port *ip_port = *res; for (cur = infos; cur != nullptr; cur = cur->ai_next) { if (cur->ai_socktype && type > 0 && cur->ai_socktype != type) { continue; } if (cur->ai_family == AF_INET) { struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cur->ai_addr; memcpy(&ip_port->ip.ip.v4, &addr->sin_addr, sizeof(IP4)); } else if (cur->ai_family == AF_INET6) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *)(void *)cur->ai_addr; memcpy(&ip_port->ip.ip.v6, &addr->sin6_addr, sizeof(IP6)); } else { continue; } const Family *const family = make_tox_family(cur->ai_family); assert(family != nullptr); if (family == nullptr) { freeaddrinfo(infos); return -1; } ip_port->ip.family = *family; ++ip_port; } freeaddrinfo(infos); return count; } void net_freeipport(IP_Port *ip_ports) { free(ip_ports); } bool bind_to_port(Socket sock, Family family, uint16_t port) { struct sockaddr_storage addr = {0}; size_t addrsize; if (net_family_is_ipv4(family)) { struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; addrsize = sizeof(struct sockaddr_in); addr4->sin_family = AF_INET; addr4->sin_port = net_htons(port); } else if (net_family_is_ipv6(family)) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; addrsize = sizeof(struct sockaddr_in6); addr6->sin6_family = AF_INET6; addr6->sin6_port = net_htons(port); } else { return false; } return bind(sock.socket, (struct sockaddr *)&addr, addrsize) == 0; } Socket net_socket(Family domain, int type, int protocol) { const int platform_domain = make_family(domain); const int platform_type = make_socktype(type); const int platform_prot = make_proto(protocol); const Socket sock = {(int)socket(platform_domain, platform_type, platform_prot)}; return sock; } int net_send(Socket sock, const void *buf, size_t len) { return send(sock.socket, (const char *)buf, len, MSG_NOSIGNAL); } int net_recv(Socket sock, void *buf, size_t len) { return recv(sock.socket, (char *)buf, len, MSG_NOSIGNAL); } int net_listen(Socket sock, int backlog) { return listen(sock.socket, backlog); } Socket net_accept(Socket sock) { const Socket newsock = {accept(sock.socket, nullptr, nullptr)}; return newsock; } size_t net_socket_data_recv_buffer(Socket sock) { #ifdef OS_WIN32 unsigned long count = 0; ioctlsocket(sock.socket, FIONREAD, &count); #else int count = 0; ioctl(sock.socket, FIONREAD, &count); #endif return count; } uint32_t net_htonl(uint32_t hostlong) { return htonl(hostlong); } uint16_t net_htons(uint16_t hostshort) { return htons(hostshort); } uint32_t net_ntohl(uint32_t hostlong) { return ntohl(hostlong); } uint16_t net_ntohs(uint16_t hostshort) { return ntohs(hostshort); } size_t net_pack_u16(uint8_t *bytes, uint16_t v) { bytes[0] = (v >> 8) & 0xff; bytes[1] = v & 0xff; return sizeof(v); } size_t net_pack_u32(uint8_t *bytes, uint32_t v) { uint8_t *p = bytes; p += net_pack_u16(p, (v >> 16) & 0xffff); p += net_pack_u16(p, v & 0xffff); return p - bytes; } size_t net_pack_u64(uint8_t *bytes, uint64_t v) { uint8_t *p = bytes; p += net_pack_u32(p, (v >> 32) & 0xffffffff); p += net_pack_u32(p, v & 0xffffffff); return p - bytes; } size_t net_unpack_u16(const uint8_t *bytes, uint16_t *v) { uint8_t hi = bytes[0]; uint8_t lo = bytes[1]; *v = ((uint16_t)hi << 8) | lo; return sizeof(*v); } size_t net_unpack_u32(const uint8_t *bytes, uint32_t *v) { const uint8_t *p = bytes; uint16_t hi; uint16_t lo; p += net_unpack_u16(p, &hi); p += net_unpack_u16(p, &lo); *v = ((uint32_t)hi << 16) | lo; return p - bytes; } size_t net_unpack_u64(const uint8_t *bytes, uint64_t *v) { const uint8_t *p = bytes; uint32_t hi; uint32_t lo; p += net_unpack_u32(p, &hi); p += net_unpack_u32(p, &lo); *v = ((uint64_t)hi << 32) | lo; return p - bytes; } bool ipv6_ipv4_in_v6(IP6 a) { return a.uint64[0] == 0 && a.uint32[2] == net_htonl(0xffff); } int net_error(void) { #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) return WSAGetLastError(); #else return errno; #endif } const char *net_new_strerror(int error) { #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) char *str = nullptr; // Windows API is weird. The 5th function arg is of char* type, but we // have to pass char** so that it could assign new memory block to our // pointer, so we have to cast our char** to char* for the compilation // not to fail (otherwise it would fail to find a variant of this function // accepting char** as the 5th arg) and Windows inside casts it back // to char** to do the assignment. So no, this cast you see here, although // it looks weird, is not a mistake. FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error, 0, (char *)&str, 0, nullptr); return str; #else return strerror(error); #endif } void net_kill_strerror(const char *strerror) { #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) LocalFree((char *)strerror); #endif } c-toxcore-0.2.13/toxcore/network.h000066400000000000000000000323031415350724400170330ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Datatypes, functions and includes for the core networking. */ #ifndef C_TOXCORE_TOXCORE_NETWORK_H #define C_TOXCORE_TOXCORE_NETWORK_H #include "logger.h" #include // bool #include // size_t #include // uint*_t #ifdef __cplusplus extern "C" { #endif typedef struct Family { uint8_t value; } Family; bool net_family_is_unspec(Family family); bool net_family_is_ipv4(Family family); bool net_family_is_ipv6(Family family); bool net_family_is_tcp_family(Family family); bool net_family_is_tcp_onion(Family family); bool net_family_is_tcp_ipv4(Family family); bool net_family_is_tcp_ipv6(Family family); bool net_family_is_tox_tcp_ipv4(Family family); bool net_family_is_tox_tcp_ipv6(Family family); extern const Family net_family_unspec; extern const Family net_family_ipv4; extern const Family net_family_ipv6; extern const Family net_family_tcp_family; extern const Family net_family_tcp_onion; extern const Family net_family_tcp_ipv4; extern const Family net_family_tcp_ipv6; extern const Family net_family_tox_tcp_ipv4; extern const Family net_family_tox_tcp_ipv6; typedef struct Socket { int socket; } Socket; Socket net_socket(Family domain, int type, int protocol); /** * Check if socket is valid. * * @return true if valid, false otherwise. */ bool sock_valid(Socket sock); extern const Socket net_invalid_socket; /** * Calls send(sockfd, buf, len, MSG_NOSIGNAL). */ int net_send(Socket sock, const void *buf, size_t len); /** * Calls recv(sockfd, buf, len, MSG_NOSIGNAL). */ int net_recv(Socket sock, void *buf, size_t len); /** * Calls listen(sockfd, backlog). */ int net_listen(Socket sock, int backlog); /** * Calls accept(sockfd, nullptr, nullptr). */ Socket net_accept(Socket sock); /** * return the amount of data in the tcp recv buffer. * return 0 on failure. */ size_t net_socket_data_recv_buffer(Socket sock); #define MAX_UDP_PACKET_SIZE 2048 typedef enum Net_Packet_Type { NET_PACKET_PING_REQUEST = 0x00, /* Ping request packet ID. */ NET_PACKET_PING_RESPONSE = 0x01, /* Ping response packet ID. */ NET_PACKET_GET_NODES = 0x02, /* Get nodes request packet ID. */ NET_PACKET_SEND_NODES_IPV6 = 0x04, /* Send nodes response packet ID for other addresses. */ NET_PACKET_COOKIE_REQUEST = 0x18, /* Cookie request packet */ NET_PACKET_COOKIE_RESPONSE = 0x19, /* Cookie response packet */ NET_PACKET_CRYPTO_HS = 0x1a, /* Crypto handshake packet */ NET_PACKET_CRYPTO_DATA = 0x1b, /* Crypto data packet */ NET_PACKET_CRYPTO = 0x20, /* Encrypted data packet ID. */ NET_PACKET_LAN_DISCOVERY = 0x21, /* LAN discovery packet ID. */ /* See: `docs/Prevent_Tracking.txt` and `onion.{c,h}` */ NET_PACKET_ONION_SEND_INITIAL = 0x80, NET_PACKET_ONION_SEND_1 = 0x81, NET_PACKET_ONION_SEND_2 = 0x82, NET_PACKET_ANNOUNCE_REQUEST = 0x83, NET_PACKET_ANNOUNCE_RESPONSE = 0x84, NET_PACKET_ONION_DATA_REQUEST = 0x85, NET_PACKET_ONION_DATA_RESPONSE = 0x86, NET_PACKET_ONION_RECV_3 = 0x8c, NET_PACKET_ONION_RECV_2 = 0x8d, NET_PACKET_ONION_RECV_1 = 0x8e, BOOTSTRAP_INFO_PACKET_ID = 0xf0, /* Only used for bootstrap nodes */ NET_PACKET_MAX = 0xff, /* This type must remain within a single uint8. */ } Net_Packet_Type; #define TOX_PORTRANGE_FROM 33445 #define TOX_PORTRANGE_TO 33545 #define TOX_PORT_DEFAULT TOX_PORTRANGE_FROM /* Redefinitions of variables for safe transfer over wire. */ #define TOX_AF_UNSPEC 0 #define TOX_AF_INET 2 #define TOX_AF_INET6 10 #define TOX_TCP_INET 130 #define TOX_TCP_INET6 138 #define TOX_SOCK_STREAM 1 #define TOX_SOCK_DGRAM 2 #define TOX_PROTO_TCP 1 #define TOX_PROTO_UDP 2 /* TCP related */ #define TCP_ONION_FAMILY (TOX_AF_INET6 + 1) #define TCP_INET (TOX_AF_INET6 + 2) #define TCP_INET6 (TOX_AF_INET6 + 3) #define TCP_FAMILY (TOX_AF_INET6 + 4) typedef union IP4 { uint32_t uint32; uint16_t uint16[2]; uint8_t uint8[4]; } IP4; IP4 get_ip4_loopback(void); extern const IP4 ip4_broadcast; typedef union IP6 { uint8_t uint8[16]; uint16_t uint16[8]; uint32_t uint32[4]; uint64_t uint64[2]; } IP6; IP6 get_ip6_loopback(void); extern const IP6 ip6_broadcast; typedef union IP_Union { IP4 v4; IP6 v6; } IP_Union; #define IP_DEFINED typedef struct IP { Family family; IP_Union ip; } IP; #define IP_PORT_DEFINED typedef struct IP_Port { IP ip; uint16_t port; } IP_Port; /* Convert values between host and network byte order. */ uint32_t net_htonl(uint32_t hostlong); uint16_t net_htons(uint16_t hostshort); uint32_t net_ntohl(uint32_t hostlong); uint16_t net_ntohs(uint16_t hostshort); size_t net_pack_u16(uint8_t *bytes, uint16_t v); size_t net_pack_u32(uint8_t *bytes, uint32_t v); size_t net_pack_u64(uint8_t *bytes, uint64_t v); size_t net_unpack_u16(const uint8_t *bytes, uint16_t *v); size_t net_unpack_u32(const uint8_t *bytes, uint32_t *v); size_t net_unpack_u64(const uint8_t *bytes, uint64_t *v); /* Does the IP6 struct a contain an IPv4 address in an IPv6 one? */ bool ipv6_ipv4_in_v6(IP6 a); #define SIZE_IP4 4 #define SIZE_IP6 16 #define SIZE_IP (1 + SIZE_IP6) #define SIZE_PORT 2 #define SIZE_IPPORT (SIZE_IP + SIZE_PORT) #define TOX_ENABLE_IPV6_DEFAULT true /* addr_resolve return values */ #define TOX_ADDR_RESOLVE_INET 1 #define TOX_ADDR_RESOLVE_INET6 2 #define TOX_INET6_ADDRSTRLEN 66 #define TOX_INET_ADDRSTRLEN 22 /* ip_ntoa * converts ip into a string * ip_str must be of length at least IP_NTOA_LEN * * IPv6 addresses are enclosed into square brackets, i.e. "[IPv6]" * writes error message into the buffer on error * * returns ip_str */ /* this would be TOX_INET6_ADDRSTRLEN, but it might be too short for the error message */ #define IP_NTOA_LEN 96 // TODO(irungentoo): magic number. Why not INET6_ADDRSTRLEN ? const char *ip_ntoa(const IP *ip, char *ip_str, size_t length); /** * Parses IP structure into an address string. * * @param ip IP of TOX_AF_INET or TOX_AF_INET6 families. * @param length length of the address buffer. * Must be at least TOX_INET_ADDRSTRLEN for TOX_AF_INET * and TOX_INET6_ADDRSTRLEN for TOX_AF_INET6 * * @param address dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6). * * @return true on success, false on failure. */ bool ip_parse_addr(const IP *ip, char *address, size_t length); /** * Directly parses the input into an IP structure. * * Tries IPv4 first, then IPv6. * * @param address dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6). * @param to family and the value is set on success. * * @return true on success, false on failure. */ bool addr_parse_ip(const char *address, IP *to); /** * Compares two IPAny structures. * * Unset means unequal. * * @return false when not equal or when uninitialized. */ bool ip_equal(const IP *a, const IP *b); /** * Compares two IPAny_Port structures. * * Unset means unequal. * * @return false when not equal or when uninitialized. */ bool ipport_equal(const IP_Port *a, const IP_Port *b); /* nulls out ip */ void ip_reset(IP *ip); /* nulls out ip, sets family according to flag */ void ip_init(IP *ip, bool ipv6enabled); /* checks if ip is valid */ bool ip_isset(const IP *ip); /* checks if ip is valid */ bool ipport_isset(const IP_Port *ipport); /* copies an ip structure */ void ip_copy(IP *target, const IP *source); /* copies an ip_port structure */ void ipport_copy(IP_Port *target, const IP_Port *source); /** * Uses getaddrinfo to resolve an address into an IP address. * * Uses the first IPv4/IPv6 addresses returned by getaddrinfo. * * @param address a hostname (or something parseable to an IP address) * @param to to.family MUST be initialized, either set to a specific IP version * (TOX_AF_INET/TOX_AF_INET6) or to the unspecified TOX_AF_UNSPEC (= 0), if both * IP versions are acceptable * @param extra can be NULL and is only set in special circumstances, see returns * * returns in `*to` a valid IPAny (v4/v6), * prefers v6 if `ip.family` was TOX_AF_UNSPEC and both available * returns in `*extra` an IPv4 address, if family was TOX_AF_UNSPEC and `*to` is TOX_AF_INET6 * * @return 0 on failure, `TOX_ADDR_RESOLVE_*` on success. */ int addr_resolve(const char *address, IP *to, IP *extra); /** * Resolves string into an IP address * * @param address a hostname (or something parseable to an IP address) * @param to to.family MUST be initialized, either set to a specific IP version * (TOX_AF_INET/TOX_AF_INET6) or to the unspecified TOX_AF_UNSPEC (= 0), if both * IP versions are acceptable * @param extra can be NULL and is only set in special circumstances, see returns * * returns in `*to` a matching address (IPv6 or IPv4) * returns in `*extra`, if not NULL, an IPv4 address, if `to->family` was TOX_AF_UNSPEC * * @return true on success, false on failure */ bool addr_resolve_or_parse_ip(const char *address, IP *to, IP *extra); /* Function to receive data, ip and port of sender is put into ip_port. * Packet data is put into data. * Packet length is put into length. */ typedef int packet_handler_cb(void *object, IP_Port ip_port, const uint8_t *data, uint16_t len, void *userdata); typedef struct Networking_Core Networking_Core; Family net_family(const Networking_Core *net); uint16_t net_port(const Networking_Core *net); /* Run this before creating sockets. * * return 0 on success * return -1 on failure */ int networking_at_startup(void); /* Close the socket. */ void kill_sock(Socket sock); /** * Set socket as nonblocking * * @return true on success, false on failure. */ bool set_socket_nonblock(Socket sock); /** * Set socket to not emit SIGPIPE * * @return true on success, false on failure. */ bool set_socket_nosigpipe(Socket sock); /** * Enable SO_REUSEADDR on socket. * * @return true on success, false on failure. */ bool set_socket_reuseaddr(Socket sock); /** * Set socket to dual (IPv4 + IPv6 socket) * * @return true on success, false on failure. */ bool set_socket_dualstack(Socket sock); /* Basic network functions: */ /* Function to send packet(data) of length length to ip_port. */ int sendpacket(Networking_Core *net, IP_Port ip_port, const uint8_t *data, uint16_t length); /* Function to call when packet beginning with byte is received. */ void networking_registerhandler(Networking_Core *net, uint8_t byte, packet_handler_cb *cb, void *object); /* Call this several times a second. */ void networking_poll(Networking_Core *net, void *userdata); /* Connect a socket to the address specified by the ip_port. */ int net_connect(Socket sock, IP_Port ip_port); /* High-level getaddrinfo implementation. * Given node, which identifies an Internet host, net_getipport() fills an array * with one or more IP_Port structures, each of which contains an Internet * address that can be specified by calling net_connect(), the port is ignored. * * Skip all addresses with socktype != type (use type = -1 to get all addresses) * To correctly deallocate array memory use net_freeipport() * * return number of elements in res array * and -1 on error. */ int32_t net_getipport(const char *node, IP_Port **res, int tox_type); /* Deallocates memory allocated by net_getipport */ void net_freeipport(IP_Port *ip_ports); /** * @return true on success, false on failure. */ bool bind_to_port(Socket sock, Family family, uint16_t port); /* Get the last networking error code. * * Similar to Unix's errno, but cross-platform, as not all platforms use errno * to indicate networking errors. * * Note that different platforms may return different codes for the same error, * so you likely shouldn't be checking the value returned by this function * unless you know what you are doing, you likely just want to use it in * combination with net_new_strerror() to print the error. * * return platform-dependent network error code, if any. */ int net_error(void); /* Get a text explanation for the error code from net_error(). * * return NULL on failure. * return pointer to a NULL-terminated string describing the error code on * success. The returned string must be freed using net_kill_strerror(). */ const char *net_new_strerror(int error); /* Frees the string returned by net_new_strerror(). * It's valid to pass NULL as the argument, the function does nothing in this * case. */ void net_kill_strerror(const char *strerror); /* Initialize networking. * bind to ip and port. * ip must be in network order EX: 127.0.0.1 = (7F000001). * port is in host byte order (this means don't worry about it). * * return Networking_Core object if no problems * return NULL if there are problems. * * If error is non NULL it is set to 0 if no issues, 1 if socket related error, 2 if other. */ Networking_Core *new_networking(const Logger *log, IP ip, uint16_t port); Networking_Core *new_networking_ex(const Logger *log, IP ip, uint16_t port_from, uint16_t port_to, unsigned int *error); Networking_Core *new_networking_no_udp(const Logger *log); /* Function to cleanup networking stuff (doesn't do much right now). */ void kill_networking(Networking_Core *net); #ifdef __cplusplus } // extern "C" #endif #endif c-toxcore-0.2.13/toxcore/onion.c000066400000000000000000000525441415350724400164700ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Implementation of the onion part of docs/Prevent_Tracking.txt */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "onion.h" #include #include #include "mono_time.h" #include "util.h" #define RETURN_1 ONION_RETURN_1 #define RETURN_2 ONION_RETURN_2 #define RETURN_3 ONION_RETURN_3 #define SEND_BASE ONION_SEND_BASE #define SEND_3 ONION_SEND_3 #define SEND_2 ONION_SEND_2 #define SEND_1 ONION_SEND_1 /* Change symmetric keys every 2 hours to make paths expire eventually. */ #define KEY_REFRESH_INTERVAL (2 * 60 * 60) static void change_symmetric_key(Onion *onion) { if (mono_time_is_timeout(onion->mono_time, onion->timestamp, KEY_REFRESH_INTERVAL)) { new_symmetric_key(onion->secret_symmetric_key); onion->timestamp = mono_time_get(onion->mono_time); } } /* packing and unpacking functions */ static void ip_pack(uint8_t *data, IP source) { data[0] = source.family.value; if (net_family_is_ipv4(source.family) || net_family_is_tox_tcp_ipv4(source.family)) { memset(data + 1, 0, SIZE_IP6); memcpy(data + 1, source.ip.v4.uint8, SIZE_IP4); } else { memcpy(data + 1, source.ip.v6.uint8, SIZE_IP6); } } /* return 0 on success, -1 on failure. */ static int ip_unpack(IP *target, const uint8_t *data, unsigned int data_size, bool disable_family_check) { if (data_size < (1 + SIZE_IP6)) { return -1; } // TODO(iphydf): Validate input. target->family.value = data[0]; if (net_family_is_ipv4(target->family) || net_family_is_tox_tcp_ipv4(target->family)) { memcpy(target->ip.v4.uint8, data + 1, SIZE_IP4); } else { memcpy(target->ip.v6.uint8, data + 1, SIZE_IP6); } bool valid = disable_family_check || net_family_is_ipv4(target->family) || net_family_is_ipv6(target->family); return valid ? 0 : -1; } static void ipport_pack(uint8_t *data, const IP_Port *source) { ip_pack(data, source->ip); memcpy(data + SIZE_IP, &source->port, SIZE_PORT); } /* return 0 on success, -1 on failure. */ static int ipport_unpack(IP_Port *target, const uint8_t *data, unsigned int data_size, bool disable_family_check) { if (data_size < (SIZE_IP + SIZE_PORT)) { return -1; } if (ip_unpack(&target->ip, data, data_size, disable_family_check) == -1) { return -1; } memcpy(&target->port, data + SIZE_IP, SIZE_PORT); return 0; } /* Create a new onion path. * * Create a new onion path out of nodes (nodes is a list of ONION_PATH_LENGTH nodes) * * new_path must be an empty memory location of at least Onion_Path size. * * return -1 on failure. * return 0 on success. */ int create_onion_path(const DHT *dht, Onion_Path *new_path, const Node_format *nodes) { if (!new_path || !nodes) { return -1; } encrypt_precompute(nodes[0].public_key, dht_get_self_secret_key(dht), new_path->shared_key1); memcpy(new_path->public_key1, dht_get_self_public_key(dht), CRYPTO_PUBLIC_KEY_SIZE); uint8_t random_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t random_secret_key[CRYPTO_SECRET_KEY_SIZE]; crypto_new_keypair(random_public_key, random_secret_key); encrypt_precompute(nodes[1].public_key, random_secret_key, new_path->shared_key2); memcpy(new_path->public_key2, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); crypto_new_keypair(random_public_key, random_secret_key); encrypt_precompute(nodes[2].public_key, random_secret_key, new_path->shared_key3); memcpy(new_path->public_key3, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); new_path->ip_port1 = nodes[0].ip_port; new_path->ip_port2 = nodes[1].ip_port; new_path->ip_port3 = nodes[2].ip_port; memcpy(new_path->node_public_key1, nodes[0].public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(new_path->node_public_key2, nodes[1].public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(new_path->node_public_key3, nodes[2].public_key, CRYPTO_PUBLIC_KEY_SIZE); return 0; } /* Dump nodes in onion path to nodes of length num_nodes. * * return -1 on failure. * return 0 on success. */ int onion_path_to_nodes(Node_format *nodes, unsigned int num_nodes, const Onion_Path *path) { if (num_nodes < ONION_PATH_LENGTH) { return -1; } nodes[0].ip_port = path->ip_port1; nodes[1].ip_port = path->ip_port2; nodes[2].ip_port = path->ip_port3; memcpy(nodes[0].public_key, path->node_public_key1, CRYPTO_PUBLIC_KEY_SIZE); memcpy(nodes[1].public_key, path->node_public_key2, CRYPTO_PUBLIC_KEY_SIZE); memcpy(nodes[2].public_key, path->node_public_key3, CRYPTO_PUBLIC_KEY_SIZE); return 0; } /* Create a onion packet. * * Use Onion_Path path to create packet for data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * packet should be at least ONION_MAX_PACKET_SIZE big. * * return -1 on failure. * return length of created packet on success. */ int create_onion_packet(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) { if (1 + length + SEND_1 > max_packet_length || length == 0) { return -1; } VLA(uint8_t, step1, SIZE_IPPORT + length); ipport_pack(step1, &dest); memcpy(step1 + SIZE_IPPORT, data, length); uint8_t nonce[CRYPTO_NONCE_SIZE]; random_nonce(nonce); VLA(uint8_t, step2, SIZE_IPPORT + SEND_BASE + length); ipport_pack(step2, &path->ip_port3); memcpy(step2 + SIZE_IPPORT, path->public_key3, CRYPTO_PUBLIC_KEY_SIZE); int len = encrypt_data_symmetric(path->shared_key3, nonce, step1, SIZEOF_VLA(step1), step2 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); if (len != SIZE_IPPORT + length + CRYPTO_MAC_SIZE) { return -1; } VLA(uint8_t, step3, SIZE_IPPORT + SEND_BASE * 2 + length); ipport_pack(step3, &path->ip_port2); memcpy(step3 + SIZE_IPPORT, path->public_key2, CRYPTO_PUBLIC_KEY_SIZE); len = encrypt_data_symmetric(path->shared_key2, nonce, step2, SIZEOF_VLA(step2), step3 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); if (len != SIZE_IPPORT + SEND_BASE + length + CRYPTO_MAC_SIZE) { return -1; } packet[0] = NET_PACKET_ONION_SEND_INITIAL; memcpy(packet + 1, nonce, CRYPTO_NONCE_SIZE); memcpy(packet + 1 + CRYPTO_NONCE_SIZE, path->public_key1, CRYPTO_PUBLIC_KEY_SIZE); len = encrypt_data_symmetric(path->shared_key1, nonce, step3, SIZEOF_VLA(step3), packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); if (len != SIZE_IPPORT + SEND_BASE * 2 + length + CRYPTO_MAC_SIZE) { return -1; } return 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + len; } /* Create a onion packet to be sent over tcp. * * Use Onion_Path path to create packet for data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * packet should be at least ONION_MAX_PACKET_SIZE big. * * return -1 on failure. * return length of created packet on success. */ int create_onion_packet_tcp(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) { if (CRYPTO_NONCE_SIZE + SIZE_IPPORT + SEND_BASE * 2 + length > max_packet_length || length == 0) { return -1; } VLA(uint8_t, step1, SIZE_IPPORT + length); ipport_pack(step1, &dest); memcpy(step1 + SIZE_IPPORT, data, length); uint8_t nonce[CRYPTO_NONCE_SIZE]; random_nonce(nonce); VLA(uint8_t, step2, SIZE_IPPORT + SEND_BASE + length); ipport_pack(step2, &path->ip_port3); memcpy(step2 + SIZE_IPPORT, path->public_key3, CRYPTO_PUBLIC_KEY_SIZE); int len = encrypt_data_symmetric(path->shared_key3, nonce, step1, SIZEOF_VLA(step1), step2 + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); if (len != SIZE_IPPORT + length + CRYPTO_MAC_SIZE) { return -1; } ipport_pack(packet + CRYPTO_NONCE_SIZE, &path->ip_port2); memcpy(packet + CRYPTO_NONCE_SIZE + SIZE_IPPORT, path->public_key2, CRYPTO_PUBLIC_KEY_SIZE); len = encrypt_data_symmetric(path->shared_key2, nonce, step2, SIZEOF_VLA(step2), packet + CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE); if (len != SIZE_IPPORT + SEND_BASE + length + CRYPTO_MAC_SIZE) { return -1; } memcpy(packet, nonce, CRYPTO_NONCE_SIZE); return CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_PUBLIC_KEY_SIZE + len; } /* 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. * * return -1 on failure. * return 0 on success. */ int send_onion_packet(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) { uint8_t packet[ONION_MAX_PACKET_SIZE]; int len = create_onion_packet(packet, sizeof(packet), path, dest, data, length); if (len == -1) { return -1; } if (sendpacket(net, path->ip_port1, packet, len) != len) { return -1; } return 0; } /* Create and send a onion response sent initially to dest with. * Maximum length of data is ONION_RESPONSE_MAX_DATA_SIZE. * * return -1 on failure. * return 0 on success. */ int send_onion_response(Networking_Core *net, IP_Port dest, const uint8_t *data, uint16_t length, const uint8_t *ret) { if (length > ONION_RESPONSE_MAX_DATA_SIZE || length == 0) { return -1; } VLA(uint8_t, packet, 1 + RETURN_3 + length); packet[0] = NET_PACKET_ONION_RECV_3; memcpy(packet + 1, ret, RETURN_3); memcpy(packet + 1 + RETURN_3, data, length); if ((uint32_t)sendpacket(net, dest, packet, SIZEOF_VLA(packet)) != SIZEOF_VLA(packet)) { return -1; } return 0; } static int handle_send_initial(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion *onion = (Onion *)object; if (length > ONION_MAX_PACKET_SIZE) { return 1; } if (length <= 1 + SEND_1) { return 1; } change_symmetric_key(onion); uint8_t plain[ONION_MAX_PACKET_SIZE]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; get_shared_key(onion->mono_time, &onion->shared_keys_1, shared_key, dht_get_self_secret_key(onion->dht), packet + 1 + CRYPTO_NONCE_SIZE); int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), plain); if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE)) { return 1; } return onion_send_1(onion, plain, len, source, packet + 1); } int onion_send_1(const Onion *onion, const uint8_t *plain, uint16_t len, IP_Port source, const uint8_t *nonce) { if (len > ONION_MAX_PACKET_SIZE + SIZE_IPPORT - (1 + CRYPTO_NONCE_SIZE + ONION_RETURN_1)) { return 1; } if (len <= SIZE_IPPORT + SEND_BASE * 2) { return 1; } IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) { return 1; } uint8_t ip_port[SIZE_IPPORT]; ipport_pack(ip_port, &source); uint8_t data[ONION_MAX_PACKET_SIZE]; data[0] = NET_PACKET_ONION_SEND_1; memcpy(data + 1, nonce, CRYPTO_NONCE_SIZE); memcpy(data + 1 + CRYPTO_NONCE_SIZE, plain + SIZE_IPPORT, len - SIZE_IPPORT); uint16_t data_len = 1 + CRYPTO_NONCE_SIZE + (len - SIZE_IPPORT); uint8_t *ret_part = data + data_len; random_nonce(ret_part); len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ip_port, SIZE_IPPORT, ret_part + CRYPTO_NONCE_SIZE); if (len != SIZE_IPPORT + CRYPTO_MAC_SIZE) { return 1; } data_len += CRYPTO_NONCE_SIZE + len; if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { return 1; } return 0; } static int handle_send_1(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion *onion = (Onion *)object; if (length > ONION_MAX_PACKET_SIZE) { return 1; } if (length <= 1 + SEND_2) { return 1; } change_symmetric_key(onion); uint8_t plain[ONION_MAX_PACKET_SIZE]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; get_shared_key(onion->mono_time, &onion->shared_keys_2, shared_key, dht_get_self_secret_key(onion->dht), packet + 1 + CRYPTO_NONCE_SIZE); int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_1), plain); if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_1 + CRYPTO_MAC_SIZE)) { return 1; } IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) { return 1; } uint8_t data[ONION_MAX_PACKET_SIZE]; data[0] = NET_PACKET_ONION_SEND_2; memcpy(data + 1, packet + 1, CRYPTO_NONCE_SIZE); memcpy(data + 1 + CRYPTO_NONCE_SIZE, plain + SIZE_IPPORT, len - SIZE_IPPORT); uint16_t data_len = 1 + CRYPTO_NONCE_SIZE + (len - SIZE_IPPORT); uint8_t *ret_part = data + data_len; random_nonce(ret_part); uint8_t ret_data[RETURN_1 + SIZE_IPPORT]; ipport_pack(ret_data, &source); memcpy(ret_data + SIZE_IPPORT, packet + (length - RETURN_1), RETURN_1); len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ret_data, sizeof(ret_data), ret_part + CRYPTO_NONCE_SIZE); if (len != RETURN_2 - CRYPTO_NONCE_SIZE) { return 1; } data_len += CRYPTO_NONCE_SIZE + len; if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { return 1; } return 0; } static int handle_send_2(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion *onion = (Onion *)object; if (length > ONION_MAX_PACKET_SIZE) { return 1; } if (length <= 1 + SEND_3) { return 1; } change_symmetric_key(onion); uint8_t plain[ONION_MAX_PACKET_SIZE]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; get_shared_key(onion->mono_time, &onion->shared_keys_3, shared_key, dht_get_self_secret_key(onion->dht), packet + 1 + CRYPTO_NONCE_SIZE); int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_2), plain); if (len != length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + RETURN_2 + CRYPTO_MAC_SIZE)) { return 1; } if (len <= SIZE_IPPORT) { return 1; } if (plain[SIZE_IPPORT] != NET_PACKET_ANNOUNCE_REQUEST && plain[SIZE_IPPORT] != NET_PACKET_ONION_DATA_REQUEST) { return 1; } IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) { return 1; } uint8_t data[ONION_MAX_PACKET_SIZE]; memcpy(data, plain + SIZE_IPPORT, len - SIZE_IPPORT); uint16_t data_len = (len - SIZE_IPPORT); uint8_t *ret_part = data + (len - SIZE_IPPORT); random_nonce(ret_part); uint8_t ret_data[RETURN_2 + SIZE_IPPORT]; ipport_pack(ret_data, &source); memcpy(ret_data + SIZE_IPPORT, packet + (length - RETURN_2), RETURN_2); len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ret_data, sizeof(ret_data), ret_part + CRYPTO_NONCE_SIZE); if (len != RETURN_3 - CRYPTO_NONCE_SIZE) { return 1; } data_len += RETURN_3; if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { return 1; } return 0; } static int handle_recv_3(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion *onion = (Onion *)object; if (length > ONION_MAX_PACKET_SIZE) { return 1; } if (length <= 1 + RETURN_3) { return 1; } if (packet[1 + RETURN_3] != NET_PACKET_ANNOUNCE_RESPONSE && packet[1 + RETURN_3] != NET_PACKET_ONION_DATA_RESPONSE) { return 1; } change_symmetric_key(onion); uint8_t plain[SIZE_IPPORT + RETURN_2]; int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, SIZE_IPPORT + RETURN_2 + CRYPTO_MAC_SIZE, plain); if ((uint32_t)len != sizeof(plain)) { return 1; } IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) { return 1; } uint8_t data[ONION_MAX_PACKET_SIZE]; data[0] = NET_PACKET_ONION_RECV_2; memcpy(data + 1, plain + SIZE_IPPORT, RETURN_2); memcpy(data + 1 + RETURN_2, packet + 1 + RETURN_3, length - (1 + RETURN_3)); uint16_t data_len = 1 + RETURN_2 + (length - (1 + RETURN_3)); if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { return 1; } return 0; } static int handle_recv_2(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion *onion = (Onion *)object; if (length > ONION_MAX_PACKET_SIZE) { return 1; } if (length <= 1 + RETURN_2) { return 1; } if (packet[1 + RETURN_2] != NET_PACKET_ANNOUNCE_RESPONSE && packet[1 + RETURN_2] != NET_PACKET_ONION_DATA_RESPONSE) { return 1; } change_symmetric_key(onion); uint8_t plain[SIZE_IPPORT + RETURN_1]; int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, SIZE_IPPORT + RETURN_1 + CRYPTO_MAC_SIZE, plain); if ((uint32_t)len != sizeof(plain)) { return 1; } IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) { return 1; } uint8_t data[ONION_MAX_PACKET_SIZE]; data[0] = NET_PACKET_ONION_RECV_1; memcpy(data + 1, plain + SIZE_IPPORT, RETURN_1); memcpy(data + 1 + RETURN_1, packet + 1 + RETURN_2, length - (1 + RETURN_2)); uint16_t data_len = 1 + RETURN_1 + (length - (1 + RETURN_2)); if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) { return 1; } return 0; } static int handle_recv_1(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion *onion = (Onion *)object; if (length > ONION_MAX_PACKET_SIZE) { return 1; } if (length <= 1 + RETURN_1) { return 1; } if (packet[1 + RETURN_1] != NET_PACKET_ANNOUNCE_RESPONSE && packet[1 + RETURN_1] != NET_PACKET_ONION_DATA_RESPONSE) { return 1; } change_symmetric_key(onion); uint8_t plain[SIZE_IPPORT]; int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE, SIZE_IPPORT + CRYPTO_MAC_SIZE, plain); if ((uint32_t)len != SIZE_IPPORT) { return 1; } IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 1) == -1) { return 1; } uint16_t data_len = length - (1 + RETURN_1); if (onion->recv_1_function && !net_family_is_ipv4(send_to.ip.family) && !net_family_is_ipv6(send_to.ip.family)) { return onion->recv_1_function(onion->callback_object, send_to, packet + (1 + RETURN_1), data_len); } if ((uint32_t)sendpacket(onion->net, send_to, packet + (1 + RETURN_1), data_len) != data_len) { return 1; } return 0; } void set_callback_handle_recv_1(Onion *onion, onion_recv_1_cb *function, void *object) { onion->recv_1_function = function; onion->callback_object = object; } Onion *new_onion(Mono_Time *mono_time, DHT *dht) { if (dht == nullptr) { return nullptr; } Onion *onion = (Onion *)calloc(1, sizeof(Onion)); if (onion == nullptr) { return nullptr; } onion->dht = dht; onion->net = dht_get_net(dht); onion->mono_time = mono_time; new_symmetric_key(onion->secret_symmetric_key); onion->timestamp = mono_time_get(onion->mono_time); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, &handle_send_initial, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, &handle_send_1, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, &handle_send_2, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_3, &handle_recv_3, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_2, &handle_recv_2, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_1, &handle_recv_1, onion); return onion; } void kill_onion(Onion *onion) { if (onion == nullptr) { return; } networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, nullptr, nullptr); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, nullptr, nullptr); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, nullptr, nullptr); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_3, nullptr, nullptr); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_2, nullptr, nullptr); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_1, nullptr, nullptr); free(onion); } c-toxcore-0.2.13/toxcore/onion.h000066400000000000000000000117021415350724400164640ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Implementation of the onion part of docs/Prevent_Tracking.txt */ #ifndef C_TOXCORE_TOXCORE_ONION_H #define C_TOXCORE_TOXCORE_ONION_H #include "DHT.h" #include "mono_time.h" typedef int onion_recv_1_cb(void *object, IP_Port dest, const uint8_t *data, uint16_t length); typedef struct Onion { Mono_Time *mono_time; DHT *dht; Networking_Core *net; uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; uint64_t timestamp; Shared_Keys shared_keys_1; Shared_Keys shared_keys_2; Shared_Keys shared_keys_3; onion_recv_1_cb *recv_1_function; void *callback_object; } Onion; #define ONION_MAX_PACKET_SIZE 1400 #define ONION_RETURN_1 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE) #define ONION_RETURN_2 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE + ONION_RETURN_1) #define ONION_RETURN_3 (CRYPTO_NONCE_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE + ONION_RETURN_2) #define ONION_SEND_BASE (CRYPTO_PUBLIC_KEY_SIZE + SIZE_IPPORT + CRYPTO_MAC_SIZE) #define ONION_SEND_3 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE + ONION_RETURN_2) #define ONION_SEND_2 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE*2 + ONION_RETURN_1) #define ONION_SEND_1 (CRYPTO_NONCE_SIZE + ONION_SEND_BASE*3) #define ONION_MAX_DATA_SIZE (ONION_MAX_PACKET_SIZE - (ONION_SEND_1 + 1)) #define ONION_RESPONSE_MAX_DATA_SIZE (ONION_MAX_PACKET_SIZE - (1 + ONION_RETURN_3)) #define ONION_PATH_LENGTH 3 typedef struct Onion_Path { uint8_t shared_key1[CRYPTO_SHARED_KEY_SIZE]; uint8_t shared_key2[CRYPTO_SHARED_KEY_SIZE]; uint8_t shared_key3[CRYPTO_SHARED_KEY_SIZE]; uint8_t public_key1[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t public_key2[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t public_key3[CRYPTO_PUBLIC_KEY_SIZE]; IP_Port ip_port1; uint8_t node_public_key1[CRYPTO_PUBLIC_KEY_SIZE]; IP_Port ip_port2; uint8_t node_public_key2[CRYPTO_PUBLIC_KEY_SIZE]; IP_Port ip_port3; uint8_t node_public_key3[CRYPTO_PUBLIC_KEY_SIZE]; uint32_t path_num; } Onion_Path; /* Create a new onion path. * * Create a new onion path out of nodes (nodes is a list of ONION_PATH_LENGTH nodes) * * new_path must be an empty memory location of at least Onion_Path size. * * return -1 on failure. * return 0 on success. */ int create_onion_path(const DHT *dht, Onion_Path *new_path, const Node_format *nodes); /* Dump nodes in onion path to nodes of length num_nodes; * * return -1 on failure. * return 0 on success. */ int onion_path_to_nodes(Node_format *nodes, unsigned int num_nodes, const Onion_Path *path); /* Create a onion packet. * * Use Onion_Path path to create packet for data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * packet should be at least ONION_MAX_PACKET_SIZE big. * * return -1 on failure. * return length of created packet on success. */ int create_onion_packet(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length); /* Create a onion packet to be sent over tcp. * * Use Onion_Path path to create packet for data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * packet should be at least ONION_MAX_PACKET_SIZE big. * * return -1 on failure. * return length of created packet on success. */ int create_onion_packet_tcp(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length); /* 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. * * return -1 on failure. * return 0 on success. */ int send_onion_packet(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length); /* Create and send a onion response sent initially to dest with. * Maximum length of data is ONION_RESPONSE_MAX_DATA_SIZE. * * return -1 on failure. * return 0 on success. */ int send_onion_response(Networking_Core *net, IP_Port dest, const uint8_t *data, uint16_t length, const uint8_t *ret); /* Function to handle/send received decrypted versions of the packet sent with send_onion_packet. * * return 0 on success. * return 1 on failure. * * Used to handle these packets that are received in a non traditional way (by TCP for example). * * Source family must be set to something else than TOX_AF_INET6 or TOX_AF_INET so that the callback gets called * when the response is received. */ int onion_send_1(const Onion *onion, const uint8_t *plain, uint16_t len, IP_Port source, const uint8_t *nonce); /* Set the callback to be called when the dest ip_port doesn't have TOX_AF_INET6 or TOX_AF_INET as the family. */ void set_callback_handle_recv_1(Onion *onion, onion_recv_1_cb *function, void *object); Onion *new_onion(Mono_Time *mono_time, DHT *dht); void kill_onion(Onion *onion); #endif c-toxcore-0.2.13/toxcore/onion_announce.c000066400000000000000000000435611415350724400203550ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Implementation of the announce part of docs/Prevent_Tracking.txt */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "onion_announce.h" #include #include #include "LAN_discovery.h" #include "mono_time.h" #include "util.h" #define PING_ID_TIMEOUT ONION_ANNOUNCE_TIMEOUT #define ANNOUNCE_REQUEST_SIZE_RECV (ONION_ANNOUNCE_REQUEST_SIZE + ONION_RETURN_3) #define DATA_REQUEST_MIN_SIZE ONION_DATA_REQUEST_MIN_SIZE #define DATA_REQUEST_MIN_SIZE_RECV (DATA_REQUEST_MIN_SIZE + ONION_RETURN_3) typedef struct Onion_Announce_Entry { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; IP_Port ret_ip_port; uint8_t ret[ONION_RETURN_3]; uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint64_t time; } Onion_Announce_Entry; struct Onion_Announce { Mono_Time *mono_time; DHT *dht; Networking_Core *net; Onion_Announce_Entry entries[ONION_ANNOUNCE_MAX_ENTRIES]; /* This is CRYPTO_SYMMETRIC_KEY_SIZE long just so we can use new_symmetric_key() to fill it */ uint8_t secret_bytes[CRYPTO_SYMMETRIC_KEY_SIZE]; Shared_Keys shared_keys_recv; }; uint8_t *onion_announce_entry_public_key(Onion_Announce *onion_a, uint32_t entry) { return onion_a->entries[entry].public_key; } void onion_announce_entry_set_time(Onion_Announce *onion_a, uint32_t entry, uint64_t time) { onion_a->entries[entry].time = time; } /* Create an onion announce request packet in packet of max_packet_length (recommended size ONION_ANNOUNCE_REQUEST_SIZE). * * dest_client_id is the public key of the node the packet will be sent to. * public_key and secret_key is the kepair which will be used to encrypt the request. * ping_id is the ping id that will be sent in the request. * client_id is the client id of the node we are searching for. * data_public_key is the public key we want others to encrypt their data packets with. * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to * receive back in the response. * * return -1 on failure. * return packet length on success. */ int create_announce_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id, const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, uint64_t sendback_data) { if (max_packet_length < ONION_ANNOUNCE_REQUEST_SIZE) { return -1; } uint8_t plain[ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH]; memcpy(plain, ping_id, ONION_PING_ID_SIZE); memcpy(plain + ONION_PING_ID_SIZE, client_id, CRYPTO_PUBLIC_KEY_SIZE); memcpy(plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE, &sendback_data, sizeof(sendback_data)); packet[0] = NET_PACKET_ANNOUNCE_REQUEST; random_nonce(packet + 1); int len = encrypt_data(dest_client_id, secret_key, packet + 1, plain, sizeof(plain), packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); if ((uint32_t)len + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE != ONION_ANNOUNCE_REQUEST_SIZE) { return -1; } memcpy(packet + 1 + CRYPTO_NONCE_SIZE, public_key, CRYPTO_PUBLIC_KEY_SIZE); return ONION_ANNOUNCE_REQUEST_SIZE; } /* Create an onion data request packet in packet of max_packet_length (recommended size ONION_MAX_PACKET_SIZE). * * public_key is the real public key of the node which we want to send the data of length length to. * encrypt_public_key is the public key used to encrypt the data packet. * * nonce is the nonce to encrypt this packet with * * return -1 on failure. * return 0 on success. */ int create_data_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length) { if (DATA_REQUEST_MIN_SIZE + length > max_packet_length) { return -1; } if (DATA_REQUEST_MIN_SIZE + length > ONION_MAX_DATA_SIZE) { return -1; } packet[0] = NET_PACKET_ONION_DATA_REQUEST; memcpy(packet + 1, public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); uint8_t random_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t random_secret_key[CRYPTO_SECRET_KEY_SIZE]; crypto_new_keypair(random_public_key, random_secret_key); memcpy(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, random_public_key, CRYPTO_PUBLIC_KEY_SIZE); int len = encrypt_data(encrypt_public_key, random_secret_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, data, length, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE); if (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + len != DATA_REQUEST_MIN_SIZE + length) { return -1; } return DATA_REQUEST_MIN_SIZE + length; } /* Create and send an onion announce request packet. * * path is the path the request will take before it is sent to dest. * * public_key and secret_key is the kepair which will be used to encrypt the request. * ping_id is the ping id that will be sent in the request. * client_id is the client id of the node we are searching for. * data_public_key is the public key we want others to encrypt their data packets with. * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to * receive back in the response. * * return -1 on failure. * return 0 on success. */ int send_announce_request(Networking_Core *net, const Onion_Path *path, Node_format dest, const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, uint64_t sendback_data) { uint8_t request[ONION_ANNOUNCE_REQUEST_SIZE]; int len = create_announce_request(request, sizeof(request), dest.public_key, public_key, secret_key, ping_id, client_id, data_public_key, sendback_data); if (len != sizeof(request)) { return -1; } uint8_t packet[ONION_MAX_PACKET_SIZE]; len = create_onion_packet(packet, sizeof(packet), path, dest.ip_port, request, sizeof(request)); if (len == -1) { return -1; } if (sendpacket(net, path->ip_port1, packet, len) != len) { return -1; } return 0; } /* Create and send an onion data request packet. * * path is the path the request will take before it is sent to dest. * (if dest knows the person with the public_key they should * send the packet to that person in the form of a response) * * public_key is the real public key of the node which we want to send the data of length length to. * encrypt_public_key is the public key used to encrypt the data packet. * * nonce is the nonce to encrypt this packet with * * return -1 on failure. * return 0 on success. */ int send_data_request(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length) { uint8_t request[ONION_MAX_DATA_SIZE]; int len = create_data_request(request, sizeof(request), public_key, encrypt_public_key, nonce, data, length); if (len == -1) { return -1; } uint8_t packet[ONION_MAX_PACKET_SIZE]; len = create_onion_packet(packet, sizeof(packet), path, dest, request, len); if (len == -1) { return -1; } if (sendpacket(net, path->ip_port1, packet, len) != len) { return -1; } return 0; } /* Generate a ping_id and put it in ping_id */ static void generate_ping_id(const Onion_Announce *onion_a, uint64_t time, const uint8_t *public_key, IP_Port ret_ip_port, uint8_t *ping_id) { time /= PING_ID_TIMEOUT; uint8_t data[CRYPTO_SYMMETRIC_KEY_SIZE + sizeof(time) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(ret_ip_port)]; memcpy(data, onion_a->secret_bytes, CRYPTO_SYMMETRIC_KEY_SIZE); memcpy(data + CRYPTO_SYMMETRIC_KEY_SIZE, &time, sizeof(time)); memcpy(data + CRYPTO_SYMMETRIC_KEY_SIZE + sizeof(time), public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(data + CRYPTO_SYMMETRIC_KEY_SIZE + sizeof(time) + CRYPTO_PUBLIC_KEY_SIZE, &ret_ip_port, sizeof(ret_ip_port)); crypto_sha256(ping_id, data, sizeof(data)); } /* check if public key is in entries list * * return -1 if no * return position in list if yes */ static int in_entries(const Onion_Announce *onion_a, const uint8_t *public_key) { unsigned int i; for (i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { if (!mono_time_is_timeout(onion_a->mono_time, onion_a->entries[i].time, ONION_ANNOUNCE_TIMEOUT) && public_key_cmp(onion_a->entries[i].public_key, public_key) == 0) { return i; } } return -1; } typedef struct Cmp_data { const Mono_Time *mono_time; const uint8_t *base_public_key; Onion_Announce_Entry entry; } Cmp_data; static int cmp_entry(const void *a, const void *b) { Cmp_data cmp1; Cmp_data cmp2; memcpy(&cmp1, a, sizeof(Cmp_data)); memcpy(&cmp2, b, sizeof(Cmp_data)); Onion_Announce_Entry entry1 = cmp1.entry; Onion_Announce_Entry entry2 = cmp2.entry; const uint8_t *cmp_public_key = cmp1.base_public_key; int t1 = mono_time_is_timeout(cmp1.mono_time, entry1.time, ONION_ANNOUNCE_TIMEOUT); int t2 = mono_time_is_timeout(cmp1.mono_time, entry2.time, ONION_ANNOUNCE_TIMEOUT); if (t1 && t2) { return 0; } if (t1) { return -1; } if (t2) { return 1; } int close = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); if (close == 1) { return 1; } if (close == 2) { return -1; } return 0; } static void sort_onion_announce_list(Onion_Announce_Entry *list, unsigned int length, const Mono_Time *mono_time, const uint8_t *comp_public_key) { // Pass comp_public_key to qsort with each Client_data entry, so the // comparison function can use it as the base of comparison. VLA(Cmp_data, cmp_list, length); for (uint32_t i = 0; i < length; ++i) { cmp_list[i].mono_time = mono_time; cmp_list[i].base_public_key = comp_public_key; cmp_list[i].entry = list[i]; } qsort(cmp_list, length, sizeof(Cmp_data), cmp_entry); for (uint32_t i = 0; i < length; ++i) { list[i] = cmp_list[i].entry; } } /* add entry to entries list * * return -1 if failure * return position if added */ static int add_to_entries(Onion_Announce *onion_a, IP_Port ret_ip_port, const uint8_t *public_key, const uint8_t *data_public_key, const uint8_t *ret) { int pos = in_entries(onion_a, public_key); if (pos == -1) { for (unsigned i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { if (mono_time_is_timeout(onion_a->mono_time, onion_a->entries[i].time, ONION_ANNOUNCE_TIMEOUT)) { pos = i; } } } if (pos == -1) { if (id_closest(dht_get_self_public_key(onion_a->dht), public_key, onion_a->entries[0].public_key) == 1) { pos = 0; } } if (pos == -1) { return -1; } memcpy(onion_a->entries[pos].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); onion_a->entries[pos].ret_ip_port = ret_ip_port; memcpy(onion_a->entries[pos].ret, ret, ONION_RETURN_3); memcpy(onion_a->entries[pos].data_public_key, data_public_key, CRYPTO_PUBLIC_KEY_SIZE); onion_a->entries[pos].time = mono_time_get(onion_a->mono_time); sort_onion_announce_list(onion_a->entries, ONION_ANNOUNCE_MAX_ENTRIES, onion_a->mono_time, dht_get_self_public_key(onion_a->dht)); return in_entries(onion_a, public_key); } static int handle_announce_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion_Announce *onion_a = (Onion_Announce *)object; if (length != ANNOUNCE_REQUEST_SIZE_RECV) { return 1; } const uint8_t *packet_public_key = packet + 1 + CRYPTO_NONCE_SIZE; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; get_shared_key(onion_a->mono_time, &onion_a->shared_keys_recv, shared_key, dht_get_self_secret_key(onion_a->dht), packet_public_key); uint8_t plain[ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH]; int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_MAC_SIZE, plain); if ((uint32_t)len != sizeof(plain)) { return 1; } uint8_t ping_id1[ONION_PING_ID_SIZE]; generate_ping_id(onion_a, mono_time_get(onion_a->mono_time), packet_public_key, source, ping_id1); uint8_t ping_id2[ONION_PING_ID_SIZE]; generate_ping_id(onion_a, mono_time_get(onion_a->mono_time) + PING_ID_TIMEOUT, packet_public_key, source, ping_id2); int index; uint8_t *data_public_key = plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE; if (crypto_memcmp(ping_id1, plain, ONION_PING_ID_SIZE) == 0 || crypto_memcmp(ping_id2, plain, ONION_PING_ID_SIZE) == 0) { index = add_to_entries(onion_a, source, packet_public_key, data_public_key, packet + (ANNOUNCE_REQUEST_SIZE_RECV - ONION_RETURN_3)); } else { index = in_entries(onion_a, plain + ONION_PING_ID_SIZE); } /*Respond with a announce response packet*/ Node_format nodes_list[MAX_SENT_NODES]; unsigned int num_nodes = get_close_nodes(onion_a->dht, plain + ONION_PING_ID_SIZE, nodes_list, net_family_unspec, ip_is_lan(source.ip), 1); uint8_t nonce[CRYPTO_NONCE_SIZE]; random_nonce(nonce); uint8_t pl[1 + ONION_PING_ID_SIZE + sizeof(nodes_list)]; if (index == -1) { pl[0] = 0; memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); } else { if (public_key_cmp(onion_a->entries[index].public_key, packet_public_key) == 0) { if (public_key_cmp(onion_a->entries[index].data_public_key, data_public_key) != 0) { pl[0] = 0; memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); } else { pl[0] = 2; memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); } } else { pl[0] = 1; memcpy(pl + 1, onion_a->entries[index].data_public_key, CRYPTO_PUBLIC_KEY_SIZE); } } int nodes_length = 0; if (num_nodes != 0) { nodes_length = pack_nodes(pl + 1 + ONION_PING_ID_SIZE, sizeof(nodes_list), nodes_list, num_nodes); if (nodes_length <= 0) { return 1; } } uint8_t data[ONION_ANNOUNCE_RESPONSE_MAX_SIZE]; len = encrypt_data_symmetric(shared_key, nonce, pl, 1 + ONION_PING_ID_SIZE + nodes_length, data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE); if (len != 1 + ONION_PING_ID_SIZE + nodes_length + CRYPTO_MAC_SIZE) { return 1; } data[0] = NET_PACKET_ANNOUNCE_RESPONSE; memcpy(data + 1, plain + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE, ONION_ANNOUNCE_SENDBACK_DATA_LENGTH); memcpy(data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, nonce, CRYPTO_NONCE_SIZE); if (send_onion_response(onion_a->net, source, data, 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE + len, packet + (ANNOUNCE_REQUEST_SIZE_RECV - ONION_RETURN_3)) == -1) { return 1; } return 0; } static int handle_data_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion_Announce *onion_a = (Onion_Announce *)object; if (length <= DATA_REQUEST_MIN_SIZE_RECV) { return 1; } if (length > ONION_MAX_PACKET_SIZE) { return 1; } int index = in_entries(onion_a, packet + 1); if (index == -1) { return 1; } VLA(uint8_t, data, length - (CRYPTO_PUBLIC_KEY_SIZE + ONION_RETURN_3)); data[0] = NET_PACKET_ONION_DATA_RESPONSE; memcpy(data + 1, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_PUBLIC_KEY_SIZE + ONION_RETURN_3)); if (send_onion_response(onion_a->net, onion_a->entries[index].ret_ip_port, data, SIZEOF_VLA(data), onion_a->entries[index].ret) == -1) { return 1; } return 0; } Onion_Announce *new_onion_announce(Mono_Time *mono_time, DHT *dht) { if (dht == nullptr) { return nullptr; } Onion_Announce *onion_a = (Onion_Announce *)calloc(1, sizeof(Onion_Announce)); if (onion_a == nullptr) { return nullptr; } onion_a->mono_time = mono_time; onion_a->dht = dht; onion_a->net = dht_get_net(dht); new_symmetric_key(onion_a->secret_bytes); networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, &handle_announce_request, onion_a); networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, &handle_data_request, onion_a); return onion_a; } void kill_onion_announce(Onion_Announce *onion_a) { if (onion_a == nullptr) { return; } networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, nullptr, nullptr); networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, nullptr, nullptr); free(onion_a); } c-toxcore-0.2.13/toxcore/onion_announce.h000066400000000000000000000121311415350724400203470ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Implementation of the announce part of docs/Prevent_Tracking.txt */ #ifndef C_TOXCORE_TOXCORE_ONION_ANNOUNCE_H #define C_TOXCORE_TOXCORE_ONION_ANNOUNCE_H #include "onion.h" #define ONION_ANNOUNCE_MAX_ENTRIES 160 #define ONION_ANNOUNCE_TIMEOUT 300 #define ONION_PING_ID_SIZE CRYPTO_SHA256_SIZE #define ONION_ANNOUNCE_SENDBACK_DATA_LENGTH (sizeof(uint64_t)) #define ONION_ANNOUNCE_REQUEST_SIZE (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_PING_ID_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_MAC_SIZE) #define ONION_ANNOUNCE_RESPONSE_MIN_SIZE (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE + 1 + ONION_PING_ID_SIZE + CRYPTO_MAC_SIZE) #define ONION_ANNOUNCE_RESPONSE_MAX_SIZE (ONION_ANNOUNCE_RESPONSE_MIN_SIZE + sizeof(Node_format)*MAX_SENT_NODES) #define ONION_DATA_RESPONSE_MIN_SIZE (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) #if ONION_PING_ID_SIZE != CRYPTO_PUBLIC_KEY_SIZE #error "announce response packets assume that ONION_PING_ID_SIZE is equal to CRYPTO_PUBLIC_KEY_SIZE" #endif #define ONION_DATA_REQUEST_MIN_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) #define MAX_DATA_REQUEST_SIZE (ONION_MAX_DATA_SIZE - ONION_DATA_REQUEST_MIN_SIZE) typedef struct Onion_Announce Onion_Announce; /* These two are not public; they are for tests only! */ uint8_t *onion_announce_entry_public_key(Onion_Announce *onion_a, uint32_t entry); void onion_announce_entry_set_time(Onion_Announce *onion_a, uint32_t entry, uint64_t time); /* Create an onion announce request packet in packet of max_packet_length (recommended size ONION_ANNOUNCE_REQUEST_SIZE). * * dest_client_id is the public key of the node the packet will be sent to. * public_key and secret_key is the kepair which will be used to encrypt the request. * ping_id is the ping id that will be sent in the request. * client_id is the client id of the node we are searching for. * data_public_key is the public key we want others to encrypt their data packets with. * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to * receive back in the response. * * return -1 on failure. * return packet length on success. */ int create_announce_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id, const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, uint64_t sendback_data); /* Create an onion data request packet in packet of max_packet_length (recommended size ONION_MAX_PACKET_SIZE). * * public_key is the real public key of the node which we want to send the data of length length to. * encrypt_public_key is the public key used to encrypt the data packet. * * nonce is the nonce to encrypt this packet with * * return -1 on failure. * return 0 on success. */ int create_data_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length); /* Create and send an onion announce request packet. * * path is the path the request will take before it is sent to dest. * * public_key and secret_key is the kepair which will be used to encrypt the request. * ping_id is the ping id that will be sent in the request. * client_id is the client id of the node we are searching for. * data_public_key is the public key we want others to encrypt their data packets with. * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to * receive back in the response. * * return -1 on failure. * return 0 on success. */ int send_announce_request(Networking_Core *net, const Onion_Path *path, Node_format dest, const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, uint64_t sendback_data); /* Create and send an onion data request packet. * * path is the path the request will take before it is sent to dest. * (if dest knows the person with the public_key they should * send the packet to that person in the form of a response) * * public_key is the real public key of the node which we want to send the data of length length to. * encrypt_public_key is the public key used to encrypt the data packet. * * nonce is the nonce to encrypt this packet with * * The maximum length of data is MAX_DATA_REQUEST_SIZE. * * return -1 on failure. * return 0 on success. */ int send_data_request(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length); Onion_Announce *new_onion_announce(Mono_Time *mono_time, DHT *dht); void kill_onion_announce(Onion_Announce *onion_a); #endif c-toxcore-0.2.13/toxcore/onion_client.c000066400000000000000000001703101415350724400200160ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Implementation of the client part of docs/Prevent_Tracking.txt (The part that * uses the onion stuff to connect to the friend) */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "onion_client.h" #include #include #include "LAN_discovery.h" #include "mono_time.h" #include "util.h" /* defines for the array size and * timeout for onion announce packets. */ #define ANNOUNCE_ARRAY_SIZE 256 #define ANNOUNCE_TIMEOUT 10 typedef struct Onion_Node { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; IP_Port ip_port; uint8_t ping_id[ONION_PING_ID_SIZE]; uint8_t data_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t is_stored; uint64_t added_time; uint64_t timestamp; uint64_t last_pinged; uint8_t unsuccessful_pings; uint32_t path_used; } Onion_Node; typedef struct Onion_Client_Paths { Onion_Path paths[NUMBER_ONION_PATHS]; uint64_t last_path_success[NUMBER_ONION_PATHS]; uint64_t last_path_used[NUMBER_ONION_PATHS]; uint64_t path_creation_time[NUMBER_ONION_PATHS]; /* number of times used without success. */ unsigned int last_path_used_times[NUMBER_ONION_PATHS]; } Onion_Client_Paths; typedef struct Last_Pinged { uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint64_t timestamp; } Last_Pinged; typedef struct Onion_Friend { uint8_t status; /* 0 if friend is not valid, 1 if friend is valid.*/ uint8_t is_online; /* Set by the onion_set_friend_status function. */ uint8_t know_dht_public_key; /* 0 if we don't know the dht public key of the other, 1 if we do. */ uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t real_public_key[CRYPTO_PUBLIC_KEY_SIZE]; Onion_Node clients_list[MAX_ONION_CLIENTS]; uint8_t temp_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; uint64_t last_dht_pk_onion_sent; uint64_t last_dht_pk_dht_sent; uint64_t last_noreplay; uint64_t last_seen; Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; uint8_t last_pinged_index; recv_tcp_relay_cb *tcp_relay_node_callback; void *tcp_relay_node_callback_object; uint32_t tcp_relay_node_callback_number; onion_dht_pk_cb *dht_pk_callback; void *dht_pk_callback_object; uint32_t dht_pk_callback_number; uint32_t run_count; } Onion_Friend; typedef struct Onion_Data_Handler { oniondata_handler_cb *function; void *object; } Onion_Data_Handler; struct Onion_Client { Mono_Time *mono_time; const Logger *logger; DHT *dht; Net_Crypto *c; Networking_Core *net; Onion_Friend *friends_list; uint16_t num_friends; Onion_Node clients_announce_list[MAX_ONION_CLIENTS_ANNOUNCE]; uint64_t last_announce; Onion_Client_Paths onion_paths_self; Onion_Client_Paths onion_paths_friends; uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; uint64_t last_run; uint64_t first_run; uint8_t temp_public_key[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t temp_secret_key[CRYPTO_SECRET_KEY_SIZE]; Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; Node_format path_nodes[MAX_PATH_NODES]; uint16_t path_nodes_index; Node_format path_nodes_bs[MAX_PATH_NODES]; uint16_t path_nodes_index_bs; Ping_Array *announce_ping_array; uint8_t last_pinged_index; Onion_Data_Handler onion_data_handlers[256]; uint64_t last_packet_recv; unsigned int onion_connected; bool udp_connected; }; DHT *onion_get_dht(const Onion_Client *onion_c) { return onion_c->dht; } Net_Crypto *onion_get_net_crypto(const Onion_Client *onion_c) { return onion_c->c; } /* Add a node to the path_nodes bootstrap array. * * return -1 on failure * return 0 on success */ int onion_add_bs_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key) { if (!net_family_is_ipv4(ip_port.ip.family) && !net_family_is_ipv6(ip_port.ip.family)) { return -1; } unsigned int i; for (i = 0; i < MAX_PATH_NODES; ++i) { if (public_key_cmp(public_key, onion_c->path_nodes_bs[i].public_key) == 0) { return -1; } } onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].ip_port = ip_port; memcpy(onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); uint16_t last = onion_c->path_nodes_index_bs; ++onion_c->path_nodes_index_bs; if (onion_c->path_nodes_index_bs < last) { onion_c->path_nodes_index_bs = MAX_PATH_NODES + 1; } return 0; } /* Add a node to the path_nodes array. * * return -1 on failure * return 0 on success */ static int onion_add_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key) { if (!net_family_is_ipv4(ip_port.ip.family) && !net_family_is_ipv6(ip_port.ip.family)) { return -1; } unsigned int i; for (i = 0; i < MAX_PATH_NODES; ++i) { if (public_key_cmp(public_key, onion_c->path_nodes[i].public_key) == 0) { return -1; } } onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].ip_port = ip_port; memcpy(onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); uint16_t last = onion_c->path_nodes_index; ++onion_c->path_nodes_index; if (onion_c->path_nodes_index < last) { onion_c->path_nodes_index = MAX_PATH_NODES + 1; } return 0; } /* Put up to max_num nodes in nodes. * * return the number of nodes. */ uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num) { if (!max_num) { return 0; } const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES); uint16_t i = 0; while (i < max_num && i < num_nodes) { nodes[i] = onion_c->path_nodes[(onion_c->path_nodes_index - (1 + i)) % num_nodes]; ++i; } for (uint16_t j = 0; i < max_num && j < MAX_PATH_NODES && j < onion_c->path_nodes_index_bs; ++j) { bool already_saved = false; for (uint16_t k = 0; k < num_nodes; ++k) { if (public_key_cmp(nodes[k].public_key, onion_c->path_nodes_bs[j].public_key) == 0) { already_saved = true; break; } } if (!already_saved) { nodes[i] = onion_c->path_nodes_bs[j]; ++i; } } return i; } /* Put up to max_num random nodes in nodes. * * return the number of nodes. */ static uint16_t random_nodes_path_onion(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num) { unsigned int i; if (!max_num) { return 0; } const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES); // if (dht_non_lan_connected(onion_c->dht)) { if (dht_isconnected(onion_c->dht)) { if (num_nodes == 0) { return 0; } for (i = 0; i < max_num; ++i) { nodes[i] = onion_c->path_nodes[random_u32() % num_nodes]; } } else { int random_tcp = get_random_tcp_con_number(onion_c->c); if (random_tcp == -1) { return 0; } if (num_nodes >= 2) { nodes[0].ip_port.ip.family = net_family_tcp_family; nodes[0].ip_port.ip.ip.v4.uint32 = random_tcp; for (i = 1; i < max_num; ++i) { nodes[i] = onion_c->path_nodes[random_u32() % num_nodes]; } } else { const uint16_t num_nodes_bs = min_u16(onion_c->path_nodes_index_bs, MAX_PATH_NODES); if (num_nodes_bs == 0) { return 0; } nodes[0].ip_port.ip.family = net_family_tcp_family; nodes[0].ip_port.ip.ip.v4.uint32 = random_tcp; for (i = 1; i < max_num; ++i) { nodes[i] = onion_c->path_nodes_bs[random_u32() % num_nodes_bs]; } } } return max_num; } /* * return -1 if nodes are suitable for creating a new path. * return path number of already existing similar path if one already exists. */ static int is_path_used(const Mono_Time *mono_time, const Onion_Client_Paths *onion_paths, const Node_format *nodes) { unsigned int i; for (i = 0; i < NUMBER_ONION_PATHS; ++i) { if (mono_time_is_timeout(mono_time, onion_paths->last_path_success[i], ONION_PATH_TIMEOUT)) { continue; } if (mono_time_is_timeout(mono_time, onion_paths->path_creation_time[i], ONION_PATH_MAX_LIFETIME)) { continue; } // TODO(irungentoo): do we really have to check it with the last node? if (ipport_equal(&onion_paths->paths[i].ip_port1, &nodes[ONION_PATH_LENGTH - 1].ip_port)) { return i; } } return -1; } /* is path timed out */ static bool path_timed_out(const Mono_Time *mono_time, Onion_Client_Paths *onion_paths, uint32_t pathnum) { pathnum = pathnum % NUMBER_ONION_PATHS; bool is_new = onion_paths->last_path_success[pathnum] == onion_paths->path_creation_time[pathnum]; uint64_t timeout = is_new ? ONION_PATH_FIRST_TIMEOUT : ONION_PATH_TIMEOUT; return ((onion_paths->last_path_used_times[pathnum] >= ONION_PATH_MAX_NO_RESPONSE_USES && mono_time_is_timeout(mono_time, onion_paths->last_path_used[pathnum], timeout)) || mono_time_is_timeout(mono_time, onion_paths->path_creation_time[pathnum], ONION_PATH_MAX_LIFETIME)); } /* should node be considered to have timed out */ static bool onion_node_timed_out(const Onion_Node *node, const Mono_Time *mono_time) { return (node->timestamp == 0 || (node->unsuccessful_pings >= ONION_NODE_MAX_PINGS && mono_time_is_timeout(mono_time, node->last_pinged, ONION_NODE_TIMEOUT))); } /* Create a new path or use an old suitable one (if pathnum is valid) * or a random one from onion_paths. * * return -1 on failure * return 0 on success * * TODO(irungentoo): Make this function better, it currently probably is * vulnerable to some attacks that could deanonimize us. */ static int random_path(const Onion_Client *onion_c, Onion_Client_Paths *onion_paths, uint32_t pathnum, Onion_Path *path) { if (pathnum == UINT32_MAX) { pathnum = random_u32() % NUMBER_ONION_PATHS; } else { pathnum = pathnum % NUMBER_ONION_PATHS; } if (path_timed_out(onion_c->mono_time, onion_paths, pathnum)) { Node_format nodes[ONION_PATH_LENGTH]; if (random_nodes_path_onion(onion_c, nodes, ONION_PATH_LENGTH) != ONION_PATH_LENGTH) { return -1; } int n = is_path_used(onion_c->mono_time, onion_paths, nodes); if (n == -1) { if (create_onion_path(onion_c->dht, &onion_paths->paths[pathnum], nodes) == -1) { return -1; } onion_paths->path_creation_time[pathnum] = mono_time_get(onion_c->mono_time); onion_paths->last_path_success[pathnum] = onion_paths->path_creation_time[pathnum]; onion_paths->last_path_used_times[pathnum] = ONION_PATH_MAX_NO_RESPONSE_USES / 2; uint32_t path_num = random_u32(); path_num /= NUMBER_ONION_PATHS; path_num *= NUMBER_ONION_PATHS; path_num += pathnum; onion_paths->paths[pathnum].path_num = path_num; } else { pathnum = n; } } if (onion_paths->last_path_used_times[pathnum] < ONION_PATH_MAX_NO_RESPONSE_USES) { onion_paths->last_path_used[pathnum] = mono_time_get(onion_c->mono_time); } ++onion_paths->last_path_used_times[pathnum]; memcpy(path, &onion_paths->paths[pathnum], sizeof(Onion_Path)); return 0; } /* Does path with path_num exist. */ static bool path_exists(const Mono_Time *mono_time, Onion_Client_Paths *onion_paths, uint32_t path_num) { if (path_timed_out(mono_time, onion_paths, path_num)) { return 0; } return onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num; } /* Set path timeouts, return the path number. * */ static uint32_t set_path_timeouts(Onion_Client *onion_c, uint32_t num, uint32_t path_num) { if (num > onion_c->num_friends) { return -1; } Onion_Client_Paths *onion_paths; if (num == 0) { onion_paths = &onion_c->onion_paths_self; } else { onion_paths = &onion_c->onion_paths_friends; } if (onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num) { onion_paths->last_path_success[path_num % NUMBER_ONION_PATHS] = mono_time_get(onion_c->mono_time); onion_paths->last_path_used_times[path_num % NUMBER_ONION_PATHS] = 0; Node_format nodes[ONION_PATH_LENGTH]; if (onion_path_to_nodes(nodes, ONION_PATH_LENGTH, &onion_paths->paths[path_num % NUMBER_ONION_PATHS]) == 0) { unsigned int i; for (i = 0; i < ONION_PATH_LENGTH; ++i) { onion_add_path_node(onion_c, nodes[i].ip_port, nodes[i].public_key); } } return path_num; } return -1; } /* Function to send onion packet via TCP and UDP. * * return -1 on failure. * return 0 on success. */ static int send_onion_packet_tcp_udp(const Onion_Client *onion_c, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) { if (net_family_is_ipv4(path->ip_port1.ip.family) || net_family_is_ipv6(path->ip_port1.ip.family)) { uint8_t packet[ONION_MAX_PACKET_SIZE]; int len = create_onion_packet(packet, sizeof(packet), path, dest, data, length); if (len == -1) { return -1; } if (sendpacket(onion_c->net, path->ip_port1, packet, len) != len) { return -1; } return 0; } if (net_family_is_tcp_family(path->ip_port1.ip.family)) { uint8_t packet[ONION_MAX_PACKET_SIZE]; int len = create_onion_packet_tcp(packet, sizeof(packet), path, dest, data, length); if (len == -1) { return -1; } return send_tcp_onion_request(onion_c->c, path->ip_port1.ip.ip.v4.uint32, packet, len); } return -1; } /* Creates a sendback for use in an announce request. * * num is 0 if we used our secret public key for the announce * num is 1 + friendnum if we use a temporary one. * * Public key is the key we will be sending it to. * ip_port is the ip_port of the node we will be sending * it to. * * sendback must be at least ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big * * return -1 on failure * return 0 on success * */ static int new_sendback(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, IP_Port ip_port, uint32_t path_num, uint64_t *sendback) { uint8_t data[sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port) + sizeof(uint32_t)]; memcpy(data, &num, sizeof(uint32_t)); memcpy(data + sizeof(uint32_t), public_key, CRYPTO_PUBLIC_KEY_SIZE); memcpy(data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE, &ip_port, sizeof(IP_Port)); memcpy(data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port), &path_num, sizeof(uint32_t)); *sendback = ping_array_add(onion_c->announce_ping_array, onion_c->mono_time, data, sizeof(data)); if (*sendback == 0) { return -1; } return 0; } /* Checks if the sendback is valid and returns the public key contained in it in ret_pubkey and the * ip contained in it in ret_ip_port * * sendback is the sendback ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big * ret_pubkey must be at least CRYPTO_PUBLIC_KEY_SIZE big * ret_ip_port must be at least 1 big * * return -1 on failure * return num (see new_sendback(...)) on success */ static uint32_t check_sendback(Onion_Client *onion_c, const uint8_t *sendback, uint8_t *ret_pubkey, IP_Port *ret_ip_port, uint32_t *path_num) { uint64_t sback; memcpy(&sback, sendback, sizeof(uint64_t)); uint8_t data[sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port) + sizeof(uint32_t)]; if (ping_array_check(onion_c->announce_ping_array, onion_c->mono_time, data, sizeof(data), sback) != sizeof(data)) { return -1; } memcpy(ret_pubkey, data + sizeof(uint32_t), CRYPTO_PUBLIC_KEY_SIZE); memcpy(ret_ip_port, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE, sizeof(IP_Port)); memcpy(path_num, data + sizeof(uint32_t) + CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port), sizeof(uint32_t)); uint32_t num; memcpy(&num, data, sizeof(uint32_t)); return num; } static int client_send_announce_request(Onion_Client *onion_c, uint32_t num, IP_Port dest, const uint8_t *dest_pubkey, const uint8_t *ping_id, uint32_t pathnum) { if (num > onion_c->num_friends) { return -1; } uint64_t sendback; Onion_Path path; if (num == 0) { if (random_path(onion_c, &onion_c->onion_paths_self, pathnum, &path) == -1) { return -1; } } else { if (random_path(onion_c, &onion_c->onion_paths_friends, pathnum, &path) == -1) { return -1; } } if (new_sendback(onion_c, num, dest_pubkey, dest, path.path_num, &sendback) == -1) { return -1; } uint8_t zero_ping_id[ONION_PING_ID_SIZE] = {0}; if (ping_id == nullptr) { ping_id = zero_ping_id; } uint8_t request[ONION_ANNOUNCE_REQUEST_SIZE]; int len; if (num == 0) { len = create_announce_request(request, sizeof(request), dest_pubkey, nc_get_self_public_key(onion_c->c), nc_get_self_secret_key(onion_c->c), ping_id, nc_get_self_public_key(onion_c->c), onion_c->temp_public_key, sendback); } else { len = create_announce_request(request, sizeof(request), dest_pubkey, onion_c->friends_list[num - 1].temp_public_key, onion_c->friends_list[num - 1].temp_secret_key, ping_id, onion_c->friends_list[num - 1].real_public_key, zero_ping_id, sendback); } if (len == -1) { return -1; } return send_onion_packet_tcp_udp(onion_c, &path, dest, request, len); } typedef struct Onion_Client_Cmp_data { const Mono_Time *mono_time; const uint8_t *base_public_key; Onion_Node entry; } Onion_Client_Cmp_data; static int onion_client_cmp_entry(const void *a, const void *b) { Onion_Client_Cmp_data cmp1; Onion_Client_Cmp_data cmp2; memcpy(&cmp1, a, sizeof(Onion_Client_Cmp_data)); memcpy(&cmp2, b, sizeof(Onion_Client_Cmp_data)); Onion_Node entry1 = cmp1.entry; Onion_Node entry2 = cmp2.entry; const uint8_t *cmp_public_key = cmp1.base_public_key; int t1 = onion_node_timed_out(&entry1, cmp1.mono_time); int t2 = onion_node_timed_out(&entry2, cmp2.mono_time); if (t1 && t2) { return 0; } if (t1) { return -1; } if (t2) { return 1; } int close = id_closest(cmp_public_key, entry1.public_key, entry2.public_key); if (close == 1) { return 1; } if (close == 2) { return -1; } return 0; } static void sort_onion_node_list(Onion_Node *list, unsigned int length, const Mono_Time *mono_time, const uint8_t *comp_public_key) { // Pass comp_public_key to qsort with each Client_data entry, so the // comparison function can use it as the base of comparison. VLA(Onion_Client_Cmp_data, cmp_list, length); for (uint32_t i = 0; i < length; ++i) { cmp_list[i].mono_time = mono_time; cmp_list[i].base_public_key = comp_public_key; cmp_list[i].entry = list[i]; } qsort(cmp_list, length, sizeof(Onion_Client_Cmp_data), onion_client_cmp_entry); for (uint32_t i = 0; i < length; ++i) { list[i] = cmp_list[i].entry; } } static int client_add_to_list(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, IP_Port ip_port, uint8_t is_stored, const uint8_t *pingid_or_key, uint32_t path_used) { if (num > onion_c->num_friends) { return -1; } Onion_Node *list_nodes = nullptr; const uint8_t *reference_id = nullptr; unsigned int list_length; if (num == 0) { list_nodes = onion_c->clients_announce_list; reference_id = nc_get_self_public_key(onion_c->c); list_length = MAX_ONION_CLIENTS_ANNOUNCE; if (is_stored == 1 && public_key_cmp(pingid_or_key, onion_c->temp_public_key) != 0) { is_stored = 0; } } else { if (is_stored >= 2) { return -1; } if (is_stored == 1) { onion_c->friends_list[num - 1].last_seen = mono_time_get(onion_c->mono_time); } list_nodes = onion_c->friends_list[num - 1].clients_list; reference_id = onion_c->friends_list[num - 1].real_public_key; list_length = MAX_ONION_CLIENTS; } sort_onion_node_list(list_nodes, list_length, onion_c->mono_time, reference_id); int index = -1; int stored = 0; unsigned int i; if (onion_node_timed_out(&list_nodes[0], onion_c->mono_time) || id_closest(reference_id, list_nodes[0].public_key, public_key) == 2) { index = 0; } for (i = 0; i < list_length; ++i) { if (public_key_cmp(list_nodes[i].public_key, public_key) == 0) { index = i; stored = 1; break; } } if (index == -1) { return 0; } memcpy(list_nodes[index].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); list_nodes[index].ip_port = ip_port; // TODO(irungentoo): remove this and find a better source of nodes to use for paths. onion_add_path_node(onion_c, ip_port, public_key); if (is_stored == 1) { memcpy(list_nodes[index].data_public_key, pingid_or_key, CRYPTO_PUBLIC_KEY_SIZE); } else { memcpy(list_nodes[index].ping_id, pingid_or_key, ONION_PING_ID_SIZE); } list_nodes[index].is_stored = is_stored; list_nodes[index].timestamp = mono_time_get(onion_c->mono_time); list_nodes[index].unsuccessful_pings = 0; if (!stored) { list_nodes[index].last_pinged = 0; list_nodes[index].added_time = mono_time_get(onion_c->mono_time); } list_nodes[index].path_used = path_used; return 0; } static int good_to_ping(Mono_Time *mono_time, Last_Pinged *last_pinged, uint8_t *last_pinged_index, const uint8_t *public_key) { unsigned int i; for (i = 0; i < MAX_STORED_PINGED_NODES; ++i) { if (!mono_time_is_timeout(mono_time, last_pinged[i].timestamp, MIN_NODE_PING_TIME)) { if (public_key_cmp(last_pinged[i].public_key, public_key) == 0) { return 0; } } } memcpy(last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].timestamp = mono_time_get(mono_time); ++*last_pinged_index; return 1; } static int client_ping_nodes(Onion_Client *onion_c, uint32_t num, const Node_format *nodes, uint16_t num_nodes, IP_Port source) { if (num > onion_c->num_friends) { return -1; } if (num_nodes == 0) { return 0; } Onion_Node *list_nodes = nullptr; const uint8_t *reference_id = nullptr; unsigned int list_length; Last_Pinged *last_pinged = nullptr; uint8_t *last_pinged_index = nullptr; if (num == 0) { list_nodes = onion_c->clients_announce_list; reference_id = nc_get_self_public_key(onion_c->c); list_length = MAX_ONION_CLIENTS_ANNOUNCE; last_pinged = onion_c->last_pinged; last_pinged_index = &onion_c->last_pinged_index; } else { list_nodes = onion_c->friends_list[num - 1].clients_list; reference_id = onion_c->friends_list[num - 1].real_public_key; list_length = MAX_ONION_CLIENTS; last_pinged = onion_c->friends_list[num - 1].last_pinged; last_pinged_index = &onion_c->friends_list[num - 1].last_pinged_index; } const bool lan_ips_accepted = ip_is_lan(source.ip); for (uint32_t i = 0; i < num_nodes; ++i) { if (!lan_ips_accepted) { if (ip_is_lan(nodes[i].ip_port.ip)) { continue; } } if (onion_node_timed_out(&list_nodes[0], onion_c->mono_time) || id_closest(reference_id, list_nodes[0].public_key, nodes[i].public_key) == 2 || onion_node_timed_out(&list_nodes[1], onion_c->mono_time) || id_closest(reference_id, list_nodes[1].public_key, nodes[i].public_key) == 2) { uint32_t j; /* check if node is already in list. */ for (j = 0; j < list_length; ++j) { if (public_key_cmp(list_nodes[j].public_key, nodes[i].public_key) == 0) { break; } } if (j == list_length && good_to_ping(onion_c->mono_time, last_pinged, last_pinged_index, nodes[i].public_key)) { client_send_announce_request(onion_c, num, nodes[i].ip_port, nodes[i].public_key, nullptr, -1); } } } return 0; } static int handle_announce_response(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion_Client *onion_c = (Onion_Client *)object; if (length < ONION_ANNOUNCE_RESPONSE_MIN_SIZE || length > ONION_ANNOUNCE_RESPONSE_MAX_SIZE) { return 1; } uint16_t len_nodes = length - ONION_ANNOUNCE_RESPONSE_MIN_SIZE; uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; IP_Port ip_port; uint32_t path_num; uint32_t num = check_sendback(onion_c, packet + 1, public_key, &ip_port, &path_num); if (num > onion_c->num_friends) { return 1; } VLA(uint8_t, plain, 1 + ONION_PING_ID_SIZE + len_nodes); int len; if (num == 0) { len = decrypt_data(public_key, nc_get_self_secret_key(onion_c->c), packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE, length - (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE), plain); } else { if (onion_c->friends_list[num - 1].status == 0) { return 1; } len = decrypt_data(public_key, onion_c->friends_list[num - 1].temp_secret_key, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE, length - (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + CRYPTO_NONCE_SIZE), plain); } if ((uint32_t)len != SIZEOF_VLA(plain)) { return 1; } uint32_t path_used = set_path_timeouts(onion_c, num, path_num); if (client_add_to_list(onion_c, num, public_key, ip_port, plain[0], plain + 1, path_used) == -1) { return 1; } if (len_nodes != 0) { Node_format nodes[MAX_SENT_NODES]; int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, nullptr, plain + 1 + ONION_PING_ID_SIZE, len_nodes, 0); if (num_nodes <= 0) { return 1; } if (client_ping_nodes(onion_c, num, nodes, num_nodes, source) == -1) { return 1; } } // TODO(irungentoo): LAN vs non LAN ips?, if we are connected only to LAN, are we offline? onion_c->last_packet_recv = mono_time_get(onion_c->mono_time); return 0; } #define DATA_IN_RESPONSE_MIN_SIZE ONION_DATA_IN_RESPONSE_MIN_SIZE static int handle_data_response(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { Onion_Client *onion_c = (Onion_Client *)object; if (length <= (ONION_DATA_RESPONSE_MIN_SIZE + DATA_IN_RESPONSE_MIN_SIZE)) { return 1; } if (length > MAX_DATA_REQUEST_SIZE) { return 1; } VLA(uint8_t, temp_plain, length - ONION_DATA_RESPONSE_MIN_SIZE); int len = decrypt_data(packet + 1 + CRYPTO_NONCE_SIZE, onion_c->temp_secret_key, packet + 1, packet + 1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, length - (1 + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE), temp_plain); if ((uint32_t)len != SIZEOF_VLA(temp_plain)) { return 1; } VLA(uint8_t, plain, SIZEOF_VLA(temp_plain) - DATA_IN_RESPONSE_MIN_SIZE); len = decrypt_data(temp_plain, nc_get_self_secret_key(onion_c->c), packet + 1, temp_plain + CRYPTO_PUBLIC_KEY_SIZE, SIZEOF_VLA(temp_plain) - CRYPTO_PUBLIC_KEY_SIZE, plain); if ((uint32_t)len != SIZEOF_VLA(plain)) { return 1; } if (!onion_c->onion_data_handlers[plain[0]].function) { return 1; } return onion_c->onion_data_handlers[plain[0]].function(onion_c->onion_data_handlers[plain[0]].object, temp_plain, plain, SIZEOF_VLA(plain), userdata); } #define DHTPK_DATA_MIN_LENGTH (1 + sizeof(uint64_t) + CRYPTO_PUBLIC_KEY_SIZE) #define DHTPK_DATA_MAX_LENGTH (DHTPK_DATA_MIN_LENGTH + sizeof(Node_format)*MAX_SENT_NODES) static int handle_dhtpk_announce(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t length, void *userdata) { Onion_Client *onion_c = (Onion_Client *)object; if (length < DHTPK_DATA_MIN_LENGTH) { return 1; } if (length > DHTPK_DATA_MAX_LENGTH) { return 1; } int friend_num = onion_friend_num(onion_c, source_pubkey); if (friend_num == -1) { return 1; } uint64_t no_replay; net_unpack_u64(data + 1, &no_replay); if (no_replay <= onion_c->friends_list[friend_num].last_noreplay) { return 1; } onion_c->friends_list[friend_num].last_noreplay = no_replay; if (onion_c->friends_list[friend_num].dht_pk_callback) { onion_c->friends_list[friend_num].dht_pk_callback(onion_c->friends_list[friend_num].dht_pk_callback_object, onion_c->friends_list[friend_num].dht_pk_callback_number, data + 1 + sizeof(uint64_t), userdata); } onion_set_friend_DHT_pubkey(onion_c, friend_num, data + 1 + sizeof(uint64_t)); onion_c->friends_list[friend_num].last_seen = mono_time_get(onion_c->mono_time); uint16_t len_nodes = length - DHTPK_DATA_MIN_LENGTH; if (len_nodes != 0) { Node_format nodes[MAX_SENT_NODES]; int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, nullptr, data + 1 + sizeof(uint64_t) + CRYPTO_PUBLIC_KEY_SIZE, len_nodes, 1); if (num_nodes <= 0) { return 1; } int i; for (i = 0; i < num_nodes; ++i) { const Family family = nodes[i].ip_port.ip.family; if (net_family_is_ipv4(family) || net_family_is_ipv6(family)) { dht_getnodes(onion_c->dht, &nodes[i].ip_port, nodes[i].public_key, onion_c->friends_list[friend_num].dht_public_key); } else if (net_family_is_tcp_ipv4(family) || net_family_is_tcp_ipv6(family)) { if (onion_c->friends_list[friend_num].tcp_relay_node_callback) { void *obj = onion_c->friends_list[friend_num].tcp_relay_node_callback_object; uint32_t number = onion_c->friends_list[friend_num].tcp_relay_node_callback_number; onion_c->friends_list[friend_num].tcp_relay_node_callback(obj, number, nodes[i].ip_port, nodes[i].public_key); } } } } return 0; } static int handle_tcp_onion(void *object, const uint8_t *data, uint16_t length, void *userdata) { if (length == 0) { return 1; } IP_Port ip_port = {{{0}}}; ip_port.ip.family = net_family_tcp_family; if (data[0] == NET_PACKET_ANNOUNCE_RESPONSE) { return handle_announce_response(object, ip_port, data, length, userdata); } if (data[0] == NET_PACKET_ONION_DATA_RESPONSE) { return handle_data_response(object, ip_port, data, length, userdata); } return 1; } /* Send data of length length to friendnum. * This data will be received by the friend using the onion_data_handlers callbacks. * * Even if this function succeeds, the friend might not receive any data. * * return the number of packets sent on success * return -1 on failure. */ int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length) { if ((uint32_t)friend_num >= onion_c->num_friends) { return -1; } if (length + DATA_IN_RESPONSE_MIN_SIZE > MAX_DATA_REQUEST_SIZE) { return -1; } if (length == 0) { return -1; } unsigned int good_nodes[MAX_ONION_CLIENTS]; unsigned int num_good = 0; unsigned int num_nodes = 0; Onion_Node *list_nodes = onion_c->friends_list[friend_num].clients_list; for (unsigned int i = 0; i < MAX_ONION_CLIENTS; ++i) { if (onion_node_timed_out(&list_nodes[i], onion_c->mono_time)) { continue; } ++num_nodes; if (list_nodes[i].is_stored) { good_nodes[num_good] = i; ++num_good; } } if (num_good < (num_nodes - 1) / 4 + 1) { return -1; } uint8_t nonce[CRYPTO_NONCE_SIZE]; random_nonce(nonce); VLA(uint8_t, packet, DATA_IN_RESPONSE_MIN_SIZE + length); memcpy(packet, nc_get_self_public_key(onion_c->c), CRYPTO_PUBLIC_KEY_SIZE); int len = encrypt_data(onion_c->friends_list[friend_num].real_public_key, nc_get_self_secret_key(onion_c->c), nonce, data, length, packet + CRYPTO_PUBLIC_KEY_SIZE); if ((uint32_t)len + CRYPTO_PUBLIC_KEY_SIZE != SIZEOF_VLA(packet)) { return -1; } unsigned int good = 0; for (unsigned int i = 0; i < num_good; ++i) { Onion_Path path; if (random_path(onion_c, &onion_c->onion_paths_friends, -1, &path) == -1) { continue; } uint8_t o_packet[ONION_MAX_PACKET_SIZE]; len = create_data_request(o_packet, sizeof(o_packet), onion_c->friends_list[friend_num].real_public_key, list_nodes[good_nodes[i]].data_public_key, nonce, packet, SIZEOF_VLA(packet)); if (len == -1) { continue; } if (send_onion_packet_tcp_udp(onion_c, &path, list_nodes[good_nodes[i]].ip_port, o_packet, len) == 0) { ++good; } } return good; } /* Try to send the dht public key via the DHT instead of onion * * Even if this function succeeds, the friend might not receive any data. * * return the number of packets sent on success * return -1 on failure. */ static int send_dht_dhtpk(const Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length) { if ((uint32_t)friend_num >= onion_c->num_friends) { return -1; } if (!onion_c->friends_list[friend_num].know_dht_public_key) { return -1; } uint8_t nonce[CRYPTO_NONCE_SIZE]; random_nonce(nonce); VLA(uint8_t, temp, DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE + length); memcpy(temp, nc_get_self_public_key(onion_c->c), CRYPTO_PUBLIC_KEY_SIZE); memcpy(temp + CRYPTO_PUBLIC_KEY_SIZE, nonce, CRYPTO_NONCE_SIZE); int len = encrypt_data(onion_c->friends_list[friend_num].real_public_key, nc_get_self_secret_key(onion_c->c), nonce, data, length, temp + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); if ((uint32_t)len + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE != SIZEOF_VLA(temp)) { return -1; } uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; len = create_request(dht_get_self_public_key(onion_c->dht), dht_get_self_secret_key(onion_c->dht), packet, onion_c->friends_list[friend_num].dht_public_key, temp, SIZEOF_VLA(temp), CRYPTO_PACKET_DHTPK); if (len == -1) { return -1; } return route_tofriend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, packet, len); } static int handle_dht_dhtpk(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet, uint16_t length, void *userdata) { Onion_Client *onion_c = (Onion_Client *)object; if (length < DHTPK_DATA_MIN_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE) { return 1; } if (length > DHTPK_DATA_MAX_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE) { return 1; } uint8_t plain[DHTPK_DATA_MAX_LENGTH]; int len = decrypt_data(packet, nc_get_self_secret_key(onion_c->c), packet + CRYPTO_PUBLIC_KEY_SIZE, packet + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, length - (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE), plain); if (len != length - (DATA_IN_RESPONSE_MIN_SIZE + CRYPTO_NONCE_SIZE)) { return 1; } if (public_key_cmp(source_pubkey, plain + 1 + sizeof(uint64_t)) != 0) { return 1; } return handle_dhtpk_announce(onion_c, packet, plain, len, userdata); } /* Send the packets to tell our friends what our DHT public key is. * * if onion_dht_both is 0, use only the onion to send the packet. * if it is 1, use only the dht. * if it is something else, use both. * * return the number of packets sent on success * return -1 on failure. */ static int send_dhtpk_announce(Onion_Client *onion_c, uint16_t friend_num, uint8_t onion_dht_both) { if (friend_num >= onion_c->num_friends) { return -1; } uint8_t data[DHTPK_DATA_MAX_LENGTH]; data[0] = ONION_DATA_DHTPK; const uint64_t no_replay = mono_time_get(onion_c->mono_time); net_pack_u64(data + 1, no_replay); memcpy(data + 1 + sizeof(uint64_t), dht_get_self_public_key(onion_c->dht), CRYPTO_PUBLIC_KEY_SIZE); Node_format nodes[MAX_SENT_NODES]; uint16_t num_relays = copy_connected_tcp_relays(onion_c->c, nodes, (MAX_SENT_NODES / 2)); uint16_t num_nodes = closelist_nodes(onion_c->dht, &nodes[num_relays], MAX_SENT_NODES - num_relays); num_nodes += num_relays; int nodes_len = 0; if (num_nodes != 0) { nodes_len = pack_nodes(data + DHTPK_DATA_MIN_LENGTH, DHTPK_DATA_MAX_LENGTH - DHTPK_DATA_MIN_LENGTH, nodes, num_nodes); if (nodes_len <= 0) { return -1; } } int num1 = -1; int num2 = -1; if (onion_dht_both != 1) { num1 = send_onion_data(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len); } if (onion_dht_both != 0) { num2 = send_dht_dhtpk(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len); } if (num1 == -1) { return num2; } if (num2 == -1) { return num1; } return num1 + num2; } /* Get the friend_num of a friend. * * return -1 on failure. * return friend number on success. */ int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key) { unsigned int i; for (i = 0; i < onion_c->num_friends; ++i) { if (onion_c->friends_list[i].status == 0) { continue; } if (public_key_cmp(public_key, onion_c->friends_list[i].real_public_key) == 0) { return i; } } return -1; } /* Set the size of the friend list to num. * * return -1 if realloc fails. * return 0 if it succeeds. */ static int realloc_onion_friends(Onion_Client *onion_c, uint32_t num) { if (num == 0) { free(onion_c->friends_list); onion_c->friends_list = nullptr; return 0; } Onion_Friend *newonion_friends = (Onion_Friend *)realloc(onion_c->friends_list, num * sizeof(Onion_Friend)); if (newonion_friends == nullptr) { return -1; } onion_c->friends_list = newonion_friends; return 0; } /* Add a friend who we want to connect to. * * return -1 on failure. * return the friend number on success or if the friend was already added. */ int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key) { int num = onion_friend_num(onion_c, public_key); if (num != -1) { return num; } unsigned int index = -1; for (unsigned int i = 0; i < onion_c->num_friends; ++i) { if (onion_c->friends_list[i].status == 0) { index = i; break; } } if (index == (uint32_t) -1) { if (realloc_onion_friends(onion_c, onion_c->num_friends + 1) == -1) { return -1; } index = onion_c->num_friends; memset(&onion_c->friends_list[onion_c->num_friends], 0, sizeof(Onion_Friend)); ++onion_c->num_friends; } onion_c->friends_list[index].status = 1; memcpy(onion_c->friends_list[index].real_public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); crypto_new_keypair(onion_c->friends_list[index].temp_public_key, onion_c->friends_list[index].temp_secret_key); return index; } /* Delete a friend. * * return -1 on failure. * return the deleted friend number on success. */ int onion_delfriend(Onion_Client *onion_c, int friend_num) { if ((uint32_t)friend_num >= onion_c->num_friends) { return -1; } #if 0 if (onion_c->friends_list[friend_num].know_dht_public_key) { dht_delfriend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, 0); } #endif crypto_memzero(&onion_c->friends_list[friend_num], sizeof(Onion_Friend)); unsigned int i; for (i = onion_c->num_friends; i != 0; --i) { if (onion_c->friends_list[i - 1].status != 0) { break; } } if (onion_c->num_friends != i) { onion_c->num_friends = i; realloc_onion_friends(onion_c, onion_c->num_friends); } return friend_num; } /* Set the function for this friend that will be callbacked with object and number * when that friends gives us one of the TCP relays he is connected to. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num, recv_tcp_relay_cb *callback, void *object, uint32_t number) { if ((uint32_t)friend_num >= onion_c->num_friends) { return -1; } onion_c->friends_list[friend_num].tcp_relay_node_callback = callback; onion_c->friends_list[friend_num].tcp_relay_node_callback_object = object; onion_c->friends_list[friend_num].tcp_relay_node_callback_number = number; return 0; } /* Set the function for this friend that will be callbacked with object and number * when that friend gives us his DHT temporary public key. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num, onion_dht_pk_cb *function, void *object, uint32_t number) { if ((uint32_t)friend_num >= onion_c->num_friends) { return -1; } onion_c->friends_list[friend_num].dht_pk_callback = function; onion_c->friends_list[friend_num].dht_pk_callback_object = object; onion_c->friends_list[friend_num].dht_pk_callback_number = number; return 0; } /* Set a friend's DHT public key. * * return -1 on failure. * return 0 on success. */ int onion_set_friend_DHT_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key) { if ((uint32_t)friend_num >= onion_c->num_friends) { return -1; } if (onion_c->friends_list[friend_num].status == 0) { return -1; } if (onion_c->friends_list[friend_num].know_dht_public_key) { if (public_key_cmp(dht_key, onion_c->friends_list[friend_num].dht_public_key) == 0) { return -1; } } onion_c->friends_list[friend_num].last_seen = mono_time_get(onion_c->mono_time); onion_c->friends_list[friend_num].know_dht_public_key = 1; memcpy(onion_c->friends_list[friend_num].dht_public_key, dht_key, CRYPTO_PUBLIC_KEY_SIZE); return 0; } /* Copy friends DHT public key into dht_key. * * return 0 on failure (no key copied). * return 1 on success (key copied). */ unsigned int onion_getfriend_DHT_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key) { if ((uint32_t)friend_num >= onion_c->num_friends) { return 0; } if (onion_c->friends_list[friend_num].status == 0) { return 0; } if (!onion_c->friends_list[friend_num].know_dht_public_key) { return 0; } memcpy(dht_key, onion_c->friends_list[friend_num].dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); return 1; } /* Get the ip of friend friendnum and put it in ip_port * * return -1, -- if public_key does NOT refer to a friend * return 0, -- if public_key refers to a friend and we failed to find the friend (yet) * return 1, ip if public_key refers to a friend and we found him * */ int onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port) { uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; if (onion_getfriend_DHT_pubkey(onion_c, friend_num, dht_public_key) == 0) { return -1; } return dht_getfriendip(onion_c->dht, dht_public_key, ip_port); } /* Set if friend is online or not. * NOTE: This function is there and should be used so that we don't send useless packets to the friend if he is online. * * is_online 1 means friend is online. * is_online 0 means friend is offline * * return -1 on failure. * return 0 on success. */ int onion_set_friend_online(Onion_Client *onion_c, int friend_num, uint8_t is_online) { if ((uint32_t)friend_num >= onion_c->num_friends) { return -1; } if (is_online == 0 && onion_c->friends_list[friend_num].is_online == 1) { onion_c->friends_list[friend_num].last_seen = mono_time_get(onion_c->mono_time); } onion_c->friends_list[friend_num].is_online = is_online; /* This should prevent some clock related issues */ if (!is_online) { onion_c->friends_list[friend_num].last_noreplay = 0; onion_c->friends_list[friend_num].run_count = 0; } return 0; } static void populate_path_nodes(Onion_Client *onion_c) { Node_format nodes_list[MAX_FRIEND_CLIENTS]; unsigned int num_nodes = randfriends_nodes(onion_c->dht, nodes_list, MAX_FRIEND_CLIENTS); unsigned int i; for (i = 0; i < num_nodes; ++i) { onion_add_path_node(onion_c, nodes_list[i].ip_port, nodes_list[i].public_key); } } static void populate_path_nodes_tcp(Onion_Client *onion_c) { Node_format nodes_list[MAX_SENT_NODES]; unsigned int num_nodes = copy_connected_tcp_relays(onion_c->c, nodes_list, MAX_SENT_NODES); unsigned int i; for (i = 0; i < num_nodes; ++i) { onion_add_bs_path_node(onion_c, nodes_list[i].ip_port, nodes_list[i].public_key); } } #define ANNOUNCE_FRIEND (ONION_NODE_PING_INTERVAL * 6) #define ANNOUNCE_FRIEND_BEGINNING 3 #define RUN_COUNT_FRIEND_ANNOUNCE_BEGINNING 17 #define ONION_FRIEND_BACKOFF_FACTOR 4 #define ONION_FRIEND_MAX_PING_INTERVAL (5*60*MAX_ONION_CLIENTS) static void do_friend(Onion_Client *onion_c, uint16_t friendnum) { if (friendnum >= onion_c->num_friends) { return; } if (onion_c->friends_list[friendnum].status == 0) { return; } unsigned int interval = ANNOUNCE_FRIEND; if (onion_c->friends_list[friendnum].run_count < RUN_COUNT_FRIEND_ANNOUNCE_BEGINNING) { interval = ANNOUNCE_FRIEND_BEGINNING; } else { if (onion_c->friends_list[friendnum].last_seen == 0) { onion_c->friends_list[friendnum].last_seen = mono_time_get(onion_c->mono_time); } uint64_t backoff_interval = (mono_time_get(onion_c->mono_time) - onion_c->friends_list[friendnum].last_seen) / ONION_FRIEND_BACKOFF_FACTOR; if (backoff_interval > ONION_FRIEND_MAX_PING_INTERVAL) { backoff_interval = ONION_FRIEND_MAX_PING_INTERVAL; } if (interval < backoff_interval) { interval = backoff_interval; } } if (!onion_c->friends_list[friendnum].is_online) { unsigned int count = 0; Onion_Node *list_nodes = onion_c->friends_list[friendnum].clients_list; // ensure we get a response from some node roughly once per // (interval / MAX_ONION_CLIENTS) bool ping_random = true; for (unsigned i = 0; i < MAX_ONION_CLIENTS; ++i) { if (!(mono_time_is_timeout(onion_c->mono_time, list_nodes[i].timestamp, interval / MAX_ONION_CLIENTS) && mono_time_is_timeout(onion_c->mono_time, list_nodes[i].last_pinged, ONION_NODE_PING_INTERVAL))) { ping_random = false; break; } } for (unsigned i = 0; i < MAX_ONION_CLIENTS; ++i) { if (onion_node_timed_out(&list_nodes[i], onion_c->mono_time)) { continue; } ++count; if (list_nodes[i].last_pinged == 0) { list_nodes[i].last_pinged = mono_time_get(onion_c->mono_time); continue; } if (list_nodes[i].unsuccessful_pings >= ONION_NODE_MAX_PINGS) { continue; } if (mono_time_is_timeout(onion_c->mono_time, list_nodes[i].last_pinged, interval) || (ping_random && random_u32() % (MAX_ONION_CLIENTS - i) == 0)) { if (client_send_announce_request(onion_c, friendnum + 1, list_nodes[i].ip_port, list_nodes[i].public_key, nullptr, -1) == 0) { list_nodes[i].last_pinged = mono_time_get(onion_c->mono_time); ++list_nodes[i].unsuccessful_pings; ping_random = false; } } } if (count != MAX_ONION_CLIENTS) { const uint16_t num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES); uint16_t n = num_nodes; if (num_nodes > (MAX_ONION_CLIENTS / 2)) { n = (MAX_ONION_CLIENTS / 2); } if (count <= random_u32() % MAX_ONION_CLIENTS) { if (num_nodes != 0) { unsigned int j; for (j = 0; j < n; ++j) { const uint32_t num = random_u32() % num_nodes; client_send_announce_request(onion_c, friendnum + 1, onion_c->path_nodes[num].ip_port, onion_c->path_nodes[num].public_key, nullptr, -1); } ++onion_c->friends_list[friendnum].run_count; } } } else { ++onion_c->friends_list[friendnum].run_count; } /* send packets to friend telling them our DHT public key. */ if (mono_time_is_timeout(onion_c->mono_time, onion_c->friends_list[friendnum].last_dht_pk_onion_sent, ONION_DHTPK_SEND_INTERVAL)) { if (send_dhtpk_announce(onion_c, friendnum, 0) >= 1) { onion_c->friends_list[friendnum].last_dht_pk_onion_sent = mono_time_get(onion_c->mono_time); } } if (mono_time_is_timeout(onion_c->mono_time, onion_c->friends_list[friendnum].last_dht_pk_dht_sent, DHT_DHTPK_SEND_INTERVAL)) { if (send_dhtpk_announce(onion_c, friendnum, 1) >= 1) { onion_c->friends_list[friendnum].last_dht_pk_dht_sent = mono_time_get(onion_c->mono_time); } } } } /* Function to call when onion data packet with contents beginning with byte is received. */ void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_cb *cb, void *object) { onion_c->onion_data_handlers[byte].function = cb; onion_c->onion_data_handlers[byte].object = object; } #define ANNOUNCE_INTERVAL_NOT_ANNOUNCED 3 #define ANNOUNCE_INTERVAL_ANNOUNCED ONION_NODE_PING_INTERVAL #define TIME_TO_STABLE (ONION_NODE_PING_INTERVAL * 6) #define ANNOUNCE_INTERVAL_STABLE (ONION_NODE_PING_INTERVAL * 8) static void do_announce(Onion_Client *onion_c) { unsigned int count = 0; Onion_Node *list_nodes = onion_c->clients_announce_list; for (unsigned int i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) { if (onion_node_timed_out(&list_nodes[i], onion_c->mono_time)) { continue; } ++count; /* Don't announce ourselves the first time this is run to new peers */ if (list_nodes[i].last_pinged == 0) { list_nodes[i].last_pinged = 1; continue; } if (list_nodes[i].unsuccessful_pings >= ONION_NODE_MAX_PINGS) { continue; } unsigned int interval = ANNOUNCE_INTERVAL_NOT_ANNOUNCED; if (list_nodes[i].is_stored && path_exists(onion_c->mono_time, &onion_c->onion_paths_self, list_nodes[i].path_used)) { interval = ANNOUNCE_INTERVAL_ANNOUNCED; uint32_t pathnum = list_nodes[i].path_used % NUMBER_ONION_PATHS; /* A node/path is considered "stable", and can be pinged less * aggressively, if it has survived for at least TIME_TO_STABLE * and the latest packets sent to it are not timing out. */ if (mono_time_is_timeout(onion_c->mono_time, list_nodes[i].added_time, TIME_TO_STABLE) && !(list_nodes[i].unsuccessful_pings > 0 && mono_time_is_timeout(onion_c->mono_time, list_nodes[i].last_pinged, ONION_NODE_TIMEOUT)) && mono_time_is_timeout(onion_c->mono_time, onion_c->onion_paths_self.path_creation_time[pathnum], TIME_TO_STABLE) && !(onion_c->onion_paths_self.last_path_used_times[pathnum] > 0 && mono_time_is_timeout(onion_c->mono_time, onion_c->onion_paths_self.last_path_used[pathnum], ONION_PATH_TIMEOUT))) { interval = ANNOUNCE_INTERVAL_STABLE; } } if (mono_time_is_timeout(onion_c->mono_time, list_nodes[i].last_pinged, interval) || (mono_time_is_timeout(onion_c->mono_time, onion_c->last_announce, ONION_NODE_PING_INTERVAL) && random_u32() % (MAX_ONION_CLIENTS_ANNOUNCE - i) == 0)) { uint32_t path_to_use = list_nodes[i].path_used; if (list_nodes[i].unsuccessful_pings == ONION_NODE_MAX_PINGS - 1 && mono_time_is_timeout(onion_c->mono_time, list_nodes[i].added_time, TIME_TO_STABLE)) { /* Last chance for a long-lived node - try a random path */ path_to_use = -1; } if (client_send_announce_request(onion_c, 0, list_nodes[i].ip_port, list_nodes[i].public_key, list_nodes[i].ping_id, path_to_use) == 0) { list_nodes[i].last_pinged = mono_time_get(onion_c->mono_time); ++list_nodes[i].unsuccessful_pings; onion_c->last_announce = mono_time_get(onion_c->mono_time); } } } if (count != MAX_ONION_CLIENTS_ANNOUNCE) { uint16_t num_nodes; Node_format *path_nodes; if (random_u08() % 2 == 0 || onion_c->path_nodes_index == 0) { num_nodes = min_u16(onion_c->path_nodes_index_bs, MAX_PATH_NODES); path_nodes = onion_c->path_nodes_bs; } else { num_nodes = min_u16(onion_c->path_nodes_index, MAX_PATH_NODES); path_nodes = onion_c->path_nodes; } if (count <= random_u32() % MAX_ONION_CLIENTS_ANNOUNCE) { if (num_nodes != 0) { for (unsigned int i = 0; i < (MAX_ONION_CLIENTS_ANNOUNCE / 2); ++i) { const uint32_t num = random_u32() % num_nodes; client_send_announce_request(onion_c, 0, path_nodes[num].ip_port, path_nodes[num].public_key, nullptr, -1); } } } } } /* return 0 if we are not connected to the network. * return 1 if we are. */ static int onion_isconnected(const Onion_Client *onion_c) { unsigned int num = 0; unsigned int announced = 0; if (mono_time_is_timeout(onion_c->mono_time, onion_c->last_packet_recv, ONION_OFFLINE_TIMEOUT)) { return 0; } if (onion_c->path_nodes_index == 0) { return 0; } for (unsigned int i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) { if (!onion_node_timed_out(&onion_c->clients_announce_list[i], onion_c->mono_time)) { ++num; if (onion_c->clients_announce_list[i].is_stored) { ++announced; } } } unsigned int pnodes = onion_c->path_nodes_index; if (pnodes > MAX_ONION_CLIENTS_ANNOUNCE) { pnodes = MAX_ONION_CLIENTS_ANNOUNCE; } /* Consider ourselves online if we are announced to half or more nodes * we are connected to */ if (num && announced) { if ((num / 2) <= announced && (pnodes / 2) <= num) { return 1; } } return 0; } #define ONION_CONNECTION_SECONDS 3 /* return 0 if we are not connected to the network. * return 1 if we are connected with TCP only. * return 2 if we are also connected with UDP. */ unsigned int onion_connection_status(const Onion_Client *onion_c) { if (onion_c->onion_connected >= ONION_CONNECTION_SECONDS) { if (onion_c->udp_connected) { return 2; } return 1; } return 0; } void do_onion_client(Onion_Client *onion_c) { if (onion_c->last_run == mono_time_get(onion_c->mono_time)) { return; } if (mono_time_is_timeout(onion_c->mono_time, onion_c->first_run, ONION_CONNECTION_SECONDS)) { populate_path_nodes(onion_c); do_announce(onion_c); } if (onion_isconnected(onion_c)) { if (onion_c->onion_connected < ONION_CONNECTION_SECONDS * 2) { ++onion_c->onion_connected; } } else { populate_path_nodes_tcp(onion_c); if (onion_c->onion_connected != 0) { --onion_c->onion_connected; } } onion_c->udp_connected = dht_non_lan_connected(onion_c->dht); if (mono_time_is_timeout(onion_c->mono_time, onion_c->first_run, ONION_CONNECTION_SECONDS * 2)) { set_tcp_onion_status(nc_get_tcp_c(onion_c->c), !onion_c->udp_connected); } if (onion_connection_status(onion_c)) { for (unsigned i = 0; i < onion_c->num_friends; ++i) { do_friend(onion_c, i); } } if (onion_c->last_run == 0) { onion_c->first_run = mono_time_get(onion_c->mono_time); } onion_c->last_run = mono_time_get(onion_c->mono_time); } Onion_Client *new_onion_client(const Logger *logger, Mono_Time *mono_time, Net_Crypto *c) { if (c == nullptr) { return nullptr; } Onion_Client *onion_c = (Onion_Client *)calloc(1, sizeof(Onion_Client)); if (onion_c == nullptr) { return nullptr; } onion_c->announce_ping_array = ping_array_new(ANNOUNCE_ARRAY_SIZE, ANNOUNCE_TIMEOUT); if (onion_c->announce_ping_array == nullptr) { free(onion_c); return nullptr; } onion_c->mono_time = mono_time; onion_c->logger = logger; onion_c->dht = nc_get_dht(c); onion_c->net = dht_get_net(onion_c->dht); onion_c->c = c; new_symmetric_key(onion_c->secret_symmetric_key); crypto_new_keypair(onion_c->temp_public_key, onion_c->temp_secret_key); networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_announce_response, onion_c); networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, &handle_data_response, onion_c); oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, &handle_dhtpk_announce, onion_c); cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, &handle_dht_dhtpk, onion_c); set_onion_packet_tcp_connection_callback(nc_get_tcp_c(onion_c->c), &handle_tcp_onion, onion_c); return onion_c; } void kill_onion_client(Onion_Client *onion_c) { if (onion_c == nullptr) { return; } ping_array_kill(onion_c->announce_ping_array); realloc_onion_friends(onion_c, 0); networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, nullptr, nullptr); networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, nullptr, nullptr); oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, nullptr, nullptr); cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, nullptr, nullptr); set_onion_packet_tcp_connection_callback(nc_get_tcp_c(onion_c->c), nullptr, nullptr); crypto_memzero(onion_c, sizeof(Onion_Client)); free(onion_c); } c-toxcore-0.2.13/toxcore/onion_client.h000066400000000000000000000143471415350724400200320ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Implementation of the client part of docs/Prevent_Tracking.txt (The part that * uses the onion stuff to connect to the friend) */ #ifndef C_TOXCORE_TOXCORE_ONION_CLIENT_H #define C_TOXCORE_TOXCORE_ONION_CLIENT_H #include "net_crypto.h" #include "onion_announce.h" #include "ping_array.h" #define MAX_ONION_CLIENTS 8 #define MAX_ONION_CLIENTS_ANNOUNCE 12 // Number of nodes to announce ourselves to. #define ONION_NODE_PING_INTERVAL 15 #define ONION_NODE_TIMEOUT ONION_NODE_PING_INTERVAL /* The interval in seconds at which to tell our friends where we are */ #define ONION_DHTPK_SEND_INTERVAL 30 #define DHT_DHTPK_SEND_INTERVAL 20 #define NUMBER_ONION_PATHS 6 /* The timeout the first time the path is added and * then for all the next consecutive times */ #define ONION_PATH_FIRST_TIMEOUT 4 #define ONION_PATH_TIMEOUT 10 #define ONION_PATH_MAX_LIFETIME 1200 #define ONION_PATH_MAX_NO_RESPONSE_USES 4 #define MAX_STORED_PINGED_NODES 9 #define MIN_NODE_PING_TIME 10 #define ONION_NODE_MAX_PINGS 3 #define MAX_PATH_NODES 32 /* If no announce response packets are received within this interval tox will * be considered offline. We give time for a node to be pinged often enough * that it times out, which leads to the network being thoroughly tested as it * is replaced. */ #define ONION_OFFLINE_TIMEOUT (ONION_NODE_PING_INTERVAL * (ONION_NODE_MAX_PINGS+2)) /* Onion data packet ids. */ #define ONION_DATA_FRIEND_REQ CRYPTO_PACKET_FRIEND_REQ #define ONION_DATA_DHTPK CRYPTO_PACKET_DHTPK typedef struct Onion_Client Onion_Client; DHT *onion_get_dht(const Onion_Client *onion_c); Net_Crypto *onion_get_net_crypto(const Onion_Client *onion_c); /* Add a node to the path_nodes bootstrap array. * * return -1 on failure * return 0 on success */ int onion_add_bs_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key); /* Put up to max_num nodes in nodes. * * return the number of nodes. */ uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num); /* Add a friend who we want to connect to. * * return -1 on failure. * return the friend number on success or if the friend was already added. */ int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key); /* Add a friend who we want to connect to. * * return -1 on failure. * return the friend number on success. */ int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key); /* Delete a friend. * * return -1 on failure. * return the deleted friend number on success. */ int onion_delfriend(Onion_Client *onion_c, int friend_num); /* Set if friend is online or not. * NOTE: This function is there and should be used so that we don't send useless packets to the friend if he is online. * * is_online 1 means friend is online. * is_online 0 means friend is offline * * return -1 on failure. * return 0 on success. */ int onion_set_friend_online(Onion_Client *onion_c, int friend_num, uint8_t is_online); /* Get the ip of friend friendnum and put it in ip_port * * return -1, -- if public_key does NOT refer to a friend * return 0, -- if public_key refers to a friend and we failed to find the friend (yet) * return 1, ip if public_key refers to a friend and we found him * */ int onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port); typedef int recv_tcp_relay_cb(void *object, uint32_t number, IP_Port ip_port, const uint8_t *public_key); /* Set the function for this friend that will be callbacked with object and number * when that friends gives us one of the TCP relays he is connected to. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num, recv_tcp_relay_cb *callback, void *object, uint32_t number); typedef void onion_dht_pk_cb(void *data, int32_t number, const uint8_t *dht_public_key, void *userdata); /* Set the function for this friend that will be callbacked with object and number * when that friend gives us his DHT temporary public key. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num, onion_dht_pk_cb *function, void *object, uint32_t number); /* Set a friends DHT public key. * * return -1 on failure. * return 0 on success. */ int onion_set_friend_DHT_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key); /* Copy friends DHT public key into dht_key. * * return 0 on failure (no key copied). * return 1 on success (key copied). */ unsigned int onion_getfriend_DHT_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key); #define ONION_DATA_IN_RESPONSE_MIN_SIZE (CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE) #define ONION_CLIENT_MAX_DATA_SIZE (MAX_DATA_REQUEST_SIZE - ONION_DATA_IN_RESPONSE_MIN_SIZE) /* Send data of length length to friendnum. * Maximum length of data is ONION_CLIENT_MAX_DATA_SIZE. * This data will be received by the friend using the Onion_Data_Handlers callbacks. * * Even if this function succeeds, the friend might not receive any data. * * return the number of packets sent on success * return -1 on failure. */ int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length); typedef int oniondata_handler_cb(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t len, void *userdata); /* Function to call when onion data packet with contents beginning with byte is received. */ void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_cb *cb, void *object); void do_onion_client(Onion_Client *onion_c); Onion_Client *new_onion_client(const Logger *logger, Mono_Time *mono_time, Net_Crypto *c); void kill_onion_client(Onion_Client *onion_c); /* return 0 if we are not connected to the network. * return 1 if we are connected with TCP only. * return 2 if we are also connected with UDP. */ unsigned int onion_connection_status(const Onion_Client *onion_c); #endif c-toxcore-0.2.13/toxcore/ping.api.h000066400000000000000000000022001415350724400170400ustar00rootroot00000000000000%{ /* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. * Copyright © 2013 plutooo */ /* * Buffered pinging using cyclic arrays. */ #ifndef C_TOXCORE_TOXCORE_PING_H #define C_TOXCORE_TOXCORE_PING_H #include "DHT.h" #include "network.h" #include %} class iP_Port { struct this; } class dHT { struct this; } class mono_Time { struct this; } class ping { struct this; static this new(const mono_Time::this *mono_time, dHT::this *dht); void kill(); /** Add nodes to the to_ping list. * All nodes in this list are pinged every TIME_TOPING seconds * and are then removed from the list. * If the list is full the nodes farthest from our public_key are replaced. * The purpose of this list is to enable quick integration of new nodes into the * network while preventing amplification attacks. * * return 0 if node was added. * return -1 if node was not added. */ int32_t add(const uint8_t *public_key, iP_Port::this ip_port); void iterate(); int32_t send_request(iP_Port::this ipp, const uint8_t *public_key); } %{ #endif // C_TOXCORE_TOXCORE_PING_H %} c-toxcore-0.2.13/toxcore/ping.c000066400000000000000000000244061415350724400162770ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. * Copyright © 2013 plutooo */ /* * Buffered pinging using cyclic arrays. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ping.h" #include #include #include "DHT.h" #include "mono_time.h" #include "network.h" #include "ping_array.h" #include "util.h" #define PING_NUM_MAX 512 /* Maximum newly announced nodes to ping per TIME_TO_PING seconds. */ #define MAX_TO_PING 32 /* Ping newly announced nodes to ping per TIME_TO_PING seconds*/ #define TIME_TO_PING 2 struct Ping { const Mono_Time *mono_time; DHT *dht; Ping_Array *ping_array; Node_format to_ping[MAX_TO_PING]; uint64_t last_to_ping; }; #define PING_PLAIN_SIZE (1 + sizeof(uint64_t)) #define DHT_PING_SIZE (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE + PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) #define PING_DATA_SIZE (CRYPTO_PUBLIC_KEY_SIZE + sizeof(IP_Port)) int32_t ping_send_request(Ping *ping, IP_Port ipp, const uint8_t *public_key) { uint8_t pk[DHT_PING_SIZE]; int rc; uint64_t ping_id; if (id_equal(public_key, dht_get_self_public_key(ping->dht))) { return 1; } uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; // generate key to encrypt ping_id with recipient privkey dht_get_shared_key_sent(ping->dht, shared_key, public_key); // Generate random ping_id. uint8_t data[PING_DATA_SIZE]; id_copy(data, public_key); memcpy(data + CRYPTO_PUBLIC_KEY_SIZE, &ipp, sizeof(IP_Port)); ping_id = ping_array_add(ping->ping_array, ping->mono_time, data, sizeof(data)); if (ping_id == 0) { return 1; } uint8_t ping_plain[PING_PLAIN_SIZE]; ping_plain[0] = NET_PACKET_PING_REQUEST; memcpy(ping_plain + 1, &ping_id, sizeof(ping_id)); pk[0] = NET_PACKET_PING_REQUEST; id_copy(pk + 1, dht_get_self_public_key(ping->dht)); // Our pubkey random_nonce(pk + 1 + CRYPTO_PUBLIC_KEY_SIZE); // Generate new nonce rc = encrypt_data_symmetric(shared_key, pk + 1 + CRYPTO_PUBLIC_KEY_SIZE, ping_plain, sizeof(ping_plain), pk + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); if (rc != PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) { return 1; } return sendpacket(dht_get_net(ping->dht), ipp, pk, sizeof(pk)); } static int ping_send_response(Ping *ping, IP_Port ipp, const uint8_t *public_key, uint64_t ping_id, uint8_t *shared_encryption_key) { uint8_t pk[DHT_PING_SIZE]; int rc; if (id_equal(public_key, dht_get_self_public_key(ping->dht))) { return 1; } uint8_t ping_plain[PING_PLAIN_SIZE]; ping_plain[0] = NET_PACKET_PING_RESPONSE; memcpy(ping_plain + 1, &ping_id, sizeof(ping_id)); pk[0] = NET_PACKET_PING_RESPONSE; id_copy(pk + 1, dht_get_self_public_key(ping->dht)); // Our pubkey random_nonce(pk + 1 + CRYPTO_PUBLIC_KEY_SIZE); // Generate new nonce // Encrypt ping_id using recipient privkey rc = encrypt_data_symmetric(shared_encryption_key, pk + 1 + CRYPTO_PUBLIC_KEY_SIZE, ping_plain, sizeof(ping_plain), pk + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE); if (rc != PING_PLAIN_SIZE + CRYPTO_MAC_SIZE) { return 1; } return sendpacket(dht_get_net(ping->dht), ipp, pk, sizeof(pk)); } static int handle_ping_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { DHT *dht = (DHT *)object; int rc; if (length != DHT_PING_SIZE) { return 1; } Ping *ping = dht_get_ping(dht); if (id_equal(packet + 1, dht_get_self_public_key(ping->dht))) { return 1; } uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint8_t ping_plain[PING_PLAIN_SIZE]; // Decrypt ping_id dht_get_shared_key_recv(dht, shared_key, packet + 1); rc = decrypt_data_symmetric(shared_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, PING_PLAIN_SIZE + CRYPTO_MAC_SIZE, ping_plain); if (rc != sizeof(ping_plain)) { return 1; } if (ping_plain[0] != NET_PACKET_PING_REQUEST) { return 1; } uint64_t ping_id; memcpy(&ping_id, ping_plain + 1, sizeof(ping_id)); // Send response ping_send_response(ping, source, packet + 1, ping_id, shared_key); ping_add(ping, packet + 1, source); return 0; } static int handle_ping_response(void *object, IP_Port source, const uint8_t *packet, uint16_t length, void *userdata) { DHT *dht = (DHT *)object; int rc; if (length != DHT_PING_SIZE) { return 1; } Ping *ping = dht_get_ping(dht); if (id_equal(packet + 1, dht_get_self_public_key(ping->dht))) { return 1; } uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; // generate key to encrypt ping_id with recipient privkey dht_get_shared_key_sent(ping->dht, shared_key, packet + 1); uint8_t ping_plain[PING_PLAIN_SIZE]; // Decrypt ping_id rc = decrypt_data_symmetric(shared_key, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_NONCE_SIZE, PING_PLAIN_SIZE + CRYPTO_MAC_SIZE, ping_plain); if (rc != sizeof(ping_plain)) { return 1; } if (ping_plain[0] != NET_PACKET_PING_RESPONSE) { return 1; } uint64_t ping_id; memcpy(&ping_id, ping_plain + 1, sizeof(ping_id)); uint8_t data[PING_DATA_SIZE]; if (ping_array_check(ping->ping_array, ping->mono_time, data, sizeof(data), ping_id) != sizeof(data)) { return 1; } if (!id_equal(packet + 1, data)) { return 1; } IP_Port ipp; memcpy(&ipp, data + CRYPTO_PUBLIC_KEY_SIZE, sizeof(IP_Port)); if (!ipport_equal(&ipp, &source)) { return 1; } addto_lists(dht, source, packet + 1); return 0; } /* Check if public_key with ip_port is in the list. * * return 1 if it is. * return 0 if it isn't. */ static int in_list(const Client_data *list, uint16_t length, const Mono_Time *mono_time, const uint8_t *public_key, IP_Port ip_port) { unsigned int i; for (i = 0; i < length; ++i) { if (id_equal(list[i].public_key, public_key)) { const IPPTsPng *ipptp; if (net_family_is_ipv4(ip_port.ip.family)) { ipptp = &list[i].assoc4; } else { ipptp = &list[i].assoc6; } if (!mono_time_is_timeout(mono_time, ipptp->timestamp, BAD_NODE_TIMEOUT) && ipport_equal(&ipptp->ip_port, &ip_port)) { return 1; } } } return 0; } /* Add nodes to the to_ping list. * All nodes in this list are pinged every TIME_TO_PING seconds * and are then removed from the list. * If the list is full the nodes farthest from our public_key are replaced. * The purpose of this list is to enable quick integration of new nodes into the * network while preventing amplification attacks. * * return 0 if node was added. * return -1 if node was not added. */ int32_t ping_add(Ping *ping, const uint8_t *public_key, IP_Port ip_port) { if (!ip_isset(&ip_port.ip)) { return -1; } if (!node_addable_to_close_list(ping->dht, public_key, ip_port)) { return -1; } if (in_list(dht_get_close_clientlist(ping->dht), LCLIENT_LIST, ping->mono_time, public_key, ip_port)) { return -1; } IP_Port temp; if (dht_getfriendip(ping->dht, public_key, &temp) == 0) { ping_send_request(ping, ip_port, public_key); return -1; } unsigned int i; for (i = 0; i < MAX_TO_PING; ++i) { if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { memcpy(ping->to_ping[i].public_key, public_key, CRYPTO_PUBLIC_KEY_SIZE); ipport_copy(&ping->to_ping[i].ip_port, &ip_port); return 0; } if (public_key_cmp(ping->to_ping[i].public_key, public_key) == 0) { return -1; } } if (add_to_list(ping->to_ping, MAX_TO_PING, public_key, ip_port, dht_get_self_public_key(ping->dht))) { return 0; } return -1; } /* Ping all the valid nodes in the to_ping list every TIME_TO_PING seconds. * This function must be run at least once every TIME_TO_PING seconds. */ void ping_iterate(Ping *ping) { if (!mono_time_is_timeout(ping->mono_time, ping->last_to_ping, TIME_TO_PING)) { return; } if (!ip_isset(&ping->to_ping[0].ip_port.ip)) { return; } unsigned int i; for (i = 0; i < MAX_TO_PING; ++i) { if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { break; } if (!node_addable_to_close_list(ping->dht, ping->to_ping[i].public_key, ping->to_ping[i].ip_port)) { continue; } ping_send_request(ping, ping->to_ping[i].ip_port, ping->to_ping[i].public_key); ip_reset(&ping->to_ping[i].ip_port.ip); } if (i != 0) { ping->last_to_ping = mono_time_get(ping->mono_time); } } Ping *ping_new(const Mono_Time *mono_time, DHT *dht) { Ping *ping = (Ping *)calloc(1, sizeof(Ping)); if (ping == nullptr) { return nullptr; } ping->ping_array = ping_array_new(PING_NUM_MAX, PING_TIMEOUT); if (ping->ping_array == nullptr) { free(ping); return nullptr; } ping->mono_time = mono_time; ping->dht = dht; networking_registerhandler(dht_get_net(ping->dht), NET_PACKET_PING_REQUEST, &handle_ping_request, dht); networking_registerhandler(dht_get_net(ping->dht), NET_PACKET_PING_RESPONSE, &handle_ping_response, dht); return ping; } void ping_kill(Ping *ping) { if (ping == nullptr) { return; } networking_registerhandler(dht_get_net(ping->dht), NET_PACKET_PING_REQUEST, nullptr, nullptr); networking_registerhandler(dht_get_net(ping->dht), NET_PACKET_PING_RESPONSE, nullptr, nullptr); ping_array_kill(ping->ping_array); free(ping); } c-toxcore-0.2.13/toxcore/ping.h000066400000000000000000000027261415350724400163050ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. * Copyright © 2013 plutooo */ /* * Buffered pinging using cyclic arrays. */ #ifndef C_TOXCORE_TOXCORE_PING_H #define C_TOXCORE_TOXCORE_PING_H #include "DHT.h" #include "network.h" #include #ifndef IP_PORT_DEFINED #define IP_PORT_DEFINED typedef struct IP_Port IP_Port; #endif /* IP_PORT_DEFINED */ #ifndef DHT_DEFINED #define DHT_DEFINED typedef struct DHT DHT; #endif /* DHT_DEFINED */ #ifndef MONO_TIME_DEFINED #define MONO_TIME_DEFINED typedef struct Mono_Time Mono_Time; #endif /* MONO_TIME_DEFINED */ #ifndef PING_DEFINED #define PING_DEFINED typedef struct Ping Ping; #endif /* PING_DEFINED */ Ping *ping_new(const struct Mono_Time *mono_time, DHT *dht); void ping_kill(Ping *ping); /** Add nodes to the to_ping list. * All nodes in this list are pinged every TIME_TOPING seconds * and are then removed from the list. * If the list is full the nodes farthest from our public_key are replaced. * The purpose of this list is to enable quick integration of new nodes into the * network while preventing amplification attacks. * * return 0 if node was added. * return -1 if node was not added. */ int32_t ping_add(Ping *ping, const uint8_t *public_key, struct IP_Port ip_port); void ping_iterate(Ping *ping); int32_t ping_send_request(Ping *ping, struct IP_Port ipp, const uint8_t *public_key); #endif // C_TOXCORE_TOXCORE_PING_H c-toxcore-0.2.13/toxcore/ping_array.api.h000066400000000000000000000025531415350724400202510ustar00rootroot00000000000000%{ /* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Implementation of an efficient array to store that we pinged something. */ #ifndef C_TOXCORE_TOXCORE_PING_ARRAY_H #define C_TOXCORE_TOXCORE_PING_ARRAY_H #include #include #ifdef __cplusplus extern "C" { #endif %} class mono_Time { struct this; } class ping { class array { struct this; /** * Initialize a Ping_Array. * * @param size represents the total size of the array and should be a power of 2. * @param timeout represents the maximum timeout in seconds for the entry. * * @return 0 on success, -1 on failure. */ static this new(uint32_t size, uint32_t timeout); /** * Free all the allocated memory in a Ping_Array. */ void kill(); /** * Add a data with length to the Ping_Array list and return a ping_id. * * @return ping_id on success, 0 on failure. */ uint64_t add(const mono_Time::this *mono_time, const uint8_t *data, uint32_t length); /** * Check if ping_id is valid and not timed out. * * On success, copies the data into data of length, * * @return length of data copied on success, -1 on failure. */ int32_t check(const mono_Time::this *mono_time, uint8_t[length] data, uint64_t ping_id); } } %{ #ifdef __cplusplus } // extern "C" #endif #endif // C_TOXCORE_TOXCORE_PING_ARRAY_H %} c-toxcore-0.2.13/toxcore/ping_array.c000066400000000000000000000104741415350724400174750ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2014 Tox project. */ /* * Implementation of an efficient array to store that we pinged something. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "ping_array.h" #include #include #include "crypto_core.h" #include "mono_time.h" #include "util.h" typedef struct Ping_Array_Entry { void *data; uint32_t length; uint64_t time; uint64_t ping_id; } Ping_Array_Entry; struct Ping_Array { Ping_Array_Entry *entries; uint32_t last_deleted; /* number representing the next entry to be deleted. */ uint32_t last_added; /* number representing the last entry to be added. */ uint32_t total_size; /* The length of entries */ uint32_t timeout; /* The timeout after which entries are cleared. */ }; Ping_Array *ping_array_new(uint32_t size, uint32_t timeout) { if (size == 0 || timeout == 0) { return nullptr; } if ((size & (size - 1)) != 0) { // Not a power of 2. return nullptr; } Ping_Array *const empty_array = (Ping_Array *)calloc(1, sizeof(Ping_Array)); if (empty_array == nullptr) { return nullptr; } empty_array->entries = (Ping_Array_Entry *)calloc(size, sizeof(Ping_Array_Entry)); if (empty_array->entries == nullptr) { free(empty_array); return nullptr; } empty_array->last_deleted = 0; empty_array->last_added = 0; empty_array->total_size = size; empty_array->timeout = timeout; return empty_array; } static void clear_entry(Ping_Array *array, uint32_t index) { const Ping_Array_Entry empty = {nullptr}; free(array->entries[index].data); array->entries[index] = empty; } void ping_array_kill(Ping_Array *array) { if (array == nullptr) { return; } while (array->last_deleted != array->last_added) { const uint32_t index = array->last_deleted % array->total_size; clear_entry(array, index); ++array->last_deleted; } free(array->entries); free(array); } /* Clear timed out entries. */ static void ping_array_clear_timedout(Ping_Array *array, const Mono_Time *mono_time) { while (array->last_deleted != array->last_added) { const uint32_t index = array->last_deleted % array->total_size; if (!mono_time_is_timeout(mono_time, array->entries[index].time, array->timeout)) { break; } clear_entry(array, index); ++array->last_deleted; } } uint64_t ping_array_add(Ping_Array *array, const Mono_Time *mono_time, const uint8_t *data, uint32_t length) { ping_array_clear_timedout(array, mono_time); const uint32_t index = array->last_added % array->total_size; if (array->entries[index].data != nullptr) { array->last_deleted = array->last_added - array->total_size; clear_entry(array, index); } array->entries[index].data = malloc(length); if (array->entries[index].data == nullptr) { return 0; } memcpy(array->entries[index].data, data, length); array->entries[index].length = length; array->entries[index].time = mono_time_get(mono_time); ++array->last_added; uint64_t ping_id = random_u64(); ping_id /= array->total_size; ping_id *= array->total_size; ping_id += index; if (ping_id == 0) { ping_id += array->total_size; } array->entries[index].ping_id = ping_id; return ping_id; } int32_t ping_array_check(Ping_Array *array, const Mono_Time *mono_time, uint8_t *data, size_t length, uint64_t ping_id) { if (ping_id == 0) { return -1; } const uint32_t index = ping_id % array->total_size; if (array->entries[index].ping_id != ping_id) { return -1; } if (mono_time_is_timeout(mono_time, array->entries[index].time, array->timeout)) { return -1; } if (array->entries[index].length > length) { return -1; } // TODO(iphydf): This can't happen? If it indeed can't, turn it into an assert. if (array->entries[index].data == nullptr) { return -1; } memcpy(data, array->entries[index].data, array->entries[index].length); const uint32_t len = array->entries[index].length; clear_entry(array, index); return len; } c-toxcore-0.2.13/toxcore/ping_array.h000066400000000000000000000032751415350724400175030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Implementation of an efficient array to store that we pinged something. */ #ifndef C_TOXCORE_TOXCORE_PING_ARRAY_H #define C_TOXCORE_TOXCORE_PING_ARRAY_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef MONO_TIME_DEFINED #define MONO_TIME_DEFINED typedef struct Mono_Time Mono_Time; #endif /* MONO_TIME_DEFINED */ #ifndef PING_ARRAY_DEFINED #define PING_ARRAY_DEFINED typedef struct Ping_Array Ping_Array; #endif /* PING_ARRAY_DEFINED */ /** * Initialize a Ping_Array. * * @param size represents the total size of the array and should be a power of 2. * @param timeout represents the maximum timeout in seconds for the entry. * * @return 0 on success, -1 on failure. */ struct Ping_Array *ping_array_new(uint32_t size, uint32_t timeout); /** * Free all the allocated memory in a Ping_Array. */ void ping_array_kill(struct Ping_Array *array); /** * Add a data with length to the Ping_Array list and return a ping_id. * * @return ping_id on success, 0 on failure. */ uint64_t ping_array_add(struct Ping_Array *array, const struct Mono_Time *mono_time, const uint8_t *data, uint32_t length); /** * Check if ping_id is valid and not timed out. * * On success, copies the data into data of length, * * @return length of data copied on success, -1 on failure. */ int32_t ping_array_check(struct Ping_Array *array, const struct Mono_Time *mono_time, uint8_t *data, size_t length, uint64_t ping_id); #ifdef __cplusplus } // extern "C" #endif #endif // C_TOXCORE_TOXCORE_PING_ARRAY_H c-toxcore-0.2.13/toxcore/ping_array_test.cc000066400000000000000000000075031415350724400206760ustar00rootroot00000000000000#include "ping_array.h" #include #include #include "mono_time.h" namespace { struct Ping_Array_Deleter { void operator()(Ping_Array *arr) { ping_array_kill(arr); } }; using Ping_Array_Ptr = std::unique_ptr; struct Mono_Time_Deleter { void operator()(Mono_Time *arr) { mono_time_free(arr); } }; using Mono_Time_Ptr = std::unique_ptr; TEST(PingArray, MinimumTimeoutIsOne) { EXPECT_EQ(ping_array_new(1, 0), nullptr); EXPECT_NE(Ping_Array_Ptr(ping_array_new(1, 1)), nullptr); } TEST(PingArray, MinimumArraySizeIsOne) { EXPECT_EQ(ping_array_new(0, 1), nullptr); EXPECT_NE(Ping_Array_Ptr(ping_array_new(1, 1)), nullptr); } TEST(PingArray, ArraySizeMustBePowerOfTwo) { Ping_Array_Ptr arr; arr.reset(ping_array_new(2, 1)); EXPECT_NE(arr, nullptr); arr.reset(ping_array_new(4, 1)); EXPECT_NE(arr, nullptr); arr.reset(ping_array_new(1024, 1)); EXPECT_NE(arr, nullptr); EXPECT_EQ(ping_array_new(1023, 1), nullptr); EXPECT_EQ(ping_array_new(1234, 1), nullptr); } TEST(PingArray, StoredDataCanBeRetrieved) { Ping_Array_Ptr const arr(ping_array_new(2, 1)); Mono_Time_Ptr const mono_time(mono_time_new()); uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), std::vector{1, 2, 3, 4}.data(), 4); EXPECT_NE(ping_id, 0); std::vector data(4); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), data.data(), data.size(), ping_id), 4); EXPECT_EQ(data, std::vector({1, 2, 3, 4})); } TEST(PingArray, RetrievingDataWithTooSmallOutputBufferHasNoEffect) { Ping_Array_Ptr const arr(ping_array_new(2, 1)); Mono_Time_Ptr const mono_time(mono_time_new()); uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), (std::vector{1, 2, 3, 4}).data(), 4); EXPECT_NE(ping_id, 0); std::vector data(4); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), data.data(), 3, ping_id), -1); // It doesn't write anything to the data array. EXPECT_EQ(data, std::vector({0, 0, 0, 0})); // Afterwards, we can still read it. EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), data.data(), 4, ping_id), 4); EXPECT_EQ(data, std::vector({1, 2, 3, 4})); } TEST(PingArray, ZeroLengthDataCanBeAdded) { Ping_Array_Ptr const arr(ping_array_new(2, 1)); Mono_Time_Ptr const mono_time(mono_time_new()); uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), nullptr, 0); EXPECT_NE(ping_id, 0); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), nullptr, 0, ping_id), 0); } TEST(PingArray, PingId0IsInvalid) { Ping_Array_Ptr const arr(ping_array_new(2, 1)); Mono_Time_Ptr const mono_time(mono_time_new()); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), nullptr, 0, 0), -1); } // Protection against replay attacks. TEST(PingArray, DataCanOnlyBeRetrievedOnce) { Ping_Array_Ptr const arr(ping_array_new(2, 1)); Mono_Time_Ptr const mono_time(mono_time_new()); uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), nullptr, 0); EXPECT_NE(ping_id, 0); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), nullptr, 0, ping_id), 0); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), nullptr, 0, ping_id), -1); } TEST(PingArray, PingIdMustMatchOnCheck) { Ping_Array_Ptr const arr(ping_array_new(1, 1)); Mono_Time_Ptr const mono_time(mono_time_new()); uint64_t const ping_id = ping_array_add(arr.get(), mono_time.get(), nullptr, 0); EXPECT_NE(ping_id, 0); uint64_t const bad_ping_id = ping_id == 1 ? 2 : 1; // bad_ping_id will also be pointing at the same element, but won't match the // actual ping_id. EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), nullptr, 0, bad_ping_id), -1); EXPECT_EQ(ping_array_check(arr.get(), mono_time.get(), nullptr, 0, ping_id), 0); } } // namespace c-toxcore-0.2.13/toxcore/state.c000066400000000000000000000100041415350724400164470ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2020 The TokTok team. * Copyright © 2014 Tox project. */ #include "state.h" #include /* state load/save */ int state_load(const Logger *log, state_load_cb *state_load_callback, void *outer, const uint8_t *data, uint32_t length, uint16_t cookie_inner) { if (state_load_callback == nullptr || data == nullptr) { LOGGER_ERROR(log, "state_load() called with invalid args."); return -1; } const uint32_t size_head = sizeof(uint32_t) * 2; while (length >= size_head) { uint32_t length_sub; lendian_bytes_to_host32(&length_sub, data); uint32_t cookie_type; lendian_bytes_to_host32(&cookie_type, data + sizeof(uint32_t)); data += size_head; length -= size_head; if (length < length_sub) { /* file truncated */ LOGGER_ERROR(log, "state file too short: %u < %u", length, length_sub); return -1; } if (lendian_to_host16((cookie_type >> 16)) != cookie_inner) { /* something is not matching up in a bad way, give up */ LOGGER_ERROR(log, "state file garbled: %04x != %04x", cookie_type >> 16, cookie_inner); return -1; } const uint16_t type = lendian_to_host16(cookie_type & 0xFFFF); switch (state_load_callback(outer, data, length_sub, type)) { case STATE_LOAD_STATUS_CONTINUE: data += length_sub; length -= length_sub; break; case STATE_LOAD_STATUS_ERROR: LOGGER_ERROR(log, "Error occcured in state file (type: %u).", type); return -1; case STATE_LOAD_STATUS_END: return 0; } } if (length != 0) { LOGGER_ERROR(log, "unparsed data in state file of length %u", length); return -1; } return 0; } uint8_t *state_write_section_header(uint8_t *data, uint16_t cookie_type, uint32_t len, uint32_t section_type) { host_to_lendian_bytes32(data, len); data += sizeof(uint32_t); host_to_lendian_bytes32(data, (host_to_lendian16(cookie_type) << 16) | host_to_lendian16(section_type)); data += sizeof(uint32_t); return data; } uint16_t lendian_to_host16(uint16_t lendian) { #ifdef WORDS_BIGENDIAN return (lendian << 8) | (lendian >> 8); #else return lendian; #endif } uint16_t host_to_lendian16(uint16_t host) { return lendian_to_host16(host); } void host_to_lendian_bytes64(uint8_t *dest, uint64_t num) { #ifdef WORDS_BIGENDIAN num = ((num << 8) & 0xFF00FF00FF00FF00) | ((num >> 8) & 0xFF00FF00FF00FF); num = ((num << 16) & 0xFFFF0000FFFF0000) | ((num >> 16) & 0xFFFF0000FFFF); num = (num << 32) | (num >> 32); #endif memcpy(dest, &num, sizeof(uint64_t)); } void lendian_bytes_to_host64(uint64_t *dest, const uint8_t *lendian) { uint64_t d; memcpy(&d, lendian, sizeof(uint64_t)); #ifdef WORDS_BIGENDIAN d = ((d << 8) & 0xFF00FF00FF00FF00) | ((d >> 8) & 0xFF00FF00FF00FF); d = ((d << 16) & 0xFFFF0000FFFF0000) | ((d >> 16) & 0xFFFF0000FFFF); d = (d << 32) | (d >> 32); #endif *dest = d; } void host_to_lendian_bytes32(uint8_t *dest, uint32_t num) { #ifdef WORDS_BIGENDIAN num = ((num << 8) & 0xFF00FF00) | ((num >> 8) & 0xFF00FF); num = (num << 16) | (num >> 16); #endif memcpy(dest, &num, sizeof(uint32_t)); } void lendian_bytes_to_host32(uint32_t *dest, const uint8_t *lendian) { uint32_t d; memcpy(&d, lendian, sizeof(uint32_t)); #ifdef WORDS_BIGENDIAN d = ((d << 8) & 0xFF00FF00) | ((d >> 8) & 0xFF00FF); d = (d << 16) | (d >> 16); #endif *dest = d; } void host_to_lendian_bytes16(uint8_t *dest, uint16_t num) { #ifdef WORDS_BIGENDIAN num = (num << 8) | (num >> 8); #endif memcpy(dest, &num, sizeof(uint16_t)); } void lendian_bytes_to_host16(uint16_t *dest, const uint8_t *lendian) { uint16_t d; memcpy(&d, lendian, sizeof(uint16_t)); #ifdef WORDS_BIGENDIAN d = (d << 8) | (d >> 8); #endif *dest = d; } c-toxcore-0.2.13/toxcore/state.h000066400000000000000000000047641415350724400164740ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2020 The TokTok team. * Copyright © 2014 Tox project. */ /** * The state module is responsible for parsing the Tox save data format and for * saving state in that format. * * This module provides functions for iterating over serialised data sections * and reading/writing numbers in the correct format (little endian). * * Note that unlike the Tox network protocol, the save data stores its values in * little endian, which is native to most desktop and server architectures in * 2018. */ #ifndef C_TOXCORE_TOXCORE_STATE_H #define C_TOXCORE_TOXCORE_STATE_H #include "logger.h" #ifdef __cplusplus extern "C" { #endif #define STATE_COOKIE_GLOBAL 0x15ed1b1f #define STATE_COOKIE_TYPE 0x01ce typedef enum State_Type { STATE_TYPE_NOSPAMKEYS = 1, STATE_TYPE_DHT = 2, STATE_TYPE_FRIENDS = 3, STATE_TYPE_NAME = 4, STATE_TYPE_STATUSMESSAGE = 5, STATE_TYPE_STATUS = 6, STATE_TYPE_TCP_RELAY = 10, STATE_TYPE_PATH_NODE = 11, STATE_TYPE_CONFERENCES = 20, STATE_TYPE_END = 255, } State_Type; // Returned by the state_load_cb to instruct the loader on what to do next. typedef enum State_Load_Status { // Continue loading state data sections. STATE_LOAD_STATUS_CONTINUE, // An error occurred. Stop loading sections. STATE_LOAD_STATUS_ERROR, // We're at the end of the save data, terminate loading successfully. STATE_LOAD_STATUS_END, } State_Load_Status; typedef State_Load_Status state_load_cb(void *outer, const uint8_t *data, uint32_t len, uint16_t type); // state load/save int state_load(const Logger *log, state_load_cb *state_load_callback, void *outer, const uint8_t *data, uint32_t length, uint16_t cookie_inner); uint8_t *state_write_section_header(uint8_t *data, uint16_t cookie_type, uint32_t len, uint32_t section_type); // Utilities for state data serialisation. uint16_t lendian_to_host16(uint16_t lendian); uint16_t host_to_lendian16(uint16_t host); void host_to_lendian_bytes64(uint8_t *dest, uint64_t num); void lendian_bytes_to_host64(uint64_t *dest, const uint8_t *lendian); void host_to_lendian_bytes32(uint8_t *dest, uint32_t num); void lendian_bytes_to_host32(uint32_t *dest, const uint8_t *lendian); void host_to_lendian_bytes16(uint8_t *dest, uint16_t num); void lendian_bytes_to_host16(uint16_t *dest, const uint8_t *lendian); #ifdef __cplusplus } // extern "C" #endif #endif // C_TOXCORE_TOXCORE_STATE_H c-toxcore-0.2.13/toxcore/tox.api.h000066400000000000000000002571351415350724400167400ustar00rootroot00000000000000%{ /* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * The Tox public API. */ #ifndef C_TOXCORE_TOXCORE_TOX_H #define C_TOXCORE_TOXCORE_TOX_H #include #include #include //!TOKSTYLE- #ifdef __cplusplus extern "C" { #endif %} /***************************************************************************** * `tox.h` SHOULD *NOT* BE EDITED MANUALLY – any changes should be made to * * `tox.api.h`, located in `toxcore/`. For instructions on how to * * generate `tox.h` from `tox.api.h` please refer to `docs/apidsl.md` * *****************************************************************************/ /** * @page core Public core API for Tox clients. * * Every function that can fail takes a function-specific error code pointer * that can be used to diagnose problems with the Tox state or the function * arguments. The error code pointer can be NULL, which does not influence the * function's behaviour, but can be done if the reason for failure is irrelevant * to the client. * * The exception to this rule are simple allocation functions whose only failure * mode is allocation failure. They return NULL in that case, and do not set an * error code. * * Every error code type has an OK value to which functions will set their error * code value on success. Clients can keep their error code uninitialised before * passing it to a function. The library guarantees that after returning, the * value pointed to by the error code pointer has been initialised. * * Functions with pointer parameters often have a NULL error code, meaning they * could not perform any operation, because one of the required parameters was * NULL. Some functions operate correctly or are defined as effectless on NULL. * * Some functions additionally return a value outside their * return type domain, or a bool containing true on success and false on * failure. * * All functions that take a Tox instance pointer will cause undefined behaviour * when passed a NULL Tox pointer. * * All integer values are expected in host byte order. * * Functions with parameters with enum types cause unspecified behaviour if the * enumeration value is outside the valid range of the type. If possible, the * function will try to use a sane default, but there will be no error code, * and one possible action for the function to take is to have no effect. * * Integer constants and the memory layout of publicly exposed structs are not * part of the ABI. */ /** * @subsection events Events and callbacks * * Events are handled by callbacks. One callback can be registered per event. * All events have a callback function type named `tox_{event}_cb` and a * function to register it named `tox_callback_{event}`. Passing a NULL * callback will result in no callback being registered for that event. Only * one callback per event can be registered, so if a client needs multiple * event listeners, it needs to implement the dispatch functionality itself. * * The last argument to a callback is the user data pointer. It is passed from * ${tox.iterate} to each callback in sequence. * * The user data pointer is never stored or dereferenced by any library code, so * can be any pointer, including NULL. Callbacks must all operate on the same * object type. In the apidsl code (tox.in.h), this is denoted with `any`. The * `any` in ${tox.iterate} must be the same `any` as in all callbacks. In C, * lacking parametric polymorphism, this is a pointer to void. * * Old style callbacks that are registered together with a user data pointer * receive that pointer as argument when they are called. They can each have * their own user data pointer of their own type. */ /** * @subsection threading Threading implications * * It is possible to run multiple concurrent threads with a Tox instance for * each thread. It is also possible to run all Tox instances in the same thread. * A common way to run Tox (multiple or single instance) is to have one thread * running a simple ${tox.iterate} loop, sleeping for ${tox.iteration_interval} * milliseconds on each iteration. * * If you want to access a single Tox instance from multiple threads, access * to the instance must be synchronised. While multiple threads can concurrently * access multiple different Tox instances, no more than one API function can * operate on a single instance at any given time. * * Functions that write to variable length byte arrays will always have a size * function associated with them. The result of this size function is only valid * until another mutating function (one that takes a pointer to non-const Tox) * is called. Thus, clients must ensure that no other thread calls a mutating * function between the call to the size function and the call to the retrieval * function. * * E.g. to get the current nickname, one would write * * @code * size_t length = ${tox.self.name.size}(tox); * uint8_t *name = malloc(length); * if (!name) abort(); * ${tox.self.name.get}(tox, name); * @endcode * * If any other thread calls ${tox.self.name.set} while this thread is allocating * memory, the length may have become invalid, and the call to * ${tox.self.name.get} may cause undefined behaviour. */ // The rest of this file is in class tox. class tox { /** * The Tox instance type. All the state associated with a connection is held * within the instance. Multiple instances can exist and operate concurrently. * The maximum number of Tox instances that can exist on a single network * device is limited. Note that this is not just a per-process limit, since the * limiting factor is the number of usable ports on a device. */ struct this; /******************************************************************************* * * :: API version * ******************************************************************************/ /** * The major version number. Incremented when the API or ABI changes in an * incompatible way. * * The function variants of these constants return the version number of the * library. They can be used to display the Tox library version or to check * whether the client is compatible with the dynamically linked version of Tox. */ const VERSION_MAJOR = 0; /** * The minor version number. Incremented when functionality is added without * breaking the API or ABI. Set to 0 when the major version number is * incremented. */ const VERSION_MINOR = 2; /** * The patch or revision number. Incremented when bugfixes are applied without * changing any functionality or API or ABI. */ const VERSION_PATCH = 13; /** * A macro to check at preprocessing time whether the client code is compatible * with the installed version of Tox. Leading zeros in the version number are * ignored. E.g. 0.1.5 is to 0.1.4 what 1.5 is to 1.4, that is: it can add new * features, but can't break the API. */ #define TOX_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ ((TOX_VERSION_MAJOR > 0 && TOX_VERSION_MAJOR == MAJOR) && ( \ /* 1.x.x, 2.x.x, etc. with matching major version. */ \ TOX_VERSION_MINOR > MINOR || \ (TOX_VERSION_MINOR == MINOR && TOX_VERSION_PATCH >= PATCH) \ )) || ((TOX_VERSION_MAJOR == 0 && MAJOR == 0) && ( \ /* 0.x.x makes minor behave like major above. */ \ ((TOX_VERSION_MINOR > 0 && TOX_VERSION_MINOR == MINOR) && ( \ TOX_VERSION_PATCH >= PATCH \ )) || ((TOX_VERSION_MINOR == 0 && MINOR == 0) && ( \ /* 0.0.x and 0.0.y are only compatible if x == y. */ \ TOX_VERSION_PATCH == PATCH \ )) \ )) static namespace version { /** * Return whether the compiled library version is compatible with the passed * version numbers. */ bool is_compatible(uint32_t major, uint32_t minor, uint32_t patch); } /** * A convenience macro to call tox_version_is_compatible with the currently * compiling API version. */ #define TOX_VERSION_IS_ABI_COMPATIBLE() \ tox_version_is_compatible(TOX_VERSION_MAJOR, TOX_VERSION_MINOR, TOX_VERSION_PATCH) /******************************************************************************* * * :: Numeric constants * * The values of these are not part of the ABI. Prefer to use the function * versions of them for code that should remain compatible with future versions * of toxcore. * ******************************************************************************/ /** * The size of a Tox Public Key in bytes. */ const PUBLIC_KEY_SIZE = 32; /** * The size of a Tox Secret Key in bytes. */ const SECRET_KEY_SIZE = 32; /** * The size of a Tox Conference unique id in bytes. * * @deprecated Use $CONFERENCE_ID_SIZE instead. */ const CONFERENCE_UID_SIZE = 32; /** * The size of a Tox Conference unique id in bytes. */ const CONFERENCE_ID_SIZE = 32; /** * The size of the nospam in bytes when written in a Tox address. */ const NOSPAM_SIZE = sizeof(uint32_t); /** * The size of a Tox address in bytes. Tox addresses are in the format * [Public Key ($PUBLIC_KEY_SIZE bytes)][nospam (4 bytes)][checksum (2 bytes)]. * * The checksum is computed over the Public Key and the nospam value. The first * byte is an XOR of all the even bytes (0, 2, 4, ...), the second byte is an * XOR of all the odd bytes (1, 3, 5, ...) of the Public Key and nospam. */ const ADDRESS_SIZE = PUBLIC_KEY_SIZE + NOSPAM_SIZE + sizeof(uint16_t); /** * Maximum length of a nickname in bytes. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ const MAX_NAME_LENGTH = 128; /** * Maximum length of a status message in bytes. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ const MAX_STATUS_MESSAGE_LENGTH = 1007; /** * Maximum length of a friend request message in bytes. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ const MAX_FRIEND_REQUEST_LENGTH = 1016; /** * Maximum length of a single message after which it should be split. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ const MAX_MESSAGE_LENGTH = 1372; /** * Maximum size of custom packets. TODO(iphydf): should be LENGTH? * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ const MAX_CUSTOM_PACKET_SIZE = 1373; /** * The number of bytes in a hash generated by $hash. */ const HASH_LENGTH = 32; /** * The number of bytes in a file id. */ const FILE_ID_LENGTH = 32; /** * Maximum file name length for file transfers. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ const MAX_FILENAME_LENGTH = 255; /** * Maximum length of a hostname, e.g. proxy or bootstrap node names. * * This length does not include the NUL byte. Hostnames are NUL-terminated C * strings, so they are 255 characters plus one NUL byte. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ const MAX_HOSTNAME_LENGTH = 255; /******************************************************************************* * * :: Global enumerations * ******************************************************************************/ /** * Represents the possible statuses a client can have. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ enum class USER_STATUS { /** * User is online and available. */ NONE, /** * User is away. Clients can set this e.g. after a user defined * inactivity time. */ AWAY, /** * User is busy. Signals to other clients that this client does not * currently wish to communicate. */ BUSY, } /** * Represents message types for ${tox.friend.send.message} and conference * messages. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ enum class MESSAGE_TYPE { /** * Normal text message. Similar to PRIVMSG on IRC. */ NORMAL, /** * A message describing an user action. This is similar to /me (CTCP ACTION) * on IRC. */ ACTION, } /******************************************************************************* * * :: Startup options * ******************************************************************************/ /** * Type of proxy used to connect to TCP relays. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ enum class PROXY_TYPE { /** * Don't use a proxy. */ NONE, /** * HTTP proxy using CONNECT. */ HTTP, /** * SOCKS proxy for simple socket pipes. */ SOCKS5, } /** * Type of savedata to create the Tox instance from. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ enum class SAVEDATA_TYPE { /** * No savedata. */ NONE, /** * Savedata is one that was obtained from ${savedata.get}. */ TOX_SAVE, /** * Savedata is a secret key of length $SECRET_KEY_SIZE. */ SECRET_KEY, } /** * Severity level of log messages. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ enum class LOG_LEVEL { /** * Very detailed traces including all network activity. */ TRACE, /** * Debug messages such as which port we bind to. */ DEBUG, /** * Informational log messages such as video call status changes. */ INFO, /** * Warnings about internal inconsistency or logic errors. */ WARNING, /** * Severe unexpected errors caused by external or internal inconsistency. */ ERROR, } /** * This event is triggered when the toxcore library logs an internal message. * This is mostly useful for debugging. This callback can be called from any * function, not just $iterate. This means the user data lifetime must at * least extend between registering and unregistering it or $kill. * * Other toxcore modules such as toxav may concurrently call this callback at * any time. Thus, user code must make sure it is equipped to handle concurrent * execution, e.g. by employing appropriate mutex locking. * * @param level The severity of the log message. * @param file The source file from which the message originated. * @param line The source line from which the message originated. * @param func The function from which the message originated. * @param message The log message. * @param user_data The user data pointer passed to $new in options. */ typedef void log_cb(LOG_LEVEL level, string file, uint32_t line, string func, string message, any user_data); static class options { /** * This struct contains all the startup options for Tox. You must $new to * allocate an object of this type. * * WARNING: Although this struct happens to be visible in the API, it is * effectively private. Do not allocate this yourself or access members * directly, as it *will* break binary compatibility frequently. * * @deprecated The memory layout of this struct (size, alignment, and field * order) is not part of the ABI. To remain compatible, prefer to use $new to * allocate the object and accessor functions to set the members. The struct * will become opaque (i.e. the definition will become private) in v0.3.0. */ struct this [get, set] { /** * The type of socket to create. * * If this is set to false, an IPv4 socket is created, which subsequently * only allows IPv4 communication. * If it is set to true, an IPv6 socket is created, allowing both IPv4 and * IPv6 communication. */ bool ipv6_enabled; /** * Enable the use of UDP communication when available. * * Setting this to false will force Tox to use TCP only. Communications will * need to be relayed through a TCP relay node, potentially slowing them down. * * If a proxy is enabled, UDP will be disabled if either toxcore or the * proxy don't support proxying UDP messages. */ bool udp_enabled; /** * Enable local network peer discovery. * * Disabling this will cause Tox to not look for peers on the local network. */ bool local_discovery_enabled; namespace proxy { /** * Pass communications through a proxy. */ PROXY_TYPE type; /** * The IP address or DNS name of the proxy to be used. * * If used, this must be non-NULL and be a valid DNS name. The name must not * exceed $MAX_HOSTNAME_LENGTH characters, and be in a NUL-terminated C string * format ($MAX_HOSTNAME_LENGTH includes the NUL byte). * * This member is ignored (it can be NULL) if proxy_type is ${PROXY_TYPE.NONE}. * * The data pointed at by this member is owned by the user, so must * outlive the options object. */ string host; /** * The port to use to connect to the proxy server. * * Ports must be in the range (1, 65535). The value is ignored if * proxy_type is ${PROXY_TYPE.NONE}. */ uint16_t port; } /** * The start port of the inclusive port range to attempt to use. * * If both start_port and end_port are 0, the default port range will be * used: [33445, 33545]. * * If either start_port or end_port is 0 while the other is non-zero, the * non-zero port will be the only port in the range. * * Having start_port > end_port will yield the same behavior as if start_port * and end_port were swapped. */ uint16_t start_port; /** * The end port of the inclusive port range to attempt to use. */ uint16_t end_port; /** * The port to use for the TCP server (relay). If 0, the TCP server is * disabled. * * Enabling it is not required for Tox to function properly. * * When enabled, your Tox instance can act as a TCP relay for other Tox * instance. This leads to increased traffic, thus when writing a client * it is recommended to enable TCP server only if the user has an option * to disable it. */ uint16_t tcp_port; /** * Enables or disables UDP hole-punching in toxcore. (Default: enabled). */ bool hole_punching_enabled; namespace savedata { /** * The type of savedata to load from. */ SAVEDATA_TYPE type; /** * The savedata. * * The data pointed at by this member is owned by the user, so must * outlive the options object. */ const uint8_t[length] data; /** * The length of the savedata. */ size_t length; } namespace log { /** * Logging callback for the new tox instance. */ log_cb *callback; /** * User data pointer passed to the logging callback. */ any user_data; } /** * These options are experimental, so avoid writing code that depends on * them. Options marked "experimental" may change their behaviour or go away * entirely in the future, or may be renamed to something non-experimental * if they become part of the supported API. */ namespace experimental { /** * Make public API functions thread-safe using a per-instance lock. * * Default: false. */ bool thread_safety; } } /** * Initialises a $this object with the default options. * * The result of this function is independent of the original options. All * values will be overwritten, no values will be read (so it is permissible * to pass an uninitialised object). * * If options is NULL, this function has no effect. * * @param options An options object to be filled with default options. */ void default(); /** * Allocates a new $this object and initialises it with the default * options. This function can be used to preserve long term ABI compatibility by * giving the responsibility of allocation and deallocation to the Tox library. * * Objects returned from this function must be freed using the $free * function. * * @return A new $this object with default options or NULL on failure. */ static this new() { /** * The function failed to allocate enough memory for the options struct. */ MALLOC, } /** * Releases all resources associated with an options objects. * * Passing a pointer that was not returned by $new results in * undefined behaviour. */ void free(); } /******************************************************************************* * * :: Creation and destruction * ******************************************************************************/ /** * @brief Creates and initialises a new Tox instance with the options passed. * * This function will bring the instance into a valid state. Running the event * loop with a new instance will operate correctly. * * If loading failed or succeeded only partially, the new or partially loaded * instance is returned and an error code is set. * * @param options An options object as described above. If this parameter is * NULL, the default options are used. * * @see $iterate for the event loop. * * @return A new Tox instance pointer on success or NULL on failure. */ static this new(const options_t *options) { NULL, /** * The function was unable to allocate enough memory to store the internal * structures for the Tox object. */ MALLOC, /** * The function was unable to bind to a port. This may mean that all ports * have already been bound, e.g. by other Tox instances, or it may mean * a permission error. You may be able to gather more information from errno. */ PORT_ALLOC, namespace PROXY { /** * proxy_type was invalid. */ BAD_TYPE, /** * proxy_type was valid but the proxy_host passed had an invalid format * or was NULL. */ BAD_HOST, /** * proxy_type was valid, but the proxy_port was invalid. */ BAD_PORT, /** * The proxy address passed could not be resolved. */ NOT_FOUND, } namespace LOAD { /** * The byte array to be loaded contained an encrypted save. */ ENCRYPTED, /** * The data format was invalid. This can happen when loading data that was * saved by an older version of Tox, or when the data has been corrupted. * When loading from badly formatted data, some data may have been loaded, * and the rest is discarded. Passing an invalid length parameter also * causes this error. */ BAD_FORMAT, } } /** * Releases all resources associated with the Tox instance and disconnects from * the network. * * After calling this function, the Tox pointer becomes invalid. No other * functions can be called, and the pointer value can no longer be read. */ void kill(); uint8_t[size] savedata { /** * Calculates the number of bytes required to store the tox instance with * $get. This function cannot fail. The result is always greater than 0. * * @see threading for concurrency implications. */ size(); /** * Store all information associated with the tox instance to a byte array. * * @param savedata A memory region large enough to store the tox instance * data. Call $size to find the number of bytes required. If this parameter * is NULL, this function has no effect. */ get(); } /******************************************************************************* * * :: Connection lifecycle and event loop * ******************************************************************************/ /** * Sends a "get nodes" request to the given bootstrap node with IP, port, and * public key to setup connections. * * This function will attempt to connect to the node using UDP. You must use * this function even if ${options.this.udp_enabled} was set to false. * * @param host The hostname or IP address (IPv4 or IPv6) of the node. Must be * at most $MAX_HOSTNAME_LENGTH chars, including the NUL byte. * @param port The port on the host on which the bootstrap Tox instance is * listening. * @param public_key The long term public key of the bootstrap node * ($PUBLIC_KEY_SIZE bytes). * @return true on success. */ bool bootstrap(string host, uint16_t port, const uint8_t[PUBLIC_KEY_SIZE] public_key) { NULL, /** * The hostname could not be resolved to an IP address, or the IP address * passed was invalid. */ BAD_HOST, /** * The port passed was invalid. The valid port range is (1, 65535). */ BAD_PORT, } /** * Adds additional host:port pair as TCP relay. * * This function can be used to initiate TCP connections to different ports on * the same bootstrap node, or to add TCP relays without using them as * bootstrap nodes. * * @param host The hostname or IP address (IPv4 or IPv6) of the TCP relay. * Must be at most $MAX_HOSTNAME_LENGTH chars, including the NUL byte. * @param port The port on the host on which the TCP relay is listening. * @param public_key The long term public key of the TCP relay * ($PUBLIC_KEY_SIZE bytes). * @return true on success. */ bool add_tcp_relay(string host, uint16_t port, const uint8_t[PUBLIC_KEY_SIZE] public_key) with error for bootstrap; /** * Protocols that can be used to connect to the network or friends. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ enum class CONNECTION { /** * There is no connection. This instance, or the friend the state change is * about, is now offline. */ NONE, /** * A TCP connection has been established. For the own instance, this means it * is connected through a TCP relay, only. For a friend, this means that the * connection to that particular friend goes through a TCP relay. */ TCP, /** * A UDP connection has been established. For the own instance, this means it * is able to send UDP packets to DHT nodes, but may still be connected to * a TCP relay. For a friend, this means that the connection to that * particular friend was built using direct UDP packets. */ UDP, } namespace self { CONNECTION connection_status { /** * Return whether we are connected to the DHT. The return value is equal to the * last value received through the `${event connection_status}` callback. * * @deprecated This getter is deprecated. Use the event and store the status * in the client state. */ get(); } /** * This event is triggered whenever there is a change in the DHT connection * state. When disconnected, a client may choose to call $bootstrap again, to * reconnect to the DHT. Note that this state may frequently change for short * amounts of time. Clients should therefore not immediately bootstrap on * receiving a disconnect. * * TODO(iphydf): how long should a client wait before bootstrapping again? */ event connection_status const { /** * @param connection_status Whether we are connected to the DHT. */ typedef void(CONNECTION connection_status); } } /** * Return the time in milliseconds before $iterate() should be called again * for optimal performance. */ const uint32_t iteration_interval(); /** * The main loop that needs to be run in intervals of $iteration_interval() * milliseconds. */ void iterate(any user_data); /******************************************************************************* * * :: Internal client information (Tox address/id) * ******************************************************************************/ namespace self { uint8_t[ADDRESS_SIZE] address { /** * Writes the Tox friend address of the client to a byte array. The address is * not in human-readable format. If a client wants to display the address, * formatting is required. * * @param address A memory region of at least $ADDRESS_SIZE bytes. If this * parameter is NULL, this function has no effect. * @see $ADDRESS_SIZE for the address format. */ get(); } uint32_t nospam { /** * Set the 4-byte nospam part of the address. This value is expected in host * byte order. I.e. 0x12345678 will form the bytes [12, 34, 56, 78] in the * nospam part of the Tox friend address. * * @param nospam Any 32 bit unsigned integer. */ set(); /** * Get the 4-byte nospam part of the address. This value is returned in host * byte order. */ get(); } uint8_t[PUBLIC_KEY_SIZE] public_key { /** * Copy the Tox Public Key (long term) from the Tox object. * * @param public_key A memory region of at least $PUBLIC_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. */ get(); } uint8_t[SECRET_KEY_SIZE] secret_key { /** * Copy the Tox Secret Key from the Tox object. * * @param secret_key A memory region of at least $SECRET_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. */ get(); } } /******************************************************************************* * * :: User-visible client information (nickname/status) * ******************************************************************************/ /** * Common error codes for all functions that set a piece of user-visible * client information. */ error for set_info { NULL, /** * Information length exceeded maximum permissible size. */ TOO_LONG, } namespace self { uint8_t[length <= MAX_NAME_LENGTH] name { /** * Set the nickname for the Tox client. * * Nickname length cannot exceed $MAX_NAME_LENGTH. If length is 0, the name * parameter is ignored (it can be NULL), and the nickname is set back to empty. * * @param name A byte array containing the new nickname. * @param length The size of the name byte array. * * @return true on success. */ set() with error for set_info; /** * Return the length of the current nickname as passed to $set. * * If no nickname was set before calling this function, the name is empty, * and this function returns 0. * * @see threading for concurrency implications. */ size(); /** * Write the nickname set by $set to a byte array. * * If no nickname was set before calling this function, the name is empty, * and this function has no effect. * * Call $size to find out how much memory to allocate for * the result. * * @param name A valid memory location large enough to hold the nickname. * If this parameter is NULL, the function has no effect. */ get(); } uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] status_message { /** * Set the client's status message. * * Status message length cannot exceed $MAX_STATUS_MESSAGE_LENGTH. If * length is 0, the status parameter is ignored (it can be NULL), and the * user status is set back to empty. */ set() with error for set_info; /** * Return the length of the current status message as passed to $set. * * If no status message was set before calling this function, the status * is empty, and this function returns 0. * * @see threading for concurrency implications. */ size(); /** * Write the status message set by $set to a byte array. * * If no status message was set before calling this function, the status is * empty, and this function has no effect. * * Call $size to find out how much memory to allocate for * the result. * * @param status_message A valid memory location large enough to hold the * status message. If this parameter is NULL, the function has no effect. */ get(); } USER_STATUS status { /** * Set the client's user status. * * @param status One of the user statuses listed in the enumeration above. */ set(); /** * Returns the client's user status. */ get(); } } /******************************************************************************* * * :: Friend list management * ******************************************************************************/ namespace friend { /** * Add a friend to the friend list and send a friend request. * * A friend request message must be at least 1 byte long and at most * $MAX_FRIEND_REQUEST_LENGTH. * * Friend numbers are unique identifiers used in all functions that operate on * friends. Once added, a friend number is stable for the lifetime of the Tox * object. After saving the state and reloading it, the friend numbers may not * be the same as before. Deleting a friend creates a gap in the friend number * set, which is filled by the next adding of a friend. Any pattern in friend * numbers should not be relied on. * * If more than INT32_MAX friends are added, this function causes undefined * behaviour. * * @param address The address of the friend (returned by ${self.address.get} of * the friend you wish to add) it must be $ADDRESS_SIZE bytes. * @param message The message that will be sent along with the friend request. * @param length The length of the data byte array. * * @return the friend number on success, an unspecified value on failure. */ uint32_t add( const uint8_t[ADDRESS_SIZE] address, const uint8_t[length <= MAX_FRIEND_REQUEST_LENGTH] message ) { NULL, /** * The length of the friend request message exceeded * $MAX_FRIEND_REQUEST_LENGTH. */ TOO_LONG, /** * The friend request message was empty. This, and the TOO_LONG code will * never be returned from $add_norequest. */ NO_MESSAGE, /** * The friend address belongs to the sending client. */ OWN_KEY, /** * A friend request has already been sent, or the address belongs to a friend * that is already on the friend list. */ ALREADY_SENT, /** * The friend address checksum failed. */ BAD_CHECKSUM, /** * The friend was already there, but the nospam value was different. */ SET_NEW_NOSPAM, /** * A memory allocation failed when trying to increase the friend list size. */ MALLOC, } /** * Add a friend without sending a friend request. * * This function is used to add a friend in response to a friend request. If the * client receives a friend request, it can be reasonably sure that the other * client added this client as a friend, eliminating the need for a friend * request. * * This function is also useful in a situation where both instances are * controlled by the same entity, so that this entity can perform the mutual * friend adding. In this case, there is no need for a friend request, either. * * @param public_key A byte array of length $PUBLIC_KEY_SIZE containing the * Public Key (not the Address) of the friend to add. * * @return the friend number on success, an unspecified value on failure. * @see $add for a more detailed description of friend numbers. */ uint32_t add_norequest(const uint8_t[PUBLIC_KEY_SIZE] public_key) with error for add; /** * Remove a friend from the friend list. * * This does not notify the friend of their deletion. After calling this * function, this client will appear offline to the friend and no communication * can occur between the two. * * @param friend_number Friend number for the friend to be deleted. * * @return true on success. */ bool delete(uint32_t friend_number) { /** * There was no friend with the given friend number. No friends were deleted. */ FRIEND_NOT_FOUND, } } /******************************************************************************* * * :: Friend list queries * ******************************************************************************/ namespace friend { /** * Return the friend number associated with that Public Key. * * @return the friend number on success, an unspecified value on failure. * @param public_key A byte array containing the Public Key. */ const uint32_t by_public_key(const uint8_t[PUBLIC_KEY_SIZE] public_key) { NULL, /** * No friend with the given Public Key exists on the friend list. */ NOT_FOUND, } /** * Checks if a friend with the given friend number exists and returns true if * it does. */ const bool exists(uint32_t friend_number); } namespace self { uint32_t[size] friend_list { /** * Return the number of friends on the friend list. * * This function can be used to determine how much memory to allocate for * $get. */ size(); /** * Copy a list of valid friend numbers into an array. * * Call $size to determine the number of elements to allocate. * * @param friend_list A memory region with enough space to hold the friend * list. If this parameter is NULL, this function has no effect. */ get(); } } namespace friend { uint8_t[PUBLIC_KEY_SIZE] public_key { /** * Copies the Public Key associated with a given friend number to a byte array. * * @param friend_number The friend number you want the Public Key of. * @param public_key A memory region of at least $PUBLIC_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. * * @return true on success. */ get(uint32_t friend_number) { /** * No friend with the given number exists on the friend list. */ FRIEND_NOT_FOUND, } } } namespace friend { uint64_t last_online { /** * Return a unix-time timestamp of the last time the friend associated with a given * friend number was seen online. This function will return UINT64_MAX on error. * * @param friend_number The friend number you want to query. */ get(uint32_t friend_number) { /** * No friend with the given number exists on the friend list. */ FRIEND_NOT_FOUND, } } } /******************************************************************************* * * :: Friend-specific state queries (can also be received through callbacks) * ******************************************************************************/ namespace friend { /** * Common error codes for friend state query functions. */ error for query { /** * The pointer parameter for storing the query result (name, message) was * NULL. Unlike the `_self_` variants of these functions, which have no effect * when a parameter is NULL, these functions return an error in that case. */ NULL, /** * The friend_number did not designate a valid friend. */ FRIEND_NOT_FOUND, } uint8_t[length <= MAX_NAME_LENGTH] name { /** * Return the length of the friend's name. If the friend number is invalid, the * return value is unspecified. * * The return value is equal to the `length` argument received by the last * `${event name}` callback. */ size(uint32_t friend_number) with error for query; /** * Write the name of the friend designated by the given friend number to a byte * array. * * Call $size to determine the allocation size for the `name` * parameter. * * The data written to `name` is equal to the data received by the last * `${event name}` callback. * * @param name A valid memory region large enough to store the friend's name. * * @return true on success. */ get(uint32_t friend_number) with error for query; } /** * This event is triggered when a friend changes their name. */ event name const { /** * @param friend_number The friend number of the friend whose name changed. * @param name A byte array containing the same data as * ${name.get} would write to its `name` parameter. * @param length A value equal to the return value of * ${name.size}. */ typedef void(uint32_t friend_number, const uint8_t[length <= MAX_NAME_LENGTH] name); } uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] status_message { /** * Return the length of the friend's status message. If the friend number is * invalid, the return value is SIZE_MAX. */ size(uint32_t friend_number) with error for query; /** * Write the status message of the friend designated by the given friend number to a byte * array. * * Call $size to determine the allocation size for the `status_message` * parameter. * * The data written to `status_message` is equal to the data received by the last * `${event status_message}` callback. * * @param status_message A valid memory region large enough to store the friend's status message. */ get(uint32_t friend_number) with error for query; } /** * This event is triggered when a friend changes their status message. */ event status_message const { /** * @param friend_number The friend number of the friend whose status message * changed. * @param message A byte array containing the same data as * ${status_message.get} would write to its `status_message` parameter. * @param length A value equal to the return value of * ${status_message.size}. */ typedef void(uint32_t friend_number, const uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] message); } USER_STATUS status { /** * Return the friend's user status (away/busy/...). If the friend number is * invalid, the return value is unspecified. * * The status returned is equal to the last status received through the * `${event status}` callback. * * @deprecated This getter is deprecated. Use the event and store the status * in the client state. */ get(uint32_t friend_number) with error for query; } /** * This event is triggered when a friend changes their user status. */ event status const { /** * @param friend_number The friend number of the friend whose user status * changed. * @param status The new user status. */ typedef void(uint32_t friend_number, USER_STATUS status); } CONNECTION connection_status { /** * Check whether a friend is currently connected to this client. * * The result of this function is equal to the last value received by the * `${event connection_status}` callback. * * @param friend_number The friend number for which to query the connection * status. * * @return the friend's connection status as it was received through the * `${event connection_status}` event. * * @deprecated This getter is deprecated. Use the event and store the status * in the client state. */ get(uint32_t friend_number) with error for query; } /** * This event is triggered when a friend goes offline after having been online, * or when a friend goes online. * * This callback is not called when adding friends. It is assumed that when * adding friends, their connection status is initially offline. */ event connection_status const { /** * @param friend_number The friend number of the friend whose connection status * changed. * @param connection_status The result of calling * ${connection_status.get} on the passed friend_number. */ typedef void(uint32_t friend_number, CONNECTION connection_status); } bool typing { /** * Check whether a friend is currently typing a message. * * @param friend_number The friend number for which to query the typing status. * * @return true if the friend is typing. * @return false if the friend is not typing, or the friend number was * invalid. Inspect the error code to determine which case it is. * * @deprecated This getter is deprecated. Use the event and store the status * in the client state. */ get(uint32_t friend_number) with error for query; } /** * This event is triggered when a friend starts or stops typing. */ event typing const { /** * @param friend_number The friend number of the friend who started or stopped * typing. * @param is_typing The result of calling ${typing.get} on the passed * friend_number. */ typedef void(uint32_t friend_number, bool is_typing); } } /******************************************************************************* * * :: Sending private messages * ******************************************************************************/ error for set_typing { /** * The friend number did not designate a valid friend. */ FRIEND_NOT_FOUND, } namespace self { bool typing { /** * Set the client's typing status for a friend. * * The client is responsible for turning it on or off. * * @param friend_number The friend to which the client is typing a message. * @param typing The typing status. True means the client is typing. * * @return true on success. */ set(uint32_t friend_number) with error for set_typing; } } namespace friend { namespace send { /** * Send a text chat message to an online friend. * * This function creates a chat message packet and pushes it into the send * queue. * * The message length may not exceed $MAX_MESSAGE_LENGTH. Larger messages * must be split by the client and sent as separate messages. Other clients can * then reassemble the fragments. Messages may not be empty. * * The return value of this function is the message ID. If a read receipt is * received, the triggered `${event read_receipt}` event will be passed this message ID. * * Message IDs are unique per friend. The first message ID is 0. Message IDs are * incremented by 1 each time a message is sent. If UINT32_MAX messages were * sent, the next message ID is 0. * * @param type Message type (normal, action, ...). * @param friend_number The friend number of the friend to send the message to. * @param message A non-NULL pointer to the first element of a byte array * containing the message text. * @param length Length of the message to be sent. */ uint32_t message(uint32_t friend_number, MESSAGE_TYPE type, const uint8_t[length <= MAX_MESSAGE_LENGTH] message) { NULL, /** * The friend number did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ FRIEND_NOT_CONNECTED, /** * An allocation error occurred while increasing the send queue size. */ SENDQ, /** * Message length exceeded $MAX_MESSAGE_LENGTH. */ TOO_LONG, /** * Attempted to send a zero-length message. */ EMPTY, } } /** * This event is triggered when the friend receives the message sent with * ${send.message} with the corresponding message ID. */ event read_receipt const { /** * @param friend_number The friend number of the friend who received the message. * @param message_id The message ID as returned from ${send.message} * corresponding to the message sent. */ typedef void(uint32_t friend_number, uint32_t message_id); } } /******************************************************************************* * * :: Receiving private messages and friend requests * ******************************************************************************/ namespace friend { /** * This event is triggered when a friend request is received. */ event request const { /** * @param public_key The Public Key of the user who sent the friend request. * @param message The message they sent along with the request. * @param length The size of the message byte array. */ typedef void(const uint8_t[PUBLIC_KEY_SIZE] public_key, const uint8_t[length <= MAX_MESSAGE_LENGTH] message); } /** * This event is triggered when a message from a friend is received. */ event message const { /** * @param friend_number The friend number of the friend who sent the message. * @param message The message data they sent. * @param length The size of the message byte array. */ typedef void(uint32_t friend_number, MESSAGE_TYPE type, const uint8_t[length <= MAX_MESSAGE_LENGTH] message); } } /******************************************************************************* * * :: File transmission: common between sending and receiving * ******************************************************************************/ /** * Generates a cryptographic hash of the given data. * * This function may be used by clients for any purpose, but is provided * primarily for validating cached avatars. This use is highly recommended to * avoid unnecessary avatar updates. * * If hash is NULL or data is NULL while length is not 0 the function returns false, * otherwise it returns true. * * This function is a wrapper to internal message-digest functions. * * @param hash A valid memory location the hash data. It must be at least * $HASH_LENGTH bytes in size. * @param data Data to be hashed or NULL. * @param length Size of the data array or 0. * * @return true if hash was not NULL. */ static bool hash(uint8_t[HASH_LENGTH] hash, const uint8_t[length] data); namespace file { /** * A list of pre-defined file kinds. Toxcore itself does not behave * differently for different file kinds. These are a hint to the client * telling it what use the sender intended for the file. The `kind` parameter * in the send function and recv callback are `uint32_t`, not $KIND, because * clients can invent their own file kind. Unknown file kinds should be * treated as ${KIND.DATA}. */ enum KIND { /** * Arbitrary file data. Clients can choose to handle it based on the file name * or magic or any other way they choose. */ DATA, /** * Avatar file_id. This consists of $hash(image). * Avatar data. This consists of the image data. * * Avatars can be sent at any time the client wishes. Generally, a client will * send the avatar to a friend when that friend comes online, and to all * friends when the avatar changed. A client can save some traffic by * remembering which friend received the updated avatar already and only send * it if the friend has an out of date avatar. * * Clients who receive avatar send requests can reject it (by sending * ${CONTROL.CANCEL} before any other controls), or accept it (by * sending ${CONTROL.RESUME}). The file_id of length $HASH_LENGTH bytes * (same length as $FILE_ID_LENGTH) will contain the hash. A client can compare * this hash with a saved hash and send ${CONTROL.CANCEL} to terminate the avatar * transfer if it matches. * * When file_size is set to 0 in the transfer request it means that the client * has no avatar. */ AVATAR, } enum class CONTROL { /** * Sent by the receiving side to accept a file send request. Also sent after a * $PAUSE command to continue sending or receiving. */ RESUME, /** * Sent by clients to pause the file transfer. The initial state of a file * transfer is always paused on the receiving side and running on the sending * side. If both the sending and receiving side pause the transfer, then both * need to send $RESUME for the transfer to resume. */ PAUSE, /** * Sent by the receiving side to reject a file send request before any other * commands are sent. Also sent by either side to terminate a file transfer. */ CANCEL, } /** * Sends a file control command to a friend for a given file transfer. * * @param friend_number The friend number of the friend the file is being * transferred to or received from. * @param file_number The friend-specific identifier for the file transfer. * @param control The control command to send. * * @return true on success. */ bool control(uint32_t friend_number, uint32_t file_number, CONTROL control) { /** * The friend_number passed did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ NOT_FOUND, /** * A RESUME control was sent, but the file transfer is running normally. */ NOT_PAUSED, /** * A RESUME control was sent, but the file transfer was paused by the other * party. Only the party that paused the transfer can resume it. */ DENIED, /** * A PAUSE control was sent, but the file transfer was already paused. */ ALREADY_PAUSED, /** * Packet queue is full. */ SENDQ, } /** * This event is triggered when a file control command is received from a * friend. */ event recv_control const { /** * When receiving ${CONTROL.CANCEL}, the client should release the * resources associated with the file number and consider the transfer failed. * * @param friend_number The friend number of the friend who is sending the file. * @param file_number The friend-specific file number the data received is * associated with. * @param control The file control command received. */ typedef void(uint32_t friend_number, uint32_t file_number, CONTROL control); } /** * Sends a file seek control command to a friend for a given file transfer. * * This function can only be called to resume a file transfer right before * ${CONTROL.RESUME} is sent. * * @param friend_number The friend number of the friend the file is being * received from. * @param file_number The friend-specific identifier for the file transfer. * @param position The position that the file should be seeked to. */ bool seek(uint32_t friend_number, uint32_t file_number, uint64_t position) { /** * The friend_number passed did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ NOT_FOUND, /** * File was not in a state where it could be seeked. */ DENIED, /** * Seek position was invalid */ INVALID_POSITION, /** * Packet queue is full. */ SENDQ, } error for get { NULL, /** * The friend_number passed did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * No file transfer with the given file number was found for the given friend. */ NOT_FOUND, } uint8_t[FILE_ID_LENGTH] file_id { /** * Copy the file id associated to the file transfer to a byte array. * * @param friend_number The friend number of the friend the file is being * transferred to or received from. * @param file_number The friend-specific identifier for the file transfer. * @param file_id A memory region of at least $FILE_ID_LENGTH bytes. If * this parameter is NULL, this function has no effect. * * @return true on success. */ get(uint32_t friend_number, uint32_t file_number) with error for get; } } /******************************************************************************* * * :: File transmission: sending * ******************************************************************************/ namespace file { /** * Send a file transmission request. * * Maximum filename length is $MAX_FILENAME_LENGTH bytes. The filename * should generally just be a file name, not a path with directory names. * * If a non-UINT64_MAX file size is provided, it can be used by both sides to * determine the sending progress. File size can be set to UINT64_MAX for streaming * data of unknown size. * * File transmission occurs in chunks, which are requested through the * `${event chunk_request}` event. * * When a friend goes offline, all file transfers associated with the friend are * purged from core. * * If the file contents change during a transfer, the behaviour is unspecified * in general. What will actually happen depends on the mode in which the file * was modified and how the client determines the file size. * * - If the file size was increased * - and sending mode was streaming (file_size = UINT64_MAX), the behaviour * will be as expected. * - and sending mode was file (file_size != UINT64_MAX), the * ${event chunk_request} callback will receive length = 0 when Core thinks * the file transfer has finished. If the client remembers the file size as * it was when sending the request, it will terminate the transfer normally. * If the client re-reads the size, it will think the friend cancelled the * transfer. * - If the file size was decreased * - and sending mode was streaming, the behaviour is as expected. * - and sending mode was file, the callback will return 0 at the new * (earlier) end-of-file, signalling to the friend that the transfer was * cancelled. * - If the file contents were modified * - at a position before the current read, the two files (local and remote) * will differ after the transfer terminates. * - at a position after the current read, the file transfer will succeed as * expected. * - In either case, both sides will regard the transfer as complete and * successful. * * @param friend_number The friend number of the friend the file send request * should be sent to. * @param kind The meaning of the file to be sent. * @param file_size Size in bytes of the file the client wants to send, UINT64_MAX if * unknown or streaming. * @param file_id A file identifier of length $FILE_ID_LENGTH that can be used to * uniquely identify file transfers across core restarts. If NULL, a random one will * be generated by core. It can then be obtained by using ${file_id.get}(). * @param filename Name of the file. Does not need to be the actual name. This * name will be sent along with the file send request. * @param filename_length Size in bytes of the filename. * * @return A file number used as an identifier in subsequent callbacks. This * number is per friend. File numbers are reused after a transfer terminates. * On failure, this function returns an unspecified value. Any pattern in file numbers * should not be relied on. */ uint32_t send(uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t[FILE_ID_LENGTH] file_id, const uint8_t[filename_length <= MAX_FILENAME_LENGTH] filename) { NULL, /** * The friend_number passed did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ FRIEND_NOT_CONNECTED, /** * Filename length exceeded $MAX_FILENAME_LENGTH bytes. */ NAME_TOO_LONG, /** * Too many ongoing transfers. The maximum number of concurrent file transfers * is 256 per friend per direction (sending and receiving). */ TOO_MANY, } /** * Send a chunk of file data to a friend. * * This function is called in response to the `${event chunk_request}` callback. The * length parameter should be equal to the one received though the callback. * If it is zero, the transfer is assumed complete. For files with known size, * Core will know that the transfer is complete after the last byte has been * received, so it is not necessary (though not harmful) to send a zero-length * chunk to terminate. For streams, core will know that the transfer is finished * if a chunk with length less than the length requested in the callback is sent. * * @param friend_number The friend number of the receiving friend for this file. * @param file_number The file transfer identifier returned by tox_file_send. * @param position The file or stream position from which to continue reading. * @return true on success. */ bool send_chunk(uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t[length] data) { /** * The length parameter was non-zero, but data was NULL. */ NULL, /** * The friend_number passed did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ NOT_FOUND, /** * File transfer was found but isn't in a transferring state: (paused, done, * broken, etc...) (happens only when not called from the request chunk callback). */ NOT_TRANSFERRING, /** * Attempted to send more or less data than requested. The requested data size is * adjusted according to maximum transmission unit and the expected end of * the file. Trying to send less or more than requested will return this error. */ INVALID_LENGTH, /** * Packet queue is full. */ SENDQ, /** * Position parameter was wrong. */ WRONG_POSITION, } /** * This event is triggered when Core is ready to send more file data. */ event chunk_request const { /** * If the length parameter is 0, the file transfer is finished, and the client's * resources associated with the file number should be released. After a call * with zero length, the file number can be reused for future file transfers. * * If the requested position is not equal to the client's idea of the current * file or stream position, it will need to seek. In case of read-once streams, * the client should keep the last read chunk so that a seek back can be * supported. A seek-back only ever needs to read from the last requested chunk. * This happens when a chunk was requested, but the send failed. A seek-back * request can occur an arbitrary number of times for any given chunk. * * In response to receiving this callback, the client should call the function * `$send_chunk` with the requested chunk. If the number of bytes sent * through that function is zero, the file transfer is assumed complete. A * client must send the full length of data requested with this callback. * * @param friend_number The friend number of the receiving friend for this file. * @param file_number The file transfer identifier returned by $send. * @param position The file or stream position from which to continue reading. * @param length The number of bytes requested for the current chunk. */ typedef void(uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length); } } /******************************************************************************* * * :: File transmission: receiving * ******************************************************************************/ namespace file { /** * This event is triggered when a file transfer request is received. */ event recv const { /** * The client should acquire resources to be associated with the file transfer. * Incoming file transfers start in the PAUSED state. After this callback * returns, a transfer can be rejected by sending a ${CONTROL.CANCEL} * control command before any other control commands. It can be accepted by * sending ${CONTROL.RESUME}. * * @param friend_number The friend number of the friend who is sending the file * transfer request. * @param file_number The friend-specific file number the data received is * associated with. * @param kind The meaning of the file that was sent. * @param file_size Size in bytes of the file the client wants to send, * UINT64_MAX if unknown or streaming. * @param filename Name of the file. Does not need to be the actual name. This * name will be sent along with the file send request. * @param filename_length Size in bytes of the filename. */ typedef void(uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, const uint8_t[filename_length <= MAX_FILENAME_LENGTH] filename); } /** * This event is first triggered when a file transfer request is received, and * subsequently when a chunk of file data for an accepted request was received. */ event recv_chunk const { /** * When length is 0, the transfer is finished and the client should release the * resources it acquired for the transfer. After a call with length = 0, the * file number can be reused for new file transfers. * * If position is equal to file_size (received in the file_receive callback) * when the transfer finishes, the file was received completely. Otherwise, if * file_size was UINT64_MAX, streaming ended successfully when length is 0. * * @param friend_number The friend number of the friend who is sending the file. * @param file_number The friend-specific file number the data received is * associated with. * @param position The file position of the first byte in data. * @param data A byte array containing the received chunk. * @param length The length of the received chunk. */ typedef void(uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t[length] data); } } /******************************************************************************* * * :: Conference management * ******************************************************************************/ namespace conference { /** * Conference types for the ${event invite} event. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ enum class TYPE { /** * Text-only conferences that must be accepted with the $join function. */ TEXT, /** * Video conference. The function to accept these is in toxav. */ AV, } /** * This event is triggered when the client is invited to join a conference. */ event invite const { /** * The invitation will remain valid until the inviting friend goes offline * or exits the conference. * * @param friend_number The friend who invited us. * @param type The conference type (text only or audio/video). * @param cookie A piece of data of variable length required to join the * conference. * @param length The length of the cookie. */ typedef void(uint32_t friend_number, TYPE type, const uint8_t[length] cookie); } /** * This event is triggered when the client successfully connects to a * conference after joining it with the $join function. */ event connected const { /** * @param conference_number The conference number of the conference to which we have connected. */ typedef void(uint32_t conference_number); } /** * This event is triggered when the client receives a conference message. */ event message const { /** * @param conference_number The conference number of the conference the message is intended for. * @param peer_number The ID of the peer who sent the message. * @param type The type of message (normal, action, ...). * @param message The message data. * @param length The length of the message. */ typedef void(uint32_t conference_number, uint32_t peer_number, MESSAGE_TYPE type, const uint8_t[length] message); } /** * This event is triggered when a peer changes the conference title. * * If peer_number == UINT32_MAX, then author is unknown (e.g. initial joining the conference). */ event title const { /** * @param conference_number The conference number of the conference the title change is intended for. * @param peer_number The ID of the peer who changed the title. * @param title The title data. * @param length The title length. */ typedef void(uint32_t conference_number, uint32_t peer_number, const uint8_t[length] title); } namespace peer { /** * This event is triggered when a peer changes their name. */ event name const { /** * @param conference_number The conference number of the conference the * peer is in. * @param peer_number The ID of the peer who changed their nickname. * @param name A byte array containing the new nickname. * @param length The size of the name byte array. */ typedef void(uint32_t conference_number, uint32_t peer_number, const uint8_t[length] name); } /** * This event is triggered when a peer joins or leaves the conference. */ event list_changed const { /** * @param conference_number The conference number of the conference the * peer is in. */ typedef void(uint32_t conference_number); } } /** * Creates a new conference. * * This function creates and connects to a new text conference. * * @return conference number on success, or an unspecified value on failure. */ uint32_t new() { /** * The conference instance failed to initialize. */ INIT, } /** * This function deletes a conference. * * @param conference_number The conference number of the conference to be deleted. * * @return true on success. */ bool delete(uint32_t conference_number) { /** * The conference number passed did not designate a valid conference. */ CONFERENCE_NOT_FOUND, } /** * Error codes for peer info queries. */ error for peer_query { /** * The conference number passed did not designate a valid conference. */ CONFERENCE_NOT_FOUND, /** * The peer number passed did not designate a valid peer. */ PEER_NOT_FOUND, /** * The client is not connected to the conference. */ NO_CONNECTION, } namespace peer { /** * Return the number of online peers in the conference. The unsigned * integers less than this number are the valid values of peer_number for * the functions querying these peers. Return value is unspecified on * failure. */ const uint32_t count(uint32_t conference_number) with error for peer_query; uint8_t[size] name { /** * Return the length of the peer's name. Return value is unspecified on failure. */ size(uint32_t conference_number, uint32_t peer_number) with error for peer_query; /** * Copy the name of peer_number who is in conference_number to name. * * Call $size to determine the allocation size for the `name` parameter. * * @param name A valid memory region large enough to store the peer's name. * * @return true on success. */ get(uint32_t conference_number, uint32_t peer_number) with error for peer_query; } /** * Copy the public key of peer_number who is in conference_number to public_key. * public_key must be $PUBLIC_KEY_SIZE long. * * @return true on success. */ uint8_t[PUBLIC_KEY_SIZE] public_key { get(uint32_t conference_number, uint32_t peer_number) with error for peer_query; } /** * Return true if passed peer_number corresponds to our own. */ const bool number_is_ours(uint32_t conference_number, uint32_t peer_number) with error for peer_query; } namespace offline_peer { /** * Return the number of offline peers in the conference. The unsigned * integers less than this number are the valid values of offline_peer_number for * the functions querying these peers. Return value is unspecified on failure. */ const uint32_t count(uint32_t conference_number) with error for peer_query; uint8_t[size] name { /** * Return the length of the offline peer's name. Return value is unspecified on failure. */ size(uint32_t conference_number, uint32_t offline_peer_number) with error for peer_query; /** * Copy the name of offline_peer_number who is in conference_number to name. * * Call $size to determine the allocation size for the `name` parameter. * * @param name A valid memory region large enough to store the peer's name. * * @return true on success. */ get(uint32_t conference_number, uint32_t offline_peer_number) with error for peer_query; } /** * Copy the public key of offline_peer_number who is in conference_number to public_key. * public_key must be $PUBLIC_KEY_SIZE long. * * @return true on success. */ uint8_t[PUBLIC_KEY_SIZE] public_key { get(uint32_t conference_number, uint32_t offline_peer_number) with error for peer_query; } /** * Return a unix-time timestamp of the last time offline_peer_number was seen to be active. */ uint64_t last_active { get(uint32_t conference_number, uint32_t offline_peer_number) with error for peer_query; } } /** * Set maximum number of offline peers to store, overriding the default. */ bool set_max_offline(uint32_t conference_number, uint32_t max_offline_peers) { /** * The conference number passed did not designate a valid conference. */ CONFERENCE_NOT_FOUND, } /** * Invites a friend to a conference. * * @param friend_number The friend number of the friend we want to invite. * @param conference_number The conference number of the conference we want to invite the friend to. * * @return true on success. */ bool invite(uint32_t friend_number, uint32_t conference_number) { /** * The conference number passed did not designate a valid conference. */ CONFERENCE_NOT_FOUND, /** * The invite packet failed to send. */ FAIL_SEND, /** * The client is not connected to the conference. */ NO_CONNECTION, } /** * Joins a conference that the client has been invited to. * * After successfully joining the conference, the client will not be "connected" * to it until a handshaking procedure has been completed. A * `${event connected}` event will then occur for the conference. The client * will then remain connected to the conference until the conference is deleted, * even across core restarts. Many operations on a conference will fail with a * corresponding error if attempted on a conference to which the client is not * yet connected. * * @param friend_number The friend number of the friend who sent the invite. * @param cookie Received via the `${event invite}` event. * @param length The size of cookie. * * @return conference number on success, an unspecified value on failure. */ uint32_t join(uint32_t friend_number, const uint8_t[length] cookie) { /** * The cookie passed has an invalid length. */ INVALID_LENGTH, /** * The conference is not the expected type. This indicates an invalid cookie. */ WRONG_TYPE, /** * The friend number passed does not designate a valid friend. */ FRIEND_NOT_FOUND, /** * Client is already in this conference. */ DUPLICATE, /** * Conference instance failed to initialize. */ INIT_FAIL, /** * The join packet failed to send. */ FAIL_SEND, } namespace send { /** * Send a text chat message to the conference. * * This function creates a conference message packet and pushes it into the send * queue. * * The message length may not exceed $MAX_MESSAGE_LENGTH. Larger messages * must be split by the client and sent as separate messages. Other clients can * then reassemble the fragments. * * @param conference_number The conference number of the conference the message is intended for. * @param type Message type (normal, action, ...). * @param message A non-NULL pointer to the first element of a byte array * containing the message text. * @param length Length of the message to be sent. * * @return true on success. */ bool message(uint32_t conference_number, MESSAGE_TYPE type, const uint8_t[length] message) { /** * The conference number passed did not designate a valid conference. */ CONFERENCE_NOT_FOUND, /** * The message is too long. */ TOO_LONG, /** * The client is not connected to the conference. */ NO_CONNECTION, /** * The message packet failed to send. */ FAIL_SEND, } } error for title { /** * The conference number passed did not designate a valid conference. */ CONFERENCE_NOT_FOUND, /** * The title is too long or empty. */ INVALID_LENGTH, /** * The title packet failed to send. */ FAIL_SEND, } uint8_t[length <= MAX_NAME_LENGTH] title { /** * Return the length of the conference title. Return value is unspecified on failure. * * The return value is equal to the `length` argument received by the last * `${event title}` callback. */ size(uint32_t conference_number) with error for title; /** * Write the title designated by the given conference number to a byte array. * * Call $size to determine the allocation size for the `title` parameter. * * The data written to `title` is equal to the data received by the last * `${event title}` callback. * * @param title A valid memory region large enough to store the title. * If this parameter is NULL, this function has no effect. * * @return true on success. */ get(uint32_t conference_number) with error for title; /** * Set the conference title and broadcast it to the rest of the conference. * * Title length cannot be longer than $MAX_NAME_LENGTH. * * @return true on success. */ set(uint32_t conference_number) with error for title; } uint32_t[size] chatlist { /** * Return the number of conferences in the Tox instance. * This should be used to determine how much memory to allocate for `$get`. */ size(); /** * Copy a list of valid conference numbers into the array chatlist. Determine * how much space to allocate for the array with the `$size` function. * * Note that `${savedata.get}` saves all connected conferences; * when toxcore is created from savedata in which conferences were saved, those * conferences will be connected at startup, and will be listed by * `$get`. * * The conference number of a loaded conference may differ from the conference * number it had when it was saved. */ get(); } /** * Returns the type of conference ($TYPE) that conference_number is. Return value is * unspecified on failure. */ TYPE type { get(uint32_t conference_number) { /** * The conference number passed did not designate a valid conference. */ CONFERENCE_NOT_FOUND, } } /** * Get the conference unique ID. * * If id is NULL, this function has no effect. * * @param id A memory region large enough to store $CONFERENCE_ID_SIZE bytes. * * @return true on success. */ const bool get_id(uint32_t conference_number, uint8_t[CONFERENCE_ID_SIZE] id); /** * Return the conference number associated with the specified id. * * @param id A byte array containing the conference id ($CONFERENCE_ID_SIZE). * * @return the conference number on success, an unspecified value on failure. */ const uint32_t by_id(const uint8_t[CONFERENCE_ID_SIZE] id) { NULL, /** * No conference with the given id exists on the conference list. */ NOT_FOUND, } /** * Get the conference unique ID. * * If uid is NULL, this function has no effect. * * @param uid A memory region large enough to store $CONFERENCE_UID_SIZE bytes. * * @return true on success. * @deprecated use $get_id instead (exactly the same function, just renamed). */ const bool get_uid(uint32_t conference_number, uint8_t[CONFERENCE_UID_SIZE] uid); /** * Return the conference number associated with the specified uid. * * @param uid A byte array containing the conference id ($CONFERENCE_UID_SIZE). * * @return the conference number on success, an unspecified value on failure. * @deprecated use $by_id instead (exactly the same function, just renamed). */ const uint32_t by_uid(const uint8_t[CONFERENCE_UID_SIZE] uid) { NULL, /** * No conference with the given uid exists on the conference list. */ NOT_FOUND, } } /******************************************************************************* * * :: Low-level custom packet sending and receiving * ******************************************************************************/ namespace friend { error for custom_packet { NULL, /** * The friend number did not designate a valid friend. */ FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ FRIEND_NOT_CONNECTED, /** * The first byte of data was not in the specified range for the packet type. * This range is 192-254 for lossy, and 69, 160-191 for lossless packets. */ INVALID, /** * Attempted to send an empty packet. */ EMPTY, /** * Packet data length exceeded $MAX_CUSTOM_PACKET_SIZE. */ TOO_LONG, /** * Packet queue is full. */ SENDQ, } namespace send { /** * Send a custom lossy packet to a friend. * * The first byte of data must be in the range 192-254. Maximum length of a * custom packet is $MAX_CUSTOM_PACKET_SIZE. * * Lossy packets behave like UDP packets, meaning they might never reach the * other side or might arrive more than once (if someone is messing with the * connection) or might arrive in the wrong order. * * Unless latency is an issue, it is recommended that you use lossless custom * packets instead. * * @param friend_number The friend number of the friend this lossy packet * should be sent to. * @param data A byte array containing the packet data. * @param length The length of the packet data byte array. * * @return true on success. */ bool lossy_packet(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data) with error for custom_packet; /** * Send a custom lossless packet to a friend. * * The first byte of data must be in the range 69, 160-191. Maximum length of a * custom packet is $MAX_CUSTOM_PACKET_SIZE. * * Lossless packet behaviour is comparable to TCP (reliability, arrive in order) * but with packets instead of a stream. * * @param friend_number The friend number of the friend this lossless packet * should be sent to. * @param data A byte array containing the packet data. * @param length The length of the packet data byte array. * * @return true on success. */ bool lossless_packet(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data) with error for custom_packet; } event lossy_packet const { /** * @param friend_number The friend number of the friend who sent a lossy packet. * @param data A byte array containing the received packet data. * @param length The length of the packet data byte array. */ typedef void(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data); } event lossless_packet const { /** * @param friend_number The friend number of the friend who sent the packet. * @param data A byte array containing the received packet data. * @param length The length of the packet data byte array. */ typedef void(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data); } } /******************************************************************************* * * :: Low-level network information * ******************************************************************************/ error for get_port { /** * The instance was not bound to any port. */ NOT_BOUND, } namespace self { uint8_t[PUBLIC_KEY_SIZE] dht_id { /** * Writes the temporary DHT public key of this instance to a byte array. * * This can be used in combination with an externally accessible IP address and * the bound port (from ${udp_port.get}) to run a temporary bootstrap node. * * Be aware that every time a new instance is created, the DHT public key * changes, meaning this cannot be used to run a permanent bootstrap node. * * @param dht_id A memory region of at least $PUBLIC_KEY_SIZE bytes. If this * parameter is NULL, this function has no effect. */ get(); } uint16_t udp_port { /** * Return the UDP port this Tox instance is bound to. */ get() with error for get_port; } uint16_t tcp_port { /** * Return the TCP port this Tox instance is bound to. This is only relevant if * the instance is acting as a TCP relay. */ get() with error for get_port; } } } // class tox %{ #ifdef __cplusplus } #endif typedef TOX_ERR_OPTIONS_NEW Tox_Err_Options_New; typedef TOX_ERR_NEW Tox_Err_New; typedef TOX_ERR_BOOTSTRAP Tox_Err_Bootstrap; typedef TOX_ERR_SET_INFO Tox_Err_Set_Info; typedef TOX_ERR_FRIEND_ADD Tox_Err_Friend_Add; typedef TOX_ERR_FRIEND_DELETE Tox_Err_Friend_Delete; typedef TOX_ERR_FRIEND_BY_PUBLIC_KEY Tox_Err_Friend_By_Public_Key; typedef TOX_ERR_FRIEND_GET_PUBLIC_KEY Tox_Err_Friend_Get_Public_Key; typedef TOX_ERR_FRIEND_GET_LAST_ONLINE Tox_Err_Friend_Get_Last_Online; typedef TOX_ERR_FRIEND_QUERY Tox_Err_Friend_Query; typedef TOX_ERR_SET_TYPING Tox_Err_Set_Typing; typedef TOX_ERR_FRIEND_SEND_MESSAGE Tox_Err_Friend_Send_Message; typedef TOX_ERR_FILE_CONTROL Tox_Err_File_Control; typedef TOX_ERR_FILE_SEEK Tox_Err_File_Seek; typedef TOX_ERR_FILE_GET Tox_Err_File_Get; typedef TOX_ERR_FILE_SEND Tox_Err_File_Send; typedef TOX_ERR_FILE_SEND_CHUNK Tox_Err_File_Send_Chunk; typedef TOX_ERR_CONFERENCE_NEW Tox_Err_Conference_New; typedef TOX_ERR_CONFERENCE_DELETE Tox_Err_Conference_Delete; typedef TOX_ERR_CONFERENCE_PEER_QUERY Tox_Err_Conference_Peer_Query; typedef TOX_ERR_CONFERENCE_SET_MAX_OFFLINE Tox_Err_Conference_Set_Max_Offline; typedef TOX_ERR_CONFERENCE_BY_ID Tox_Err_Conference_By_Id; typedef TOX_ERR_CONFERENCE_BY_UID Tox_Err_Conference_By_Uid; typedef TOX_ERR_CONFERENCE_INVITE Tox_Err_Conference_Invite; typedef TOX_ERR_CONFERENCE_JOIN Tox_Err_Conference_Join; typedef TOX_ERR_CONFERENCE_SEND_MESSAGE Tox_Err_Conference_Send_Message; typedef TOX_ERR_CONFERENCE_TITLE Tox_Err_Conference_Title; typedef TOX_ERR_CONFERENCE_GET_TYPE Tox_Err_Conference_Get_Type; typedef TOX_ERR_FRIEND_CUSTOM_PACKET Tox_Err_Friend_Custom_Packet; typedef TOX_ERR_GET_PORT Tox_Err_Get_Port; typedef TOX_USER_STATUS Tox_User_Status; typedef TOX_MESSAGE_TYPE Tox_Message_Type; typedef TOX_PROXY_TYPE Tox_Proxy_Type; typedef TOX_SAVEDATA_TYPE Tox_Savedata_Type; typedef TOX_LOG_LEVEL Tox_Log_Level; typedef TOX_CONNECTION Tox_Connection; typedef TOX_FILE_CONTROL Tox_File_Control; typedef TOX_CONFERENCE_TYPE Tox_Conference_Type; //!TOKSTYLE+ #endif // C_TOXCORE_TOXCORE_TOX_H %} c-toxcore-0.2.13/toxcore/tox.c000066400000000000000000002156121415350724400161550ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * The Tox public API. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifndef __cplusplus #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #endif #include "tox.h" #include "tox_private.h" #include #include #include #include "Messenger.h" #include "group.h" #include "logger.h" #include "mono_time.h" #include "../toxencryptsave/defines.h" #define SET_ERROR_PARAMETER(param, x) do { if (param) { *param = x; } } while (0) #if TOX_HASH_LENGTH != CRYPTO_SHA256_SIZE #error "TOX_HASH_LENGTH is assumed to be equal to CRYPTO_SHA256_SIZE" #endif #if FILE_ID_LENGTH != CRYPTO_SYMMETRIC_KEY_SIZE #error "FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE" #endif #if TOX_FILE_ID_LENGTH != CRYPTO_SYMMETRIC_KEY_SIZE #error "TOX_FILE_ID_LENGTH is assumed to be equal to CRYPTO_SYMMETRIC_KEY_SIZE" #endif #if TOX_FILE_ID_LENGTH != TOX_HASH_LENGTH #error "TOX_FILE_ID_LENGTH is assumed to be equal to TOX_HASH_LENGTH" #endif #if TOX_PUBLIC_KEY_SIZE != CRYPTO_PUBLIC_KEY_SIZE #error "TOX_PUBLIC_KEY_SIZE is assumed to be equal to CRYPTO_PUBLIC_KEY_SIZE" #endif #if TOX_SECRET_KEY_SIZE != CRYPTO_SECRET_KEY_SIZE #error "TOX_SECRET_KEY_SIZE is assumed to be equal to CRYPTO_SECRET_KEY_SIZE" #endif #if TOX_MAX_NAME_LENGTH != MAX_NAME_LENGTH #error "TOX_MAX_NAME_LENGTH is assumed to be equal to MAX_NAME_LENGTH" #endif #if TOX_MAX_STATUS_MESSAGE_LENGTH != MAX_STATUSMESSAGE_LENGTH #error "TOX_MAX_STATUS_MESSAGE_LENGTH is assumed to be equal to MAX_STATUSMESSAGE_LENGTH" #endif struct Tox { // XXX: Messenger *must* be the first member, because toxav casts its // `Tox *` to `Messenger **`. Messenger *m; Mono_Time *mono_time; pthread_mutex_t *mutex; tox_self_connection_status_cb *self_connection_status_callback; tox_friend_name_cb *friend_name_callback; tox_friend_status_message_cb *friend_status_message_callback; tox_friend_status_cb *friend_status_callback; tox_friend_connection_status_cb *friend_connection_status_callback; tox_friend_typing_cb *friend_typing_callback; tox_friend_read_receipt_cb *friend_read_receipt_callback; tox_friend_request_cb *friend_request_callback; tox_friend_message_cb *friend_message_callback; tox_file_recv_control_cb *file_recv_control_callback; tox_file_chunk_request_cb *file_chunk_request_callback; tox_file_recv_cb *file_recv_callback; tox_file_recv_chunk_cb *file_recv_chunk_callback; tox_conference_invite_cb *conference_invite_callback; tox_conference_connected_cb *conference_connected_callback; tox_conference_message_cb *conference_message_callback; tox_conference_title_cb *conference_title_callback; tox_conference_peer_name_cb *conference_peer_name_callback; tox_conference_peer_list_changed_cb *conference_peer_list_changed_callback; tox_friend_lossy_packet_cb *friend_lossy_packet_callback_per_pktid[UINT8_MAX + 1]; tox_friend_lossless_packet_cb *friend_lossless_packet_callback_per_pktid[UINT8_MAX + 1]; void *toxav_object; // workaround to store a ToxAV object (setter and getter functions are available) }; static void lock(const Tox *tox) { if (tox->mutex != nullptr) { pthread_mutex_lock(tox->mutex); } } static void unlock(const Tox *tox) { if (tox->mutex != nullptr) { pthread_mutex_unlock(tox->mutex); } } struct Tox_Userdata { Tox *tox; void *user_data; }; static void tox_self_connection_status_handler(Messenger *m, unsigned int connection_status, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->self_connection_status_callback != nullptr) { tox_data->tox->self_connection_status_callback(tox_data->tox, (Tox_Connection)connection_status, tox_data->user_data); } } static void tox_friend_name_handler(Messenger *m, uint32_t friend_number, const uint8_t *name, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_name_callback != nullptr) { tox_data->tox->friend_name_callback(tox_data->tox, friend_number, name, length, tox_data->user_data); } } static void tox_friend_status_message_handler(Messenger *m, uint32_t friend_number, const uint8_t *message, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_status_message_callback != nullptr) { tox_data->tox->friend_status_message_callback(tox_data->tox, friend_number, message, length, tox_data->user_data); } } static void tox_friend_status_handler(Messenger *m, uint32_t friend_number, unsigned int status, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_status_callback != nullptr) { tox_data->tox->friend_status_callback(tox_data->tox, friend_number, (Tox_User_Status)status, tox_data->user_data); } } static void tox_friend_connection_status_handler(Messenger *m, uint32_t friend_number, unsigned int connection_status, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_connection_status_callback != nullptr) { tox_data->tox->friend_connection_status_callback(tox_data->tox, friend_number, (Tox_Connection)connection_status, tox_data->user_data); } } static void tox_friend_typing_handler(Messenger *m, uint32_t friend_number, bool is_typing, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_typing_callback != nullptr) { tox_data->tox->friend_typing_callback(tox_data->tox, friend_number, is_typing, tox_data->user_data); } } static void tox_friend_read_receipt_handler(Messenger *m, uint32_t friend_number, uint32_t message_id, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_read_receipt_callback != nullptr) { tox_data->tox->friend_read_receipt_callback(tox_data->tox, friend_number, message_id, tox_data->user_data); } } static void tox_friend_request_handler(Messenger *m, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_request_callback != nullptr) { tox_data->tox->friend_request_callback(tox_data->tox, public_key, message, length, tox_data->user_data); } } static void tox_friend_message_handler(Messenger *m, uint32_t friend_number, unsigned int type, const uint8_t *message, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_message_callback != nullptr) { tox_data->tox->friend_message_callback(tox_data->tox, friend_number, (Tox_Message_Type)type, message, length, tox_data->user_data); } } static void tox_file_recv_control_handler(Messenger *m, uint32_t friend_number, uint32_t file_number, unsigned int control, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->file_recv_control_callback != nullptr) { tox_data->tox->file_recv_control_callback(tox_data->tox, friend_number, file_number, (Tox_File_Control)control, tox_data->user_data); } } static void tox_file_chunk_request_handler(Messenger *m, uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->file_chunk_request_callback != nullptr) { tox_data->tox->file_chunk_request_callback(tox_data->tox, friend_number, file_number, position, length, tox_data->user_data); } } static void tox_file_recv_handler(Messenger *m, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, const uint8_t *filename, size_t filename_length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->file_recv_callback != nullptr) { tox_data->tox->file_recv_callback(tox_data->tox, friend_number, file_number, kind, file_size, filename, filename_length, tox_data->user_data); } } static void tox_file_recv_chunk_handler(Messenger *m, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->file_recv_chunk_callback != nullptr) { tox_data->tox->file_recv_chunk_callback(tox_data->tox, friend_number, file_number, position, data, length, tox_data->user_data); } } static void tox_conference_invite_handler(Messenger *m, uint32_t friend_number, int type, const uint8_t *cookie, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->conference_invite_callback != nullptr) { tox_data->tox->conference_invite_callback(tox_data->tox, friend_number, (Tox_Conference_Type)type, cookie, length, tox_data->user_data); } } static void tox_conference_connected_handler(Messenger *m, uint32_t conference_number, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->conference_connected_callback != nullptr) { tox_data->tox->conference_connected_callback(tox_data->tox, conference_number, tox_data->user_data); } } static void tox_conference_message_handler(Messenger *m, uint32_t conference_number, uint32_t peer_number, int type, const uint8_t *message, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->conference_message_callback != nullptr) { tox_data->tox->conference_message_callback(tox_data->tox, conference_number, peer_number, (Tox_Message_Type)type, message, length, tox_data->user_data); } } static void tox_conference_title_handler(Messenger *m, uint32_t conference_number, uint32_t peer_number, const uint8_t *title, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->conference_title_callback != nullptr) { tox_data->tox->conference_title_callback(tox_data->tox, conference_number, peer_number, title, length, tox_data->user_data); } } static void tox_conference_peer_name_handler(Messenger *m, uint32_t conference_number, uint32_t peer_number, const uint8_t *name, size_t length, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->conference_peer_name_callback != nullptr) { tox_data->tox->conference_peer_name_callback(tox_data->tox, conference_number, peer_number, name, length, tox_data->user_data); } } static void tox_conference_peer_list_changed_handler(Messenger *m, uint32_t conference_number, void *user_data) { struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->conference_peer_list_changed_callback != nullptr) { tox_data->tox->conference_peer_list_changed_callback(tox_data->tox, conference_number, tox_data->user_data); } } static void tox_friend_lossy_packet_handler(Messenger *m, uint32_t friend_number, uint8_t packet_id, const uint8_t *data, size_t length, void *user_data) { assert(data != nullptr); assert(length > 0); struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_lossy_packet_callback_per_pktid[packet_id] != nullptr) { tox_data->tox->friend_lossy_packet_callback_per_pktid[packet_id](tox_data->tox, friend_number, data, length, tox_data->user_data); } } static void tox_friend_lossless_packet_handler(Messenger *m, uint32_t friend_number, uint8_t packet_id, const uint8_t *data, size_t length, void *user_data) { assert(data != nullptr); assert(length > 0); struct Tox_Userdata *tox_data = (struct Tox_Userdata *)user_data; if (tox_data->tox->friend_lossless_packet_callback_per_pktid[packet_id] != nullptr) { tox_data->tox->friend_lossless_packet_callback_per_pktid[packet_id](tox_data->tox, friend_number, data, length, tox_data->user_data); } } bool tox_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) { return TOX_VERSION_IS_API_COMPATIBLE(major, minor, patch); } static State_Load_Status state_load_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type) { const Tox *tox = (const Tox *)outer; State_Load_Status status = STATE_LOAD_STATUS_CONTINUE; if (messenger_load_state_section(tox->m, data, length, type, &status) || conferences_load_state_section(tox->m->conferences_object, data, length, type, &status)) { return status; } if (type == STATE_TYPE_END) { if (length != 0) { return STATE_LOAD_STATUS_ERROR; } return STATE_LOAD_STATUS_END; } LOGGER_ERROR(tox->m->log, "Load state: contains unrecognized part (len %u, type %u)", length, type); return STATE_LOAD_STATUS_CONTINUE; } /* Load tox from data of size length. */ static int tox_load(Tox *tox, const uint8_t *data, uint32_t length) { uint32_t data32[2]; const uint32_t cookie_len = sizeof(data32); if (length < cookie_len) { return -1; } memcpy(data32, data, sizeof(uint32_t)); lendian_bytes_to_host32(data32 + 1, data + sizeof(uint32_t)); if (data32[0] != 0 || data32[1] != STATE_COOKIE_GLOBAL) { return -1; } return state_load(tox->m->log, state_load_callback, tox, data + cookie_len, length - cookie_len, STATE_COOKIE_TYPE); } Tox *tox_new(const struct Tox_Options *options, Tox_Err_New *error) { Tox *tox = (Tox *)calloc(1, sizeof(Tox)); if (tox == nullptr) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); return nullptr; } Messenger_Options m_options = {0}; bool load_savedata_sk = false; bool load_savedata_tox = false; struct Tox_Options *default_options = nullptr; if (options == nullptr) { Tox_Err_Options_New err; default_options = tox_options_new(&err); switch (err) { case TOX_ERR_OPTIONS_NEW_OK: break; case TOX_ERR_OPTIONS_NEW_MALLOC: SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); free(tox); return nullptr; } } const struct Tox_Options *const opts = options != nullptr ? options : default_options; assert(opts != nullptr); if (tox_options_get_savedata_type(opts) != TOX_SAVEDATA_TYPE_NONE) { if (tox_options_get_savedata_data(opts) == nullptr || tox_options_get_savedata_length(opts) == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); tox_options_free(default_options); free(tox); return nullptr; } } if (tox_options_get_savedata_type(opts) == TOX_SAVEDATA_TYPE_SECRET_KEY) { if (tox_options_get_savedata_length(opts) != TOX_SECRET_KEY_SIZE) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); tox_options_free(default_options); free(tox); return nullptr; } load_savedata_sk = true; } else if (tox_options_get_savedata_type(opts) == TOX_SAVEDATA_TYPE_TOX_SAVE) { if (tox_options_get_savedata_length(opts) < TOX_ENC_SAVE_MAGIC_LENGTH) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); tox_options_free(default_options); free(tox); return nullptr; } if (crypto_memcmp(tox_options_get_savedata_data(opts), TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_ENCRYPTED); tox_options_free(default_options); free(tox); return nullptr; } load_savedata_tox = true; } m_options.ipv6enabled = tox_options_get_ipv6_enabled(opts); m_options.udp_disabled = !tox_options_get_udp_enabled(opts); m_options.port_range[0] = tox_options_get_start_port(opts); m_options.port_range[1] = tox_options_get_end_port(opts); m_options.tcp_server_port = tox_options_get_tcp_port(opts); m_options.hole_punching_enabled = tox_options_get_hole_punching_enabled(opts); m_options.local_discovery_enabled = tox_options_get_local_discovery_enabled(opts); m_options.log_callback = (logger_cb *)tox_options_get_log_callback(opts); m_options.log_context = tox; m_options.log_user_data = tox_options_get_log_user_data(opts); switch (tox_options_get_proxy_type(opts)) { case TOX_PROXY_TYPE_HTTP: m_options.proxy_info.proxy_type = TCP_PROXY_HTTP; break; case TOX_PROXY_TYPE_SOCKS5: m_options.proxy_info.proxy_type = TCP_PROXY_SOCKS5; break; case TOX_PROXY_TYPE_NONE: m_options.proxy_info.proxy_type = TCP_PROXY_NONE; break; default: SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_TYPE); tox_options_free(default_options); free(tox); return nullptr; } if (m_options.proxy_info.proxy_type != TCP_PROXY_NONE) { if (tox_options_get_proxy_port(opts) == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_PORT); tox_options_free(default_options); free(tox); return nullptr; } ip_init(&m_options.proxy_info.ip_port.ip, m_options.ipv6enabled); if (m_options.ipv6enabled) { m_options.proxy_info.ip_port.ip.family = net_family_unspec; } if (addr_resolve_or_parse_ip(tox_options_get_proxy_host(opts), &m_options.proxy_info.ip_port.ip, nullptr) == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_HOST); // TODO(irungentoo): TOX_ERR_NEW_PROXY_NOT_FOUND if domain. tox_options_free(default_options); free(tox); return nullptr; } m_options.proxy_info.ip_port.port = net_htons(tox_options_get_proxy_port(opts)); } tox->mono_time = mono_time_new(); if (tox->mono_time == nullptr) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); tox_options_free(default_options); free(tox); return nullptr; } if (tox_options_get_experimental_thread_safety(opts)) { tox->mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t)); if (tox->mutex == nullptr) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); tox_options_free(default_options); free(tox); return nullptr; } pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(tox->mutex, &attr); } else { tox->mutex = nullptr; } lock(tox); unsigned int m_error; tox->m = new_messenger(tox->mono_time, &m_options, &m_error); // TODO(iphydf): Clarify this code, check for NULL before new_groupchats, so // new_groupchats can assume m is non-NULL. if (!new_groupchats(tox->mono_time, tox->m)) { kill_messenger(tox->m); if (m_error == MESSENGER_ERROR_PORT) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PORT_ALLOC); } else if (m_error == MESSENGER_ERROR_TCP_SERVER) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PORT_ALLOC); } else { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); } mono_time_free(tox->mono_time); tox_options_free(default_options); unlock(tox); if (tox->mutex != nullptr) { pthread_mutex_destroy(tox->mutex); } free(tox->mutex); free(tox); return nullptr; } if (load_savedata_tox && tox_load(tox, tox_options_get_savedata_data(opts), tox_options_get_savedata_length(opts)) == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); } else if (load_savedata_sk) { load_secret_key(tox->m->net_crypto, tox_options_get_savedata_data(opts)); SET_ERROR_PARAMETER(error, TOX_ERR_NEW_OK); } else { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_OK); } m_callback_namechange(tox->m, tox_friend_name_handler); m_callback_core_connection(tox->m, tox_self_connection_status_handler); m_callback_statusmessage(tox->m, tox_friend_status_message_handler); m_callback_userstatus(tox->m, tox_friend_status_handler); m_callback_connectionstatus(tox->m, tox_friend_connection_status_handler); m_callback_typingchange(tox->m, tox_friend_typing_handler); m_callback_read_receipt(tox->m, tox_friend_read_receipt_handler); m_callback_friendrequest(tox->m, tox_friend_request_handler); m_callback_friendmessage(tox->m, tox_friend_message_handler); callback_file_control(tox->m, tox_file_recv_control_handler); callback_file_reqchunk(tox->m, tox_file_chunk_request_handler); callback_file_sendrequest(tox->m, tox_file_recv_handler); callback_file_data(tox->m, tox_file_recv_chunk_handler); g_callback_group_invite(tox->m->conferences_object, tox_conference_invite_handler); g_callback_group_connected(tox->m->conferences_object, tox_conference_connected_handler); g_callback_group_message(tox->m->conferences_object, tox_conference_message_handler); g_callback_group_title(tox->m->conferences_object, tox_conference_title_handler); g_callback_peer_name(tox->m->conferences_object, tox_conference_peer_name_handler); g_callback_peer_list_changed(tox->m->conferences_object, tox_conference_peer_list_changed_handler); custom_lossy_packet_registerhandler(tox->m, tox_friend_lossy_packet_handler); custom_lossless_packet_registerhandler(tox->m, tox_friend_lossless_packet_handler); tox_options_free(default_options); unlock(tox); return tox; } void tox_kill(Tox *tox) { if (tox == nullptr) { return; } lock(tox); LOGGER_ASSERT(tox->m->log, tox->m->msi_packet == nullptr, "Attempted to kill tox while toxav is still alive"); kill_groupchats(tox->m->conferences_object); kill_messenger(tox->m); mono_time_free(tox->mono_time); unlock(tox); if (tox->mutex != nullptr) { pthread_mutex_destroy(tox->mutex); free(tox->mutex); } free(tox); } static uint32_t end_size(void) { return 2 * sizeof(uint32_t); } static void end_save(uint8_t *data) { state_write_section_header(data, STATE_COOKIE_TYPE, 0, STATE_TYPE_END); } size_t tox_get_savedata_size(const Tox *tox) { assert(tox != nullptr); lock(tox); size_t ret = 2 * sizeof(uint32_t) + messenger_size(tox->m) + conferences_size(tox->m->conferences_object) + end_size(); unlock(tox); return ret; } void tox_get_savedata(const Tox *tox, uint8_t *savedata) { assert(tox != nullptr); if (savedata == nullptr) { return; } memset(savedata, 0, tox_get_savedata_size(tox)); lock(tox); const uint32_t size32 = sizeof(uint32_t); // write cookie memset(savedata, 0, size32); savedata += size32; host_to_lendian_bytes32(savedata, STATE_COOKIE_GLOBAL); savedata += size32; savedata = messenger_save(tox->m, savedata); savedata = conferences_save(tox->m->conferences_object, savedata); end_save(savedata); unlock(tox); } bool tox_bootstrap(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, Tox_Err_Bootstrap *error) { assert(tox != nullptr); if (!host || !public_key) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_NULL); return 0; } if (port == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_PORT); return 0; } IP_Port *root; const int32_t count = net_getipport(host, &root, TOX_SOCK_DGRAM); if (count == -1) { net_freeipport(root); SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); return 0; } unsigned int i; lock(tox); for (i = 0; i < count; ++i) { root[i].port = net_htons(port); onion_add_bs_path_node(tox->m->onion_c, root[i], public_key); dht_bootstrap(tox->m->dht, root[i], public_key); } unlock(tox); net_freeipport(root); if (count) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_OK); return 1; } SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); return 0; } bool tox_add_tcp_relay(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, Tox_Err_Bootstrap *error) { assert(tox != nullptr); if (!host || !public_key) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_NULL); return 0; } if (port == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_PORT); return 0; } IP_Port *root; int32_t count = net_getipport(host, &root, TOX_SOCK_STREAM); if (count == -1) { net_freeipport(root); SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); return 0; } unsigned int i; lock(tox); for (i = 0; i < count; ++i) { root[i].port = net_htons(port); add_tcp_relay(tox->m->net_crypto, root[i], public_key); } unlock(tox); net_freeipport(root); if (count) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_OK); return 1; } SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); return 0; } Tox_Connection tox_self_get_connection_status(const Tox *tox) { assert(tox != nullptr); lock(tox); const unsigned int ret = onion_connection_status(tox->m->onion_c); unlock(tox); if (ret == 2) { return TOX_CONNECTION_UDP; } if (ret == 1) { return TOX_CONNECTION_TCP; } return TOX_CONNECTION_NONE; } void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback) { assert(tox != nullptr); tox->self_connection_status_callback = callback; } uint32_t tox_iteration_interval(const Tox *tox) { assert(tox != nullptr); lock(tox); uint32_t ret = messenger_run_interval(tox->m); unlock(tox); return ret; } void tox_iterate(Tox *tox, void *user_data) { assert(tox != nullptr); lock(tox); mono_time_update(tox->mono_time); struct Tox_Userdata tox_data = { tox, user_data }; do_messenger(tox->m, &tox_data); do_groupchats(tox->m->conferences_object, &tox_data); unlock(tox); } void tox_self_get_address(const Tox *tox, uint8_t *address) { assert(tox != nullptr); if (address) { lock(tox); getaddress(tox->m, address); unlock(tox); } } void tox_self_set_nospam(Tox *tox, uint32_t nospam) { assert(tox != nullptr); lock(tox); set_nospam(tox->m->fr, net_htonl(nospam)); unlock(tox); } uint32_t tox_self_get_nospam(const Tox *tox) { assert(tox != nullptr); lock(tox); uint32_t ret = net_ntohl(get_nospam(tox->m->fr)); unlock(tox); return ret; } void tox_self_get_public_key(const Tox *tox, uint8_t *public_key) { assert(tox != nullptr); if (public_key) { lock(tox); memcpy(public_key, nc_get_self_public_key(tox->m->net_crypto), CRYPTO_PUBLIC_KEY_SIZE); unlock(tox); } } void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key) { assert(tox != nullptr); if (secret_key) { lock(tox); memcpy(secret_key, nc_get_self_secret_key(tox->m->net_crypto), CRYPTO_SECRET_KEY_SIZE); unlock(tox); } } bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, Tox_Err_Set_Info *error) { assert(tox != nullptr); if (!name && length != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_NULL); return 0; } lock(tox); if (setname(tox->m, name, length) == 0) { // TODO(irungentoo): function to set different per group names? send_name_all_groups(tox->m->conferences_object); SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_OK); unlock(tox); return 1; } SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_TOO_LONG); unlock(tox); return 0; } size_t tox_self_get_name_size(const Tox *tox) { assert(tox != nullptr); lock(tox); size_t ret = m_get_self_name_size(tox->m); unlock(tox); return ret; } void tox_self_get_name(const Tox *tox, uint8_t *name) { assert(tox != nullptr); if (name) { lock(tox); getself_name(tox->m, name); unlock(tox); } } bool tox_self_set_status_message(Tox *tox, const uint8_t *status_message, size_t length, Tox_Err_Set_Info *error) { assert(tox != nullptr); if (!status_message && length != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_NULL); return 0; } lock(tox); if (m_set_statusmessage(tox->m, status_message, length) == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_OK); unlock(tox); return 1; } SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_TOO_LONG); unlock(tox); return 0; } size_t tox_self_get_status_message_size(const Tox *tox) { assert(tox != nullptr); lock(tox); size_t ret = m_get_self_statusmessage_size(tox->m); unlock(tox); return ret; } void tox_self_get_status_message(const Tox *tox, uint8_t *status_message) { assert(tox != nullptr); if (status_message) { lock(tox); m_copy_self_statusmessage(tox->m, status_message); unlock(tox); } } void tox_self_set_status(Tox *tox, Tox_User_Status status) { assert(tox != nullptr); lock(tox); m_set_userstatus(tox->m, status); unlock(tox); } Tox_User_Status tox_self_get_status(const Tox *tox) { assert(tox != nullptr); lock(tox); const uint8_t status = m_get_self_userstatus(tox->m); unlock(tox); return (Tox_User_Status)status; } static void set_friend_error(const Logger *log, int32_t ret, Tox_Err_Friend_Add *error) { switch (ret) { case FAERR_TOOLONG: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_TOO_LONG); break; case FAERR_NOMESSAGE: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NO_MESSAGE); break; case FAERR_OWNKEY: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OWN_KEY); break; case FAERR_ALREADYSENT: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_ALREADY_SENT); break; case FAERR_BADCHECKSUM: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_BAD_CHECKSUM); break; case FAERR_SETNEWNOSPAM: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM); break; case FAERR_NOMEM: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_MALLOC); break; default: /* can't happen */ LOGGER_FATAL(log, "impossible: unknown friend-add error"); break; } } uint32_t tox_friend_add(Tox *tox, const uint8_t *address, const uint8_t *message, size_t length, Tox_Err_Friend_Add *error) { assert(tox != nullptr); if (!address || !message) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NULL); return UINT32_MAX; } lock(tox); const int32_t ret = m_addfriend(tox->m, address, message, length); if (ret >= 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OK); unlock(tox); return ret; } set_friend_error(tox->m->log, ret, error); unlock(tox); return UINT32_MAX; } uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, Tox_Err_Friend_Add *error) { assert(tox != nullptr); if (!public_key) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NULL); return UINT32_MAX; } lock(tox); const int32_t ret = m_addfriend_norequest(tox->m, public_key); if (ret >= 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OK); unlock(tox); return ret; } set_friend_error(tox->m->log, ret, error); unlock(tox); return UINT32_MAX; } bool tox_friend_delete(Tox *tox, uint32_t friend_number, Tox_Err_Friend_Delete *error) { assert(tox != nullptr); lock(tox); const int ret = m_delfriend(tox->m, friend_number); unlock(tox); // TODO(irungentoo): handle if realloc fails? if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_DELETE_OK); return 1; } uint32_t tox_friend_by_public_key(const Tox *tox, const uint8_t *public_key, Tox_Err_Friend_By_Public_Key *error) { assert(tox != nullptr); if (!public_key) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL); return UINT32_MAX; } lock(tox); const int32_t ret = getfriend_id(tox->m, public_key); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND); return UINT32_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK); return ret; } bool tox_friend_get_public_key(const Tox *tox, uint32_t friend_number, uint8_t *public_key, Tox_Err_Friend_Get_Public_Key *error) { assert(tox != nullptr); if (!public_key) { return 0; } lock(tox); if (get_real_pk(tox->m, friend_number, public_key) == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND); unlock(tox); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK); unlock(tox); return 1; } bool tox_friend_exists(const Tox *tox, uint32_t friend_number) { assert(tox != nullptr); lock(tox); bool ret = m_friend_exists(tox->m, friend_number); unlock(tox); return ret; } uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Get_Last_Online *error) { assert(tox != nullptr); lock(tox); const uint64_t timestamp = m_get_last_online(tox->m, friend_number); unlock(tox); if (timestamp == UINT64_MAX) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND); return UINT64_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_LAST_ONLINE_OK); return timestamp; } size_t tox_self_get_friend_list_size(const Tox *tox) { assert(tox != nullptr); lock(tox); size_t ret = count_friendlist(tox->m); unlock(tox); return ret; } void tox_self_get_friend_list(const Tox *tox, uint32_t *friend_list) { assert(tox != nullptr); if (friend_list) { lock(tox); // TODO(irungentoo): size parameter? copy_friendlist(tox->m, friend_list, count_friendlist(tox->m)); unlock(tox); } } size_t tox_friend_get_name_size(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) { assert(tox != nullptr); lock(tox); const int ret = m_get_name_size(tox->m, friend_number); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return SIZE_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return ret; } bool tox_friend_get_name(const Tox *tox, uint32_t friend_number, uint8_t *name, Tox_Err_Friend_Query *error) { assert(tox != nullptr); if (!name) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_NULL); return 0; } lock(tox); const int ret = getname(tox->m, friend_number, name); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return 1; } void tox_callback_friend_name(Tox *tox, tox_friend_name_cb *callback) { assert(tox != nullptr); tox->friend_name_callback = callback; } size_t tox_friend_get_status_message_size(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) { assert(tox != nullptr); lock(tox); const int ret = m_get_statusmessage_size(tox->m, friend_number); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return SIZE_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return ret; } bool tox_friend_get_status_message(const Tox *tox, uint32_t friend_number, uint8_t *status_message, Tox_Err_Friend_Query *error) { assert(tox != nullptr); if (!status_message) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_NULL); return false; } lock(tox); const int size = m_get_statusmessage_size(tox->m, friend_number); if (size == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); unlock(tox); return false; } const int ret = m_copy_statusmessage(tox->m, friend_number, status_message, size); LOGGER_ASSERT(tox->m->log, ret == size, "concurrency problem: friend status message changed"); SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); unlock(tox); return ret == size; } void tox_callback_friend_status_message(Tox *tox, tox_friend_status_message_cb *callback) { assert(tox != nullptr); tox->friend_status_message_callback = callback; } Tox_User_Status tox_friend_get_status(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) { assert(tox != nullptr); lock(tox); const int ret = m_get_userstatus(tox->m, friend_number); unlock(tox); if (ret == USERSTATUS_INVALID) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return (Tox_User_Status)(TOX_USER_STATUS_BUSY + 1); } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return (Tox_User_Status)ret; } void tox_callback_friend_status(Tox *tox, tox_friend_status_cb *callback) { assert(tox != nullptr); tox->friend_status_callback = callback; } Tox_Connection tox_friend_get_connection_status(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) { assert(tox != nullptr); lock(tox); const int ret = m_get_friend_connectionstatus(tox->m, friend_number); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return TOX_CONNECTION_NONE; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return (Tox_Connection)ret; } void tox_callback_friend_connection_status(Tox *tox, tox_friend_connection_status_cb *callback) { assert(tox != nullptr); tox->friend_connection_status_callback = callback; } bool tox_friend_get_typing(const Tox *tox, uint32_t friend_number, Tox_Err_Friend_Query *error) { assert(tox != nullptr); lock(tox); const int ret = m_get_istyping(tox->m, friend_number); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return !!ret; } void tox_callback_friend_typing(Tox *tox, tox_friend_typing_cb *callback) { assert(tox != nullptr); tox->friend_typing_callback = callback; } bool tox_self_set_typing(Tox *tox, uint32_t friend_number, bool typing, Tox_Err_Set_Typing *error) { assert(tox != nullptr); lock(tox); if (m_set_usertyping(tox->m, friend_number, typing) == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND); unlock(tox); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_SET_TYPING_OK); unlock(tox); return 1; } static void set_message_error(const Logger *log, int ret, Tox_Err_Friend_Send_Message *error) { switch (ret) { case 0: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_OK); break; case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND); break; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG); break; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED); break; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ); break; case -5: LOGGER_FATAL(log, "impossible: Messenger and Tox disagree on message types"); break; default: /* can't happen */ LOGGER_FATAL(log, "impossible: unknown send-message error: %d", ret); break; } } uint32_t tox_friend_send_message(Tox *tox, uint32_t friend_number, Tox_Message_Type type, const uint8_t *message, size_t length, Tox_Err_Friend_Send_Message *error) { assert(tox != nullptr); if (!message) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_NULL); return 0; } if (!length) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY); return 0; } uint32_t message_id = 0; lock(tox); set_message_error(tox->m->log, m_send_message_generic(tox->m, friend_number, type, message, length, &message_id), error); unlock(tox); return message_id; } void tox_callback_friend_read_receipt(Tox *tox, tox_friend_read_receipt_cb *callback) { assert(tox != nullptr); tox->friend_read_receipt_callback = callback; } void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback) { assert(tox != nullptr); tox->friend_request_callback = callback; } void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback) { assert(tox != nullptr); tox->friend_message_callback = callback; } bool tox_hash(uint8_t *hash, const uint8_t *data, size_t length) { if (!hash || (length && !data)) { return 0; } crypto_sha256(hash, data, length); return 1; } bool tox_file_control(Tox *tox, uint32_t friend_number, uint32_t file_number, Tox_File_Control control, Tox_Err_File_Control *error) { assert(tox != nullptr); lock(tox); const int ret = file_control(tox->m, friend_number, file_number, control); unlock(tox); if (ret == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_OK); return 1; } switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND); return 0; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED); return 0; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_NOT_FOUND); return 0; case -4: /* can't happen */ return 0; case -5: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_ALREADY_PAUSED); return 0; case -6: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_DENIED); return 0; case -7: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_NOT_PAUSED); return 0; case -8: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_SENDQ); return 0; } /* can't happen */ return 0; } bool tox_file_seek(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, Tox_Err_File_Seek *error) { assert(tox != nullptr); lock(tox); const int ret = file_seek(tox->m, friend_number, file_number, position); unlock(tox); if (ret == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_OK); return 1; } switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND); return 0; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED); return 0; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_NOT_FOUND); return 0; case -4: // fall-through case -5: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_DENIED); return 0; case -6: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_INVALID_POSITION); return 0; case -8: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_SENDQ); return 0; } /* can't happen */ return 0; } void tox_callback_file_recv_control(Tox *tox, tox_file_recv_control_cb *callback) { assert(tox != nullptr); tox->file_recv_control_callback = callback; } bool tox_file_get_file_id(const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, Tox_Err_File_Get *error) { assert(tox != nullptr); if (!file_id) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_NULL); return 0; } lock(tox); const int ret = file_get_id(tox->m, friend_number, file_number, file_id); unlock(tox); if (ret == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_OK); return 1; } if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_FRIEND_NOT_FOUND); } else { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_NOT_FOUND); } return 0; } uint32_t tox_file_send(Tox *tox, uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t *file_id, const uint8_t *filename, size_t filename_length, Tox_Err_File_Send *error) { assert(tox != nullptr); if (filename_length && !filename) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_NULL); return UINT32_MAX; } uint8_t f_id[FILE_ID_LENGTH]; if (!file_id) { /* Tox keys are 32 bytes like FILE_ID_LENGTH. */ new_symmetric_key(f_id); file_id = f_id; } lock(tox); const long int file_num = new_filesender(tox->m, friend_number, kind, file_size, file_id, filename, filename_length); unlock(tox); if (file_num >= 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_OK); return file_num; } switch (file_num) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND); return UINT32_MAX; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_NAME_TOO_LONG); return UINT32_MAX; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_TOO_MANY); return UINT32_MAX; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED); return UINT32_MAX; } /* can't happen */ return UINT32_MAX; } bool tox_file_send_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, Tox_Err_File_Send_Chunk *error) { assert(tox != nullptr); lock(tox); const int ret = file_data(tox->m, friend_number, file_number, position, data, length); unlock(tox); if (ret == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_OK); return 1; } switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND); return 0; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED); return 0; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND); return 0; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING); return 0; case -5: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH); return 0; case -6: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_SENDQ); return 0; case -7: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION); return 0; } /* can't happen */ return 0; } void tox_callback_file_chunk_request(Tox *tox, tox_file_chunk_request_cb *callback) { assert(tox != nullptr); tox->file_chunk_request_callback = callback; } void tox_callback_file_recv(Tox *tox, tox_file_recv_cb *callback) { assert(tox != nullptr); tox->file_recv_callback = callback; } void tox_callback_file_recv_chunk(Tox *tox, tox_file_recv_chunk_cb *callback) { assert(tox != nullptr); tox->file_recv_chunk_callback = callback; } void tox_callback_conference_invite(Tox *tox, tox_conference_invite_cb *callback) { assert(tox != nullptr); tox->conference_invite_callback = callback; } void tox_callback_conference_connected(Tox *tox, tox_conference_connected_cb *callback) { assert(tox != nullptr); tox->conference_connected_callback = callback; } void tox_callback_conference_message(Tox *tox, tox_conference_message_cb *callback) { assert(tox != nullptr); tox->conference_message_callback = callback; } void tox_callback_conference_title(Tox *tox, tox_conference_title_cb *callback) { assert(tox != nullptr); tox->conference_title_callback = callback; } void tox_callback_conference_peer_name(Tox *tox, tox_conference_peer_name_cb *callback) { assert(tox != nullptr); tox->conference_peer_name_callback = callback; } void tox_callback_conference_peer_list_changed(Tox *tox, tox_conference_peer_list_changed_cb *callback) { assert(tox != nullptr); tox->conference_peer_list_changed_callback = callback; } uint32_t tox_conference_new(Tox *tox, Tox_Err_Conference_New *error) { assert(tox != nullptr); lock(tox); const int ret = add_groupchat(tox->m->conferences_object, GROUPCHAT_TYPE_TEXT); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_NEW_INIT); return UINT32_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_NEW_OK); return ret; } bool tox_conference_delete(Tox *tox, uint32_t conference_number, Tox_Err_Conference_Delete *error) { assert(tox != nullptr); lock(tox); const int ret = del_groupchat(tox->m->conferences_object, conference_number, true); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_DELETE_OK); return true; } uint32_t tox_conference_peer_count(const Tox *tox, uint32_t conference_number, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_number_peers(tox->m->conferences_object, conference_number, false); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return UINT32_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return ret; } size_t tox_conference_peer_get_name_size(const Tox *tox, uint32_t conference_number, uint32_t peer_number, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_peername_size(tox->m->conferences_object, conference_number, peer_number, false); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return -1; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); return -1; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return ret; } bool tox_conference_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t peer_number, uint8_t *name, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_peername(tox->m->conferences_object, conference_number, peer_number, name, false); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return true; } bool tox_conference_peer_get_public_key(const Tox *tox, uint32_t conference_number, uint32_t peer_number, uint8_t *public_key, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_peer_pubkey(tox->m->conferences_object, conference_number, peer_number, public_key, false); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return true; } bool tox_conference_peer_number_is_ours(const Tox *tox, uint32_t conference_number, uint32_t peer_number, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_peernumber_is_ours(tox->m->conferences_object, conference_number, peer_number); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); return false; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return ret; } uint32_t tox_conference_offline_peer_count(const Tox *tox, uint32_t conference_number, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_number_peers(tox->m->conferences_object, conference_number, true); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return UINT32_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return ret; } size_t tox_conference_offline_peer_get_name_size(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_peername_size(tox->m->conferences_object, conference_number, offline_peer_number, true); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return -1; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); return -1; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return ret; } bool tox_conference_offline_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, uint8_t *name, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_peername(tox->m->conferences_object, conference_number, offline_peer_number, name, true); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return true; } bool tox_conference_offline_peer_get_public_key(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, uint8_t *public_key, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); lock(tox); const int ret = group_peer_pubkey(tox->m->conferences_object, conference_number, offline_peer_number, public_key, true); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return true; } uint64_t tox_conference_offline_peer_get_last_active(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, Tox_Err_Conference_Peer_Query *error) { assert(tox != nullptr); uint64_t last_active = UINT64_MAX; lock(tox); const int ret = group_frozen_last_active(tox->m->conferences_object, conference_number, offline_peer_number, &last_active); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND); return UINT64_MAX; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND); return UINT64_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_PEER_QUERY_OK); return last_active; } bool tox_conference_set_max_offline(Tox *tox, uint32_t conference_number, uint32_t max_offline_peers, Tox_Err_Conference_Set_Max_Offline *error) { assert(tox != nullptr); lock(tox); const int ret = group_set_max_frozen(tox->m->conferences_object, conference_number, max_offline_peers); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_CONFERENCE_NOT_FOUND); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_OK); return true; } bool tox_conference_invite(Tox *tox, uint32_t friend_number, uint32_t conference_number, Tox_Err_Conference_Invite *error) { assert(tox != nullptr); lock(tox); const int ret = invite_friend(tox->m->conferences_object, friend_number, conference_number); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_FAIL_SEND); return false; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_NO_CONNECTION); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_INVITE_OK); return true; } uint32_t tox_conference_join(Tox *tox, uint32_t friend_number, const uint8_t *cookie, size_t length, Tox_Err_Conference_Join *error) { assert(tox != nullptr); lock(tox); const int ret = join_groupchat(tox->m->conferences_object, friend_number, GROUPCHAT_TYPE_TEXT, cookie, length); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH); return UINT32_MAX; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE); return UINT32_MAX; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND); return UINT32_MAX; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_DUPLICATE); return UINT32_MAX; case -5: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_INIT_FAIL); return UINT32_MAX; case -6: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_FAIL_SEND); return UINT32_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_JOIN_OK); return ret; } bool tox_conference_send_message(Tox *tox, uint32_t conference_number, Tox_Message_Type type, const uint8_t *message, size_t length, Tox_Err_Conference_Send_Message *error) { assert(tox != nullptr); lock(tox); int ret = 0; if (type == TOX_MESSAGE_TYPE_NORMAL) { ret = group_message_send(tox->m->conferences_object, conference_number, message, length); } else { ret = group_action_send(tox->m->conferences_object, conference_number, message, length); } unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG); return false; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION); return false; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_SEND_MESSAGE_OK); return true; } size_t tox_conference_get_title_size(const Tox *tox, uint32_t conference_number, Tox_Err_Conference_Title *error) { assert(tox != nullptr); lock(tox); const int ret = group_title_get_size(tox->m->conferences_object, conference_number); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); return -1; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); return -1; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); return ret; } bool tox_conference_get_title(const Tox *tox, uint32_t conference_number, uint8_t *title, Tox_Err_Conference_Title *error) { assert(tox != nullptr); lock(tox); const int ret = group_title_get(tox->m->conferences_object, conference_number, title); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); return true; } bool tox_conference_set_title(Tox *tox, uint32_t conference_number, const uint8_t *title, size_t length, Tox_Err_Conference_Title *error) { assert(tox != nullptr); lock(tox); const int ret = group_title_send(tox->m->conferences_object, conference_number, title, length); unlock(tox); switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND); return false; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH); return false; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_FAIL_SEND); return false; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_TITLE_OK); return true; } size_t tox_conference_get_chatlist_size(const Tox *tox) { assert(tox != nullptr); lock(tox); size_t ret = count_chatlist(tox->m->conferences_object); unlock(tox); return ret; } void tox_conference_get_chatlist(const Tox *tox, uint32_t *chatlist) { assert(tox != nullptr); lock(tox); const size_t list_size = count_chatlist(tox->m->conferences_object); copy_chatlist(tox->m->conferences_object, chatlist, list_size); unlock(tox); } Tox_Conference_Type tox_conference_get_type(const Tox *tox, uint32_t conference_number, Tox_Err_Conference_Get_Type *error) { assert(tox != nullptr); lock(tox); const int ret = group_get_type(tox->m->conferences_object, conference_number); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND); return (Tox_Conference_Type)ret; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_GET_TYPE_OK); return (Tox_Conference_Type)ret; } /* id is TOX_CONFERENCE_ID_SIZE bytes */ bool tox_conference_get_id(const Tox *tox, uint32_t conference_number, uint8_t *id) { assert(tox != nullptr); lock(tox); bool ret = conference_get_id(tox->m->conferences_object, conference_number, id); unlock(tox); return ret; } // TODO(iphydf): Delete in 0.3.0. /* uid is TOX_CONFERENCE_ID_SIZE bytes */ bool tox_conference_get_uid(const Tox *tox, uint32_t conference_number, uint8_t *uid) { assert(tox != nullptr); return tox_conference_get_id(tox, conference_number, uid); } uint32_t tox_conference_by_id(const Tox *tox, const uint8_t *id, Tox_Err_Conference_By_Id *error) { assert(tox != nullptr); if (!id) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_ID_NULL); return UINT32_MAX; } lock(tox); const int32_t ret = conference_by_id(tox->m->conferences_object, id); unlock(tox); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_ID_NOT_FOUND); return UINT32_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_ID_OK); return ret; } // TODO(iphydf): Delete in 0.3.0. uint32_t tox_conference_by_uid(const Tox *tox, const uint8_t *uid, Tox_Err_Conference_By_Uid *error) { assert(tox != nullptr); Tox_Err_Conference_By_Id id_error; const uint32_t res = tox_conference_by_id(tox, uid, &id_error); switch (id_error) { case TOX_ERR_CONFERENCE_BY_ID_OK: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_OK); break; case TOX_ERR_CONFERENCE_BY_ID_NULL: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_NULL); break; case TOX_ERR_CONFERENCE_BY_ID_NOT_FOUND: SET_ERROR_PARAMETER(error, TOX_ERR_CONFERENCE_BY_UID_NOT_FOUND); break; } return res; } static void set_custom_packet_error(int ret, Tox_Err_Friend_Custom_Packet *error) { switch (ret) { case 0: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_OK); break; case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_FOUND); break; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_TOO_LONG); break; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID); break; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_CONNECTED); break; case -5: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_SENDQ); break; } } bool tox_friend_send_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, Tox_Err_Friend_Custom_Packet *error) { assert(tox != nullptr); if (!data) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_NULL); return 0; } if (length == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY); return 0; } if (data[0] < PACKET_ID_RANGE_LOSSY_START || data[0] > PACKET_ID_RANGE_LOSSY_END) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID); return 0; } lock(tox); const int ret = m_send_custom_lossy_packet(tox->m, friend_number, data, length); unlock(tox); set_custom_packet_error(ret, error); if (ret == 0) { return 1; } return 0; } void tox_callback_friend_lossy_packet(Tox *tox, tox_friend_lossy_packet_cb *callback) { assert(tox != nullptr); /* start at PACKET_ID_RANGE_LOSSY_CUSTOM_START so ToxAV Packets are excluded */ for (uint8_t i = PACKET_ID_RANGE_LOSSY_CUSTOM_START; i <= PACKET_ID_RANGE_LOSSY_END; ++i) { tox->friend_lossy_packet_callback_per_pktid[i] = callback; } } void tox_callback_friend_lossy_packet_per_pktid(Tox *tox, tox_friend_lossy_packet_cb *callback, uint8_t pktid) { assert(tox != nullptr); if (pktid >= PACKET_ID_RANGE_LOSSY_START && pktid <= PACKET_ID_RANGE_LOSSY_END) { tox->friend_lossy_packet_callback_per_pktid[pktid] = callback; } } bool tox_friend_send_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, Tox_Err_Friend_Custom_Packet *error) { assert(tox != nullptr); if (!data) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_NULL); return 0; } if (length == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY); return 0; } lock(tox); const int ret = send_custom_lossless_packet(tox->m, friend_number, data, length); unlock(tox); set_custom_packet_error(ret, error); if (ret == 0) { return 1; } return 0; } void tox_callback_friend_lossless_packet(Tox *tox, tox_friend_lossless_packet_cb *callback) { assert(tox != nullptr); for (uint8_t i = PACKET_ID_RANGE_LOSSLESS_CUSTOM_START; i <= PACKET_ID_RANGE_LOSSLESS_CUSTOM_END; ++i) { tox->friend_lossless_packet_callback_per_pktid[i] = callback; } } void tox_callback_friend_lossless_packet_per_pktid(Tox *tox, tox_friend_lossless_packet_cb *callback, uint8_t pktid) { assert(tox != nullptr); if ((pktid >= PACKET_ID_RANGE_LOSSLESS_CUSTOM_START && pktid <= PACKET_ID_RANGE_LOSSLESS_CUSTOM_END) || pktid == PACKET_ID_MSI) { tox->friend_lossless_packet_callback_per_pktid[pktid] = callback; } } void tox_self_get_dht_id(const Tox *tox, uint8_t *dht_id) { assert(tox != nullptr); if (dht_id) { lock(tox); memcpy(dht_id, dht_get_self_public_key(tox->m->dht), CRYPTO_PUBLIC_KEY_SIZE); unlock(tox); } } void tox_set_av_object(Tox *tox, void *object) { assert(tox != nullptr); lock(tox); tox->toxav_object = object; unlock(tox); } void *tox_get_av_object(const Tox *tox) { assert(tox != nullptr); lock(tox); void *object = tox->toxav_object; unlock(tox); return object; } uint16_t tox_self_get_udp_port(const Tox *tox, Tox_Err_Get_Port *error) { assert(tox != nullptr); lock(tox); const uint16_t port = net_htons(net_port(tox->m->net)); unlock(tox); if (port) { SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_OK); } else { SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_NOT_BOUND); } return port; } uint16_t tox_self_get_tcp_port(const Tox *tox, Tox_Err_Get_Port *error) { assert(tox != nullptr); lock(tox); if (tox->m->tcp_server) { SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_OK); uint16_t ret = tox->m->options.tcp_server_port; unlock(tox); return ret; } SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_NOT_BOUND); unlock(tox); return 0; } c-toxcore-0.2.13/toxcore/tox.h000066400000000000000000003165111415350724400161620ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * The Tox public API. */ #ifndef C_TOXCORE_TOXCORE_TOX_H #define C_TOXCORE_TOXCORE_TOX_H #include #include #include //!TOKSTYLE- #ifdef __cplusplus extern "C" { #endif /******************************************************************************* * `tox.h` SHOULD *NOT* BE EDITED MANUALLY – any changes should be made to * * `tox.api.h`, located in `toxcore/`. For instructions on how to * * generate `tox.h` from `tox.api.h` please refer to `docs/apidsl.md` * ******************************************************************************/ /** * @page core Public core API for Tox clients. * * Every function that can fail takes a function-specific error code pointer * that can be used to diagnose problems with the Tox state or the function * arguments. The error code pointer can be NULL, which does not influence the * function's behaviour, but can be done if the reason for failure is irrelevant * to the client. * * The exception to this rule are simple allocation functions whose only failure * mode is allocation failure. They return NULL in that case, and do not set an * error code. * * Every error code type has an OK value to which functions will set their error * code value on success. Clients can keep their error code uninitialised before * passing it to a function. The library guarantees that after returning, the * value pointed to by the error code pointer has been initialised. * * Functions with pointer parameters often have a NULL error code, meaning they * could not perform any operation, because one of the required parameters was * NULL. Some functions operate correctly or are defined as effectless on NULL. * * Some functions additionally return a value outside their * return type domain, or a bool containing true on success and false on * failure. * * All functions that take a Tox instance pointer will cause undefined behaviour * when passed a NULL Tox pointer. * * All integer values are expected in host byte order. * * Functions with parameters with enum types cause unspecified behaviour if the * enumeration value is outside the valid range of the type. If possible, the * function will try to use a sane default, but there will be no error code, * and one possible action for the function to take is to have no effect. * * Integer constants and the memory layout of publicly exposed structs are not * part of the ABI. */ /** * @subsection events Events and callbacks * * Events are handled by callbacks. One callback can be registered per event. * All events have a callback function type named `tox_{event}_cb` and a * function to register it named `tox_callback_{event}`. Passing a NULL * callback will result in no callback being registered for that event. Only * one callback per event can be registered, so if a client needs multiple * event listeners, it needs to implement the dispatch functionality itself. * * The last argument to a callback is the user data pointer. It is passed from * tox_iterate to each callback in sequence. * * The user data pointer is never stored or dereferenced by any library code, so * can be any pointer, including NULL. Callbacks must all operate on the same * object type. In the apidsl code (tox.in.h), this is denoted with `any`. The * `any` in tox_iterate must be the same `any` as in all callbacks. In C, * lacking parametric polymorphism, this is a pointer to void. * * Old style callbacks that are registered together with a user data pointer * receive that pointer as argument when they are called. They can each have * their own user data pointer of their own type. */ /** * @subsection threading Threading implications * * It is possible to run multiple concurrent threads with a Tox instance for * each thread. It is also possible to run all Tox instances in the same thread. * A common way to run Tox (multiple or single instance) is to have one thread * running a simple tox_iterate loop, sleeping for tox_iteration_interval * milliseconds on each iteration. * * If you want to access a single Tox instance from multiple threads, access * to the instance must be synchronised. While multiple threads can concurrently * access multiple different Tox instances, no more than one API function can * operate on a single instance at any given time. * * Functions that write to variable length byte arrays will always have a size * function associated with them. The result of this size function is only valid * until another mutating function (one that takes a pointer to non-const Tox) * is called. Thus, clients must ensure that no other thread calls a mutating * function between the call to the size function and the call to the retrieval * function. * * E.g. to get the current nickname, one would write * * @code * size_t length = tox_self_get_name_size(tox); * uint8_t *name = malloc(length); * if (!name) abort(); * tox_self_get_name(tox, name); * @endcode * * If any other thread calls tox_self_set_name while this thread is allocating * memory, the length may have become invalid, and the call to * tox_self_get_name may cause undefined behaviour. */ /** * The Tox instance type. All the state associated with a connection is held * within the instance. Multiple instances can exist and operate concurrently. * The maximum number of Tox instances that can exist on a single network * device is limited. Note that this is not just a per-process limit, since the * limiting factor is the number of usable ports on a device. */ #ifndef TOX_DEFINED #define TOX_DEFINED typedef struct Tox Tox; #endif /* TOX_DEFINED */ /******************************************************************************* * * :: API version * ******************************************************************************/ /** * The major version number. Incremented when the API or ABI changes in an * incompatible way. * * The function variants of these constants return the version number of the * library. They can be used to display the Tox library version or to check * whether the client is compatible with the dynamically linked version of Tox. */ #define TOX_VERSION_MAJOR 0 uint32_t tox_version_major(void); /** * The minor version number. Incremented when functionality is added without * breaking the API or ABI. Set to 0 when the major version number is * incremented. */ #define TOX_VERSION_MINOR 2 uint32_t tox_version_minor(void); /** * The patch or revision number. Incremented when bugfixes are applied without * changing any functionality or API or ABI. */ #define TOX_VERSION_PATCH 13 uint32_t tox_version_patch(void); /** * A macro to check at preprocessing time whether the client code is compatible * with the installed version of Tox. Leading zeros in the version number are * ignored. E.g. 0.1.5 is to 0.1.4 what 1.5 is to 1.4, that is: it can add new * features, but can't break the API. */ #define TOX_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ ((TOX_VERSION_MAJOR > 0 && TOX_VERSION_MAJOR == MAJOR) && ( \ /* 1.x.x, 2.x.x, etc. with matching major version. */ \ TOX_VERSION_MINOR > MINOR || \ (TOX_VERSION_MINOR == MINOR && TOX_VERSION_PATCH >= PATCH) \ )) || ((TOX_VERSION_MAJOR == 0 && MAJOR == 0) && ( \ /* 0.x.x makes minor behave like major above. */ \ ((TOX_VERSION_MINOR > 0 && TOX_VERSION_MINOR == MINOR) && ( \ TOX_VERSION_PATCH >= PATCH \ )) || ((TOX_VERSION_MINOR == 0 && MINOR == 0) && ( \ /* 0.0.x and 0.0.y are only compatible if x == y. */ \ TOX_VERSION_PATCH == PATCH \ )) \ )) /** * Return whether the compiled library version is compatible with the passed * version numbers. */ bool tox_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch); /** * A convenience macro to call tox_version_is_compatible with the currently * compiling API version. */ #define TOX_VERSION_IS_ABI_COMPATIBLE() \ tox_version_is_compatible(TOX_VERSION_MAJOR, TOX_VERSION_MINOR, TOX_VERSION_PATCH) /******************************************************************************* * * :: Numeric constants * * The values of these are not part of the ABI. Prefer to use the function * versions of them for code that should remain compatible with future versions * of toxcore. * ******************************************************************************/ /** * The size of a Tox Public Key in bytes. */ #define TOX_PUBLIC_KEY_SIZE 32 uint32_t tox_public_key_size(void); /** * The size of a Tox Secret Key in bytes. */ #define TOX_SECRET_KEY_SIZE 32 uint32_t tox_secret_key_size(void); /** * The size of a Tox Conference unique id in bytes. * * @deprecated Use TOX_CONFERENCE_ID_SIZE instead. */ #define TOX_CONFERENCE_UID_SIZE 32 uint32_t tox_conference_uid_size(void); /** * The size of a Tox Conference unique id in bytes. */ #define TOX_CONFERENCE_ID_SIZE 32 uint32_t tox_conference_id_size(void); /** * The size of the nospam in bytes when written in a Tox address. */ #define TOX_NOSPAM_SIZE (sizeof(uint32_t)) uint32_t tox_nospam_size(void); /** * The size of a Tox address in bytes. Tox addresses are in the format * [Public Key (TOX_PUBLIC_KEY_SIZE bytes)][nospam (4 bytes)][checksum (2 bytes)]. * * The checksum is computed over the Public Key and the nospam value. The first * byte is an XOR of all the even bytes (0, 2, 4, ...), the second byte is an * XOR of all the odd bytes (1, 3, 5, ...) of the Public Key and nospam. */ #define TOX_ADDRESS_SIZE (TOX_PUBLIC_KEY_SIZE + TOX_NOSPAM_SIZE + sizeof(uint16_t)) uint32_t tox_address_size(void); /** * Maximum length of a nickname in bytes. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ #define TOX_MAX_NAME_LENGTH 128 uint32_t tox_max_name_length(void); /** * Maximum length of a status message in bytes. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ #define TOX_MAX_STATUS_MESSAGE_LENGTH 1007 uint32_t tox_max_status_message_length(void); /** * Maximum length of a friend request message in bytes. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ #define TOX_MAX_FRIEND_REQUEST_LENGTH 1016 uint32_t tox_max_friend_request_length(void); /** * Maximum length of a single message after which it should be split. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ #define TOX_MAX_MESSAGE_LENGTH 1372 uint32_t tox_max_message_length(void); /** * Maximum size of custom packets. TODO(iphydf): should be LENGTH? * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ #define TOX_MAX_CUSTOM_PACKET_SIZE 1373 uint32_t tox_max_custom_packet_size(void); /** * The number of bytes in a hash generated by tox_hash. */ #define TOX_HASH_LENGTH 32 uint32_t tox_hash_length(void); /** * The number of bytes in a file id. */ #define TOX_FILE_ID_LENGTH 32 uint32_t tox_file_id_length(void); /** * Maximum file name length for file transfers. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ #define TOX_MAX_FILENAME_LENGTH 255 uint32_t tox_max_filename_length(void); /** * Maximum length of a hostname, e.g. proxy or bootstrap node names. * * This length does not include the NUL byte. Hostnames are NUL-terminated C * strings, so they are 255 characters plus one NUL byte. * * @deprecated The macro will be removed in 0.3.0. Use the function instead. */ #define TOX_MAX_HOSTNAME_LENGTH 255 uint32_t tox_max_hostname_length(void); /******************************************************************************* * * :: Global enumerations * ******************************************************************************/ /** * Represents the possible statuses a client can have. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ typedef enum TOX_USER_STATUS { /** * User is online and available. */ TOX_USER_STATUS_NONE, /** * User is away. Clients can set this e.g. after a user defined * inactivity time. */ TOX_USER_STATUS_AWAY, /** * User is busy. Signals to other clients that this client does not * currently wish to communicate. */ TOX_USER_STATUS_BUSY, } TOX_USER_STATUS; /** * Represents message types for tox_friend_send_message and conference * messages. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ typedef enum TOX_MESSAGE_TYPE { /** * Normal text message. Similar to PRIVMSG on IRC. */ TOX_MESSAGE_TYPE_NORMAL, /** * A message describing an user action. This is similar to /me (CTCP ACTION) * on IRC. */ TOX_MESSAGE_TYPE_ACTION, } TOX_MESSAGE_TYPE; /******************************************************************************* * * :: Startup options * ******************************************************************************/ /** * Type of proxy used to connect to TCP relays. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ typedef enum TOX_PROXY_TYPE { /** * Don't use a proxy. */ TOX_PROXY_TYPE_NONE, /** * HTTP proxy using CONNECT. */ TOX_PROXY_TYPE_HTTP, /** * SOCKS proxy for simple socket pipes. */ TOX_PROXY_TYPE_SOCKS5, } TOX_PROXY_TYPE; /** * Type of savedata to create the Tox instance from. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ typedef enum TOX_SAVEDATA_TYPE { /** * No savedata. */ TOX_SAVEDATA_TYPE_NONE, /** * Savedata is one that was obtained from tox_get_savedata. */ TOX_SAVEDATA_TYPE_TOX_SAVE, /** * Savedata is a secret key of length TOX_SECRET_KEY_SIZE. */ TOX_SAVEDATA_TYPE_SECRET_KEY, } TOX_SAVEDATA_TYPE; /** * Severity level of log messages. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ typedef enum TOX_LOG_LEVEL { /** * Very detailed traces including all network activity. */ TOX_LOG_LEVEL_TRACE, /** * Debug messages such as which port we bind to. */ TOX_LOG_LEVEL_DEBUG, /** * Informational log messages such as video call status changes. */ TOX_LOG_LEVEL_INFO, /** * Warnings about internal inconsistency or logic errors. */ TOX_LOG_LEVEL_WARNING, /** * Severe unexpected errors caused by external or internal inconsistency. */ TOX_LOG_LEVEL_ERROR, } TOX_LOG_LEVEL; /** * This event is triggered when the toxcore library logs an internal message. * This is mostly useful for debugging. This callback can be called from any * function, not just tox_iterate. This means the user data lifetime must at * least extend between registering and unregistering it or tox_kill. * * Other toxcore modules such as toxav may concurrently call this callback at * any time. Thus, user code must make sure it is equipped to handle concurrent * execution, e.g. by employing appropriate mutex locking. * * @param level The severity of the log message. * @param file The source file from which the message originated. * @param line The source line from which the message originated. * @param func The function from which the message originated. * @param message The log message. * @param user_data The user data pointer passed to tox_new in options. */ typedef void tox_log_cb(Tox *tox, TOX_LOG_LEVEL level, const char *file, uint32_t line, const char *func, const char *message, void *user_data); /** * This struct contains all the startup options for Tox. You must tox_options_new to * allocate an object of this type. * * WARNING: Although this struct happens to be visible in the API, it is * effectively private. Do not allocate this yourself or access members * directly, as it *will* break binary compatibility frequently. * * @deprecated The memory layout of this struct (size, alignment, and field * order) is not part of the ABI. To remain compatible, prefer to use tox_options_new to * allocate the object and accessor functions to set the members. The struct * will become opaque (i.e. the definition will become private) in v0.3.0. */ struct Tox_Options { /** * The type of socket to create. * * If this is set to false, an IPv4 socket is created, which subsequently * only allows IPv4 communication. * If it is set to true, an IPv6 socket is created, allowing both IPv4 and * IPv6 communication. */ bool ipv6_enabled; /** * Enable the use of UDP communication when available. * * Setting this to false will force Tox to use TCP only. Communications will * need to be relayed through a TCP relay node, potentially slowing them down. * * If a proxy is enabled, UDP will be disabled if either toxcore or the * proxy don't support proxying UDP messages. */ bool udp_enabled; /** * Enable local network peer discovery. * * Disabling this will cause Tox to not look for peers on the local network. */ bool local_discovery_enabled; /** * Pass communications through a proxy. */ TOX_PROXY_TYPE proxy_type; /** * The IP address or DNS name of the proxy to be used. * * If used, this must be non-NULL and be a valid DNS name. The name must not * exceed TOX_MAX_HOSTNAME_LENGTH characters, and be in a NUL-terminated C string * format (TOX_MAX_HOSTNAME_LENGTH includes the NUL byte). * * This member is ignored (it can be NULL) if proxy_type is TOX_PROXY_TYPE_NONE. * * The data pointed at by this member is owned by the user, so must * outlive the options object. */ const char *proxy_host; /** * The port to use to connect to the proxy server. * * Ports must be in the range (1, 65535). The value is ignored if * proxy_type is TOX_PROXY_TYPE_NONE. */ uint16_t proxy_port; /** * The start port of the inclusive port range to attempt to use. * * If both start_port and end_port are 0, the default port range will be * used: [33445, 33545]. * * If either start_port or end_port is 0 while the other is non-zero, the * non-zero port will be the only port in the range. * * Having start_port > end_port will yield the same behavior as if start_port * and end_port were swapped. */ uint16_t start_port; /** * The end port of the inclusive port range to attempt to use. */ uint16_t end_port; /** * The port to use for the TCP server (relay). If 0, the TCP server is * disabled. * * Enabling it is not required for Tox to function properly. * * When enabled, your Tox instance can act as a TCP relay for other Tox * instance. This leads to increased traffic, thus when writing a client * it is recommended to enable TCP server only if the user has an option * to disable it. */ uint16_t tcp_port; /** * Enables or disables UDP hole-punching in toxcore. (Default: enabled). */ bool hole_punching_enabled; /** * The type of savedata to load from. */ TOX_SAVEDATA_TYPE savedata_type; /** * The savedata. * * The data pointed at by this member is owned by the user, so must * outlive the options object. */ const uint8_t *savedata_data; /** * The length of the savedata. */ size_t savedata_length; /** * Logging callback for the new tox instance. */ tox_log_cb *log_callback; /** * User data pointer passed to the logging callback. */ void *log_user_data; /** * These options are experimental, so avoid writing code that depends on * them. Options marked "experimental" may change their behaviour or go away * entirely in the future, or may be renamed to something non-experimental * if they become part of the supported API. */ /** * Make public API functions thread-safe using a per-instance lock. * * Default: false. */ bool experimental_thread_safety; }; bool tox_options_get_ipv6_enabled(const struct Tox_Options *options); void tox_options_set_ipv6_enabled(struct Tox_Options *options, bool ipv6_enabled); bool tox_options_get_udp_enabled(const struct Tox_Options *options); void tox_options_set_udp_enabled(struct Tox_Options *options, bool udp_enabled); bool tox_options_get_local_discovery_enabled(const struct Tox_Options *options); void tox_options_set_local_discovery_enabled(struct Tox_Options *options, bool local_discovery_enabled); TOX_PROXY_TYPE tox_options_get_proxy_type(const struct Tox_Options *options); void tox_options_set_proxy_type(struct Tox_Options *options, TOX_PROXY_TYPE type); const char *tox_options_get_proxy_host(const struct Tox_Options *options); void tox_options_set_proxy_host(struct Tox_Options *options, const char *host); uint16_t tox_options_get_proxy_port(const struct Tox_Options *options); void tox_options_set_proxy_port(struct Tox_Options *options, uint16_t port); uint16_t tox_options_get_start_port(const struct Tox_Options *options); void tox_options_set_start_port(struct Tox_Options *options, uint16_t start_port); uint16_t tox_options_get_end_port(const struct Tox_Options *options); void tox_options_set_end_port(struct Tox_Options *options, uint16_t end_port); uint16_t tox_options_get_tcp_port(const struct Tox_Options *options); void tox_options_set_tcp_port(struct Tox_Options *options, uint16_t tcp_port); bool tox_options_get_hole_punching_enabled(const struct Tox_Options *options); void tox_options_set_hole_punching_enabled(struct Tox_Options *options, bool hole_punching_enabled); TOX_SAVEDATA_TYPE tox_options_get_savedata_type(const struct Tox_Options *options); void tox_options_set_savedata_type(struct Tox_Options *options, TOX_SAVEDATA_TYPE type); const uint8_t *tox_options_get_savedata_data(const struct Tox_Options *options); void tox_options_set_savedata_data(struct Tox_Options *options, const uint8_t *data, size_t length); size_t tox_options_get_savedata_length(const struct Tox_Options *options); void tox_options_set_savedata_length(struct Tox_Options *options, size_t length); tox_log_cb *tox_options_get_log_callback(const struct Tox_Options *options); void tox_options_set_log_callback(struct Tox_Options *options, tox_log_cb *callback); void *tox_options_get_log_user_data(const struct Tox_Options *options); void tox_options_set_log_user_data(struct Tox_Options *options, void *user_data); bool tox_options_get_experimental_thread_safety(const struct Tox_Options *options); void tox_options_set_experimental_thread_safety(struct Tox_Options *options, bool thread_safety); /** * Initialises a Tox_Options object with the default options. * * The result of this function is independent of the original options. All * values will be overwritten, no values will be read (so it is permissible * to pass an uninitialised object). * * If options is NULL, this function has no effect. * * @param options An options object to be filled with default options. */ void tox_options_default(struct Tox_Options *options); typedef enum TOX_ERR_OPTIONS_NEW { /** * The function returned successfully. */ TOX_ERR_OPTIONS_NEW_OK, /** * The function failed to allocate enough memory for the options struct. */ TOX_ERR_OPTIONS_NEW_MALLOC, } TOX_ERR_OPTIONS_NEW; /** * Allocates a new Tox_Options object and initialises it with the default * options. This function can be used to preserve long term ABI compatibility by * giving the responsibility of allocation and deallocation to the Tox library. * * Objects returned from this function must be freed using the tox_options_free * function. * * @return A new Tox_Options object with default options or NULL on failure. */ struct Tox_Options *tox_options_new(TOX_ERR_OPTIONS_NEW *error); /** * Releases all resources associated with an options objects. * * Passing a pointer that was not returned by tox_options_new results in * undefined behaviour. */ void tox_options_free(struct Tox_Options *options); /******************************************************************************* * * :: Creation and destruction * ******************************************************************************/ typedef enum TOX_ERR_NEW { /** * The function returned successfully. */ TOX_ERR_NEW_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_NEW_NULL, /** * The function was unable to allocate enough memory to store the internal * structures for the Tox object. */ TOX_ERR_NEW_MALLOC, /** * The function was unable to bind to a port. This may mean that all ports * have already been bound, e.g. by other Tox instances, or it may mean * a permission error. You may be able to gather more information from errno. */ TOX_ERR_NEW_PORT_ALLOC, /** * proxy_type was invalid. */ TOX_ERR_NEW_PROXY_BAD_TYPE, /** * proxy_type was valid but the proxy_host passed had an invalid format * or was NULL. */ TOX_ERR_NEW_PROXY_BAD_HOST, /** * proxy_type was valid, but the proxy_port was invalid. */ TOX_ERR_NEW_PROXY_BAD_PORT, /** * The proxy address passed could not be resolved. */ TOX_ERR_NEW_PROXY_NOT_FOUND, /** * The byte array to be loaded contained an encrypted save. */ TOX_ERR_NEW_LOAD_ENCRYPTED, /** * The data format was invalid. This can happen when loading data that was * saved by an older version of Tox, or when the data has been corrupted. * When loading from badly formatted data, some data may have been loaded, * and the rest is discarded. Passing an invalid length parameter also * causes this error. */ TOX_ERR_NEW_LOAD_BAD_FORMAT, } TOX_ERR_NEW; /** * @brief Creates and initialises a new Tox instance with the options passed. * * This function will bring the instance into a valid state. Running the event * loop with a new instance will operate correctly. * * If loading failed or succeeded only partially, the new or partially loaded * instance is returned and an error code is set. * * @param options An options object as described above. If this parameter is * NULL, the default options are used. * * @see tox_iterate for the event loop. * * @return A new Tox instance pointer on success or NULL on failure. */ Tox *tox_new(const struct Tox_Options *options, TOX_ERR_NEW *error); /** * Releases all resources associated with the Tox instance and disconnects from * the network. * * After calling this function, the Tox pointer becomes invalid. No other * functions can be called, and the pointer value can no longer be read. */ void tox_kill(Tox *tox); /** * Calculates the number of bytes required to store the tox instance with * tox_get_savedata. This function cannot fail. The result is always greater than 0. * * @see threading for concurrency implications. */ size_t tox_get_savedata_size(const Tox *tox); /** * Store all information associated with the tox instance to a byte array. * * @param savedata A memory region large enough to store the tox instance * data. Call tox_get_savedata_size to find the number of bytes required. If this parameter * is NULL, this function has no effect. */ void tox_get_savedata(const Tox *tox, uint8_t *savedata); /******************************************************************************* * * :: Connection lifecycle and event loop * ******************************************************************************/ typedef enum TOX_ERR_BOOTSTRAP { /** * The function returned successfully. */ TOX_ERR_BOOTSTRAP_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_BOOTSTRAP_NULL, /** * The hostname could not be resolved to an IP address, or the IP address * passed was invalid. */ TOX_ERR_BOOTSTRAP_BAD_HOST, /** * The port passed was invalid. The valid port range is (1, 65535). */ TOX_ERR_BOOTSTRAP_BAD_PORT, } TOX_ERR_BOOTSTRAP; /** * Sends a "get nodes" request to the given bootstrap node with IP, port, and * public key to setup connections. * * This function will attempt to connect to the node using UDP. You must use * this function even if Tox_Options.udp_enabled was set to false. * * @param host The hostname or IP address (IPv4 or IPv6) of the node. Must be * at most TOX_MAX_HOSTNAME_LENGTH chars, including the NUL byte. * @param port The port on the host on which the bootstrap Tox instance is * listening. * @param public_key The long term public key of the bootstrap node * (TOX_PUBLIC_KEY_SIZE bytes). * @return true on success. */ bool tox_bootstrap(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error); /** * Adds additional host:port pair as TCP relay. * * This function can be used to initiate TCP connections to different ports on * the same bootstrap node, or to add TCP relays without using them as * bootstrap nodes. * * @param host The hostname or IP address (IPv4 or IPv6) of the TCP relay. * Must be at most TOX_MAX_HOSTNAME_LENGTH chars, including the NUL byte. * @param port The port on the host on which the TCP relay is listening. * @param public_key The long term public key of the TCP relay * (TOX_PUBLIC_KEY_SIZE bytes). * @return true on success. */ bool tox_add_tcp_relay(Tox *tox, const char *host, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error); /** * Protocols that can be used to connect to the network or friends. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ typedef enum TOX_CONNECTION { /** * There is no connection. This instance, or the friend the state change is * about, is now offline. */ TOX_CONNECTION_NONE, /** * A TCP connection has been established. For the own instance, this means it * is connected through a TCP relay, only. For a friend, this means that the * connection to that particular friend goes through a TCP relay. */ TOX_CONNECTION_TCP, /** * A UDP connection has been established. For the own instance, this means it * is able to send UDP packets to DHT nodes, but may still be connected to * a TCP relay. For a friend, this means that the connection to that * particular friend was built using direct UDP packets. */ TOX_CONNECTION_UDP, } TOX_CONNECTION; /** * Return whether we are connected to the DHT. The return value is equal to the * last value received through the `self_connection_status` callback. * * @deprecated This getter is deprecated. Use the event and store the status * in the client state. */ TOX_CONNECTION tox_self_get_connection_status(const Tox *tox); /** * @param connection_status Whether we are connected to the DHT. */ typedef void tox_self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data); /** * Set the callback for the `self_connection_status` event. Pass NULL to unset. * * This event is triggered whenever there is a change in the DHT connection * state. When disconnected, a client may choose to call tox_bootstrap again, to * reconnect to the DHT. Note that this state may frequently change for short * amounts of time. Clients should therefore not immediately bootstrap on * receiving a disconnect. * * TODO(iphydf): how long should a client wait before bootstrapping again? */ void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback); /** * Return the time in milliseconds before tox_iterate() should be called again * for optimal performance. */ uint32_t tox_iteration_interval(const Tox *tox); /** * The main loop that needs to be run in intervals of tox_iteration_interval() * milliseconds. */ void tox_iterate(Tox *tox, void *user_data); /******************************************************************************* * * :: Internal client information (Tox address/id) * ******************************************************************************/ /** * Writes the Tox friend address of the client to a byte array. The address is * not in human-readable format. If a client wants to display the address, * formatting is required. * * @param address A memory region of at least TOX_ADDRESS_SIZE bytes. If this * parameter is NULL, this function has no effect. * @see TOX_ADDRESS_SIZE for the address format. */ void tox_self_get_address(const Tox *tox, uint8_t *address); /** * Set the 4-byte nospam part of the address. This value is expected in host * byte order. I.e. 0x12345678 will form the bytes [12, 34, 56, 78] in the * nospam part of the Tox friend address. * * @param nospam Any 32 bit unsigned integer. */ void tox_self_set_nospam(Tox *tox, uint32_t nospam); /** * Get the 4-byte nospam part of the address. This value is returned in host * byte order. */ uint32_t tox_self_get_nospam(const Tox *tox); /** * Copy the Tox Public Key (long term) from the Tox object. * * @param public_key A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. */ void tox_self_get_public_key(const Tox *tox, uint8_t *public_key); /** * Copy the Tox Secret Key from the Tox object. * * @param secret_key A memory region of at least TOX_SECRET_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. */ void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key); /******************************************************************************* * * :: User-visible client information (nickname/status) * ******************************************************************************/ /** * Common error codes for all functions that set a piece of user-visible * client information. */ typedef enum TOX_ERR_SET_INFO { /** * The function returned successfully. */ TOX_ERR_SET_INFO_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_SET_INFO_NULL, /** * Information length exceeded maximum permissible size. */ TOX_ERR_SET_INFO_TOO_LONG, } TOX_ERR_SET_INFO; /** * Set the nickname for the Tox client. * * Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is 0, the name * parameter is ignored (it can be NULL), and the nickname is set back to empty. * * @param name A byte array containing the new nickname. * @param length The size of the name byte array. * * @return true on success. */ bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, TOX_ERR_SET_INFO *error); /** * Return the length of the current nickname as passed to tox_self_set_name. * * If no nickname was set before calling this function, the name is empty, * and this function returns 0. * * @see threading for concurrency implications. */ size_t tox_self_get_name_size(const Tox *tox); /** * Write the nickname set by tox_self_set_name to a byte array. * * If no nickname was set before calling this function, the name is empty, * and this function has no effect. * * Call tox_self_get_name_size to find out how much memory to allocate for * the result. * * @param name A valid memory location large enough to hold the nickname. * If this parameter is NULL, the function has no effect. */ void tox_self_get_name(const Tox *tox, uint8_t *name); /** * Set the client's status message. * * Status message length cannot exceed TOX_MAX_STATUS_MESSAGE_LENGTH. If * length is 0, the status parameter is ignored (it can be NULL), and the * user status is set back to empty. */ bool tox_self_set_status_message(Tox *tox, const uint8_t *status_message, size_t length, TOX_ERR_SET_INFO *error); /** * Return the length of the current status message as passed to tox_self_set_status_message. * * If no status message was set before calling this function, the status * is empty, and this function returns 0. * * @see threading for concurrency implications. */ size_t tox_self_get_status_message_size(const Tox *tox); /** * Write the status message set by tox_self_set_status_message to a byte array. * * If no status message was set before calling this function, the status is * empty, and this function has no effect. * * Call tox_self_get_status_message_size to find out how much memory to allocate for * the result. * * @param status_message A valid memory location large enough to hold the * status message. If this parameter is NULL, the function has no effect. */ void tox_self_get_status_message(const Tox *tox, uint8_t *status_message); /** * Set the client's user status. * * @param status One of the user statuses listed in the enumeration above. */ void tox_self_set_status(Tox *tox, TOX_USER_STATUS status); /** * Returns the client's user status. */ TOX_USER_STATUS tox_self_get_status(const Tox *tox); /******************************************************************************* * * :: Friend list management * ******************************************************************************/ typedef enum TOX_ERR_FRIEND_ADD { /** * The function returned successfully. */ TOX_ERR_FRIEND_ADD_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FRIEND_ADD_NULL, /** * The length of the friend request message exceeded * TOX_MAX_FRIEND_REQUEST_LENGTH. */ TOX_ERR_FRIEND_ADD_TOO_LONG, /** * The friend request message was empty. This, and the TOO_LONG code will * never be returned from tox_friend_add_norequest. */ TOX_ERR_FRIEND_ADD_NO_MESSAGE, /** * The friend address belongs to the sending client. */ TOX_ERR_FRIEND_ADD_OWN_KEY, /** * A friend request has already been sent, or the address belongs to a friend * that is already on the friend list. */ TOX_ERR_FRIEND_ADD_ALREADY_SENT, /** * The friend address checksum failed. */ TOX_ERR_FRIEND_ADD_BAD_CHECKSUM, /** * The friend was already there, but the nospam value was different. */ TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM, /** * A memory allocation failed when trying to increase the friend list size. */ TOX_ERR_FRIEND_ADD_MALLOC, } TOX_ERR_FRIEND_ADD; /** * Add a friend to the friend list and send a friend request. * * A friend request message must be at least 1 byte long and at most * TOX_MAX_FRIEND_REQUEST_LENGTH. * * Friend numbers are unique identifiers used in all functions that operate on * friends. Once added, a friend number is stable for the lifetime of the Tox * object. After saving the state and reloading it, the friend numbers may not * be the same as before. Deleting a friend creates a gap in the friend number * set, which is filled by the next adding of a friend. Any pattern in friend * numbers should not be relied on. * * If more than INT32_MAX friends are added, this function causes undefined * behaviour. * * @param address The address of the friend (returned by tox_self_get_address of * the friend you wish to add) it must be TOX_ADDRESS_SIZE bytes. * @param message The message that will be sent along with the friend request. * @param length The length of the data byte array. * * @return the friend number on success, an unspecified value on failure. */ uint32_t tox_friend_add(Tox *tox, const uint8_t *address, const uint8_t *message, size_t length, TOX_ERR_FRIEND_ADD *error); /** * Add a friend without sending a friend request. * * This function is used to add a friend in response to a friend request. If the * client receives a friend request, it can be reasonably sure that the other * client added this client as a friend, eliminating the need for a friend * request. * * This function is also useful in a situation where both instances are * controlled by the same entity, so that this entity can perform the mutual * friend adding. In this case, there is no need for a friend request, either. * * @param public_key A byte array of length TOX_PUBLIC_KEY_SIZE containing the * Public Key (not the Address) of the friend to add. * * @return the friend number on success, an unspecified value on failure. * @see tox_friend_add for a more detailed description of friend numbers. */ uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_ADD *error); typedef enum TOX_ERR_FRIEND_DELETE { /** * The function returned successfully. */ TOX_ERR_FRIEND_DELETE_OK, /** * There was no friend with the given friend number. No friends were deleted. */ TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND, } TOX_ERR_FRIEND_DELETE; /** * Remove a friend from the friend list. * * This does not notify the friend of their deletion. After calling this * function, this client will appear offline to the friend and no communication * can occur between the two. * * @param friend_number Friend number for the friend to be deleted. * * @return true on success. */ bool tox_friend_delete(Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_DELETE *error); /******************************************************************************* * * :: Friend list queries * ******************************************************************************/ typedef enum TOX_ERR_FRIEND_BY_PUBLIC_KEY { /** * The function returned successfully. */ TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL, /** * No friend with the given Public Key exists on the friend list. */ TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND, } TOX_ERR_FRIEND_BY_PUBLIC_KEY; /** * Return the friend number associated with that Public Key. * * @return the friend number on success, an unspecified value on failure. * @param public_key A byte array containing the Public Key. */ uint32_t tox_friend_by_public_key(const Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_BY_PUBLIC_KEY *error); /** * Checks if a friend with the given friend number exists and returns true if * it does. */ bool tox_friend_exists(const Tox *tox, uint32_t friend_number); /** * Return the number of friends on the friend list. * * This function can be used to determine how much memory to allocate for * tox_self_get_friend_list. */ size_t tox_self_get_friend_list_size(const Tox *tox); /** * Copy a list of valid friend numbers into an array. * * Call tox_self_get_friend_list_size to determine the number of elements to allocate. * * @param friend_list A memory region with enough space to hold the friend * list. If this parameter is NULL, this function has no effect. */ void tox_self_get_friend_list(const Tox *tox, uint32_t *friend_list); typedef enum TOX_ERR_FRIEND_GET_PUBLIC_KEY { /** * The function returned successfully. */ TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK, /** * No friend with the given number exists on the friend list. */ TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND, } TOX_ERR_FRIEND_GET_PUBLIC_KEY; /** * Copies the Public Key associated with a given friend number to a byte array. * * @param friend_number The friend number you want the Public Key of. * @param public_key A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. * * @return true on success. */ bool tox_friend_get_public_key(const Tox *tox, uint32_t friend_number, uint8_t *public_key, TOX_ERR_FRIEND_GET_PUBLIC_KEY *error); typedef enum TOX_ERR_FRIEND_GET_LAST_ONLINE { /** * The function returned successfully. */ TOX_ERR_FRIEND_GET_LAST_ONLINE_OK, /** * No friend with the given number exists on the friend list. */ TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND, } TOX_ERR_FRIEND_GET_LAST_ONLINE; /** * Return a unix-time timestamp of the last time the friend associated with a given * friend number was seen online. This function will return UINT64_MAX on error. * * @param friend_number The friend number you want to query. */ uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_GET_LAST_ONLINE *error); /******************************************************************************* * * :: Friend-specific state queries (can also be received through callbacks) * ******************************************************************************/ /** * Common error codes for friend state query functions. */ typedef enum TOX_ERR_FRIEND_QUERY { /** * The function returned successfully. */ TOX_ERR_FRIEND_QUERY_OK, /** * The pointer parameter for storing the query result (name, message) was * NULL. Unlike the `_self_` variants of these functions, which have no effect * when a parameter is NULL, these functions return an error in that case. */ TOX_ERR_FRIEND_QUERY_NULL, /** * The friend_number did not designate a valid friend. */ TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND, } TOX_ERR_FRIEND_QUERY; /** * Return the length of the friend's name. If the friend number is invalid, the * return value is unspecified. * * The return value is equal to the `length` argument received by the last * `friend_name` callback. */ size_t tox_friend_get_name_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * Write the name of the friend designated by the given friend number to a byte * array. * * Call tox_friend_get_name_size to determine the allocation size for the `name` * parameter. * * The data written to `name` is equal to the data received by the last * `friend_name` callback. * * @param name A valid memory region large enough to store the friend's name. * * @return true on success. */ bool tox_friend_get_name(const Tox *tox, uint32_t friend_number, uint8_t *name, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend whose name changed. * @param name A byte array containing the same data as * tox_friend_get_name would write to its `name` parameter. * @param length A value equal to the return value of * tox_friend_get_name_size. */ typedef void tox_friend_name_cb(Tox *tox, uint32_t friend_number, const uint8_t *name, size_t length, void *user_data); /** * Set the callback for the `friend_name` event. Pass NULL to unset. * * This event is triggered when a friend changes their name. */ void tox_callback_friend_name(Tox *tox, tox_friend_name_cb *callback); /** * Return the length of the friend's status message. If the friend number is * invalid, the return value is SIZE_MAX. */ size_t tox_friend_get_status_message_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * Write the status message of the friend designated by the given friend number to a byte * array. * * Call tox_friend_get_status_message_size to determine the allocation size for the `status_message` * parameter. * * The data written to `status_message` is equal to the data received by the last * `friend_status_message` callback. * * @param status_message A valid memory region large enough to store the friend's status message. */ bool tox_friend_get_status_message(const Tox *tox, uint32_t friend_number, uint8_t *status_message, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend whose status message * changed. * @param message A byte array containing the same data as * tox_friend_get_status_message would write to its `status_message` parameter. * @param length A value equal to the return value of * tox_friend_get_status_message_size. */ typedef void tox_friend_status_message_cb(Tox *tox, uint32_t friend_number, const uint8_t *message, size_t length, void *user_data); /** * Set the callback for the `friend_status_message` event. Pass NULL to unset. * * This event is triggered when a friend changes their status message. */ void tox_callback_friend_status_message(Tox *tox, tox_friend_status_message_cb *callback); /** * Return the friend's user status (away/busy/...). If the friend number is * invalid, the return value is unspecified. * * The status returned is equal to the last status received through the * `friend_status` callback. * * @deprecated This getter is deprecated. Use the event and store the status * in the client state. */ TOX_USER_STATUS tox_friend_get_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend whose user status * changed. * @param status The new user status. */ typedef void tox_friend_status_cb(Tox *tox, uint32_t friend_number, TOX_USER_STATUS status, void *user_data); /** * Set the callback for the `friend_status` event. Pass NULL to unset. * * This event is triggered when a friend changes their user status. */ void tox_callback_friend_status(Tox *tox, tox_friend_status_cb *callback); /** * Check whether a friend is currently connected to this client. * * The result of this function is equal to the last value received by the * `friend_connection_status` callback. * * @param friend_number The friend number for which to query the connection * status. * * @return the friend's connection status as it was received through the * `friend_connection_status` event. * * @deprecated This getter is deprecated. Use the event and store the status * in the client state. */ TOX_CONNECTION tox_friend_get_connection_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend whose connection status * changed. * @param connection_status The result of calling * tox_friend_get_connection_status on the passed friend_number. */ typedef void tox_friend_connection_status_cb(Tox *tox, uint32_t friend_number, TOX_CONNECTION connection_status, void *user_data); /** * Set the callback for the `friend_connection_status` event. Pass NULL to unset. * * This event is triggered when a friend goes offline after having been online, * or when a friend goes online. * * This callback is not called when adding friends. It is assumed that when * adding friends, their connection status is initially offline. */ void tox_callback_friend_connection_status(Tox *tox, tox_friend_connection_status_cb *callback); /** * Check whether a friend is currently typing a message. * * @param friend_number The friend number for which to query the typing status. * * @return true if the friend is typing. * @return false if the friend is not typing, or the friend number was * invalid. Inspect the error code to determine which case it is. * * @deprecated This getter is deprecated. Use the event and store the status * in the client state. */ bool tox_friend_get_typing(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend who started or stopped * typing. * @param is_typing The result of calling tox_friend_get_typing on the passed * friend_number. */ typedef void tox_friend_typing_cb(Tox *tox, uint32_t friend_number, bool is_typing, void *user_data); /** * Set the callback for the `friend_typing` event. Pass NULL to unset. * * This event is triggered when a friend starts or stops typing. */ void tox_callback_friend_typing(Tox *tox, tox_friend_typing_cb *callback); /******************************************************************************* * * :: Sending private messages * ******************************************************************************/ typedef enum TOX_ERR_SET_TYPING { /** * The function returned successfully. */ TOX_ERR_SET_TYPING_OK, /** * The friend number did not designate a valid friend. */ TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND, } TOX_ERR_SET_TYPING; /** * Set the client's typing status for a friend. * * The client is responsible for turning it on or off. * * @param friend_number The friend to which the client is typing a message. * @param typing The typing status. True means the client is typing. * * @return true on success. */ bool tox_self_set_typing(Tox *tox, uint32_t friend_number, bool typing, TOX_ERR_SET_TYPING *error); typedef enum TOX_ERR_FRIEND_SEND_MESSAGE { /** * The function returned successfully. */ TOX_ERR_FRIEND_SEND_MESSAGE_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FRIEND_SEND_MESSAGE_NULL, /** * The friend number did not designate a valid friend. */ TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED, /** * An allocation error occurred while increasing the send queue size. */ TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ, /** * Message length exceeded TOX_MAX_MESSAGE_LENGTH. */ TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG, /** * Attempted to send a zero-length message. */ TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY, } TOX_ERR_FRIEND_SEND_MESSAGE; /** * Send a text chat message to an online friend. * * This function creates a chat message packet and pushes it into the send * queue. * * The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages * must be split by the client and sent as separate messages. Other clients can * then reassemble the fragments. Messages may not be empty. * * The return value of this function is the message ID. If a read receipt is * received, the triggered `friend_read_receipt` event will be passed this message ID. * * Message IDs are unique per friend. The first message ID is 0. Message IDs are * incremented by 1 each time a message is sent. If UINT32_MAX messages were * sent, the next message ID is 0. * * @param type Message type (normal, action, ...). * @param friend_number The friend number of the friend to send the message to. * @param message A non-NULL pointer to the first element of a byte array * containing the message text. * @param length Length of the message to be sent. */ uint32_t tox_friend_send_message(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, TOX_ERR_FRIEND_SEND_MESSAGE *error); /** * @param friend_number The friend number of the friend who received the message. * @param message_id The message ID as returned from tox_friend_send_message * corresponding to the message sent. */ typedef void tox_friend_read_receipt_cb(Tox *tox, uint32_t friend_number, uint32_t message_id, void *user_data); /** * Set the callback for the `friend_read_receipt` event. Pass NULL to unset. * * This event is triggered when the friend receives the message sent with * tox_friend_send_message with the corresponding message ID. */ void tox_callback_friend_read_receipt(Tox *tox, tox_friend_read_receipt_cb *callback); /******************************************************************************* * * :: Receiving private messages and friend requests * ******************************************************************************/ /** * @param public_key The Public Key of the user who sent the friend request. * @param message The message they sent along with the request. * @param length The size of the message byte array. */ typedef void tox_friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data); /** * Set the callback for the `friend_request` event. Pass NULL to unset. * * This event is triggered when a friend request is received. */ void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback); /** * @param friend_number The friend number of the friend who sent the message. * @param message The message data they sent. * @param length The size of the message byte array. */ typedef void tox_friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, void *user_data); /** * Set the callback for the `friend_message` event. Pass NULL to unset. * * This event is triggered when a message from a friend is received. */ void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback); /******************************************************************************* * * :: File transmission: common between sending and receiving * ******************************************************************************/ /** * Generates a cryptographic hash of the given data. * * This function may be used by clients for any purpose, but is provided * primarily for validating cached avatars. This use is highly recommended to * avoid unnecessary avatar updates. * * If hash is NULL or data is NULL while length is not 0 the function returns false, * otherwise it returns true. * * This function is a wrapper to internal message-digest functions. * * @param hash A valid memory location the hash data. It must be at least * TOX_HASH_LENGTH bytes in size. * @param data Data to be hashed or NULL. * @param length Size of the data array or 0. * * @return true if hash was not NULL. */ bool tox_hash(uint8_t *hash, const uint8_t *data, size_t length); /** * A list of pre-defined file kinds. Toxcore itself does not behave * differently for different file kinds. These are a hint to the client * telling it what use the sender intended for the file. The `kind` parameter * in the send function and recv callback are `uint32_t`, not TOX_FILE_KIND, because * clients can invent their own file kind. Unknown file kinds should be * treated as TOX_FILE_KIND_DATA. */ enum TOX_FILE_KIND { /** * Arbitrary file data. Clients can choose to handle it based on the file name * or magic or any other way they choose. */ TOX_FILE_KIND_DATA, /** * Avatar file_id. This consists of tox_hash(image). * Avatar data. This consists of the image data. * * Avatars can be sent at any time the client wishes. Generally, a client will * send the avatar to a friend when that friend comes online, and to all * friends when the avatar changed. A client can save some traffic by * remembering which friend received the updated avatar already and only send * it if the friend has an out of date avatar. * * Clients who receive avatar send requests can reject it (by sending * TOX_FILE_CONTROL_CANCEL before any other controls), or accept it (by * sending TOX_FILE_CONTROL_RESUME). The file_id of length TOX_HASH_LENGTH bytes * (same length as TOX_FILE_ID_LENGTH) will contain the hash. A client can compare * this hash with a saved hash and send TOX_FILE_CONTROL_CANCEL to terminate the avatar * transfer if it matches. * * When file_size is set to 0 in the transfer request it means that the client * has no avatar. */ TOX_FILE_KIND_AVATAR, }; typedef enum TOX_FILE_CONTROL { /** * Sent by the receiving side to accept a file send request. Also sent after a * TOX_FILE_CONTROL_PAUSE command to continue sending or receiving. */ TOX_FILE_CONTROL_RESUME, /** * Sent by clients to pause the file transfer. The initial state of a file * transfer is always paused on the receiving side and running on the sending * side. If both the sending and receiving side pause the transfer, then both * need to send TOX_FILE_CONTROL_RESUME for the transfer to resume. */ TOX_FILE_CONTROL_PAUSE, /** * Sent by the receiving side to reject a file send request before any other * commands are sent. Also sent by either side to terminate a file transfer. */ TOX_FILE_CONTROL_CANCEL, } TOX_FILE_CONTROL; typedef enum TOX_ERR_FILE_CONTROL { /** * The function returned successfully. */ TOX_ERR_FILE_CONTROL_OK, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ TOX_ERR_FILE_CONTROL_NOT_FOUND, /** * A RESUME control was sent, but the file transfer is running normally. */ TOX_ERR_FILE_CONTROL_NOT_PAUSED, /** * A RESUME control was sent, but the file transfer was paused by the other * party. Only the party that paused the transfer can resume it. */ TOX_ERR_FILE_CONTROL_DENIED, /** * A PAUSE control was sent, but the file transfer was already paused. */ TOX_ERR_FILE_CONTROL_ALREADY_PAUSED, /** * Packet queue is full. */ TOX_ERR_FILE_CONTROL_SENDQ, } TOX_ERR_FILE_CONTROL; /** * Sends a file control command to a friend for a given file transfer. * * @param friend_number The friend number of the friend the file is being * transferred to or received from. * @param file_number The friend-specific identifier for the file transfer. * @param control The control command to send. * * @return true on success. */ bool tox_file_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, TOX_ERR_FILE_CONTROL *error); /** * When receiving TOX_FILE_CONTROL_CANCEL, the client should release the * resources associated with the file number and consider the transfer failed. * * @param friend_number The friend number of the friend who is sending the file. * @param file_number The friend-specific file number the data received is * associated with. * @param control The file control command received. */ typedef void tox_file_recv_control_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, void *user_data); /** * Set the callback for the `file_recv_control` event. Pass NULL to unset. * * This event is triggered when a file control command is received from a * friend. */ void tox_callback_file_recv_control(Tox *tox, tox_file_recv_control_cb *callback); typedef enum TOX_ERR_FILE_SEEK { /** * The function returned successfully. */ TOX_ERR_FILE_SEEK_OK, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ TOX_ERR_FILE_SEEK_NOT_FOUND, /** * File was not in a state where it could be seeked. */ TOX_ERR_FILE_SEEK_DENIED, /** * Seek position was invalid */ TOX_ERR_FILE_SEEK_INVALID_POSITION, /** * Packet queue is full. */ TOX_ERR_FILE_SEEK_SENDQ, } TOX_ERR_FILE_SEEK; /** * Sends a file seek control command to a friend for a given file transfer. * * This function can only be called to resume a file transfer right before * TOX_FILE_CONTROL_RESUME is sent. * * @param friend_number The friend number of the friend the file is being * received from. * @param file_number The friend-specific identifier for the file transfer. * @param position The position that the file should be seeked to. */ bool tox_file_seek(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, TOX_ERR_FILE_SEEK *error); typedef enum TOX_ERR_FILE_GET { /** * The function returned successfully. */ TOX_ERR_FILE_GET_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FILE_GET_NULL, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, /** * No file transfer with the given file number was found for the given friend. */ TOX_ERR_FILE_GET_NOT_FOUND, } TOX_ERR_FILE_GET; /** * Copy the file id associated to the file transfer to a byte array. * * @param friend_number The friend number of the friend the file is being * transferred to or received from. * @param file_number The friend-specific identifier for the file transfer. * @param file_id A memory region of at least TOX_FILE_ID_LENGTH bytes. If * this parameter is NULL, this function has no effect. * * @return true on success. */ bool tox_file_get_file_id(const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, TOX_ERR_FILE_GET *error); /******************************************************************************* * * :: File transmission: sending * ******************************************************************************/ typedef enum TOX_ERR_FILE_SEND { /** * The function returned successfully. */ TOX_ERR_FILE_SEND_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FILE_SEND_NULL, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED, /** * Filename length exceeded TOX_MAX_FILENAME_LENGTH bytes. */ TOX_ERR_FILE_SEND_NAME_TOO_LONG, /** * Too many ongoing transfers. The maximum number of concurrent file transfers * is 256 per friend per direction (sending and receiving). */ TOX_ERR_FILE_SEND_TOO_MANY, } TOX_ERR_FILE_SEND; /** * Send a file transmission request. * * Maximum filename length is TOX_MAX_FILENAME_LENGTH bytes. The filename * should generally just be a file name, not a path with directory names. * * If a non-UINT64_MAX file size is provided, it can be used by both sides to * determine the sending progress. File size can be set to UINT64_MAX for streaming * data of unknown size. * * File transmission occurs in chunks, which are requested through the * `file_chunk_request` event. * * When a friend goes offline, all file transfers associated with the friend are * purged from core. * * If the file contents change during a transfer, the behaviour is unspecified * in general. What will actually happen depends on the mode in which the file * was modified and how the client determines the file size. * * - If the file size was increased * - and sending mode was streaming (file_size = UINT64_MAX), the behaviour * will be as expected. * - and sending mode was file (file_size != UINT64_MAX), the * file_chunk_request callback will receive length = 0 when Core thinks * the file transfer has finished. If the client remembers the file size as * it was when sending the request, it will terminate the transfer normally. * If the client re-reads the size, it will think the friend cancelled the * transfer. * - If the file size was decreased * - and sending mode was streaming, the behaviour is as expected. * - and sending mode was file, the callback will return 0 at the new * (earlier) end-of-file, signalling to the friend that the transfer was * cancelled. * - If the file contents were modified * - at a position before the current read, the two files (local and remote) * will differ after the transfer terminates. * - at a position after the current read, the file transfer will succeed as * expected. * - In either case, both sides will regard the transfer as complete and * successful. * * @param friend_number The friend number of the friend the file send request * should be sent to. * @param kind The meaning of the file to be sent. * @param file_size Size in bytes of the file the client wants to send, UINT64_MAX if * unknown or streaming. * @param file_id A file identifier of length TOX_FILE_ID_LENGTH that can be used to * uniquely identify file transfers across core restarts. If NULL, a random one will * be generated by core. It can then be obtained by using tox_file_get_file_id(). * @param filename Name of the file. Does not need to be the actual name. This * name will be sent along with the file send request. * @param filename_length Size in bytes of the filename. * * @return A file number used as an identifier in subsequent callbacks. This * number is per friend. File numbers are reused after a transfer terminates. * On failure, this function returns an unspecified value. Any pattern in file numbers * should not be relied on. */ uint32_t tox_file_send(Tox *tox, uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t *file_id, const uint8_t *filename, size_t filename_length, TOX_ERR_FILE_SEND *error); typedef enum TOX_ERR_FILE_SEND_CHUNK { /** * The function returned successfully. */ TOX_ERR_FILE_SEND_CHUNK_OK, /** * The length parameter was non-zero, but data was NULL. */ TOX_ERR_FILE_SEND_CHUNK_NULL, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND, /** * File transfer was found but isn't in a transferring state: (paused, done, * broken, etc...) (happens only when not called from the request chunk callback). */ TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING, /** * Attempted to send more or less data than requested. The requested data size is * adjusted according to maximum transmission unit and the expected end of * the file. Trying to send less or more than requested will return this error. */ TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH, /** * Packet queue is full. */ TOX_ERR_FILE_SEND_CHUNK_SENDQ, /** * Position parameter was wrong. */ TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION, } TOX_ERR_FILE_SEND_CHUNK; /** * Send a chunk of file data to a friend. * * This function is called in response to the `file_chunk_request` callback. The * length parameter should be equal to the one received though the callback. * If it is zero, the transfer is assumed complete. For files with known size, * Core will know that the transfer is complete after the last byte has been * received, so it is not necessary (though not harmful) to send a zero-length * chunk to terminate. For streams, core will know that the transfer is finished * if a chunk with length less than the length requested in the callback is sent. * * @param friend_number The friend number of the receiving friend for this file. * @param file_number The file transfer identifier returned by tox_file_send. * @param position The file or stream position from which to continue reading. * @return true on success. */ bool tox_file_send_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, TOX_ERR_FILE_SEND_CHUNK *error); /** * If the length parameter is 0, the file transfer is finished, and the client's * resources associated with the file number should be released. After a call * with zero length, the file number can be reused for future file transfers. * * If the requested position is not equal to the client's idea of the current * file or stream position, it will need to seek. In case of read-once streams, * the client should keep the last read chunk so that a seek back can be * supported. A seek-back only ever needs to read from the last requested chunk. * This happens when a chunk was requested, but the send failed. A seek-back * request can occur an arbitrary number of times for any given chunk. * * In response to receiving this callback, the client should call the function * `tox_file_send_chunk` with the requested chunk. If the number of bytes sent * through that function is zero, the file transfer is assumed complete. A * client must send the full length of data requested with this callback. * * @param friend_number The friend number of the receiving friend for this file. * @param file_number The file transfer identifier returned by tox_file_send. * @param position The file or stream position from which to continue reading. * @param length The number of bytes requested for the current chunk. */ typedef void tox_file_chunk_request_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length, void *user_data); /** * Set the callback for the `file_chunk_request` event. Pass NULL to unset. * * This event is triggered when Core is ready to send more file data. */ void tox_callback_file_chunk_request(Tox *tox, tox_file_chunk_request_cb *callback); /******************************************************************************* * * :: File transmission: receiving * ******************************************************************************/ /** * The client should acquire resources to be associated with the file transfer. * Incoming file transfers start in the PAUSED state. After this callback * returns, a transfer can be rejected by sending a TOX_FILE_CONTROL_CANCEL * control command before any other control commands. It can be accepted by * sending TOX_FILE_CONTROL_RESUME. * * @param friend_number The friend number of the friend who is sending the file * transfer request. * @param file_number The friend-specific file number the data received is * associated with. * @param kind The meaning of the file that was sent. * @param file_size Size in bytes of the file the client wants to send, * UINT64_MAX if unknown or streaming. * @param filename Name of the file. Does not need to be the actual name. This * name will be sent along with the file send request. * @param filename_length Size in bytes of the filename. */ typedef void tox_file_recv_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, const uint8_t *filename, size_t filename_length, void *user_data); /** * Set the callback for the `file_recv` event. Pass NULL to unset. * * This event is triggered when a file transfer request is received. */ void tox_callback_file_recv(Tox *tox, tox_file_recv_cb *callback); /** * When length is 0, the transfer is finished and the client should release the * resources it acquired for the transfer. After a call with length = 0, the * file number can be reused for new file transfers. * * If position is equal to file_size (received in the file_receive callback) * when the transfer finishes, the file was received completely. Otherwise, if * file_size was UINT64_MAX, streaming ended successfully when length is 0. * * @param friend_number The friend number of the friend who is sending the file. * @param file_number The friend-specific file number the data received is * associated with. * @param position The file position of the first byte in data. * @param data A byte array containing the received chunk. * @param length The length of the received chunk. */ typedef void tox_file_recv_chunk_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, void *user_data); /** * Set the callback for the `file_recv_chunk` event. Pass NULL to unset. * * This event is first triggered when a file transfer request is received, and * subsequently when a chunk of file data for an accepted request was received. */ void tox_callback_file_recv_chunk(Tox *tox, tox_file_recv_chunk_cb *callback); /******************************************************************************* * * :: Conference management * ******************************************************************************/ /** * Conference types for the conference_invite event. * * @deprecated All UPPER_CASE enum type names are deprecated. Use the * Camel_Snake_Case versions, instead. */ typedef enum TOX_CONFERENCE_TYPE { /** * Text-only conferences that must be accepted with the tox_conference_join function. */ TOX_CONFERENCE_TYPE_TEXT, /** * Video conference. The function to accept these is in toxav. */ TOX_CONFERENCE_TYPE_AV, } TOX_CONFERENCE_TYPE; /** * The invitation will remain valid until the inviting friend goes offline * or exits the conference. * * @param friend_number The friend who invited us. * @param type The conference type (text only or audio/video). * @param cookie A piece of data of variable length required to join the * conference. * @param length The length of the cookie. */ typedef void tox_conference_invite_cb(Tox *tox, uint32_t friend_number, TOX_CONFERENCE_TYPE type, const uint8_t *cookie, size_t length, void *user_data); /** * Set the callback for the `conference_invite` event. Pass NULL to unset. * * This event is triggered when the client is invited to join a conference. */ void tox_callback_conference_invite(Tox *tox, tox_conference_invite_cb *callback); /** * @param conference_number The conference number of the conference to which we have connected. */ typedef void tox_conference_connected_cb(Tox *tox, uint32_t conference_number, void *user_data); /** * Set the callback for the `conference_connected` event. Pass NULL to unset. * * This event is triggered when the client successfully connects to a * conference after joining it with the tox_conference_join function. */ void tox_callback_conference_connected(Tox *tox, tox_conference_connected_cb *callback); /** * @param conference_number The conference number of the conference the message is intended for. * @param peer_number The ID of the peer who sent the message. * @param type The type of message (normal, action, ...). * @param message The message data. * @param length The length of the message. */ typedef void tox_conference_message_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, void *user_data); /** * Set the callback for the `conference_message` event. Pass NULL to unset. * * This event is triggered when the client receives a conference message. */ void tox_callback_conference_message(Tox *tox, tox_conference_message_cb *callback); /** * @param conference_number The conference number of the conference the title change is intended for. * @param peer_number The ID of the peer who changed the title. * @param title The title data. * @param length The title length. */ typedef void tox_conference_title_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, const uint8_t *title, size_t length, void *user_data); /** * Set the callback for the `conference_title` event. Pass NULL to unset. * * This event is triggered when a peer changes the conference title. * * If peer_number == UINT32_MAX, then author is unknown (e.g. initial joining the conference). */ void tox_callback_conference_title(Tox *tox, tox_conference_title_cb *callback); /** * @param conference_number The conference number of the conference the * peer is in. * @param peer_number The ID of the peer who changed their nickname. * @param name A byte array containing the new nickname. * @param length The size of the name byte array. */ typedef void tox_conference_peer_name_cb(Tox *tox, uint32_t conference_number, uint32_t peer_number, const uint8_t *name, size_t length, void *user_data); /** * Set the callback for the `conference_peer_name` event. Pass NULL to unset. * * This event is triggered when a peer changes their name. */ void tox_callback_conference_peer_name(Tox *tox, tox_conference_peer_name_cb *callback); /** * @param conference_number The conference number of the conference the * peer is in. */ typedef void tox_conference_peer_list_changed_cb(Tox *tox, uint32_t conference_number, void *user_data); /** * Set the callback for the `conference_peer_list_changed` event. Pass NULL to unset. * * This event is triggered when a peer joins or leaves the conference. */ void tox_callback_conference_peer_list_changed(Tox *tox, tox_conference_peer_list_changed_cb *callback); typedef enum TOX_ERR_CONFERENCE_NEW { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_NEW_OK, /** * The conference instance failed to initialize. */ TOX_ERR_CONFERENCE_NEW_INIT, } TOX_ERR_CONFERENCE_NEW; /** * Creates a new conference. * * This function creates and connects to a new text conference. * * @return conference number on success, or an unspecified value on failure. */ uint32_t tox_conference_new(Tox *tox, TOX_ERR_CONFERENCE_NEW *error); typedef enum TOX_ERR_CONFERENCE_DELETE { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_DELETE_OK, /** * The conference number passed did not designate a valid conference. */ TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND, } TOX_ERR_CONFERENCE_DELETE; /** * This function deletes a conference. * * @param conference_number The conference number of the conference to be deleted. * * @return true on success. */ bool tox_conference_delete(Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_DELETE *error); /** * Error codes for peer info queries. */ typedef enum TOX_ERR_CONFERENCE_PEER_QUERY { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_PEER_QUERY_OK, /** * The conference number passed did not designate a valid conference. */ TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND, /** * The peer number passed did not designate a valid peer. */ TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND, /** * The client is not connected to the conference. */ TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION, } TOX_ERR_CONFERENCE_PEER_QUERY; /** * Return the number of online peers in the conference. The unsigned * integers less than this number are the valid values of peer_number for * the functions querying these peers. Return value is unspecified on * failure. */ uint32_t tox_conference_peer_count(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Return the length of the peer's name. Return value is unspecified on failure. */ size_t tox_conference_peer_get_name_size(const Tox *tox, uint32_t conference_number, uint32_t peer_number, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Copy the name of peer_number who is in conference_number to name. * * Call tox_conference_peer_get_name_size to determine the allocation size for the `name` parameter. * * @param name A valid memory region large enough to store the peer's name. * * @return true on success. */ bool tox_conference_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t peer_number, uint8_t *name, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Copy the public key of peer_number who is in conference_number to public_key. * public_key must be TOX_PUBLIC_KEY_SIZE long. * * @return true on success. */ bool tox_conference_peer_get_public_key(const Tox *tox, uint32_t conference_number, uint32_t peer_number, uint8_t *public_key, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Return true if passed peer_number corresponds to our own. */ bool tox_conference_peer_number_is_ours(const Tox *tox, uint32_t conference_number, uint32_t peer_number, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Return the number of offline peers in the conference. The unsigned * integers less than this number are the valid values of offline_peer_number for * the functions querying these peers. Return value is unspecified on failure. */ uint32_t tox_conference_offline_peer_count(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Return the length of the offline peer's name. Return value is unspecified on failure. */ size_t tox_conference_offline_peer_get_name_size(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Copy the name of offline_peer_number who is in conference_number to name. * * Call tox_conference_offline_peer_get_name_size to determine the allocation size for the `name` parameter. * * @param name A valid memory region large enough to store the peer's name. * * @return true on success. */ bool tox_conference_offline_peer_get_name(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, uint8_t *name, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Copy the public key of offline_peer_number who is in conference_number to public_key. * public_key must be TOX_PUBLIC_KEY_SIZE long. * * @return true on success. */ bool tox_conference_offline_peer_get_public_key(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, uint8_t *public_key, TOX_ERR_CONFERENCE_PEER_QUERY *error); /** * Return a unix-time timestamp of the last time offline_peer_number was seen to be active. */ uint64_t tox_conference_offline_peer_get_last_active(const Tox *tox, uint32_t conference_number, uint32_t offline_peer_number, TOX_ERR_CONFERENCE_PEER_QUERY *error); typedef enum TOX_ERR_CONFERENCE_SET_MAX_OFFLINE { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_OK, /** * The conference number passed did not designate a valid conference. */ TOX_ERR_CONFERENCE_SET_MAX_OFFLINE_CONFERENCE_NOT_FOUND, } TOX_ERR_CONFERENCE_SET_MAX_OFFLINE; /** * Set maximum number of offline peers to store, overriding the default. */ bool tox_conference_set_max_offline(Tox *tox, uint32_t conference_number, uint32_t max_offline_peers, TOX_ERR_CONFERENCE_SET_MAX_OFFLINE *error); typedef enum TOX_ERR_CONFERENCE_INVITE { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_INVITE_OK, /** * The conference number passed did not designate a valid conference. */ TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND, /** * The invite packet failed to send. */ TOX_ERR_CONFERENCE_INVITE_FAIL_SEND, /** * The client is not connected to the conference. */ TOX_ERR_CONFERENCE_INVITE_NO_CONNECTION, } TOX_ERR_CONFERENCE_INVITE; /** * Invites a friend to a conference. * * @param friend_number The friend number of the friend we want to invite. * @param conference_number The conference number of the conference we want to invite the friend to. * * @return true on success. */ bool tox_conference_invite(Tox *tox, uint32_t friend_number, uint32_t conference_number, TOX_ERR_CONFERENCE_INVITE *error); typedef enum TOX_ERR_CONFERENCE_JOIN { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_JOIN_OK, /** * The cookie passed has an invalid length. */ TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH, /** * The conference is not the expected type. This indicates an invalid cookie. */ TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE, /** * The friend number passed does not designate a valid friend. */ TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND, /** * Client is already in this conference. */ TOX_ERR_CONFERENCE_JOIN_DUPLICATE, /** * Conference instance failed to initialize. */ TOX_ERR_CONFERENCE_JOIN_INIT_FAIL, /** * The join packet failed to send. */ TOX_ERR_CONFERENCE_JOIN_FAIL_SEND, } TOX_ERR_CONFERENCE_JOIN; /** * Joins a conference that the client has been invited to. * * After successfully joining the conference, the client will not be "connected" * to it until a handshaking procedure has been completed. A * `conference_connected` event will then occur for the conference. The client * will then remain connected to the conference until the conference is deleted, * even across core restarts. Many operations on a conference will fail with a * corresponding error if attempted on a conference to which the client is not * yet connected. * * @param friend_number The friend number of the friend who sent the invite. * @param cookie Received via the `conference_invite` event. * @param length The size of cookie. * * @return conference number on success, an unspecified value on failure. */ uint32_t tox_conference_join(Tox *tox, uint32_t friend_number, const uint8_t *cookie, size_t length, TOX_ERR_CONFERENCE_JOIN *error); typedef enum TOX_ERR_CONFERENCE_SEND_MESSAGE { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_SEND_MESSAGE_OK, /** * The conference number passed did not designate a valid conference. */ TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND, /** * The message is too long. */ TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG, /** * The client is not connected to the conference. */ TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION, /** * The message packet failed to send. */ TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND, } TOX_ERR_CONFERENCE_SEND_MESSAGE; /** * Send a text chat message to the conference. * * This function creates a conference message packet and pushes it into the send * queue. * * The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages * must be split by the client and sent as separate messages. Other clients can * then reassemble the fragments. * * @param conference_number The conference number of the conference the message is intended for. * @param type Message type (normal, action, ...). * @param message A non-NULL pointer to the first element of a byte array * containing the message text. * @param length Length of the message to be sent. * * @return true on success. */ bool tox_conference_send_message(Tox *tox, uint32_t conference_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, TOX_ERR_CONFERENCE_SEND_MESSAGE *error); typedef enum TOX_ERR_CONFERENCE_TITLE { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_TITLE_OK, /** * The conference number passed did not designate a valid conference. */ TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND, /** * The title is too long or empty. */ TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH, /** * The title packet failed to send. */ TOX_ERR_CONFERENCE_TITLE_FAIL_SEND, } TOX_ERR_CONFERENCE_TITLE; /** * Return the length of the conference title. Return value is unspecified on failure. * * The return value is equal to the `length` argument received by the last * `conference_title` callback. */ size_t tox_conference_get_title_size(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_TITLE *error); /** * Write the title designated by the given conference number to a byte array. * * Call tox_conference_get_title_size to determine the allocation size for the `title` parameter. * * The data written to `title` is equal to the data received by the last * `conference_title` callback. * * @param title A valid memory region large enough to store the title. * If this parameter is NULL, this function has no effect. * * @return true on success. */ bool tox_conference_get_title(const Tox *tox, uint32_t conference_number, uint8_t *title, TOX_ERR_CONFERENCE_TITLE *error); /** * Set the conference title and broadcast it to the rest of the conference. * * Title length cannot be longer than TOX_MAX_NAME_LENGTH. * * @return true on success. */ bool tox_conference_set_title(Tox *tox, uint32_t conference_number, const uint8_t *title, size_t length, TOX_ERR_CONFERENCE_TITLE *error); /** * Return the number of conferences in the Tox instance. * This should be used to determine how much memory to allocate for `tox_conference_get_chatlist`. */ size_t tox_conference_get_chatlist_size(const Tox *tox); /** * Copy a list of valid conference numbers into the array chatlist. Determine * how much space to allocate for the array with the `tox_conference_get_chatlist_size` function. * * Note that `tox_get_savedata` saves all connected conferences; * when toxcore is created from savedata in which conferences were saved, those * conferences will be connected at startup, and will be listed by * `tox_conference_get_chatlist`. * * The conference number of a loaded conference may differ from the conference * number it had when it was saved. */ void tox_conference_get_chatlist(const Tox *tox, uint32_t *chatlist); /** * Returns the type of conference (TOX_CONFERENCE_TYPE) that conference_number is. Return value is * unspecified on failure. */ typedef enum TOX_ERR_CONFERENCE_GET_TYPE { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_GET_TYPE_OK, /** * The conference number passed did not designate a valid conference. */ TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND, } TOX_ERR_CONFERENCE_GET_TYPE; TOX_CONFERENCE_TYPE tox_conference_get_type(const Tox *tox, uint32_t conference_number, TOX_ERR_CONFERENCE_GET_TYPE *error); /** * Get the conference unique ID. * * If id is NULL, this function has no effect. * * @param id A memory region large enough to store TOX_CONFERENCE_ID_SIZE bytes. * * @return true on success. */ bool tox_conference_get_id(const Tox *tox, uint32_t conference_number, uint8_t *id); typedef enum TOX_ERR_CONFERENCE_BY_ID { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_BY_ID_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_CONFERENCE_BY_ID_NULL, /** * No conference with the given id exists on the conference list. */ TOX_ERR_CONFERENCE_BY_ID_NOT_FOUND, } TOX_ERR_CONFERENCE_BY_ID; /** * Return the conference number associated with the specified id. * * @param id A byte array containing the conference id (TOX_CONFERENCE_ID_SIZE). * * @return the conference number on success, an unspecified value on failure. */ uint32_t tox_conference_by_id(const Tox *tox, const uint8_t *id, TOX_ERR_CONFERENCE_BY_ID *error); /** * Get the conference unique ID. * * If uid is NULL, this function has no effect. * * @param uid A memory region large enough to store TOX_CONFERENCE_UID_SIZE bytes. * * @return true on success. * @deprecated use tox_conference_get_id instead (exactly the same function, just renamed). */ bool tox_conference_get_uid(const Tox *tox, uint32_t conference_number, uint8_t *uid); typedef enum TOX_ERR_CONFERENCE_BY_UID { /** * The function returned successfully. */ TOX_ERR_CONFERENCE_BY_UID_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_CONFERENCE_BY_UID_NULL, /** * No conference with the given uid exists on the conference list. */ TOX_ERR_CONFERENCE_BY_UID_NOT_FOUND, } TOX_ERR_CONFERENCE_BY_UID; /** * Return the conference number associated with the specified uid. * * @param uid A byte array containing the conference id (TOX_CONFERENCE_UID_SIZE). * * @return the conference number on success, an unspecified value on failure. * @deprecated use tox_conference_by_id instead (exactly the same function, just renamed). */ uint32_t tox_conference_by_uid(const Tox *tox, const uint8_t *uid, TOX_ERR_CONFERENCE_BY_UID *error); /******************************************************************************* * * :: Low-level custom packet sending and receiving * ******************************************************************************/ typedef enum TOX_ERR_FRIEND_CUSTOM_PACKET { /** * The function returned successfully. */ TOX_ERR_FRIEND_CUSTOM_PACKET_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FRIEND_CUSTOM_PACKET_NULL, /** * The friend number did not designate a valid friend. */ TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_CONNECTED, /** * The first byte of data was not in the specified range for the packet type. * This range is 192-254 for lossy, and 69, 160-191 for lossless packets. */ TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID, /** * Attempted to send an empty packet. */ TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY, /** * Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE. */ TOX_ERR_FRIEND_CUSTOM_PACKET_TOO_LONG, /** * Packet queue is full. */ TOX_ERR_FRIEND_CUSTOM_PACKET_SENDQ, } TOX_ERR_FRIEND_CUSTOM_PACKET; /** * Send a custom lossy packet to a friend. * * The first byte of data must be in the range 192-254. Maximum length of a * custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. * * Lossy packets behave like UDP packets, meaning they might never reach the * other side or might arrive more than once (if someone is messing with the * connection) or might arrive in the wrong order. * * Unless latency is an issue, it is recommended that you use lossless custom * packets instead. * * @param friend_number The friend number of the friend this lossy packet * should be sent to. * @param data A byte array containing the packet data. * @param length The length of the packet data byte array. * * @return true on success. */ bool tox_friend_send_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, TOX_ERR_FRIEND_CUSTOM_PACKET *error); /** * Send a custom lossless packet to a friend. * * The first byte of data must be in the range 69, 160-191. Maximum length of a * custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. * * Lossless packet behaviour is comparable to TCP (reliability, arrive in order) * but with packets instead of a stream. * * @param friend_number The friend number of the friend this lossless packet * should be sent to. * @param data A byte array containing the packet data. * @param length The length of the packet data byte array. * * @return true on success. */ bool tox_friend_send_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, TOX_ERR_FRIEND_CUSTOM_PACKET *error); /** * @param friend_number The friend number of the friend who sent a lossy packet. * @param data A byte array containing the received packet data. * @param length The length of the packet data byte array. */ typedef void tox_friend_lossy_packet_cb(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data); /** * Set the callback for the `friend_lossy_packet` event. Pass NULL to unset. * */ void tox_callback_friend_lossy_packet(Tox *tox, tox_friend_lossy_packet_cb *callback); /** * @param friend_number The friend number of the friend who sent the packet. * @param data A byte array containing the received packet data. * @param length The length of the packet data byte array. */ typedef void tox_friend_lossless_packet_cb(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data); /** * Set the callback for the `friend_lossless_packet` event. Pass NULL to unset. * */ void tox_callback_friend_lossless_packet(Tox *tox, tox_friend_lossless_packet_cb *callback); /******************************************************************************* * * :: Low-level network information * ******************************************************************************/ typedef enum TOX_ERR_GET_PORT { /** * The function returned successfully. */ TOX_ERR_GET_PORT_OK, /** * The instance was not bound to any port. */ TOX_ERR_GET_PORT_NOT_BOUND, } TOX_ERR_GET_PORT; /** * Writes the temporary DHT public key of this instance to a byte array. * * This can be used in combination with an externally accessible IP address and * the bound port (from tox_self_get_udp_port) to run a temporary bootstrap node. * * Be aware that every time a new instance is created, the DHT public key * changes, meaning this cannot be used to run a permanent bootstrap node. * * @param dht_id A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this * parameter is NULL, this function has no effect. */ void tox_self_get_dht_id(const Tox *tox, uint8_t *dht_id); /** * Return the UDP port this Tox instance is bound to. */ uint16_t tox_self_get_udp_port(const Tox *tox, TOX_ERR_GET_PORT *error); /** * Return the TCP port this Tox instance is bound to. This is only relevant if * the instance is acting as a TCP relay. */ uint16_t tox_self_get_tcp_port(const Tox *tox, TOX_ERR_GET_PORT *error); #ifdef __cplusplus } #endif typedef TOX_ERR_OPTIONS_NEW Tox_Err_Options_New; typedef TOX_ERR_NEW Tox_Err_New; typedef TOX_ERR_BOOTSTRAP Tox_Err_Bootstrap; typedef TOX_ERR_SET_INFO Tox_Err_Set_Info; typedef TOX_ERR_FRIEND_ADD Tox_Err_Friend_Add; typedef TOX_ERR_FRIEND_DELETE Tox_Err_Friend_Delete; typedef TOX_ERR_FRIEND_BY_PUBLIC_KEY Tox_Err_Friend_By_Public_Key; typedef TOX_ERR_FRIEND_GET_PUBLIC_KEY Tox_Err_Friend_Get_Public_Key; typedef TOX_ERR_FRIEND_GET_LAST_ONLINE Tox_Err_Friend_Get_Last_Online; typedef TOX_ERR_FRIEND_QUERY Tox_Err_Friend_Query; typedef TOX_ERR_SET_TYPING Tox_Err_Set_Typing; typedef TOX_ERR_FRIEND_SEND_MESSAGE Tox_Err_Friend_Send_Message; typedef TOX_ERR_FILE_CONTROL Tox_Err_File_Control; typedef TOX_ERR_FILE_SEEK Tox_Err_File_Seek; typedef TOX_ERR_FILE_GET Tox_Err_File_Get; typedef TOX_ERR_FILE_SEND Tox_Err_File_Send; typedef TOX_ERR_FILE_SEND_CHUNK Tox_Err_File_Send_Chunk; typedef TOX_ERR_CONFERENCE_NEW Tox_Err_Conference_New; typedef TOX_ERR_CONFERENCE_DELETE Tox_Err_Conference_Delete; typedef TOX_ERR_CONFERENCE_PEER_QUERY Tox_Err_Conference_Peer_Query; typedef TOX_ERR_CONFERENCE_SET_MAX_OFFLINE Tox_Err_Conference_Set_Max_Offline; typedef TOX_ERR_CONFERENCE_BY_ID Tox_Err_Conference_By_Id; typedef TOX_ERR_CONFERENCE_BY_UID Tox_Err_Conference_By_Uid; typedef TOX_ERR_CONFERENCE_INVITE Tox_Err_Conference_Invite; typedef TOX_ERR_CONFERENCE_JOIN Tox_Err_Conference_Join; typedef TOX_ERR_CONFERENCE_SEND_MESSAGE Tox_Err_Conference_Send_Message; typedef TOX_ERR_CONFERENCE_TITLE Tox_Err_Conference_Title; typedef TOX_ERR_CONFERENCE_GET_TYPE Tox_Err_Conference_Get_Type; typedef TOX_ERR_FRIEND_CUSTOM_PACKET Tox_Err_Friend_Custom_Packet; typedef TOX_ERR_GET_PORT Tox_Err_Get_Port; typedef TOX_USER_STATUS Tox_User_Status; typedef TOX_MESSAGE_TYPE Tox_Message_Type; typedef TOX_PROXY_TYPE Tox_Proxy_Type; typedef TOX_SAVEDATA_TYPE Tox_Savedata_Type; typedef TOX_LOG_LEVEL Tox_Log_Level; typedef TOX_CONNECTION Tox_Connection; typedef TOX_FILE_CONTROL Tox_File_Control; typedef TOX_CONFERENCE_TYPE Tox_Conference_Type; //!TOKSTYLE+ #endif // C_TOXCORE_TOXCORE_TOX_H c-toxcore-0.2.13/toxcore/tox_api.c000066400000000000000000000064371415350724400170110ustar00rootroot00000000000000#include "tox.h" #include "ccompat.h" #include #include #define SET_ERROR_PARAMETER(param, x) do { if (param) { *param = x; } } while (0) //!TOKSTYLE- #define CONST_FUNCTION(lowercase, uppercase) \ uint32_t tox_##lowercase(void) \ { \ return TOX_##uppercase; \ } CONST_FUNCTION(version_major, VERSION_MAJOR) CONST_FUNCTION(version_minor, VERSION_MINOR) CONST_FUNCTION(version_patch, VERSION_PATCH) CONST_FUNCTION(public_key_size, PUBLIC_KEY_SIZE) CONST_FUNCTION(secret_key_size, SECRET_KEY_SIZE) CONST_FUNCTION(conference_uid_size, CONFERENCE_UID_SIZE) CONST_FUNCTION(conference_id_size, CONFERENCE_ID_SIZE) CONST_FUNCTION(nospam_size, NOSPAM_SIZE) CONST_FUNCTION(address_size, ADDRESS_SIZE) CONST_FUNCTION(max_name_length, MAX_NAME_LENGTH) CONST_FUNCTION(max_status_message_length, MAX_STATUS_MESSAGE_LENGTH) CONST_FUNCTION(max_friend_request_length, MAX_FRIEND_REQUEST_LENGTH) CONST_FUNCTION(max_message_length, MAX_MESSAGE_LENGTH) CONST_FUNCTION(max_custom_packet_size, MAX_CUSTOM_PACKET_SIZE) CONST_FUNCTION(hash_length, HASH_LENGTH) CONST_FUNCTION(file_id_length, FILE_ID_LENGTH) CONST_FUNCTION(max_filename_length, MAX_FILENAME_LENGTH) CONST_FUNCTION(max_hostname_length, MAX_HOSTNAME_LENGTH) #define ACCESSORS(type, ns, name) \ type tox_options_get_##ns##name(const struct Tox_Options *options) \ { \ return options->ns##name; \ } \ void tox_options_set_##ns##name(struct Tox_Options *options, type name) \ { \ options->ns##name = name; \ } ACCESSORS(bool,, ipv6_enabled) ACCESSORS(bool,, udp_enabled) ACCESSORS(Tox_Proxy_Type, proxy_, type) ACCESSORS(const char *, proxy_, host) ACCESSORS(uint16_t, proxy_, port) ACCESSORS(uint16_t,, start_port) ACCESSORS(uint16_t,, end_port) ACCESSORS(uint16_t,, tcp_port) ACCESSORS(bool,, hole_punching_enabled) ACCESSORS(Tox_Savedata_Type, savedata_, type) ACCESSORS(size_t, savedata_, length) ACCESSORS(tox_log_cb *, log_, callback) ACCESSORS(void *, log_, user_data) ACCESSORS(bool,, local_discovery_enabled) ACCESSORS(bool,, experimental_thread_safety) //!TOKSTYLE+ const uint8_t *tox_options_get_savedata_data(const struct Tox_Options *options) { return options->savedata_data; } void tox_options_set_savedata_data(struct Tox_Options *options, const uint8_t *data, size_t length) { options->savedata_data = data; options->savedata_length = length; } void tox_options_default(struct Tox_Options *options) { if (options) { struct Tox_Options default_options = { 0 }; *options = default_options; tox_options_set_ipv6_enabled(options, true); tox_options_set_udp_enabled(options, true); tox_options_set_proxy_type(options, TOX_PROXY_TYPE_NONE); tox_options_set_hole_punching_enabled(options, true); tox_options_set_local_discovery_enabled(options, true); tox_options_set_experimental_thread_safety(options, false); } } struct Tox_Options *tox_options_new(Tox_Err_Options_New *error) { struct Tox_Options *options = (struct Tox_Options *)malloc(sizeof(struct Tox_Options)); if (options) { tox_options_default(options); SET_ERROR_PARAMETER(error, TOX_ERR_OPTIONS_NEW_OK); return options; } SET_ERROR_PARAMETER(error, TOX_ERR_OPTIONS_NEW_MALLOC); return nullptr; } void tox_options_free(struct Tox_Options *options) { free(options); } c-toxcore-0.2.13/toxcore/tox_private.h000066400000000000000000000022741415350724400177120ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2020 The TokTok team. * Copyright © 2013 Tox project. */ #ifndef C_TOXCORE_TOXCORE_TOX_PRIVATE_H #define C_TOXCORE_TOXCORE_TOX_PRIVATE_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * Set the callback for the `friend_lossy_packet` event for a specific packet ID. * Pass NULL to unset. * * allowed packet ID range: * from `PACKET_ID_RANGE_LOSSY_START` to `PACKET_ID_RANGE_LOSSY_END` (both inclusive) */ void tox_callback_friend_lossy_packet_per_pktid(Tox *tox, tox_friend_lossy_packet_cb *callback, uint8_t pktid); /** * Set the callback for the `friend_lossless_packet` event for a specific packet ID. * Pass NULL to unset. * * allowed packet ID range: * from `PACKET_ID_RANGE_LOSSLESS_CUSTOM_START` to `PACKET_ID_RANGE_LOSSLESS_CUSTOM_END` (both inclusive) * and * `PACKET_ID_MSI` */ void tox_callback_friend_lossless_packet_per_pktid(Tox *tox, tox_friend_lossless_packet_cb *callback, uint8_t pktid); void tox_set_av_object(Tox *tox, void *object); void *tox_get_av_object(const Tox *tox); #ifdef __cplusplus } #endif #endif // C_TOXCORE_TOXCORE_TOX_PRIVATE_H c-toxcore-0.2.13/toxcore/util.c000066400000000000000000000037421415350724400163170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. * Copyright © 2013 plutooo */ /* * Utilities. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #include "util.h" #include #include #include #include "crypto_core.h" /* for CRYPTO_PUBLIC_KEY_SIZE */ /* id functions */ bool id_equal(const uint8_t *dest, const uint8_t *src) { return public_key_cmp(dest, src) == 0; } uint32_t id_copy(uint8_t *dest, const uint8_t *src) { memcpy(dest, src, CRYPTO_PUBLIC_KEY_SIZE); return CRYPTO_PUBLIC_KEY_SIZE; } int create_recursive_mutex(pthread_mutex_t *mutex) { pthread_mutexattr_t attr; if (pthread_mutexattr_init(&attr) != 0) { return -1; } if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) { pthread_mutexattr_destroy(&attr); return -1; } /* Create queue mutex */ if (pthread_mutex_init(mutex, &attr) != 0) { pthread_mutexattr_destroy(&attr); return -1; } pthread_mutexattr_destroy(&attr); return 0; } int16_t max_s16(int16_t a, int16_t b) { return a > b ? a : b; } int32_t max_s32(int32_t a, int32_t b) { return a > b ? a : b; } int64_t max_s64(int64_t a, int64_t b) { return a > b ? a : b; } int16_t min_s16(int16_t a, int16_t b) { return a < b ? a : b; } int32_t min_s32(int32_t a, int32_t b) { return a < b ? a : b; } int64_t min_s64(int64_t a, int64_t b) { return a < b ? a : b; } uint16_t max_u16(uint16_t a, uint16_t b) { return a > b ? a : b; } uint32_t max_u32(uint32_t a, uint32_t b) { return a > b ? a : b; } uint64_t max_u64(uint64_t a, uint64_t b) { return a > b ? a : b; } uint16_t min_u16(uint16_t a, uint16_t b) { return a < b ? a : b; } uint32_t min_u32(uint32_t a, uint32_t b) { return a < b ? a : b; } uint64_t min_u64(uint64_t a, uint64_t b) { return a < b ? a : b; } c-toxcore-0.2.13/toxcore/util.h000066400000000000000000000027031415350724400163200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. * Copyright © 2013 plutooo */ /* * Utilities. */ #ifndef C_TOXCORE_TOXCORE_UTIL_H #define C_TOXCORE_TOXCORE_UTIL_H #include #include #include #include "logger.h" #ifdef __cplusplus extern "C" { #endif /* id functions */ bool id_equal(const uint8_t *dest, const uint8_t *src); uint32_t id_copy(uint8_t *dest, const uint8_t *src); /* return value is CLIENT_ID_SIZE */ /* Returns -1 if failed or 0 if success */ int create_recursive_mutex(pthread_mutex_t *mutex); // Safe min/max functions with specific types. This forces the conversion to the // desired type before the comparison expression, giving the choice of // conversion to the caller. Use these instead of inline comparisons or MIN/MAX // macros (effectively inline comparisons). int16_t max_s16(int16_t a, int16_t b); int32_t max_s32(int32_t a, int32_t b); int64_t max_s64(int64_t a, int64_t b); int16_t min_s16(int16_t a, int16_t b); int32_t min_s32(int32_t a, int32_t b); int64_t min_s64(int64_t a, int64_t b); uint16_t max_u16(uint16_t a, uint16_t b); uint32_t max_u32(uint32_t a, uint32_t b); uint64_t max_u64(uint64_t a, uint64_t b); uint16_t min_u16(uint16_t a, uint16_t b); uint32_t min_u32(uint32_t a, uint32_t b); uint64_t min_u64(uint64_t a, uint64_t b); #ifdef __cplusplus } // extern "C" #endif #endif // C_TOXCORE_TOXCORE_UTIL_H c-toxcore-0.2.13/toxcore/util_test.cc000066400000000000000000000012121415350724400175070ustar00rootroot00000000000000#include "util.h" #include #include "crypto_core.h" namespace { TEST(Util, TwoRandomIdsAreNotEqual) { 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]; crypto_new_keypair(pk1, sk1); crypto_new_keypair(pk2, sk2); EXPECT_FALSE(id_equal(pk1, pk2)); } TEST(Util, IdCopyMakesKeysEqual) { uint8_t pk1[CRYPTO_PUBLIC_KEY_SIZE]; uint8_t sk1[CRYPTO_SECRET_KEY_SIZE]; uint8_t pk2[CRYPTO_PUBLIC_KEY_SIZE] = {0}; crypto_new_keypair(pk1, sk1); id_copy(pk2, pk1); EXPECT_TRUE(id_equal(pk1, pk2)); } } // namespace c-toxcore-0.2.13/toxencryptsave/000077500000000000000000000000001415350724400166035ustar00rootroot00000000000000c-toxcore-0.2.13/toxencryptsave/BUILD.bazel000066400000000000000000000020361415350724400204620ustar00rootroot00000000000000load("//tools:no_undefined.bzl", "cc_library") filegroup( name = "public_headers", srcs = ["toxencryptsave.h"], visibility = ["//c-toxcore:__pkg__"], ) cc_library( name = "defines", hdrs = ["defines.h"], visibility = ["//c-toxcore/toxcore:__pkg__"], ) cc_library( name = "toxencryptsave", srcs = ["toxencryptsave.c"], hdrs = ["toxencryptsave.h"], visibility = ["//c-toxcore:__subpackages__"], deps = [ ":defines", "//c-toxcore/toxcore:ccompat", "//c-toxcore/toxcore:crypto_core", ], ) cc_library( name = "monolith", hdrs = glob([ "*.c", "*.h", ]), visibility = ["//c-toxcore/other:__pkg__"], deps = ["//c-toxcore/toxcore:crypto_core"], ) CIMPLE_SRCS = glob( [ "*.c", "*.h", ], exclude = ["*.api.h"], ) sh_test( name = "cimple_test", size = "small", srcs = ["//hs-tokstyle/tools:check-cimple"], args = ["$(location %s)" % f for f in CIMPLE_SRCS], data = CIMPLE_SRCS, tags = ["haskell"], ) c-toxcore-0.2.13/toxencryptsave/Makefile.inc000066400000000000000000000047521415350724400210230ustar00rootroot00000000000000lib_LTLIBRARIES += libtoxencryptsave.la libtoxencryptsave_la_include_HEADERS = \ ../toxencryptsave/toxencryptsave.h libtoxencryptsave_la_includedir = $(includedir)/tox libtoxencryptsave_la_SOURCES = ../toxencryptsave/toxencryptsave.h \ ../toxencryptsave/toxencryptsave.c \ ../toxencryptsave/defines.h if WITH_NACL libtoxencryptsave_la_SOURCES += ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_pwhash_scryptsalsa208sha256.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_scrypt.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pbkdf2-sha256.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/runtime.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_scrypt-common.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/export.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pbkdf2-sha256.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/runtime.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/scrypt_platform.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sysendian.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sse/pwhash_scryptsalsa208sha256_sse.c endif libtoxencryptsave_la_CFLAGS = -I$(top_srcdir) \ -I$(top_srcdir)/toxcore \ $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) \ $(PTHREAD_CFLAGS) libtoxencryptsave_la_LDFLAGS = $(LT_LDFLAGS) \ $(EXTRA_LT_LDFLAGS) \ $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ $(MATH_LDFLAGS) \ $(RT_LIBS) \ $(WINSOCK2_LIBS) libtoxencryptsave_la_LIBADD = $(LIBSODIUM_LIBS) \ $(NACL_OBJECTS) \ $(NACL_LIBS) \ $(PTHREAD_LIBS) \ libtoxcore.la if SET_SO_VERSION EXTRA_libtoxencryptsave_la_DEPENDENCIES = ../so.version endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/000077500000000000000000000000001415350724400253105ustar00rootroot00000000000000crypto_pwhash_scryptsalsa208sha256.h000066400000000000000000000074511415350724400341160ustar00rootroot00000000000000c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256#ifndef C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_H #define C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef crypto_pwhash_scryptsalsa208sha256_H #define crypto_pwhash_scryptsalsa208sha256_H #include #include #include "export.h" #ifdef __cplusplus # if __GNUC__ # pragma GCC diagnostic ignored "-Wlong-long" # endif extern "C" { #endif #define crypto_pwhash_scryptsalsa208sha256_SALTBYTES 32U SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_saltbytes(void); #define crypto_pwhash_scryptsalsa208sha256_STRBYTES 102U SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_strbytes(void); #define crypto_pwhash_scryptsalsa208sha256_STRPREFIX "$7$" SODIUM_EXPORT const char *crypto_pwhash_scryptsalsa208sha256_strprefix(void); #define crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE 524288ULL SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_opslimit_interactive(void); #define crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE 16777216ULL SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_memlimit_interactive(void); #define crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE 33554432ULL SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive(void); #define crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE 1073741824ULL SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive(void); SODIUM_EXPORT int crypto_pwhash_scryptsalsa208sha256(unsigned char * const out, unsigned long long outlen, const char * const passwd, unsigned long long passwdlen, const unsigned char * const salt, unsigned long long opslimit, size_t memlimit); SODIUM_EXPORT int crypto_pwhash_scryptsalsa208sha256_str(char out[crypto_pwhash_scryptsalsa208sha256_STRBYTES], const char * const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit); SODIUM_EXPORT int crypto_pwhash_scryptsalsa208sha256_str_verify(const char str[crypto_pwhash_scryptsalsa208sha256_STRBYTES], const char * const passwd, unsigned long long passwdlen); SODIUM_EXPORT int crypto_pwhash_scryptsalsa208sha256_ll(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p, uint8_t * buf, size_t buflen); #ifdef __cplusplus } #endif /* Backward compatibility with version 0.5.0 */ #define crypto_pwhash_scryptxsalsa208sha256_SALTBYTES crypto_pwhash_scryptsalsa208sha256_SALTBYTES #define crypto_pwhash_scryptxsalsa208sha256_saltbytes crypto_pwhash_scryptsalsa208sha256_saltbytes #define crypto_pwhash_scryptxsalsa208sha256_STRBYTES crypto_pwhash_scryptsalsa208sha256_STRBYTES #define crypto_pwhash_scryptxsalsa208sha256_strbytes crypto_pwhash_scryptsalsa208sha256_strbytes #define crypto_pwhash_scryptxsalsa208sha256 crypto_pwhash_scryptsalsa208sha256 #define crypto_pwhash_scryptxsalsa208sha256_str crypto_pwhash_scryptsalsa208sha256_str #define crypto_pwhash_scryptxsalsa208sha256_str_verify crypto_pwhash_scryptsalsa208sha256_str_verify #endif #endif #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_scrypt-common.c000066400000000000000000000152301415350724400316670ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include "crypto_pwhash_scryptsalsa208sha256.h" #include "crypto_scrypt.h" #include "runtime.h" #include "../../toxcore/crypto_core.h" static const char * const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static uint8_t * encode64_uint32(uint8_t * dst, size_t dstlen, uint32_t src, uint32_t srcbits) { uint32_t bit; for (bit = 0; bit < srcbits; bit += 6) { if (dstlen < 1) { return NULL; } *dst++ = itoa64[src & 0x3f]; dstlen--; src >>= 6; } return dst; } static uint8_t * encode64(uint8_t * dst, size_t dstlen, const uint8_t * src, size_t srclen) { size_t i; for (i = 0; i < srclen; ) { uint8_t * dnext; uint32_t value = 0, bits = 0; do { value |= (uint32_t)src[i++] << bits; bits += 8; } while (bits < 24 && i < srclen); dnext = encode64_uint32(dst, dstlen, value, bits); if (!dnext) { return NULL; } dstlen -= dnext - dst; dst = dnext; } return dst; } static int decode64_one(uint32_t * dst, uint8_t src) { const char *ptr = strchr(itoa64, src); if (ptr) { *dst = ptr - itoa64; return 0; } *dst = 0; return -1; } static const uint8_t * decode64_uint32(uint32_t * dst, uint32_t dstbits, const uint8_t * src) { uint32_t bit; uint32_t value; value = 0; for (bit = 0; bit < dstbits; bit += 6) { uint32_t one; if (decode64_one(&one, *src)) { *dst = 0; return NULL; } src++; value |= one << bit; } *dst = value; return src; } uint8_t * escrypt_r(escrypt_local_t * local, const uint8_t * passwd, size_t passwdlen, const uint8_t * setting, uint8_t * buf, size_t buflen) { uint8_t hash[crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES]; escrypt_kdf_t escrypt_kdf; const uint8_t *src; const uint8_t *salt; uint8_t *dst; size_t prefixlen; size_t saltlen; size_t need; uint64_t N; uint32_t N_log2; uint32_t r; uint32_t p; if (setting[0] != '$' || setting[1] != '7' || setting[2] != '$') { return NULL; } src = setting + 3; if (decode64_one(&N_log2, *src)) { return NULL; } src++; N = (uint64_t)1 << N_log2; src = decode64_uint32(&r, 30, src); if (!src) { return NULL; } src = decode64_uint32(&p, 30, src); if (!src) { return NULL; } prefixlen = src - setting; salt = src; src = (uint8_t *) strrchr((const char *)salt, '$'); if (src) { saltlen = src - salt; } else { saltlen = strlen((const char *)salt); } need = prefixlen + saltlen + 1 + crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES_ENCODED + 1; if (need > buflen || need < saltlen) { return NULL; } #if defined(HAVE_EMMINTRIN_H) || defined(_MSC_VER) escrypt_kdf = sodium_runtime_has_sse2() ? escrypt_kdf_sse : escrypt_kdf_nosse; #else escrypt_kdf = escrypt_kdf_nosse; #endif if (escrypt_kdf(local, passwd, passwdlen, salt, saltlen, N, r, p, hash, sizeof(hash))) { return NULL; } dst = buf; memcpy(dst, setting, prefixlen + saltlen); dst += prefixlen + saltlen; *dst++ = '$'; dst = encode64(dst, buflen - (dst - buf), hash, sizeof(hash)); crypto_memzero(hash, sizeof hash); if (!dst || dst >= buf + buflen) { /* Can't happen */ return NULL; } *dst = 0; /* NUL termination */ return buf; } uint8_t * escrypt_gensalt_r(uint32_t N_log2, uint32_t r, uint32_t p, const uint8_t * src, size_t srclen, uint8_t * buf, size_t buflen) { uint8_t *dst; size_t prefixlen = (sizeof "$7$" - 1U) + (1U /* N_log2 */) + (5U /* r */) + (5U /* p */); size_t saltlen = BYTES2CHARS(srclen); size_t need; need = prefixlen + saltlen + 1; if (need > buflen || need < saltlen || saltlen < srclen) { return NULL; } if (N_log2 > 63 || ((uint64_t)r * (uint64_t)p >= (1U << 30))) { return NULL; } dst = buf; *dst++ = '$'; *dst++ = '7'; *dst++ = '$'; *dst++ = itoa64[N_log2]; dst = encode64_uint32(dst, buflen - (dst - buf), r, 30); if (!dst) { /* Can't happen */ return NULL; } dst = encode64_uint32(dst, buflen - (dst - buf), p, 30); if (!dst) { /* Can't happen */ return NULL; } dst = encode64(dst, buflen - (dst - buf), src, srclen); if (!dst || dst >= buf + buflen) { /* Can't happen */ return NULL; } *dst = 0; /* NUL termination */ return buf; } int crypto_pwhash_scryptsalsa208sha256_ll(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p, uint8_t * buf, size_t buflen) { escrypt_kdf_t escrypt_kdf; escrypt_local_t local; int retval; if (escrypt_init_local(&local)) { return -1; } #if defined(HAVE_EMMINTRIN_H) || defined(_MSC_VER) escrypt_kdf = sodium_runtime_has_sse2() ? escrypt_kdf_sse : escrypt_kdf_nosse; #else escrypt_kdf = escrypt_kdf_nosse; #endif retval = escrypt_kdf(&local, passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen); if (escrypt_free_local(&local)) { return -1; } return retval; } #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_scrypt.h000066400000000000000000000074221415350724400304120ustar00rootroot00000000000000#ifndef C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_CRYPTO_SCRYPT_H #define C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_CRYPTO_SCRYPT_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2009 Colin Percival * Copyright 2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file was originally written by Colin Percival as part of the Tarsnap * online backup system. */ #ifndef _CRYPTO_SCRYPT_H_ #define _CRYPTO_SCRYPT_H_ #include #define crypto_pwhash_scryptsalsa208sha256_STRPREFIXBYTES 14 #define crypto_pwhash_scryptsalsa208sha256_STRSETTINGBYTES 57 #define crypto_pwhash_scryptsalsa208sha256_STRSALTBYTES 32 #define crypto_pwhash_scryptsalsa208sha256_STRSALTBYTES_ENCODED 43 #define crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES 32 #define crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES_ENCODED 43 #define BYTES2CHARS(bytes) ((((bytes) * 8) + 5) / 6) typedef struct { void * base, * aligned; size_t size; } escrypt_region_t; typedef escrypt_region_t escrypt_local_t; extern int escrypt_init_local(escrypt_local_t * __local); extern int escrypt_free_local(escrypt_local_t * __local); extern void *alloc_region(escrypt_region_t * region, size_t size); extern int free_region(escrypt_region_t * region); typedef int (*escrypt_kdf_t)(escrypt_local_t * __local, const uint8_t * __passwd, size_t __passwdlen, const uint8_t * __salt, size_t __saltlen, uint64_t __N, uint32_t __r, uint32_t __p, uint8_t * __buf, size_t __buflen); extern int escrypt_kdf_nosse(escrypt_local_t * __local, const uint8_t * __passwd, size_t __passwdlen, const uint8_t * __salt, size_t __saltlen, uint64_t __N, uint32_t __r, uint32_t __p, uint8_t * __buf, size_t __buflen); extern int escrypt_kdf_sse(escrypt_local_t * __local, const uint8_t * __passwd, size_t __passwdlen, const uint8_t * __salt, size_t __saltlen, uint64_t __N, uint32_t __r, uint32_t __p, uint8_t * __buf, size_t __buflen); extern uint8_t * escrypt_r(escrypt_local_t * __local, const uint8_t * __passwd, size_t __passwdlen, const uint8_t * __setting, uint8_t * __buf, size_t __buflen); extern uint8_t * escrypt_gensalt_r( uint32_t __N_log2, uint32_t __r, uint32_t __p, const uint8_t * __src, size_t __srclen, uint8_t * __buf, size_t __buflen); #endif /* !_CRYPTO_SCRYPT_H_ */ #endif #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/export.h000066400000000000000000000016441415350724400270070ustar00rootroot00000000000000#ifndef C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_EXPORT_H #define C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_EXPORT_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef __SODIUM_EXPORT_H__ #define __SODIUM_EXPORT_H__ #ifndef __GNUC__ # ifdef __attribute__ # undef __attribute__ # endif # define __attribute__(a) #endif #ifdef SODIUM_STATIC # define SODIUM_EXPORT #else # if defined(_MSC_VER) # ifdef DLL_EXPORT # define SODIUM_EXPORT __declspec(dllexport) # else # define SODIUM_EXPORT __declspec(dllimport) # endif # else # if defined(__SUNPRO_C) # define SODIUM_EXPORT __attribute__ __global # elif defined(_MSG_VER) # define SODIUM_EXPORT extern __declspec(dllexport) # else # define SODIUM_EXPORT __attribute__ ((visibility ("default"))) # endif # endif #endif #endif #endif #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/nosse/000077500000000000000000000000001415350724400264375ustar00rootroot00000000000000pwhash_scryptsalsa208sha256_nosse.c000066400000000000000000000202501415350724400350370ustar00rootroot00000000000000c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/nosse#ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2009 Colin Percival * Copyright 2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file was originally written by Colin Percival as part of the Tarsnap * online backup system. */ #include #include #include #include #include #include "../pbkdf2-sha256.h" #include "../sysendian.h" #include "../crypto_scrypt.h" static inline void blkcpy(void * dest, const void * src, size_t len) { size_t * D = (size_t *) dest; const size_t * S = (const size_t *) src; size_t L = len / sizeof(size_t); size_t i; for (i = 0; i < L; i++) D[i] = S[i]; } static inline void blkxor(void * dest, const void * src, size_t len) { size_t * D = (size_t *) dest; const size_t * S = (const size_t *) src; size_t L = len / sizeof(size_t); size_t i; for (i = 0; i < L; i++) D[i] ^= S[i]; } /** * salsa20_8(B): * Apply the salsa20/8 core to the provided block. */ static void salsa20_8(uint32_t B[16]) { uint32_t x[16]; size_t i; blkcpy(x, B, 64); for (i = 0; i < 8; i += 2) { #define R(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) /* Operate on columns. */ x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); /* Operate on rows. */ x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); #undef R } for (i = 0; i < 16; i++) B[i] += x[i]; } /** * blockmix_salsa8(Bin, Bout, X, r): * Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r * bytes in length; the output Bout must also be the same size. The * temporary space X must be 64 bytes. */ static void blockmix_salsa8(const uint32_t * Bin, uint32_t * Bout, uint32_t * X, size_t r) { size_t i; /* 1: X <-- B_{2r - 1} */ blkcpy(X, &Bin[(2 * r - 1) * 16], 64); /* 2: for i = 0 to 2r - 1 do */ for (i = 0; i < 2 * r; i += 2) { /* 3: X <-- H(X \xor B_i) */ blkxor(X, &Bin[i * 16], 64); salsa20_8(X); /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ blkcpy(&Bout[i * 8], X, 64); /* 3: X <-- H(X \xor B_i) */ blkxor(X, &Bin[i * 16 + 16], 64); salsa20_8(X); /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ blkcpy(&Bout[i * 8 + r * 16], X, 64); } } /** * integerify(B, r): * Return the result of parsing B_{2r-1} as a little-endian integer. */ static inline uint64_t integerify(const void * B, size_t r) { const uint32_t * X = (const uint32_t *)((uintptr_t)(B) + (2 * r - 1) * 64); return (((uint64_t)(X[1]) << 32) + X[0]); } /** * smix(B, r, N, V, XY): * Compute B = SMix_r(B, N). The input B must be 128r bytes in length; * the temporary storage V must be 128rN bytes in length; the temporary * storage XY must be 256r + 64 bytes in length. The value N must be a * power of 2 greater than 1. The arrays B, V, and XY must be aligned to a * multiple of 64 bytes. */ static void smix(uint8_t * B, size_t r, uint64_t N, uint32_t * V, uint32_t * XY) { uint32_t * X = XY; uint32_t * Y = &XY[32 * r]; uint32_t * Z = &XY[64 * r]; uint64_t i; uint64_t j; size_t k; /* 1: X <-- B */ for (k = 0; k < 32 * r; k++) X[k] = le32dec(&B[4 * k]); /* 2: for i = 0 to N - 1 do */ for (i = 0; i < N; i += 2) { /* 3: V_i <-- X */ blkcpy(&V[i * (32 * r)], X, 128 * r); /* 4: X <-- H(X) */ blockmix_salsa8(X, Y, Z, r); /* 3: V_i <-- X */ blkcpy(&V[(i + 1) * (32 * r)], Y, 128 * r); /* 4: X <-- H(X) */ blockmix_salsa8(Y, X, Z, r); } /* 6: for i = 0 to N - 1 do */ for (i = 0; i < N; i += 2) { /* 7: j <-- Integerify(X) mod N */ j = integerify(X, r) & (N - 1); /* 8: X <-- H(X \xor V_j) */ blkxor(X, &V[j * (32 * r)], 128 * r); blockmix_salsa8(X, Y, Z, r); /* 7: j <-- Integerify(X) mod N */ j = integerify(Y, r) & (N - 1); /* 8: X <-- H(X \xor V_j) */ blkxor(Y, &V[j * (32 * r)], 128 * r); blockmix_salsa8(Y, X, Z, r); } /* 10: B' <-- X */ for (k = 0; k < 32 * r; k++) le32enc(&B[4 * k], X[k]); } /** * escrypt_kdf(local, passwd, passwdlen, salt, saltlen, * N, r, p, buf, buflen): * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, * p, buflen) and write the result into buf. The parameters r, p, and buflen * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32. The parameter N * must be a power of 2 greater than 1. * * Return 0 on success; or -1 on error. */ int escrypt_kdf_nosse(escrypt_local_t * local, const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t _r, uint32_t _p, uint8_t * buf, size_t buflen) { size_t B_size, V_size, XY_size, need; uint8_t * B; uint32_t * V, * XY; size_t r = _r, p = _p; uint32_t i; /* Sanity-check parameters. */ #if SIZE_MAX > UINT32_MAX if (buflen > (((uint64_t)(1) << 32) - 1) * 32) { errno = EFBIG; return -1; } #endif if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) { errno = EFBIG; return -1; } if (((N & (N - 1)) != 0) || (N < 2)) { errno = EINVAL; return -1; } if (r == 0 || p == 0) { errno = EINVAL; return -1; } if ((r > SIZE_MAX / 128 / p) || #if SIZE_MAX / 256 <= UINT32_MAX (r > SIZE_MAX / 256) || #endif (N > SIZE_MAX / 128 / r)) { errno = ENOMEM; return -1; } /* Allocate memory. */ B_size = (size_t)128 * r * p; V_size = (size_t)128 * r * N; need = B_size + V_size; if (need < V_size) { errno = ENOMEM; return -1; } XY_size = (size_t)256 * r + 64; need += XY_size; if (need < XY_size) { errno = ENOMEM; return -1; } if (local->size < need) { if (free_region(local)) return -1; if (!alloc_region(local, need)) return -1; } B = (uint8_t *)local->aligned; V = (uint32_t *)((uint8_t *)B + B_size); XY = (uint32_t *)((uint8_t *)V + V_size); /* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */ PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, B_size); /* 2: for i = 0 to p - 1 do */ for (i = 0; i < p; i++) { /* 3: B_i <-- MF(B_i, N) */ smix(&B[(size_t)128 * i * r], r, N, V, XY); } /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, buf, buflen); /* Success! */ return 0; } #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/note_to_maintainers.txt000066400000000000000000000011651415350724400321150ustar00rootroot00000000000000This folder is only meant for use with nacl, i.e. when sodium is unavailable. The files in this folder were mostly copied from https://github.com/jedisct1/libsodium/tree/0.7.0/src/libsodium/crypto_pwhash/scryptsalsa208sha256, with #ifdef VANILLA_NACL added around each of them as required for this module. export.h, utils.h, and runtime.h were copied from https://github.com/jedisct1/libsodium/tree/0.7.0/src/libsodium/include/sodium. utils.h was significantly truncated. utils.c and runtime.c were copied from https://github.com/jedisct1/libsodium/blob/0.7.0/src/libsodium/sodium. utils.c was also significantly truncated. c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pbkdf2-sha256.c000066400000000000000000000066541415350724400276450ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2005,2007,2009 Colin Percival * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include "pbkdf2-sha256.h" #include "sysendian.h" #include "../../toxcore/crypto_core.h" /** * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). */ void PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen) { uint8_t key[32] = {0}; size_t i; uint8_t salt_and_ivec[saltlen + 4]; uint8_t U[32]; uint8_t T[32]; uint64_t j; int k; size_t clen; if (passwdlen > 32) { /* For some reason libsodium allows 64byte keys meaning keys * between 32byte and 64bytes are not compatible with libsodium. toxencryptsave should only give 32byte passwds so this isn't an issue here.*/ crypto_hash_sha256(key, passwd, passwdlen); } else { memcpy(key, passwd, passwdlen); } memcpy(salt_and_ivec, salt, saltlen); for (i = 0; i * 32 < dkLen; i++) { be32enc(salt_and_ivec + saltlen, (uint32_t)(i + 1)); crypto_auth_hmacsha256(U, salt_and_ivec, sizeof(salt_and_ivec), key); memcpy(T, U, 32); for (j = 2; j <= c; j++) { crypto_auth_hmacsha256(U, U, 32, key); for (k = 0; k < 32; k++) { T[k] ^= U[k]; } } clen = dkLen - i * 32; if (clen > 32) { clen = 32; } memcpy(&buf[i * 32], T, clen); } crypto_memzero((void *) key, sizeof(key)); } #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pbkdf2-sha256.h000066400000000000000000000041761415350724400276470ustar00rootroot00000000000000#ifndef C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_PBKDF2_SHA256_H #define C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_PBKDF2_SHA256_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2005,2007,2009 Colin Percival * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #ifndef _SHA256_H_ #define _SHA256_H_ #include #include #include "crypto_auth_hmacsha256.h" /** * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). */ void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t, uint8_t *, size_t); #endif /* !_SHA256_H_ */ #endif #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c000066400000000000000000000150421415350724400325630ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #include #include #include #include #include //#include #include "crypto_pwhash_scryptsalsa208sha256.h" #include "crypto_scrypt.h" #include "../../toxcore/crypto_core.h" #define SETTING_SIZE(saltbytes) \ (sizeof "$7$" - 1U) + \ (1U /* N_log2 */) + (5U /* r */) + (5U /* p */) + BYTES2CHARS(saltbytes) static int pickparams(unsigned long long opslimit, const size_t memlimit, uint32_t * const N_log2, uint32_t * const p, uint32_t * const r) { unsigned long long maxN; unsigned long long maxrp; if (opslimit < 32768) { opslimit = 32768; } *r = 8; if (opslimit < memlimit / 32) { *p = 1; maxN = opslimit / (*r * 4); for (*N_log2 = 1; *N_log2 < 63; *N_log2 += 1) { if ((uint64_t)(1) << *N_log2 > maxN / 2) { break; } } } else { maxN = memlimit / (*r * 128); for (*N_log2 = 1; *N_log2 < 63; *N_log2 += 1) { if ((uint64_t) (1) << *N_log2 > maxN / 2) { break; } } maxrp = (opslimit / 4) / ((uint64_t) (1) << *N_log2); if (maxrp > 0x3fffffff) { maxrp = 0x3fffffff; } *p = (uint32_t) (maxrp) / *r; } return 0; } size_t crypto_pwhash_scryptsalsa208sha256_saltbytes(void) { return crypto_pwhash_scryptsalsa208sha256_SALTBYTES; } size_t crypto_pwhash_scryptsalsa208sha256_strbytes(void) { return crypto_pwhash_scryptsalsa208sha256_STRBYTES; } const char * crypto_pwhash_scryptsalsa208sha256_strprefix(void) { return crypto_pwhash_scryptsalsa208sha256_STRPREFIX; } size_t crypto_pwhash_scryptsalsa208sha256_opslimit_interactive(void) { return crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE; } size_t crypto_pwhash_scryptsalsa208sha256_memlimit_interactive(void) { return crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE; } size_t crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive(void) { return crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE; } size_t crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive(void) { return crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE; } int crypto_pwhash_scryptsalsa208sha256(unsigned char * const out, unsigned long long outlen, const char * const passwd, unsigned long long passwdlen, const unsigned char * const salt, unsigned long long opslimit, size_t memlimit) { //fprintf(stderr, "Doing that dirty thang!!!!\n"); uint32_t N_log2; uint32_t p; uint32_t r; memset(out, 0, outlen); if (passwdlen > SIZE_MAX || outlen > SIZE_MAX) { errno = EFBIG; return -1; } if (pickparams(opslimit, memlimit, &N_log2, &p, &r) != 0) { errno = EINVAL; return -1; } return crypto_pwhash_scryptsalsa208sha256_ll((const uint8_t *) passwd, (size_t) passwdlen, (const uint8_t *) salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES, (uint64_t) (1) << N_log2, r, p, out, (size_t) outlen); } int crypto_pwhash_scryptsalsa208sha256_str(char out[crypto_pwhash_scryptsalsa208sha256_STRBYTES], const char * const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit) { uint8_t salt[crypto_pwhash_scryptsalsa208sha256_STRSALTBYTES]; char setting[crypto_pwhash_scryptsalsa208sha256_STRSETTINGBYTES + 1U]; escrypt_local_t escrypt_local; uint32_t N_log2; uint32_t p; uint32_t r; memset(out, 0, crypto_pwhash_scryptsalsa208sha256_STRBYTES); if (passwdlen > SIZE_MAX) { errno = EFBIG; return -1; } if (pickparams(opslimit, memlimit, &N_log2, &p, &r) != 0) { errno = EINVAL; return -1; } random_bytes(salt, sizeof salt); if (escrypt_gensalt_r(N_log2, r, p, salt, sizeof salt, (uint8_t *) setting, sizeof setting) == NULL) { errno = EINVAL; return -1; } if (escrypt_init_local(&escrypt_local) != 0) { return -1; } if (escrypt_r(&escrypt_local, (const uint8_t *) passwd, (size_t) passwdlen, (const uint8_t *) setting, (uint8_t *) out, crypto_pwhash_scryptsalsa208sha256_STRBYTES) == NULL) { escrypt_free_local(&escrypt_local); errno = EINVAL; return -1; } escrypt_free_local(&escrypt_local); (void) sizeof (int[SETTING_SIZE(crypto_pwhash_scryptsalsa208sha256_STRSALTBYTES) == crypto_pwhash_scryptsalsa208sha256_STRSETTINGBYTES ? 1 : -1]); (void) sizeof (int[crypto_pwhash_scryptsalsa208sha256_STRSETTINGBYTES + 1U + crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES_ENCODED + 1U == crypto_pwhash_scryptsalsa208sha256_STRBYTES ? 1 : -1]); return 0; } int crypto_pwhash_scryptsalsa208sha256_str_verify(const char str[crypto_pwhash_scryptsalsa208sha256_STRBYTES], const char * const passwd, unsigned long long passwdlen) { char wanted[crypto_pwhash_scryptsalsa208sha256_STRBYTES]; escrypt_local_t escrypt_local; int ret = -1; if (memchr(str, 0, crypto_pwhash_scryptsalsa208sha256_STRBYTES) != &str[crypto_pwhash_scryptsalsa208sha256_STRBYTES - 1U]) { return -1; } if (escrypt_init_local(&escrypt_local) != 0) { return -1; } if (escrypt_r(&escrypt_local, (const uint8_t *) passwd, (size_t) passwdlen, (const uint8_t *) str, (uint8_t *) wanted, sizeof wanted) == NULL) { escrypt_free_local(&escrypt_local); return -1; } escrypt_free_local(&escrypt_local); ret = crypto_memcmp((const uint8_t *) wanted, (const uint8_t *) str, sizeof wanted); crypto_memzero(wanted, sizeof wanted); return ret; } #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/runtime.c000066400000000000000000000070741415350724400271470ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifdef HAVE_ANDROID_GETCPUFEATURES # include #endif #include "runtime.h" typedef struct CPUFeatures_ { int initialized; int has_neon; int has_sse2; int has_sse3; } CPUFeatures; static CPUFeatures _cpu_features; #ifdef HAVE_EMMINTRIN_H #define CPUID_SSE2 0x04000000 #endif #ifdef HAVE_PMMINTRIN_H #define CPUIDECX_SSE3 0x00000001 #endif static int _sodium_runtime_arm_cpu_features(CPUFeatures * const cpu_features) { #ifndef __arm__ cpu_features->has_neon = 0; return -1; #else # ifdef __APPLE__ # ifdef __ARM_NEON__ cpu_features->has_neon = 1; # else cpu_features->has_neon = 0; # endif # elif defined(HAVE_ANDROID_GETCPUFEATURES) && defined(ANDROID_CPU_ARM_FEATURE_NEON) cpu_features->has_neon = (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0x0; # else cpu_features->has_neon = 0; # endif return 0; #endif } static void _cpuid(unsigned int cpu_info[4U], const unsigned int cpu_info_type) { #ifdef _MSC_VER __cpuidex((int *) cpu_info, cpu_info_type, 0); #elif defined(HAVE_CPUID) cpu_info[0] = cpu_info[1] = cpu_info[2] = cpu_info[3] = 0; # ifdef __i386__ __asm__ __volatile__ ("pushfl; pushfl; " "popl %0; " "movl %0, %1; xorl %2, %0; " "pushl %0; " "popfl; pushfl; popl %0; popfl" : "=&r" (cpu_info[0]), "=&r" (cpu_info[1]) : "i" (0x200000)); if (((cpu_info[0] ^ cpu_info[1]) & 0x200000) == 0x0) { return; } # endif # ifdef __i386__ __asm__ __volatile__ ("xchgl %%ebx, %k1; cpuid; xchgl %%ebx, %k1" : "=a" (cpu_info[0]), "=&r" (cpu_info[1]), "=c" (cpu_info[2]), "=d" (cpu_info[3]) : "0" (cpu_info_type), "2" (0U)); # elif defined(__x86_64__) __asm__ __volatile__ ("xchgq %%rbx, %q1; cpuid; xchgq %%rbx, %q1" : "=a" (cpu_info[0]), "=&r" (cpu_info[1]), "=c" (cpu_info[2]), "=d" (cpu_info[3]) : "0" (cpu_info_type), "2" (0U)); # else __asm__ __volatile__ ("cpuid" : "=a" (cpu_info[0]), "=b" (cpu_info[1]), "=c" (cpu_info[2]), "=d" (cpu_info[3]) : "0" (cpu_info_type), "2" (0U)); # endif #else cpu_info[0] = cpu_info[1] = cpu_info[2] = cpu_info[3] = 0; #endif } static int _sodium_runtime_intel_cpu_features(CPUFeatures * const cpu_features) { unsigned int cpu_info[4]; unsigned int id; _cpuid(cpu_info, 0x0); if ((id = cpu_info[0]) == 0U) { return -1; } _cpuid(cpu_info, 0x00000001); #ifndef HAVE_EMMINTRIN_H cpu_features->has_sse2 = 0; #else cpu_features->has_sse2 = ((cpu_info[3] & CPUID_SSE2) != 0x0); #endif #ifndef HAVE_PMMINTRIN_H cpu_features->has_sse3 = 0; #else cpu_features->has_sse3 = ((cpu_info[2] & CPUIDECX_SSE3) != 0x0); #endif return 0; } int sodium_runtime_get_cpu_features(void) { int ret = -1; ret &= _sodium_runtime_arm_cpu_features(&_cpu_features); ret &= _sodium_runtime_intel_cpu_features(&_cpu_features); _cpu_features.initialized = 1; return ret; } int sodium_runtime_has_neon(void) { return _cpu_features.has_neon; } int sodium_runtime_has_sse2(void) { return _cpu_features.has_sse2; } int sodium_runtime_has_sse3(void) { return _cpu_features.has_sse3; } #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/runtime.h000066400000000000000000000012341415350724400271440ustar00rootroot00000000000000#ifndef C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_RUNTIME_H #define C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_RUNTIME_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef __SODIUM_RUNTIME_H__ #define __SODIUM_RUNTIME_H__ 1 #include "export.h" #ifdef __cplusplus extern "C" { #endif SODIUM_EXPORT int sodium_runtime_get_cpu_features(void); SODIUM_EXPORT int sodium_runtime_has_neon(void); SODIUM_EXPORT int sodium_runtime_has_sse2(void); SODIUM_EXPORT int sodium_runtime_has_sse3(void); #ifdef __cplusplus } #endif #endif #endif #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/scrypt_platform.c000066400000000000000000000050301415350724400307020ustar00rootroot00000000000000#ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_SYS_MMAN_H # include #endif #include #include #include "crypto_scrypt.h" #include "runtime.h" #if !defined(MAP_ANON) && defined(MAP_ANONYMOUS) # define MAP_ANON MAP_ANONYMOUS #endif void * alloc_region(escrypt_region_t * region, size_t size) { uint8_t * base, * aligned; #ifdef MAP_ANON if ((base = (uint8_t *) mmap(NULL, size, PROT_READ | PROT_WRITE, #ifdef MAP_NOCORE MAP_ANON | MAP_PRIVATE | MAP_NOCORE, #else MAP_ANON | MAP_PRIVATE, #endif -1, 0)) == MAP_FAILED) base = NULL; aligned = base; #elif defined(HAVE_POSIX_MEMALIGN) if ((errno = posix_memalign((void **) &base, 64, size)) != 0) base = NULL; aligned = base; #else base = aligned = NULL; if (size + 63 < size) errno = ENOMEM; else if ((base = (uint8_t *) malloc(size + 63)) != NULL) { aligned = base + 63; aligned -= (uintptr_t)aligned & 63; } #endif region->base = base; region->aligned = aligned; region->size = base ? size : 0; return aligned; } static inline void init_region(escrypt_region_t * region) { region->base = region->aligned = NULL; region->size = 0; } int free_region(escrypt_region_t * region) { if (region->base) { #ifdef MAP_ANON if (munmap(region->base, region->size)) return -1; #else free(region->base); #endif } init_region(region); return 0; } int escrypt_init_local(escrypt_local_t * local) { init_region(local); return 0; } int escrypt_free_local(escrypt_local_t * local) { return free_region(local); } #endif c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sse/000077500000000000000000000000001415350724400261025ustar00rootroot00000000000000pwhash_scryptsalsa208sha256_sse.c000066400000000000000000000246201415350724400341520ustar00rootroot00000000000000c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sse#ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2009 Colin Percival * Copyright 2012,2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file was originally written by Colin Percival as part of the Tarsnap * online backup system. */ #if defined(HAVE_EMMINTRIN_H) || defined(_MSC_VER) #if __GNUC__ # pragma GCC target("sse2") #endif #include #if defined(__XOP__) && defined(DISABLED) # include #endif #include #include #include #include #include #include "../pbkdf2-sha256.h" #include "../sysendian.h" #include "../crypto_scrypt.h" #if defined(__XOP__) && defined(DISABLED) #define ARX(out, in1, in2, s) \ out = _mm_xor_si128(out, _mm_roti_epi32(_mm_add_epi32(in1, in2), s)); #else #define ARX(out, in1, in2, s) \ { \ __m128i T = _mm_add_epi32(in1, in2); \ out = _mm_xor_si128(out, _mm_slli_epi32(T, s)); \ out = _mm_xor_si128(out, _mm_srli_epi32(T, 32-s)); \ } #endif #define SALSA20_2ROUNDS \ /* Operate on "columns". */ \ ARX(X1, X0, X3, 7) \ ARX(X2, X1, X0, 9) \ ARX(X3, X2, X1, 13) \ ARX(X0, X3, X2, 18) \ \ /* Rearrange data. */ \ X1 = _mm_shuffle_epi32(X1, 0x93); \ X2 = _mm_shuffle_epi32(X2, 0x4E); \ X3 = _mm_shuffle_epi32(X3, 0x39); \ \ /* Operate on "rows". */ \ ARX(X3, X0, X1, 7) \ ARX(X2, X3, X0, 9) \ ARX(X1, X2, X3, 13) \ ARX(X0, X1, X2, 18) \ \ /* Rearrange data. */ \ X1 = _mm_shuffle_epi32(X1, 0x39); \ X2 = _mm_shuffle_epi32(X2, 0x4E); \ X3 = _mm_shuffle_epi32(X3, 0x93); /** * Apply the salsa20/8 core to the block provided in (X0 ... X3) ^ (Z0 ... Z3). */ #define SALSA20_8_XOR(in, out) \ { \ __m128i Y0 = X0 = _mm_xor_si128(X0, (in)[0]); \ __m128i Y1 = X1 = _mm_xor_si128(X1, (in)[1]); \ __m128i Y2 = X2 = _mm_xor_si128(X2, (in)[2]); \ __m128i Y3 = X3 = _mm_xor_si128(X3, (in)[3]); \ SALSA20_2ROUNDS \ SALSA20_2ROUNDS \ SALSA20_2ROUNDS \ SALSA20_2ROUNDS \ (out)[0] = X0 = _mm_add_epi32(X0, Y0); \ (out)[1] = X1 = _mm_add_epi32(X1, Y1); \ (out)[2] = X2 = _mm_add_epi32(X2, Y2); \ (out)[3] = X3 = _mm_add_epi32(X3, Y3); \ } /** * blockmix_salsa8(Bin, Bout, r): * Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r * bytes in length; the output Bout must also be the same size. */ static inline void blockmix_salsa8(const __m128i * Bin, __m128i * Bout, size_t r) { __m128i X0, X1, X2, X3; size_t i; /* 1: X <-- B_{2r - 1} */ X0 = Bin[8 * r - 4]; X1 = Bin[8 * r - 3]; X2 = Bin[8 * r - 2]; X3 = Bin[8 * r - 1]; /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ SALSA20_8_XOR(Bin, Bout) /* 2: for i = 0 to 2r - 1 do */ r--; for (i = 0; i < r;) { /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ SALSA20_8_XOR(&Bin[i * 8 + 4], &Bout[(r + i) * 4 + 4]) i++; /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ SALSA20_8_XOR(&Bin[i * 8], &Bout[i * 4]) } /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ SALSA20_8_XOR(&Bin[i * 8 + 4], &Bout[(r + i) * 4 + 4]) } #define XOR4(in) \ X0 = _mm_xor_si128(X0, (in)[0]); \ X1 = _mm_xor_si128(X1, (in)[1]); \ X2 = _mm_xor_si128(X2, (in)[2]); \ X3 = _mm_xor_si128(X3, (in)[3]); #define XOR4_2(in1, in2) \ X0 = _mm_xor_si128((in1)[0], (in2)[0]); \ X1 = _mm_xor_si128((in1)[1], (in2)[1]); \ X2 = _mm_xor_si128((in1)[2], (in2)[2]); \ X3 = _mm_xor_si128((in1)[3], (in2)[3]); static inline uint32_t blockmix_salsa8_xor(const __m128i * Bin1, const __m128i * Bin2, __m128i * Bout, size_t r) { __m128i X0, X1, X2, X3; size_t i; /* 1: X <-- B_{2r - 1} */ XOR4_2(&Bin1[8 * r - 4], &Bin2[8 * r - 4]) /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ XOR4(Bin1) SALSA20_8_XOR(Bin2, Bout) /* 2: for i = 0 to 2r - 1 do */ r--; for (i = 0; i < r;) { /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ XOR4(&Bin1[i * 8 + 4]) SALSA20_8_XOR(&Bin2[i * 8 + 4], &Bout[(r + i) * 4 + 4]) i++; /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ XOR4(&Bin1[i * 8]) SALSA20_8_XOR(&Bin2[i * 8], &Bout[i * 4]) } /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ XOR4(&Bin1[i * 8 + 4]) SALSA20_8_XOR(&Bin2[i * 8 + 4], &Bout[(r + i) * 4 + 4]) return _mm_cvtsi128_si32(X0); } #undef ARX #undef SALSA20_2ROUNDS #undef SALSA20_8_XOR #undef XOR4 #undef XOR4_2 /** * integerify(B, r): * Return the result of parsing B_{2r-1} as a little-endian integer. */ static inline uint32_t integerify(const void * B, size_t r) { return *(const uint32_t *)((uintptr_t)(B) + (2 * r - 1) * 64); } /** * smix(B, r, N, V, XY): * Compute B = SMix_r(B, N). The input B must be 128r bytes in length; * the temporary storage V must be 128rN bytes in length; the temporary * storage XY must be 256r + 64 bytes in length. The value N must be a * power of 2 greater than 1. The arrays B, V, and XY must be aligned to a * multiple of 64 bytes. */ static void smix(uint8_t * B, size_t r, uint32_t N, void * V, void * XY) { size_t s = 128 * r; __m128i * X = (__m128i *) V, * Y; uint32_t * X32 = (uint32_t *) V; uint32_t i, j; size_t k; /* 1: X <-- B */ /* 3: V_i <-- X */ for (k = 0; k < 2 * r; k++) { for (i = 0; i < 16; i++) { X32[k * 16 + i] = le32dec(&B[(k * 16 + (i * 5 % 16)) * 4]); } } /* 2: for i = 0 to N - 1 do */ for (i = 1; i < N - 1; i += 2) { /* 4: X <-- H(X) */ /* 3: V_i <-- X */ Y = (__m128i *)((uintptr_t)(V) + i * s); blockmix_salsa8(X, Y, r); /* 4: X <-- H(X) */ /* 3: V_i <-- X */ X = (__m128i *)((uintptr_t)(V) + (i + 1) * s); blockmix_salsa8(Y, X, r); } /* 4: X <-- H(X) */ /* 3: V_i <-- X */ Y = (__m128i *)((uintptr_t)(V) + i * s); blockmix_salsa8(X, Y, r); /* 4: X <-- H(X) */ /* 3: V_i <-- X */ X = (__m128i *) XY; blockmix_salsa8(Y, X, r); X32 = (uint32_t *) XY; Y = (__m128i *)((uintptr_t)(XY) + s); /* 7: j <-- Integerify(X) mod N */ j = integerify(X, r) & (N - 1); /* 6: for i = 0 to N - 1 do */ for (i = 0; i < N; i += 2) { __m128i * V_j = (__m128i *)((uintptr_t)(V) + j * s); /* 8: X <-- H(X \xor V_j) */ /* 7: j <-- Integerify(X) mod N */ j = blockmix_salsa8_xor(X, V_j, Y, r) & (N - 1); V_j = (__m128i *)((uintptr_t)(V) + j * s); /* 8: X <-- H(X \xor V_j) */ /* 7: j <-- Integerify(X) mod N */ j = blockmix_salsa8_xor(Y, V_j, X, r) & (N - 1); } /* 10: B' <-- X */ for (k = 0; k < 2 * r; k++) { for (i = 0; i < 16; i++) { le32enc(&B[(k * 16 + (i * 5 % 16)) * 4], X32[k * 16 + i]); } } } /** * escrypt_kdf(local, passwd, passwdlen, salt, saltlen, * N, r, p, buf, buflen): * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, * p, buflen) and write the result into buf. The parameters r, p, and buflen * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32. The parameter N * must be a power of 2 greater than 1. * * Return 0 on success; or -1 on error. */ int escrypt_kdf_sse(escrypt_local_t * local, const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t _r, uint32_t _p, uint8_t * buf, size_t buflen) { size_t B_size, V_size, XY_size, need; uint8_t * B; uint32_t * V, * XY; size_t r = _r, p = _p; uint32_t i; /* Sanity-check parameters. */ #if SIZE_MAX > UINT32_MAX if (buflen > (((uint64_t)(1) << 32) - 1) * 32) { errno = EFBIG; return -1; } #endif if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) { errno = EFBIG; return -1; } if (N > UINT32_MAX) { errno = EFBIG; return -1; } if (((N & (N - 1)) != 0) || (N < 2)) { errno = EINVAL; return -1; } if (r == 0 || p == 0) { errno = EINVAL; return -1; } if ((r > SIZE_MAX / 128 / p) || #if SIZE_MAX / 256 <= UINT32_MAX (r > SIZE_MAX / 256) || #endif (N > SIZE_MAX / 128 / r)) { errno = ENOMEM; return -1; } /* Allocate memory. */ B_size = (size_t)128 * r * p; V_size = (size_t)128 * r * N; need = B_size + V_size; if (need < V_size) { errno = ENOMEM; return -1; } XY_size = (size_t)256 * r + 64; need += XY_size; if (need < XY_size) { errno = ENOMEM; return -1; } if (local->size < need) { if (free_region(local)) return -1; if (!alloc_region(local, need)) return -1; } B = (uint8_t *)local->aligned; V = (uint32_t *)((uint8_t *)B + B_size); XY = (uint32_t *)((uint8_t *)V + V_size); /* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */ PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, B_size); /* 2: for i = 0 to p - 1 do */ for (i = 0; i < p; i++) { /* 3: B_i <-- MF(B_i, N) */ smix(&B[(size_t)128 * i * r], r, N, V, XY); } /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, buf, buflen); /* Success! */ return 0; } #endif #endif /* ISO C requires a translation unit to contain at least one declaration */ typedef int non_empty_tu_decl; c-toxcore-0.2.13/toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sysendian.h000066400000000000000000000064571415350724400274720ustar00rootroot00000000000000#ifndef C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SYSENDIAN_H #define C_TOXCORE_TOXENCRYPTSAVE_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SYSENDIAN_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef _SYSENDIAN_H_ #define _SYSENDIAN_H_ #include /* Avoid namespace collisions with BSD . */ #define be16dec scrypt_be16dec #define be16enc scrypt_be16enc #define be32dec scrypt_be32dec #define be32enc scrypt_be32enc #define be64dec scrypt_be64dec #define be64enc scrypt_be64enc #define le16dec scrypt_le16dec #define le16enc scrypt_le16enc #define le32dec scrypt_le32dec #define le32enc scrypt_le32enc #define le64dec scrypt_le64dec #define le64enc scrypt_le64enc static inline uint16_t be16dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint16_t)(p[1]) + ((uint16_t)(p[0]) << 8)); } static inline void be16enc(void *pp, uint16_t x) { uint8_t * p = (uint8_t *)pp; p[1] = x & 0xff; p[0] = (x >> 8) & 0xff; } static inline uint32_t be32dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24)); } static inline void be32enc(void *pp, uint32_t x) { uint8_t * p = (uint8_t *)pp; p[3] = x & 0xff; p[2] = (x >> 8) & 0xff; p[1] = (x >> 16) & 0xff; p[0] = (x >> 24) & 0xff; } static inline uint64_t be64dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint64_t)(p[7]) + ((uint64_t)(p[6]) << 8) + ((uint64_t)(p[5]) << 16) + ((uint64_t)(p[4]) << 24) + ((uint64_t)(p[3]) << 32) + ((uint64_t)(p[2]) << 40) + ((uint64_t)(p[1]) << 48) + ((uint64_t)(p[0]) << 56)); } static inline void be64enc(void *pp, uint64_t x) { uint8_t * p = (uint8_t *)pp; p[7] = x & 0xff; p[6] = (x >> 8) & 0xff; p[5] = (x >> 16) & 0xff; p[4] = (x >> 24) & 0xff; p[3] = (x >> 32) & 0xff; p[2] = (x >> 40) & 0xff; p[1] = (x >> 48) & 0xff; p[0] = (x >> 56) & 0xff; } static inline uint16_t le16dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint16_t)(p[0]) + ((uint16_t)(p[1]) << 8)); } static inline void le16enc(void *pp, uint16_t x) { uint8_t * p = (uint8_t *)pp; p[0] = x & 0xff; p[1] = (x >> 8) & 0xff; } static inline uint32_t le32dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) + ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24)); } static inline void le32enc(void *pp, uint32_t x) { uint8_t * p = (uint8_t *)pp; p[0] = x & 0xff; p[1] = (x >> 8) & 0xff; p[2] = (x >> 16) & 0xff; p[3] = (x >> 24) & 0xff; } static inline uint64_t le64dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) + ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) + ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) + ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56)); } static inline void le64enc(void *pp, uint64_t x) { uint8_t * p = (uint8_t *)pp; p[0] = x & 0xff; p[1] = (x >> 8) & 0xff; p[2] = (x >> 16) & 0xff; p[3] = (x >> 24) & 0xff; p[4] = (x >> 32) & 0xff; p[5] = (x >> 40) & 0xff; p[6] = (x >> 48) & 0xff; p[7] = (x >> 56) & 0xff; } #endif /* !_SYSENDIAN_H_ */ #endif #endif c-toxcore-0.2.13/toxencryptsave/defines.h000066400000000000000000000003031415350724400203650ustar00rootroot00000000000000#ifndef C_TOXCORE_TOXENCRYPTSAVE_DEFINES_H #define C_TOXCORE_TOXENCRYPTSAVE_DEFINES_H #define TOX_ENC_SAVE_MAGIC_NUMBER ((const uint8_t *)"toxEsave") #define TOX_ENC_SAVE_MAGIC_LENGTH 8 #endif c-toxcore-0.2.13/toxencryptsave/toxencryptsave.api.h000066400000000000000000000236471415350724400226360ustar00rootroot00000000000000%{ /* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2016 Tox Developers. */ /* * Batch encryption functions. */ #ifndef C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H #define C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H //!TOKSTYLE- #ifdef __cplusplus extern "C" { #endif #include #include #include %} /******************************************************************************* * * This module is organized into two parts. * * 1. A simple API operating on plain text/cipher text data and a password to * encrypt or decrypt it. * 2. A more advanced API that splits key derivation and encryption into two * separate function calls. * * The first part is implemented in terms of the second part and simply calls * the separate functions in sequence. Since key derivation is very expensive * compared to the actual encryption, clients that do a lot of crypto should * prefer the advanced API and reuse pass-key objects. * * To use the second part, first derive an encryption key from a password with * ${tox.pass_Key.derive}, then use the derived key to encrypt the data. * * The encrypted data is prepended with a magic number, to aid validity * checking (no guarantees are made of course). Any data to be decrypted must * start with the magic number. * * Clients should consider alerting their users that, unlike plain data, if * even one bit becomes corrupted, the data will be entirely unrecoverable. * Ditto if they forget their password, there is no way to recover the data. * *******************************************************************************/ class tox { /** * The size of the salt part of a pass-key. */ const PASS_SALT_LENGTH = 32; /** * The size of the key part of a pass-key. */ const PASS_KEY_LENGTH = 32; /** * The amount of additional data required to store any encrypted byte array. * Encrypting an array of N bytes requires N + $PASS_ENCRYPTION_EXTRA_LENGTH * bytes in the encrypted byte array. */ const PASS_ENCRYPTION_EXTRA_LENGTH = 80; error for key_derivation { NULL, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. */ FAILED, } error for encryption { NULL, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. The functions accepting keys * do not produce this error. */ KEY_DERIVATION_FAILED, /** * The encryption itself failed. */ FAILED, } error for decryption { NULL, /** * The input data was shorter than $PASS_ENCRYPTION_EXTRA_LENGTH bytes */ INVALID_LENGTH, /** * The input data is missing the magic number (i.e. wasn't created by this * module, or is corrupted). */ BAD_FORMAT, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. The functions accepting keys * do not produce this error. */ KEY_DERIVATION_FAILED, /** * The encrypted byte array could not be decrypted. Either the data was * corrupted or the password/key was incorrect. */ FAILED, } /******************************************************************************* * * BEGIN PART 1 * * The simple API is presented first. If your code spends too much time using * these functions, consider using the advanced functions instead and caching * the generated pass-key. * *******************************************************************************/ /** * Encrypts the given data with the given passphrase. * * The output array must be at least `plaintext_len + $PASS_ENCRYPTION_EXTRA_LENGTH` * bytes long. This delegates to ${pass_Key.derive} and * ${pass_Key.encrypt}. * * @param plaintext A byte array of length `plaintext_len`. * @param plaintext_len The length of the plain text array. Bigger than 0. * @param passphrase The user-provided password. Can be empty. * @param passphrase_len The length of the password. * @param ciphertext The cipher text array to write the encrypted data to. * * @return true on success. */ static bool pass_encrypt(const uint8_t[plaintext_len] plaintext, const uint8_t[passphrase_len] passphrase, uint8_t *ciphertext) with error for encryption; /** * Decrypts the given data with the given passphrase. * * The output array must be at least `ciphertext_len - $PASS_ENCRYPTION_EXTRA_LENGTH` * bytes long. This delegates to ${pass_Key.decrypt}. * * @param ciphertext A byte array of length `ciphertext_len`. * @param ciphertext_len The length of the cipher text array. At least $PASS_ENCRYPTION_EXTRA_LENGTH. * @param passphrase The user-provided password. Can be empty. * @param passphrase_len The length of the password. * @param plaintext The plain text array to write the decrypted data to. * * @return true on success. */ static bool pass_decrypt(const uint8_t[ciphertext_len] ciphertext, const uint8_t[passphrase_len] passphrase, uint8_t *plaintext) with error for decryption; /******************************************************************************* * * BEGIN PART 2 * * And now part 2, which does the actual encryption, and can be used to write * less CPU intensive client code than part one. * *******************************************************************************/ class pass_Key { /** * This type represents a pass-key. * * A pass-key and a password are two different concepts: a password is given * by the user in plain text. A pass-key is the generated symmetric key used * for encryption and decryption. It is derived from a salt and the user- * provided password. * * The $this structure is hidden in the implementation. It can be created * using $derive or $derive_with_salt and must be deallocated using $free. */ struct this; /** * Deallocate a $this. This function behaves like free(), so NULL is an * acceptable argument value. */ void free(); /** * Generates a secret symmetric key from the given passphrase. * * Be sure to not compromise the key! Only keep it in memory, do not write * it to disk. * * Note that this function is not deterministic; to derive the same key from * a password, you also must know the random salt that was used. A * deterministic version of this function is $derive_with_salt. * * @param passphrase The user-provided password. Can be empty. * @param passphrase_len The length of the password. * * @return true on success. */ static this derive(const uint8_t[passphrase_len] passphrase) with error for key_derivation; /** * Same as above, except use the given salt for deterministic key derivation. * * @param passphrase The user-provided password. Can be empty. * @param passphrase_len The length of the password. * @param salt An array of at least $PASS_SALT_LENGTH bytes. * * @return true on success. */ static this derive_with_salt(const uint8_t[passphrase_len] passphrase, const uint8_t[PASS_SALT_LENGTH] salt) with error for key_derivation; /** * Encrypt a plain text with a key produced by $derive or $derive_with_salt. * * The output array must be at least `plaintext_len + $PASS_ENCRYPTION_EXTRA_LENGTH` * bytes long. * * @param plaintext A byte array of length `plaintext_len`. * @param plaintext_len The length of the plain text array. Bigger than 0. * @param ciphertext The cipher text array to write the encrypted data to. * * @return true on success. */ const bool encrypt(const uint8_t[plaintext_len] plaintext, uint8_t *ciphertext) with error for encryption; /** * This is the inverse of $encrypt, also using only keys produced by * $derive or $derive_with_salt. * * @param ciphertext A byte array of length `ciphertext_len`. * @param ciphertext_len The length of the cipher text array. At least $PASS_ENCRYPTION_EXTRA_LENGTH. * @param plaintext The plain text array to write the decrypted data to. * * @return true on success. */ const bool decrypt(const uint8_t[ciphertext_len] ciphertext, uint8_t *plaintext) with error for decryption; } /** * Retrieves the salt used to encrypt the given data. * * The retrieved salt can then be passed to ${pass_Key.derive_with_salt} to * produce the same key as was previously used. Any data encrypted with this * module can be used as input. * * The cipher text must be at least $PASS_ENCRYPTION_EXTRA_LENGTH bytes in length. * The salt must be $PASS_SALT_LENGTH bytes in length. * If the passed byte arrays are smaller than required, the behaviour is * undefined. * * If the cipher text pointer or the salt is NULL, this function returns false. * * Success does not say anything about the validity of the data, only that * data of the appropriate size was copied. * * @return true on success. */ static bool get_salt(const uint8_t *ciphertext, uint8_t[PASS_SALT_LENGTH] salt) { NULL, /** * The input data is missing the magic number (i.e. wasn't created by this * module, or is corrupted). */ BAD_FORMAT, } /** * Determines whether or not the given data is encrypted by this module. * * It does this check by verifying that the magic number is the one put in * place by the encryption functions. * * The data must be at least $PASS_ENCRYPTION_EXTRA_LENGTH bytes in length. * If the passed byte array is smaller than required, the behaviour is * undefined. * * If the data pointer is NULL, the behaviour is undefined * * @return true if the data is encrypted by this module. */ static bool is_data_encrypted(const uint8_t *data); } %{ #ifdef __cplusplus } #endif typedef TOX_ERR_KEY_DERIVATION Tox_Err_Key_Derivation; typedef TOX_ERR_ENCRYPTION Tox_Err_Encryption; typedef TOX_ERR_DECRYPTION Tox_Err_Decryption; typedef TOX_ERR_GET_SALT Tox_Err_Get_Salt; //!TOKSTYLE+ #endif // C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H %} c-toxcore-0.2.13/toxencryptsave/toxencryptsave.c000066400000000000000000000265611415350724400220570ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013 Tox project. */ /* * Batch encryption functions. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "../toxcore/ccompat.h" #include "../toxcore/crypto_core.h" #include "defines.h" #include "toxencryptsave.h" #define SET_ERROR_PARAMETER(param, x) do { if (param) { *param = x; } } while (0) #ifdef VANILLA_NACL #include #include #include "crypto_pwhash_scryptsalsa208sha256/crypto_pwhash_scryptsalsa208sha256.h" #define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES) #else #include #endif #include #include //!TOKSTYLE- static_assert(TOX_PASS_SALT_LENGTH == crypto_pwhash_scryptsalsa208sha256_SALTBYTES, "TOX_PASS_SALT_LENGTH is assumed to be equal to crypto_pwhash_scryptsalsa208sha256_SALTBYTES"); static_assert(TOX_PASS_KEY_LENGTH == CRYPTO_SHARED_KEY_SIZE, "TOX_PASS_KEY_LENGTH is assumed to be equal to CRYPTO_SHARED_KEY_SIZE"); static_assert(TOX_PASS_ENCRYPTION_EXTRA_LENGTH == (crypto_box_MACBYTES + crypto_box_NONCEBYTES + crypto_pwhash_scryptsalsa208sha256_SALTBYTES + TOX_ENC_SAVE_MAGIC_LENGTH), "TOX_PASS_ENCRYPTION_EXTRA_LENGTH is assumed to be equal to (crypto_box_MACBYTES + crypto_box_NONCEBYTES + crypto_pwhash_scryptsalsa208sha256_SALTBYTES + TOX_ENC_SAVE_MAGIC_LENGTH)"); //!TOKSTYLE+ uint32_t tox_pass_salt_length(void) { return TOX_PASS_SALT_LENGTH; } uint32_t tox_pass_key_length(void) { return TOX_PASS_KEY_LENGTH; } uint32_t tox_pass_encryption_extra_length(void) { return TOX_PASS_ENCRYPTION_EXTRA_LENGTH; } struct Tox_Pass_Key { uint8_t salt[TOX_PASS_SALT_LENGTH]; uint8_t key[TOX_PASS_KEY_LENGTH]; }; void tox_pass_key_free(Tox_Pass_Key *pass_key) { free(pass_key); } /* Clients should consider alerting their users that, unlike plain data, if even one bit * becomes corrupted, the data will be entirely unrecoverable. * Ditto if they forget their password, there is no way to recover the data. */ /* This retrieves the salt used to encrypt the given data, which can then be passed to * tox_pass_key_derive_with_salt to produce the same key as was previously used. Any encrpyted * data with this module can be used as input. * * returns true if magic number matches * success does not say anything about the validity of the data, only that data of * the appropriate size was copied */ bool tox_get_salt(const uint8_t *data, uint8_t *salt, Tox_Err_Get_Salt *error) { if (!data || !salt) { SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_NULL); return false; } if (memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_BAD_FORMAT); return false; } data += TOX_ENC_SAVE_MAGIC_LENGTH; memcpy(salt, data, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_OK); return true; } /* Generates a secret symmetric key from the given passphrase. out_key must be at least * TOX_PASS_KEY_LENGTH bytes long. * Be sure to not compromise the key! Only keep it in memory, do not write to disk. * The password is zeroed after key derivation. * The key should only be used with the other functions in this module, as it * includes a salt. * Note that this function is not deterministic; to derive the same key from a * password, you also must know the random salt that was used. See below. * * returns true on success */ Tox_Pass_Key *tox_pass_key_derive(const uint8_t *passphrase, size_t pplength, Tox_Err_Key_Derivation *error) { uint8_t salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES]; random_bytes(salt, sizeof(salt)); return tox_pass_key_derive_with_salt(passphrase, pplength, salt, error); } /* Same as above, except with use the given salt for deterministic key derivation. * The salt must be TOX_PASS_SALT_LENGTH bytes in length. */ Tox_Pass_Key *tox_pass_key_derive_with_salt(const uint8_t *passphrase, size_t pplength, const uint8_t *salt, Tox_Err_Key_Derivation *error) { if (!salt || (!passphrase && pplength != 0)) { SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_NULL); return nullptr; } uint8_t passkey[crypto_hash_sha256_BYTES]; crypto_hash_sha256(passkey, passphrase, pplength); uint8_t key[CRYPTO_SHARED_KEY_SIZE]; // Derive a key from the password // http://doc.libsodium.org/key_derivation/README.html // note that, according to the documentation, a generic pwhash interface will be created // once the pwhash competition (https://password-hashing.net/) is over */ if (crypto_pwhash_scryptsalsa208sha256( key, sizeof(key), (char *)passkey, sizeof(passkey), salt, crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE * 2, /* slightly stronger */ crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE) != 0) { /* out of memory most likely */ SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED); return nullptr; } crypto_memzero(passkey, crypto_hash_sha256_BYTES); /* wipe plaintext pw */ Tox_Pass_Key *out_key = (Tox_Pass_Key *)malloc(sizeof(Tox_Pass_Key)); if (!out_key) { SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED); return nullptr; } memcpy(out_key->salt, salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); memcpy(out_key->key, key, CRYPTO_SHARED_KEY_SIZE); SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_OK); return out_key; } /** * Encrypt arbitrary with a key produced by `tox_derive_key_*`. The output * array must be at least data_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. * key must be TOX_PASS_KEY_LENGTH bytes. * If you already have a symmetric key from somewhere besides this module, simply * call encrypt_data_symmetric in toxcore/crypto_core directly. * * returns true on success */ bool tox_pass_key_encrypt(const Tox_Pass_Key *key, const uint8_t *data, size_t data_len, uint8_t *out, Tox_Err_Encryption *error) { if (data_len == 0 || !data || !key || !out) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_NULL); return 0; } // the output data consists of, in order: // salt, nonce, mac, enc_data // where the mac is automatically prepended by the encrypt() // the salt+nonce is called the prefix // I'm not sure what else I'm supposed to do with the salt and nonce, since we // need them to decrypt the data /* first add the magic number */ memcpy(out, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH); out += TOX_ENC_SAVE_MAGIC_LENGTH; /* then add the rest prefix */ memcpy(out, key->salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); out += crypto_pwhash_scryptsalsa208sha256_SALTBYTES; uint8_t nonce[crypto_box_NONCEBYTES]; random_nonce(nonce); memcpy(out, nonce, crypto_box_NONCEBYTES); out += crypto_box_NONCEBYTES; /* now encrypt */ if (encrypt_data_symmetric(key->key, nonce, data, data_len, out) != data_len + crypto_box_MACBYTES) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_FAILED); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_OK); return 1; } /* Encrypts the given data with the given passphrase. The output array must be * at least data_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. This delegates * to tox_derive_key and tox_pass_key_encrypt. * * returns true on success */ bool tox_pass_encrypt(const uint8_t *data, size_t data_len, const uint8_t *passphrase, size_t pplength, uint8_t *out, Tox_Err_Encryption *error) { Tox_Err_Key_Derivation err; Tox_Pass_Key *key = tox_pass_key_derive(passphrase, pplength, &err); if (!key) { if (err == TOX_ERR_KEY_DERIVATION_NULL) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_NULL); } else if (err == TOX_ERR_KEY_DERIVATION_FAILED) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED); } return 0; } bool result = tox_pass_key_encrypt(key, data, data_len, out, error); tox_pass_key_free(key); return result; } /* This is the inverse of tox_pass_key_encrypt, also using only keys produced by * tox_derive_key. * * the output data has size data_length - TOX_PASS_ENCRYPTION_EXTRA_LENGTH * * returns true on success */ bool tox_pass_key_decrypt(const Tox_Pass_Key *key, const uint8_t *data, size_t length, uint8_t *out, Tox_Err_Decryption *error) { if (length <= TOX_PASS_ENCRYPTION_EXTRA_LENGTH) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_INVALID_LENGTH); return 0; } if (!data || !key || !out) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_NULL); return 0; } if (memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_BAD_FORMAT); return 0; } data += TOX_ENC_SAVE_MAGIC_LENGTH; data += crypto_pwhash_scryptsalsa208sha256_SALTBYTES; // salt only affects key derivation size_t decrypt_length = length - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; uint8_t nonce[crypto_box_NONCEBYTES]; memcpy(nonce, data, crypto_box_NONCEBYTES); data += crypto_box_NONCEBYTES; /* decrypt the data */ if (decrypt_data_symmetric(key->key, nonce, data, decrypt_length + crypto_box_MACBYTES, out) != decrypt_length) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_FAILED); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_OK); return 1; } /* Decrypts the given data with the given passphrase. The output array must be * at least data_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. This delegates * to tox_pass_key_decrypt. * * the output data has size data_length - TOX_PASS_ENCRYPTION_EXTRA_LENGTH * * returns true on success */ bool tox_pass_decrypt(const uint8_t *data, size_t length, const uint8_t *passphrase, size_t pplength, uint8_t *out, Tox_Err_Decryption *error) { if (length <= TOX_PASS_ENCRYPTION_EXTRA_LENGTH) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_INVALID_LENGTH); return 0; } if (!data || !passphrase || !out) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_NULL); return 0; } if (memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_BAD_FORMAT); return 0; } uint8_t salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES]; memcpy(salt, data + TOX_ENC_SAVE_MAGIC_LENGTH, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); /* derive the key */ Tox_Pass_Key *key = tox_pass_key_derive_with_salt(passphrase, pplength, salt, nullptr); if (!key) { /* out of memory most likely */ SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED); return 0; } bool result = tox_pass_key_decrypt(key, data, length, out, error); tox_pass_key_free(key); return result; } /* Determines whether or not the given data is encrypted (by checking the magic number) */ bool tox_is_data_encrypted(const uint8_t *data) { if (memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) == 0) { return 1; } return 0; } c-toxcore-0.2.13/toxencryptsave/toxencryptsave.h000066400000000000000000000271431415350724400220610ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright © 2016-2018 The TokTok team. * Copyright © 2013-2016 Tox Developers. */ /* * Batch encryption functions. */ #ifndef C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H #define C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H //!TOKSTYLE- #ifdef __cplusplus extern "C" { #endif #include #include #include /******************************************************************************* * * This module is organized into two parts. * * 1. A simple API operating on plain text/cipher text data and a password to * encrypt or decrypt it. * 2. A more advanced API that splits key derivation and encryption into two * separate function calls. * * The first part is implemented in terms of the second part and simply calls * the separate functions in sequence. Since key derivation is very expensive * compared to the actual encryption, clients that do a lot of crypto should * prefer the advanced API and reuse pass-key objects. * * To use the second part, first derive an encryption key from a password with * tox_pass_key_derive, then use the derived key to encrypt the data. * * The encrypted data is prepended with a magic number, to aid validity * checking (no guarantees are made of course). Any data to be decrypted must * start with the magic number. * * Clients should consider alerting their users that, unlike plain data, if * even one bit becomes corrupted, the data will be entirely unrecoverable. * Ditto if they forget their password, there is no way to recover the data. * ******************************************************************************/ /** * The size of the salt part of a pass-key. */ #define TOX_PASS_SALT_LENGTH 32 uint32_t tox_pass_salt_length(void); /** * The size of the key part of a pass-key. */ #define TOX_PASS_KEY_LENGTH 32 uint32_t tox_pass_key_length(void); /** * The amount of additional data required to store any encrypted byte array. * Encrypting an array of N bytes requires N + TOX_PASS_ENCRYPTION_EXTRA_LENGTH * bytes in the encrypted byte array. */ #define TOX_PASS_ENCRYPTION_EXTRA_LENGTH 80 uint32_t tox_pass_encryption_extra_length(void); typedef enum TOX_ERR_KEY_DERIVATION { /** * The function returned successfully. */ TOX_ERR_KEY_DERIVATION_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_KEY_DERIVATION_NULL, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. */ TOX_ERR_KEY_DERIVATION_FAILED, } TOX_ERR_KEY_DERIVATION; typedef enum TOX_ERR_ENCRYPTION { /** * The function returned successfully. */ TOX_ERR_ENCRYPTION_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_ENCRYPTION_NULL, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. The functions accepting keys * do not produce this error. */ TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED, /** * The encryption itself failed. */ TOX_ERR_ENCRYPTION_FAILED, } TOX_ERR_ENCRYPTION; typedef enum TOX_ERR_DECRYPTION { /** * The function returned successfully. */ TOX_ERR_DECRYPTION_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_DECRYPTION_NULL, /** * The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes */ TOX_ERR_DECRYPTION_INVALID_LENGTH, /** * The input data is missing the magic number (i.e. wasn't created by this * module, or is corrupted). */ TOX_ERR_DECRYPTION_BAD_FORMAT, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. The functions accepting keys * do not produce this error. */ TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED, /** * The encrypted byte array could not be decrypted. Either the data was * corrupted or the password/key was incorrect. */ TOX_ERR_DECRYPTION_FAILED, } TOX_ERR_DECRYPTION; /******************************************************************************* * * BEGIN PART 1 * * The simple API is presented first. If your code spends too much time using * these functions, consider using the advanced functions instead and caching * the generated pass-key. * ******************************************************************************/ /** * Encrypts the given data with the given passphrase. * * The output array must be at least `plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH` * bytes long. This delegates to tox_pass_key_derive and * tox_pass_key_encrypt. * * @param plaintext A byte array of length `plaintext_len`. * @param plaintext_len The length of the plain text array. Bigger than 0. * @param passphrase The user-provided password. Can be empty. * @param passphrase_len The length of the password. * @param ciphertext The cipher text array to write the encrypted data to. * * @return true on success. */ bool tox_pass_encrypt(const uint8_t *plaintext, size_t plaintext_len, const uint8_t *passphrase, size_t passphrase_len, uint8_t *ciphertext, TOX_ERR_ENCRYPTION *error); /** * Decrypts the given data with the given passphrase. * * The output array must be at least `ciphertext_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH` * bytes long. This delegates to tox_pass_key_decrypt. * * @param ciphertext A byte array of length `ciphertext_len`. * @param ciphertext_len The length of the cipher text array. At least TOX_PASS_ENCRYPTION_EXTRA_LENGTH. * @param passphrase The user-provided password. Can be empty. * @param passphrase_len The length of the password. * @param plaintext The plain text array to write the decrypted data to. * * @return true on success. */ bool tox_pass_decrypt(const uint8_t *ciphertext, size_t ciphertext_len, const uint8_t *passphrase, size_t passphrase_len, uint8_t *plaintext, TOX_ERR_DECRYPTION *error); /******************************************************************************* * * BEGIN PART 2 * * And now part 2, which does the actual encryption, and can be used to write * less CPU intensive client code than part one. * ******************************************************************************/ /** * This type represents a pass-key. * * A pass-key and a password are two different concepts: a password is given * by the user in plain text. A pass-key is the generated symmetric key used * for encryption and decryption. It is derived from a salt and the user- * provided password. * * The Tox_Pass_Key structure is hidden in the implementation. It can be created * using tox_pass_key_derive or tox_pass_key_derive_with_salt and must be deallocated using tox_pass_key_free. */ #ifndef TOX_PASS_KEY_DEFINED #define TOX_PASS_KEY_DEFINED typedef struct Tox_Pass_Key Tox_Pass_Key; #endif /* TOX_PASS_KEY_DEFINED */ /** * Deallocate a Tox_Pass_Key. This function behaves like free(), so NULL is an * acceptable argument value. */ void tox_pass_key_free(struct Tox_Pass_Key *_key); /** * Generates a secret symmetric key from the given passphrase. * * Be sure to not compromise the key! Only keep it in memory, do not write * it to disk. * * Note that this function is not deterministic; to derive the same key from * a password, you also must know the random salt that was used. A * deterministic version of this function is tox_pass_key_derive_with_salt. * * @param passphrase The user-provided password. Can be empty. * @param passphrase_len The length of the password. * * @return true on success. */ struct Tox_Pass_Key *tox_pass_key_derive(const uint8_t *passphrase, size_t passphrase_len, TOX_ERR_KEY_DERIVATION *error); /** * Same as above, except use the given salt for deterministic key derivation. * * @param passphrase The user-provided password. Can be empty. * @param passphrase_len The length of the password. * @param salt An array of at least TOX_PASS_SALT_LENGTH bytes. * * @return true on success. */ struct Tox_Pass_Key *tox_pass_key_derive_with_salt(const uint8_t *passphrase, size_t passphrase_len, const uint8_t *salt, TOX_ERR_KEY_DERIVATION *error); /** * Encrypt a plain text with a key produced by tox_pass_key_derive or tox_pass_key_derive_with_salt. * * The output array must be at least `plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH` * bytes long. * * @param plaintext A byte array of length `plaintext_len`. * @param plaintext_len The length of the plain text array. Bigger than 0. * @param ciphertext The cipher text array to write the encrypted data to. * * @return true on success. */ bool tox_pass_key_encrypt(const struct Tox_Pass_Key *_key, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, TOX_ERR_ENCRYPTION *error); /** * This is the inverse of tox_pass_key_encrypt, also using only keys produced by * tox_pass_key_derive or tox_pass_key_derive_with_salt. * * @param ciphertext A byte array of length `ciphertext_len`. * @param ciphertext_len The length of the cipher text array. At least TOX_PASS_ENCRYPTION_EXTRA_LENGTH. * @param plaintext The plain text array to write the decrypted data to. * * @return true on success. */ bool tox_pass_key_decrypt(const struct Tox_Pass_Key *_key, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, TOX_ERR_DECRYPTION *error); typedef enum TOX_ERR_GET_SALT { /** * The function returned successfully. */ TOX_ERR_GET_SALT_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_GET_SALT_NULL, /** * The input data is missing the magic number (i.e. wasn't created by this * module, or is corrupted). */ TOX_ERR_GET_SALT_BAD_FORMAT, } TOX_ERR_GET_SALT; /** * Retrieves the salt used to encrypt the given data. * * The retrieved salt can then be passed to tox_pass_key_derive_with_salt to * produce the same key as was previously used. Any data encrypted with this * module can be used as input. * * The cipher text must be at least TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes in length. * The salt must be TOX_PASS_SALT_LENGTH bytes in length. * If the passed byte arrays are smaller than required, the behaviour is * undefined. * * If the cipher text pointer or the salt is NULL, this function returns false. * * Success does not say anything about the validity of the data, only that * data of the appropriate size was copied. * * @return true on success. */ bool tox_get_salt(const uint8_t *ciphertext, uint8_t *salt, TOX_ERR_GET_SALT *error); /** * Determines whether or not the given data is encrypted by this module. * * It does this check by verifying that the magic number is the one put in * place by the encryption functions. * * The data must be at least TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes in length. * If the passed byte array is smaller than required, the behaviour is * undefined. * * If the data pointer is NULL, the behaviour is undefined * * @return true if the data is encrypted by this module. */ bool tox_is_data_encrypted(const uint8_t *data); #ifdef __cplusplus } #endif typedef TOX_ERR_KEY_DERIVATION Tox_Err_Key_Derivation; typedef TOX_ERR_ENCRYPTION Tox_Err_Encryption; typedef TOX_ERR_DECRYPTION Tox_Err_Decryption; typedef TOX_ERR_GET_SALT Tox_Err_Get_Salt; //!TOKSTYLE+ #endif // C_TOXCORE_TOXENCRYPTSAVE_TOXENCRYPTSAVE_H