Compare commits

..
Author SHA1 Message Date
Adrien Béraud 3d42ad6b9b dht: fix recycling of existing search 2026-03-03 17:01:51 -05:00
90 changed files with 1234 additions and 7685 deletions

No files matched your search

+10 -10
View File
@@ -7,16 +7,16 @@ env:
UBUNTU_FULL_DEPS: "libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
UBUNTU_MESON_DEPS: "ninja-build libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-dev python3-setuptools python3-pip libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
UBUNTU_MINIMAL_DEPS: "libncurses5-dev libreadline-dev nettle-dev libfmt-dev libgnutls28-dev libcppunit-dev libmsgpack-dev libargon2-dev"
DEBIAN13_DEPS: "git libtool pkg-config wget cmake libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
DEBIAN12_DEPS: "git libtool pkg-config wget cmake libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
on: [push, pull_request]
jobs:
build-ubuntu-meson:
build-ubuntu-meson:
name: Ubuntu/GCC Meson build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Dependencies
run: |
@@ -40,7 +40,7 @@ jobs:
matrix:
cxx_standard: [17, 20, 23]
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Minimal Dependencies
run: |
@@ -65,12 +65,12 @@ jobs:
- name: Run Unit tests
run: ctest --output-on-failure --test-dir build
build-debian13:
name: Debian 13/GCC build
build-debian12:
name: Debian 12/GCC build
runs-on: ubuntu-latest
container:
image: debian:13
image: debian:12
volumes:
- .:/workspace
options: --workdir /workspace
@@ -78,12 +78,12 @@ jobs:
DEBIAN_FRONTEND: noninteractive
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Dependencies
run: |
apt-get update
apt-get install -y $DEBIAN13_DEPS
apt-get install -y $DEBIAN12_DEPS
- name: Build llhttp dependency
run: |
@@ -131,7 +131,7 @@ jobs:
matrix:
cxx_standard: [17, 20, 23]
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Install Homebrew Dependencies
run: brew install msgpack-cxx asio gnutls nettle readline fmt jsoncpp argon2 openssl cppunit
+1 -1
View File
@@ -6,7 +6,7 @@ jobs:
name: Clang Static Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: deps
run: |
sudo apt update && \
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
+9 -9
View File
@@ -16,18 +16,18 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-15]
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
# Run locally with: pipx run cibuildwheel --platform linux python
- name: Build wheels
uses: pypa/cibuildwheel@v4.1.1
uses: pypa/cibuildwheel@v3.3.1
with:
package-dir: python
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v6
with:
name: opendht-wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl
@@ -36,7 +36,7 @@ jobs:
name: Build wheels on Windows
runs-on: windows-latest
env:
VCPKG_VERSION: "2026.06.01"
VCPKG_VERSION: "2025.07.25"
VCPKG_INSTALLATION_ROOT: "C:\\vcpkg"
VCPKG_DISABLE_METRICS: "1"
VCPKG_DEFAULT_TRIPLET: x64-windows-release
@@ -44,11 +44,11 @@ jobs:
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}\vcpkg_binary_cache
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Cache vcpkg installed packages
id: cache-vcpkg
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -86,7 +86,7 @@ jobs:
pipx run cibuildwheel python
shell: pwsh
- uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v6
with:
name: opendht-wheels-win_amd64
path: ./build/wheelhouse/*.whl
@@ -95,7 +95,7 @@ jobs:
runs-on: ubuntu-latest
needs: [build_wheels, build_wheels_windows]
steps:
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v7
with:
pattern: opendht-wheels-*
path: dist
+56 -56
View File
@@ -25,16 +25,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -42,12 +42,12 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DEPS }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileDeps
@@ -66,16 +66,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -83,13 +83,13 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
@@ -107,16 +107,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -124,12 +124,12 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DEPS_LLVM }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileDepsLlvm
@@ -148,16 +148,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -165,13 +165,13 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LLVM }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileLlvm
@@ -190,16 +190,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -207,13 +207,13 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DHTNODE }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileDhtnode
@@ -231,16 +231,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -248,13 +248,13 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE_DEPS }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileDepsAlpine
@@ -273,16 +273,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -290,13 +290,13 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE }}
- name: Build and push Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: docker/DockerfileAlpine
@@ -309,7 +309,7 @@ jobs:
name: Build Windows MSI Installer
runs-on: windows-latest
env:
VCPKG_VERSION: "2026.06.01"
VCPKG_VERSION: "2025.07.25"
VCPKG_INSTALLATION_ROOT: "C:\\vcpkg"
VCPKG_DISABLE_METRICS: "1"
VCPKG_DEFAULT_TRIPLET: x64-windows
@@ -319,12 +319,12 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up MSVC developer environment
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Install WIX using .net tools
run: |
@@ -334,7 +334,7 @@ jobs:
- name: Cache vcpkg installed packages
id: cache-vcpkg
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -403,7 +403,7 @@ jobs:
}
- name: Upload MSI Artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-msi
path: build/${{ steps.cpack_msi.outputs.msi_path }} # Path to the MSI in the build directory
@@ -413,7 +413,7 @@ jobs:
name: Build Windows dhtnode
runs-on: windows-latest
env:
VCPKG_VERSION: "2026.06.01"
VCPKG_VERSION: "2025.07.25"
VCPKG_INSTALLATION_ROOT: "C:\\vcpkg"
VCPKG_DISABLE_METRICS: "1"
VCPKG_DEFAULT_TRIPLET: x64-windows-static-release
@@ -423,16 +423,16 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up MSVC developer environment
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Cache vcpkg static installed packages
id: cache-vcpkg-static
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -483,7 +483,7 @@ jobs:
cmake --build build_static --config Release --parallel $([System.Environment]::ProcessorCount)
- name: Upload Static dhtnode Artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-dhtnode
path: |
@@ -499,23 +499,23 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Download Windows MSI artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: opendht-windows-msi
path: downloaded_artifacts/windows_msi
- name: Download Static dhtnode artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v7
with:
name: opendht-windows-dhtnode
path: downloaded_artifacts/static_dhtnode
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@v2
with:
draft: true
files: |
+17 -17
View File
@@ -9,7 +9,7 @@ on:
branches: [ main, master ]
env:
VCPKG_VERSION: "2026.06.01"
VCPKG_VERSION: "2026.01.16"
VCPKG_INSTALLATION_ROOT: "C:\\vcpkg"
VCPKG_DISABLE_METRICS: "1"
VCPKG_INSTALLED_DIR: ${{ github.workspace }}\vcpkg_installed
@@ -27,10 +27,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up MSVC
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Generate Cache Key
id: generate_cache_key
@@ -41,7 +41,7 @@ jobs:
- name: Cache vcpkg installed packages
id: cache-vcpkg
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -75,10 +75,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up MSVC
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Generate Cache Key for Static Libs
id: generate_static_cache_key
@@ -89,7 +89,7 @@ jobs:
- name: Cache vcpkg static installed packages
id: cache-vcpkg-static
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -125,14 +125,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up MSVC
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Restore vcpkg installed packages
id: cache-vcpkg-restore
uses: actions/cache/restore@v6
uses: actions/cache/restore@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -181,7 +181,7 @@ jobs:
- name: Upload build artifacts
if: matrix.build_type == 'Release'
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-${{ matrix.arch }}-cxx${{ matrix.cxx_standard }}-${{ matrix.build_type }}
path: |
@@ -202,14 +202,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up MSVC
uses: microsoft/setup-msbuild@v3
uses: microsoft/setup-msbuild@v2
- name: Restore vcpkg static installed packages
id: cache-vcpkg-static-restore
uses: actions/cache/restore@v6
uses: actions/cache/restore@v5
with:
path: |
${{ env.VCPKG_INSTALLED_DIR }}
@@ -256,7 +256,7 @@ jobs:
ctest --build-config ${{ matrix.build_type }} --output-on-failure --parallel 4
- name: Upload static build artifacts
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-static-${{ matrix.arch }}-cxx${{ matrix.cxx_standard }}-${{ matrix.build_type }}
path: |
@@ -275,7 +275,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Setup MinGW
uses: msys2/setup-msys2@v2
@@ -375,7 +375,7 @@ jobs:
- name: Upload MinGW artifacts
if: matrix.build_type == 'Release'
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v6
with:
name: opendht-windows-mingw-${{ matrix.build_type }}
path: |
+47 -14
View File
@@ -32,21 +32,14 @@ meson setup build . && cd build && ninja
ctest --output-on-failure --test-dir build
# Single test executable
./build/test_testName
./build/test_dhtrunner
./build/test_crypto
./build/test_value
./build/test_infohash
./build/test_storage
./build/test_threadpool
```
## dhtnode
The `dhtnode` CLI tool is a simple DHT node implementation using `DhtRunner`.
---
## Commit
After implementing a feature or fixing a bug, commit your changes in clean, atomic commits with descriptive messages with format:
`topic: short description`
If any test are added or modified, commit them separately before the fix (if any).
Network-dependent tests (`test_http`, `test_dhtproxy`, `test_peerdiscovery`) require their corresponding CMake features enabled at configure time.
---
@@ -68,7 +61,47 @@ DhtRunner (public API — thread-safe, manages lifecycle & I/O threads)
- **`RoutingTable`** (`include/opendht/routing_table.h`, `src/routing_table.cpp`) — Kademlia routing table with bucket splitting.
- **`Value`** (`include/opendht/value.h`) — the storable unit: binary blob, optional signature, optional encryption; typed via `ValueType`.
- **`InfoHash`** (`include/opendht/infohash.h`) — 20-byte SHA-1 identifier; alias `Hash<HASH_LEN>`.
- **Internal-only headers** (`src/storage.h`, `src/search.h`, `src/listener.h`, `src/request.h`, `src/parsed_message.h`) are not part of the public API.
Optional components (`DhtProxyServer`, `DhtProxyClient`, `PeerDiscovery`) are compiled in only when the corresponding CMake options are enabled.
---
## Key conventions
### Namespaces
All library code lives in `namespace dht`. Sub-namespaces: `dht::net`, `dht::crypto`, `dht::indexation`.
### Naming
- Types/classes: `PascalCase` — `DhtRunner`, `NetworkEngine`, `InfoHash`
- Methods/functions: `camelCase` — `getNodeId()`, `putEncrypted()`
- Constants: `UPPER_SNAKE_CASE` — `HASH_LEN`, `STORAGE_LIMIT_DEFAULT`, `STORAGE_LIMIT_UNLIMITED`
- Private member variables: `snake_case_` (trailing underscore)
### Smart pointers
`Sp<T>` is a project-wide alias for `std::shared_ptr<T>` (defined in `def.h`). Prefer it over spelling out `std::shared_ptr`.
### Code style (`.clang-format` enforced)
- Indent: 4 spaces, no tabs
- Column limit: 120
- Pointer alignment: left (`T* ptr`)
- Braces wrap after `class`, `struct`, function definitions; not after control statements
- Binary operators break before (not after)
- Constructor initializers: `BeforeComma` style
Run `clang-format -i <file>` before committing. CI also runs `clang-tidy` (config in `.clang-tidy`).
### Tests
Tests use **CppUnit**. Each feature has a `test_<name>.h` / `test_<name>.cpp` pair in `tests/`. Test classes inherit from `CppUnit::TestFixture` and use `CPPUNIT_TEST_SUITE()` macros. All test executables share `tests/tests_runner.cpp` as the entry point.
### Language bindings
- **C bindings**: `c/opendht.cpp` implements the C API declared in `c/opendht_c.h`.
- **Python bindings**: Cython (`.pyx`) in `python/opendht/`. An `asyncio`-compatible layer is in `python/opendht/aio.py`.
- **Rust bindings**: `rust/` is a separate Cargo workspace; it wraps the C bindings.
### Copyright header
Every source file should begin with:
```cpp
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
```
+4 -3
View File
@@ -67,7 +67,7 @@ sudo dnf install python3-Cython python3-devel redhat-rpm-config
### macOS
```sh
brew install gnutls msgpack-cxx argon2 asio readline jsoncpp fmt llhttp
brew install gnutls msgpack-cxx argon2 asio readline jsoncpp fmt
```
## Build
@@ -81,7 +81,7 @@ git clone https://github.com/savoirfairelinux/opendht.git
# Build and install
cd opendht
mkdir build && cd build
cmake ..
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make -j$(nproc)
sudo make install
```
@@ -100,7 +100,8 @@ cmake -DOPENDHT_PYTHON=ON \
-DOPENDHT_PROXY_SERVER=ON \
-DOPENDHT_PROXY_SERVER_IDENTITY=ON \
-DOPENDHT_PROXY_CLIENT=ON \
-DOPENDHT_PUSH_NOTIFICATIONS=ON ..
-DOPENDHT_PUSH_NOTIFICATIONS=ON \
-DCMAKE_INSTALL_PREFIX=/usr ..
```
#### Common CMake options
+3 -18
View File
@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.21...4.4)
cmake_minimum_required (VERSION 3.21...3.31)
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
@@ -9,8 +9,8 @@ if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW)
endif()
set (opendht_VERSION_MAJOR 4)
set (opendht_VERSION_MINOR 3.0)
set (opendht_VERSION_MAJOR 3)
set (opendht_VERSION_MINOR 7.2)
set (opendht_VERSION ${opendht_VERSION_MAJOR}.${opendht_VERSION_MINOR})
set (PACKAGE_VERSION ${opendht_VERSION})
set (VERSION "${opendht_VERSION}")
@@ -130,7 +130,6 @@ if (NOT MSVC)
message(STATUS "simdutf not found with pkgconfig")
if (OPENDHT_DOWNLOAD_DEPS)
message(STATUS "Fallback mode specified, downloading simdutf")
set(SIMDUTF_CXX_STANDARD ${CMAKE_CXX_STANDARD} CACHE INTERNAL "C++ standard for simdutf downloader")
set(SIMDUTF_TESTS OFF CACHE INTERNAL "Disable tests for simdutf downloader")
set(SIMDUTF_TOOLS OFF CACHE INTERNAL "Disable tools for simdutf downloader")
FetchContent_Declare(simdutf
@@ -866,21 +865,7 @@ if (BUILD_TESTING)
tests/test_storage.cpp
tests/test_threadpool.h
tests/test_threadpool.cpp
tests/test_parsedmessage.h
tests/test_parsedmessage.cpp
tests/test_networkengine.h
tests/test_networkengine.cpp
tests/test_opcache.h
tests/test_opcache.cpp
tests/test_valuecache.h
tests/test_valuecache.cpp
)
if (OPENDHT_PROXY_CLIENT)
list (APPEND test_FILES
tests/test_dhtproxy_client.h
tests/test_dhtproxy_client.cpp
)
endif()
if (OPENDHT_TESTS_NETWORK)
if (OPENDHT_PROXY_SERVER AND OPENDHT_PROXY_CLIENT)
list (APPEND test_FILES
+2 -2
View File
@@ -600,7 +600,7 @@ dht_runner_put_signed(
void
dht_runner_put_encrypted(dht_runner* r,
const dht_infohash* h,
const dht_pkid* to,
const dht_infohash* to,
const dht_value* v,
dht_done_cb done_cb,
void* cb_user_data,
@@ -608,7 +608,7 @@ dht_runner_put_encrypted(dht_runner* r,
{
auto runner = reinterpret_cast<dht::DhtRunner*>(r);
auto hash = reinterpret_cast<const dht::InfoHash*>(h);
auto toHash = reinterpret_cast<const dht::PkId*>(to);
auto toHash = reinterpret_cast<const dht::InfoHash*>(to);
auto value = reinterpret_cast<const ValueSp*>(v);
runner->putEncrypted(
*hash,
+1 -1
View File
@@ -195,7 +195,7 @@ OPENDHT_C_PUBLIC void dht_runner_put_signed(dht_runner* runner,
bool permanent);
OPENDHT_C_PUBLIC void dht_runner_put_encrypted(dht_runner* runner,
const dht_infohash* hash,
const dht_pkid* to,
const dht_infohash* to,
const dht_value* value,
dht_done_cb done_cb,
void* cb_user_data,
+54 -145
View File
@@ -1,193 +1,102 @@
.TH DHTNODE 1 2026-04-18
.TH DHTNODE 1 2019-06-08
.SH NAME
.B dhtnode
- a simple OpenDHT command line node runner.
.SH SYNOPSIS
.B dhtnode
[\fB\-h\fR]
[\fB\-V\fR]
[\fB\-v\fR [\fB\-l\fR \fIlogfile\fR] [\fB\-L\fR]]
[\fB\-i\fR [\fB\-I\fR \fIfile\fR]]
[\fB\-c\fR \fIfile\fR]
[\fB\-k\fR \fIfile\fR]
[\fB\-m\fR \fIpassword\fR]
[\fB\-i\fR [\fB\-\-save\-identity\fR \fIfile\fR]]
[\fB\-d\fR] [\fB\-s\fR]
[\fB\-D\fR] [\fB\-U\fR] [\fB\-P\fR]
[\fB\-n\fR \fInetwork_id\fR]
[\fB\-p\fR \fIlocal_port\fR]
[\fB\-b\fR \fIbootstrap_host\fR[:\fIport\fR]]
[\fB\-f\fR \fIpath\fR]
[\fB\-S\fR \fIport\fR]
[\fB\-e\fR \fIport\fR]
[\fB\-a\fR \fIaddress\fR]
[\fB\-w\fR \fIfile\fR]
[\fB\-K\fR \fIfile\fR]
[\fB\-M\fR \fIpassword\fR]
[\fB\-C\fR \fIserver\fR]
[\fB\-y\fR \fIendpoint\fR]
[\fB\-z\fR \fIkey\fR]
[\fB\-u\fR \fIbundleid\fR]
[\fB\-\-certificate\fR \fIfile\fR]
[\fB\-\-privkey\fR \fIfile\fR]
[\fB\-\-privkey\-password\fR \fIpassword\fR]
[\fB\-\-proxyserver\fR \fIport\fR]
[\fB\-\-proxyclient\fR \fIserver\fR]
.SH DESCRIPTION
Runs an OpenDHT node, with an interactive CLI (default) or as a daemon (with \fB'-d'\fP or \fB'-s'\fP).
Runs an OpenDHT node, with a CLI (default) or as a daemon (with \fB'-d'\fP or \fB'-s'\fP).
Commands available in the interactive shell are:
.EX
h, help Print this help message.
x, quit Quit the program.
q, quit Quit the program.
log Start/stop printing DHT logs.
Node information:
ll Print basic information and stats about the current node.
ls [key] Print basic information about current search(es).
ld [key] Print basic information about currenty stored values on this
node (or for a specific key).
lr Print the full current routing table of this node.
ls Print basic information about current searches.
ld Print basic information about currenty stored values on this node.
lr Print the full current routing table of this node
Operations on the DHT:
b <ip:port> Ping potential node at given IP address/port.
g <key> Get values at <key>.
l <key> Listen for value changes at <key>.
cl <key> <token> Cancel listen for <token> and <key>.
p <key> <str> Put string value at <key>.
pp <key> <str> Put string value at <key> (persistent version).
cpp <key> <id> Cancel persistent put operation for <key> and
value <id>.
s <key> <str> Put string value at <key>, signed with our
generated private key.
e <key> <dest> <str> Put string value at <key>, encrypted for <dest>
with its public key (if found).
cc Trigger connectivity changed signal.
Operations with the proxy (if compiled with proxy server support):
pst [port] <pushServer> Start the proxy interface on port.
psx [port] Start the proxy SSL interface on port.
psp [port] Stop the proxy interface on port.
Operations with the proxy (if compiled with proxy client support):
stt [server_address] <device_key> Start the proxy client.
stp Stop the proxy client.
rs [token] Resubscribe to opendht.
rp [token] Inject a push notification.
b ip:port Ping potential node at given IP address/port.
g [key] Get values at [key].
l [key] Listen for value changes at [key].
p [key] [str] Put string value at [key].
s [key] [str] Put string value at [key], signed with our generated
private key.
e [key] [dest] [str] Put string value at [key], encrypted for [dest] with
its public key (if found).
.EE
.SH OPTIONS
.TP
\fB\-h\fP, \fB\-\-help\fP
Prints help and exits.
\fB\-h\fP
Prints some help.
.TP
\fB\-V\fP, \fB\-\-version\fP
Prints version information and exits.
\fB\-v\fP
Enable the verbose mode (log to stdout/stderr by default)
.TP
\fB\-v\fP, \fB\-\-verbose\fP
Enable verbose mode (log to stdout/stderr by default).
\fB\-l\fP \fIlog_file\fP
Write log to file instead of stdout/stderr
.TP
\fB\-l\fP \fIlogfile\fP, \fB\-\-logfile\fP \fIlogfile\fP
Write log to file instead of stdout/stderr.
\fB\-L\fP
Write log to syslog instead of stdout/stderr
.TP
\fB\-L\fP, \fB\-\-syslog\fP
Write log to syslog instead of stdout/stderr (enables verbose mode).
\fB\-i\fP
Generate cryptographic identity for the node.
.TP
\fB\-i\fP, \fB\-\-identity\fP
Generate a cryptographic identity (RSA key pair and self-signed certificate)
for the node. Required to perform cryptographic operations (encrypt/sign values)
if no private key file is provided.
.TP
\fB\-I\fP \fIfile\fP, \fB\-\-save\-identity\fP \fIfile\fP
\fB\-\-save\-identity\fP \fIfile\fP
Save generated identity (certificate and private key) to given file prefix.
Files are saved as \fIfile\fP and \fIfile\fP_ca.
.TP
\fB\-c\fP \fIfile\fP, \fB\-\-certificate\fP \fIfile\fP
Load identity certificate from given file. If specified, the certificate is
published on the DHT.
\fB\-\-certificate\fP \fIfile\fP
Load identity certificate from given file.
.TP
\fB\-k\fP \fIfile\fP, \fB\-\-privkey\fP \fIfile\fP
Load identity private key from given file. Required to perform cryptographic
operations if no identity is generated.
\fB\-\-privkey\fP \fIfile\fP
Load identity private key from given file.
.TP
\fB\-m\fP \fIpassword\fP, \fB\-\-privkey\-password\fP \fIpassword\fP
\fB\-\-privkey\-password\fP \fIpassword\fP
Password to use for private key encryption or decryption (optional).
.TP
\fB\-d\fP, \fB\-\-daemonize\fP
Run the program in daemon mode (will fork in the background). When verbose mode
is enabled, logs to syslog by default.
\fB\-d\fP
Run the program in daemon mode (will fork in the background).
.TP
\fB\-s\fP, \fB\-\-service\fP
\fB\-s\fP
Run the program in service mode (non\-forking daemon).
.TP
\fB\-D\fP, \fB\-\-peer\-discovery\fP
\fB\-D\fP
Enables multicast automatic local peer discovery.
.TP
\fB\-U\fP, \fB\-\-no\-rate\-limit\fP
Disable rate limiting (allows unlimited requests and storage).
\fB\-f\fP \fIfile\fP
Specify a file path to persist/load the node state.
.TP
\fB\-P\fP, \fB\-\-public\-stable\fP
Enable public stable mode.
\fB\-n\fP \fInetwork_id\fP
Specify the network id. This let you connect to distinct networks and prevents
the merge of two different networks (available since OpenDHT v0.6.1).
.TP
\fB\-f\fP \fIpath\fP, \fB\-\-persist\fP \fIpath\fP
Specify a file path to persist/load the node state (routing table and values).
\fB\-p\fP \fIlocal_port\fP
Use UDP port \fIlocal_port\fP for the program to bind to.
.TP
\fB\-n\fP \fInetwork_id\fP, \fB\-\-net\fP \fInetwork_id\fP
Specify the network id. This lets you connect to distinct networks and prevents
the merge of two different networks. Defaults to 0 (the main public network).
\fB\-b\fP \fIbootstrap_host\fP[:\fIport\fP]
The program needs to be given a node to connect to the network. You use this
option to provide the ip address of that node.
.TP
\fB\-p\fP \fIlocal_port\fP, \fB\-\-port\fP \fIlocal_port\fP
Use UDP port \fIlocal_port\fP for the program to bind to. If not set, will use
any available port. The default OpenDHT port for public nodes is 4222.
\fB\-\-proxyserver\fP \fIlocal_port\fP
Run a proxy server bound to this DHT node on HTTP port \fIlocal_port\fP
.TP
\fB\-b\fP \fIbootstrap_host\fP[:\fIport\fP], \fB\-\-bootstrap\fP \fIbootstrap_host\fP[:\fIport\fP]
Specify a bootstrap node address to connect to the network.
.TP
\fB\-S\fP \fIlocal_port\fP, \fB\-\-proxyserver\fP \fIlocal_port\fP
Run a REST API proxy server bound to this DHT node on HTTP port \fIlocal_port\fP.
.TP
\fB\-e\fP \fIlocal_port\fP, \fB\-\-proxyserverssl\fP \fIlocal_port\fP
Run a REST API proxy server with SSL bound to this DHT node on HTTPS port \fIlocal_port\fP.
.TP
\fB\-a\fP \fIaddress\fP, \fB\-\-proxy\-addr\fP \fIaddress\fP
Proxy server bind address.
.TP
\fB\-w\fP \fIfile\fP, \fB\-\-proxy\-certificate\fP \fIfile\fP
Load proxy SSL certificate from given file.
.TP
\fB\-K\fP \fIfile\fP, \fB\-\-proxy\-privkey\fP \fIfile\fP
Load proxy SSL private key from given file.
.TP
\fB\-M\fP \fIpassword\fP, \fB\-\-proxy\-privkey\-password\fP \fIpassword\fP
Password for the proxy private key encryption or decryption (optional).
.TP
\fB\-C\fP \fIserver\fP, \fB\-\-proxyclient\fP \fIserver\fP
Run this DHT node in proxy client mode, and connect to \fIserver\fP.
.TP
\fB\-y\fP \fIendpoint\fP, \fB\-\-pushserver\fP \fIendpoint\fP
Push notification server endpoint.
.TP
\fB\-z\fP \fIkey\fP, \fB\-\-devicekey\fP \fIkey\fP
Device key for push notifications.
.TP
\fB\-u\fP \fIbundleid\fP, \fB\-\-bundleid\fP \fIbundleid\fP
Bundle ID for push notifications.
.SH EXAMPLES
Run a node using bootstrap.jami.net as a bootstrap:
.PP
.EX
dhtnode -b bootstrap.jami.net:4222
.EE
.PP
Run a node as a non-forking daemon on port 4222:
.PP
.EX
dhtnode -b bootstrap.jami.net -p 4222 -s
.EE
.PP
Run a proxy server on port 8000:
.PP
.EX
dhtnode -b bootstrap.jami.net:4222 --proxyserver 8000
.EE
.PP
Use a proxy client to connect to a proxy server:
.PP
.EX
dhtnode --proxyclient dhtproxy.jami.net:80
.EE
\fB\-\-proxyclient\fP \fIserver\fP
Run this DHT node in proxy client mode, and connect to \fIserver\fP
.SH AUTHORS
.TP
Program written by
+3 -1
View File
@@ -23,7 +23,7 @@ struct Node;
*/
enum class NodeStatus {
Disconnected, // 0 nodes
Connecting, // bootstrap pending or 1+ nodes
Connecting, // 1+ nodes
Connected // 1+ good nodes
};
@@ -187,6 +187,8 @@ using ShutdownCallback = std::function<void()>;
using IdentityAnnouncedCb = std::function<void(bool)>;
using PublicAddressChangedCb = std::function<void(std::vector<SockAddr>)>;
using CertificateStoreQueryLegacy
= std::function<std::vector<std::shared_ptr<crypto::Certificate>>(const InfoHash& pk_id)>;
using CertificateStoreQuery = std::function<std::vector<std::shared_ptr<crypto::Certificate>>(const PkId& pk_id)>;
using DoneCallback = std::function<void(bool success, const std::vector<std::shared_ptr<Node>>& nodes)>;
+5 -17
View File
@@ -204,13 +204,7 @@ struct OPENDHT_PUBLIC PrivateKey
* @param algo : the public key algorithm to use, GNUTLS_PK_RSA or GNUTLS_PK_RSA_OAEP
*/
static PrivateKey generate(unsigned key_length = 4096, gnutls_pk_algorithm_t algo = GNUTLS_PK_RSA);
/**
* Generate a new EC key pair
* @param curve : the elliptic curve to use. If GNUTLS_ECC_CURVE_INVALID is provided, the default curve will be used
* (currently GNUTLS_ECC_CURVE_SECP384R1).
*/
static PrivateKey generateEC(gnutls_ecc_curve_t curve = GNUTLS_ECC_CURVE_INVALID);
static PrivateKey generateEC();
gnutls_privkey_t key {};
gnutls_x509_privkey_t x509_key {};
@@ -220,7 +214,7 @@ private:
PrivateKey& operator=(const PrivateKey&) = delete;
Blob decryptBloc(const uint8_t* src, size_t src_size) const;
mutable std::mutex publicKeyMutex_;
mutable std::mutex publicKeyMutex_ {};
mutable std::shared_ptr<PublicKey> publicKey_ {};
};
@@ -273,15 +267,9 @@ public:
/**
* Sign this revocation list using provided key and certificate.
* Validity_period sets the duration until next update (default to no next update).
* If `number` is non-zero, it is stored verbatim (big-endian) as the CRL Number
* extension. Otherwise the number is initialized randomly on the first signature
* and incremented on subsequent ones.
*/
void sign(const PrivateKey&, const Certificate&, duration validity_period = {}, uint64_t number = 0);
void sign(const Identity& id, duration validity_period = {}, uint64_t number = 0)
{
sign(*id.first, *id.second, validity_period, number);
}
void sign(const PrivateKey&, const Certificate&, duration validity_period = {});
void sign(const Identity& id) { sign(*id.first, *id.second); }
bool isSignedBy(const Certificate& issuer) const;
@@ -697,7 +685,7 @@ private:
std::set<std::shared_ptr<RevocationList>, crlNumberCmp> revocation_lists;
mutable std::mutex publicKeyMutex_;
mutable std::mutex publicKeyMutex_ {};
mutable std::shared_ptr<PublicKey> publicKey_ {};
};
-6
View File
@@ -159,13 +159,7 @@ public:
{}
static Value::Filter getFilter() { return EncryptedValue::getFilter(); }
virtual void unpackValue(const dht::Value& v) override
{
BaseClass::unpackValue(v);
id = v.id;
}
dht::Value::Id id {0}; /// Value ID of the trust request, used for deserialization only.
std::string service;
std::string conversationId;
Blob payload;
+2 -4
View File
@@ -411,7 +411,6 @@ private:
std::vector<std::pair<std::string, std::string>> bootstrap_nodes {};
std::chrono::steady_clock::duration bootstrap_period {BOOTSTRAP_PERIOD};
Sp<Scheduler::Job> bootstrapJob {};
bool bootstrap_pending {false};
std::map<InfoHash, Storage> store;
std::map<SockAddr, StorageBucket, SockAddr::ipCmp> store_quota;
@@ -465,8 +464,7 @@ private:
const Sp<Value>& value,
time_point created,
const SockAddr& sa = {},
bool permanent = false,
time_point expiration = time_point::min());
bool permanent = false);
bool storageRefresh(const InfoHash& id, Value::Id vid);
void expireStore();
void expireStorage(InfoHash h);
@@ -628,7 +626,7 @@ private:
/* when we receive a listen request */
net::RequestAnswer onListen(
Sp<Node> node, const InfoHash& hash, const Blob& token, size_t socket_id, const Query& query, int version = 0);
void onListenDone(const Sp<Node>& node, net::RequestAnswer& a, Sp<Search>& sr);
void onListenDone(const Sp<Node>& status, net::RequestAnswer& a, Sp<Search>& sr);
/* when we receive an announce request */
net::RequestAnswer onAnnounce(Sp<Node> node,
const InfoHash& hash,
+10 -16
View File
@@ -76,14 +76,8 @@ public:
asio::io_context& io_context() const;
// Monotonic clock for elapsed-time statistics (request rate, uptime).
// Must stay on steady_clock so NTP/wall-clock corrections don't skew durations.
using clock = std::chrono::steady_clock;
using time_point = clock::time_point;
// Wall clock for persisted subscribe/permanent-put lease expirations,
// so they map directly to epoch time and survive restarts.
using sys_clock = std::chrono::system_clock;
using sys_time_point = sys_clock::time_point;
struct PushStats
{
@@ -407,12 +401,12 @@ private:
std::shared_ptr<ConnectionListener> connListener_;
struct PermanentPut
{
sys_time_point expiration;
time_point expiration;
std::string pushToken;
std::string clientId;
std::shared_ptr<PushSessionContext> sessionCtx;
std::unique_ptr<asio::system_timer> expireTimer;
std::unique_ptr<asio::system_timer> expireNotifyTimer;
std::unique_ptr<asio::steady_timer> expireTimer;
std::unique_ptr<asio::steady_timer> expireNotifyTimer;
Sp<Value> value;
PushType type;
std::string topic;
@@ -425,13 +419,13 @@ private:
p.pack("value");
p.pack(value);
p.pack("exp");
p.pack(sys_clock::to_time_t(expiration));
p.pack(to_time_t(expiration));
if (not clientId.empty()) {
p.pack("cid");
p.pack(clientId);
}
if (sessionCtx) {
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
p.pack("sid");
p.pack(sessionCtx->sessionId);
}
@@ -466,12 +460,12 @@ private:
#ifdef OPENDHT_PUSH_NOTIFICATIONS
struct Listener
{
sys_time_point expiration;
time_point expiration;
std::string clientId;
std::shared_ptr<PushSessionContext> sessionCtx;
std::future<size_t> internalToken;
std::unique_ptr<asio::system_timer> expireTimer;
std::unique_ptr<asio::system_timer> expireNotifyTimer;
std::unique_ptr<asio::steady_timer> expireTimer;
std::unique_ptr<asio::steady_timer> expireNotifyTimer;
PushType type;
std::string topic;
@@ -482,9 +476,9 @@ private:
p.pack("cid");
p.pack(clientId);
p.pack("exp");
p.pack(sys_clock::to_time_t(expiration));
p.pack(to_time_t(expiration));
if (sessionCtx) {
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
p.pack("sid");
p.pack(sessionCtx->sessionId);
}
+27 -13
View File
@@ -61,7 +61,8 @@ public:
std::unique_ptr<net::DatagramSocket> sock;
std::shared_ptr<PeerDiscovery> peerDiscovery {};
StatusCallback statusChangedCallback {};
CertificateStoreQuery certificateStore {};
CertificateStoreQueryLegacy certificateStore {};
CertificateStoreQuery certificateStorePkId {};
IdentityAnnouncedCb identityAnnouncedCb {};
PublicAddressChangedCb publicAddressChangedCb {};
std::unique_ptr<std::mt19937_64> rng {};
@@ -285,6 +286,21 @@ public:
}
void putSigned(const std::string& key, Value&& value, DoneCallbackSimple cb = {}, bool permanent = false);
void putEncrypted(
InfoHash hash, InfoHash to, std::shared_ptr<Value> value, DoneCallback cb = {}, bool permanent = false);
void putEncrypted(
InfoHash hash, InfoHash to, std::shared_ptr<Value> value, DoneCallbackSimple cb, bool permanent = false)
{
putEncrypted(hash, to, value, bindDoneCb(cb), permanent);
}
void putEncrypted(InfoHash hash, InfoHash to, Value&& value, DoneCallback cb = {}, bool permanent = false);
void putEncrypted(InfoHash hash, InfoHash to, Value&& value, DoneCallbackSimple cb, bool permanent = false)
{
putEncrypted(hash, to, std::forward<Value>(value), bindDoneCb(cb), permanent);
}
void putEncrypted(const std::string& key, InfoHash to, Value&& value, DoneCallback cb = {}, bool permanent = false);
void putEncrypted(InfoHash hash,
const std::shared_ptr<crypto::PublicKey>& to,
std::shared_ptr<Value> value,
@@ -299,11 +315,13 @@ public:
putEncrypted(hash, to, value, bindDoneCb(cb), permanent);
}
[[deprecated("Use the shared_ptr version instead")]]
void putEncrypted(InfoHash hash,
const std::shared_ptr<crypto::PublicKey>& to,
Value&& value,
DoneCallback cb = {},
bool permanent = false);
[[deprecated("Use the shared_ptr version instead")]]
void putEncrypted(InfoHash hash,
const std::shared_ptr<crypto::PublicKey>& to,
Value&& value,
@@ -320,14 +338,6 @@ public:
{
putEncrypted(hash, to, value, bindDoneCb(cb), permanent);
}
void putEncrypted(InfoHash hash, const PkId& to, Value&& value, DoneCallback cb = {}, bool permanent = false)
{
putEncrypted(hash, to, std::make_shared<Value>(std::move(value)), std::move(cb), permanent);
}
void putEncrypted(InfoHash hash, const PkId& to, Value&& value, DoneCallbackSimple cb, bool permanent = false)
{
putEncrypted(hash, to, std::make_shared<Value>(std::move(value)), bindDoneCb(cb), permanent);
}
/**
* Insert known nodes to the routing table, without necessarly ping them.
@@ -440,9 +450,13 @@ public:
void getPublicAddress(std::function<void(std::vector<SockAddr>&&)>, sa_family_t af = AF_UNSPEC);
// securedht methods
[[deprecated("Use PkId version")]] void findCertificate(
InfoHash hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)>);
void findCertificate(PkId hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)>);
void registerCertificate(const std::shared_ptr<crypto::Certificate>& cert);
void setLocalCertificateStore(CertificateStoreQueryLegacy&& query_method);
void setLocalCertificateStore(CertificateStoreQuery&& query_method);
/**
@@ -485,7 +499,7 @@ public:
*/
time_point loop()
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return loop_();
}
@@ -565,16 +579,16 @@ private:
*/
void resetDht();
mutable std::mutex dht_mtx;
mutable std::mutex dht_mtx {};
std::thread dht_thread {};
std::condition_variable cv {};
std::mutex sock_mtx;
std::mutex sock_mtx {};
net::PacketList rcv {};
decltype(rcv) rcv_free {};
std::queue<std::function<void(SecureDht&)>> pending_ops_prio {};
std::queue<std::function<void(SecureDht&)>> pending_ops {};
std::mutex storage_mtx;
std::mutex storage_mtx {};
std::atomic<State> running {State::Idle};
std::atomic_size_t ongoing_ops {0};
+14 -15
View File
@@ -51,11 +51,11 @@ using ConnectHandlerCb = std::function<void(const asio::error_code& ec, const as
using ssl_socket_t = restinio::impl::tls_socket_t;
using socket_t = asio::ip::tcp::socket;
struct OPENDHT_PUBLIC Url
class OPENDHT_PUBLIC Url
{
public:
Url() = default;
Url(std::string url);
Url(std::string_view url);
std::string url;
std::string protocol {"http"};
std::string_view user;
@@ -106,16 +106,9 @@ public:
void timeout(const std::chrono::seconds& timeout, HandlerCb cb = {});
/**
* Close the underlying socket.
* Safe to call from any thread: the actual close is dispatched to
* the thread running the io_context.
*/
void close();
private:
void doClose();
template<typename T>
T wrapCallback(T cb) const
{
@@ -156,7 +149,7 @@ public:
using ResolverCb
= std::function<void(const asio::error_code& ec, const std::vector<asio::ip::tcp::endpoint>& endpoints)>;
Resolver(asio::io_context& ctx, std::string url, std::shared_ptr<log::Logger> logger = {});
Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
Resolver(asio::io_context& ctx,
std::string_view host,
std::string_view service,
@@ -168,6 +161,10 @@ public:
std::vector<asio::ip::tcp::endpoint> endpoints,
const bool ssl = false,
std::shared_ptr<log::Logger> logger = {});
Resolver(asio::io_context& ctx,
const std::string& url,
std::vector<asio::ip::tcp::endpoint> endpoints,
std::shared_ptr<log::Logger> logger = {});
~Resolver();
@@ -219,19 +216,19 @@ public:
// resolves implicitly
Request(asio::io_context& ctx,
std::string url,
const std::string& url,
const Json::Value& json,
OnJsonCb jsoncb,
std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, std::string url, OnJsonCb jsoncb, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, const std::string& url, OnJsonCb jsoncb, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, std::string url, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx,
std::string_view host,
std::string_view service,
const bool ssl = false,
std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, std::string url, OnDoneCb onDone, std::shared_ptr<log::Logger> logger = {});
Request(asio::io_context& ctx, const std::string& url, OnDoneCb onDone, std::shared_ptr<log::Logger> logger = {});
// user defined resolver
Request(asio::io_context& ctx, std::shared_ptr<Resolver> resolver, sa_family_t family = AF_UNSPEC);
@@ -347,6 +344,8 @@ private:
dht::crypto::Identity client_identity_;
std::shared_ptr<dht::crypto::Certificate> server_ca_;
std::string service_;
std::string host_;
unsigned int id_;
static std::atomic_uint ids_;
+51 -98
View File
@@ -42,7 +42,8 @@ OPENDHT_PUBLIC void hash(const uint8_t* data, size_t data_length, uint8_t* hash,
}
/**
* Represents an Hash, a byte array of N bytes.
* Represents an Hash,
* a byte array of N bytes.
* Hashes identify nodes and values in the Dht.
*/
template<size_t N>
@@ -53,25 +54,31 @@ public:
typedef typename T::iterator iterator;
typedef typename T::const_iterator const_iterator;
constexpr Hash() noexcept
: data_ {}
{}
constexpr Hash(const uint8_t* h, size_t data_len)
: data_(fromBytes(h, data_len))
{}
Hash() noexcept { data_.fill(0); }
Hash(const uint8_t* h, size_t data_len)
{
if (data_len < N)
data_.fill(0);
else
std::copy_n(h, N, data_.begin());
}
/**
* Constructor from an hexadecimal string (without "0x").
* hex must be at least 2.N characters long.
* If too long, only the first 2.N characters are read.
* hex must be at least 2.HASH_LEN characters long.
* If too long, only the first 2.HASH_LEN characters are read.
*/
explicit constexpr Hash(std::string_view hex)
: data_(fromString(hex))
{}
explicit Hash(std::string_view hex)
{
if (hex.size() < 2 * N)
data_.fill(0);
else
fromString(hex.data());
}
Hash(const msgpack::object& o) { msgpack_unpack(o); }
static constexpr size_t size() noexcept { return N; }
constexpr const uint8_t* data() const { return data_.data(); }
const uint8_t* data() const { return data_.data(); }
uint8_t* data() { return data_.data(); }
iterator begin() { return data_.begin(); }
const_iterator cbegin() const { return data_.cbegin(); }
@@ -80,21 +87,10 @@ public:
static constexpr inline Hash zero() noexcept { return Hash {}; }
constexpr bool operator==(const Hash& h) const
{
#if __cplusplus >= 202002L
return data_ == h.data_;
#else
for (size_t i = 0; i < N; ++i) {
if (data_[i] != h.data_[i])
return false;
}
return true;
#endif
}
constexpr bool operator!=(const Hash& h) const { return !(*this == h); }
bool operator==(const Hash& h) const { return data_ == h.data_; }
bool operator!=(const Hash& h) const { return !(*this == h); }
constexpr bool operator<(const Hash& o) const
bool operator<(const Hash& o) const
{
for (unsigned i = 0; i < N; i++) {
if (data_[i] != o.data_[i])
@@ -103,7 +99,7 @@ public:
return false;
}
constexpr Hash operator^(const Hash& o) const
Hash operator^(const Hash& o) const
{
Hash result;
for (auto i = 0u; i < N; i++) {
@@ -112,9 +108,8 @@ public:
return result;
}
explicit constexpr operator bool() const
explicit operator bool() const
{
#if __cplusplus >= 202002L
auto a = reinterpret_cast<const uint32_t*>(data_.data());
auto b = reinterpret_cast<const uint32_t*>(data_.data() + N);
for (; a != b; a++) {
@@ -122,32 +117,24 @@ public:
return true;
}
return false;
#else
for (size_t i = 0; i < N; ++i) {
if (data_[i] != 0)
return true;
}
return false;
#endif
}
uint8_t& operator[](size_t index) { return data_[index]; }
constexpr const uint8_t& operator[](size_t index) const { return data_[index]; }
const uint8_t& operator[](size_t index) const { return data_[index]; }
/**
* Find the lowest 1 bit in an id.
* Result will allways be lower than 8*N
*/
constexpr int lowbit() const
inline int lowbit() const
{
int i = N - 1;
for (; i >= 0; i--)
int i, j;
for (i = N - 1; i >= 0; i--)
if (data_[i] != 0)
break;
if (i < 0)
return -1;
int j = 7;
for (; j >= 0; j--)
for (j = 7; j >= 0; j--)
if ((data_[i] & (0x80 >> j)) != 0)
break;
return 8 * i + j;
@@ -159,10 +146,11 @@ public:
}
/** Find how many bits two ids have in common. */
static constexpr inline unsigned commonBits(const Hash& id1, const Hash& id2)
static inline unsigned commonBits(const Hash& id1, const Hash& id2)
{
unsigned i = 0;
for (; i < N; i++) {
unsigned i, j;
uint8_t x;
for (i = 0; i < N; i++) {
if (id1.data_[i] != id2.data_[i])
break;
}
@@ -170,9 +158,9 @@ public:
if (i == N)
return 8 * N;
uint8_t x = id1.data_[i] ^ id2.data_[i];
x = id1.data_[i] ^ id2.data_[i];
unsigned j = 0;
j = 0;
while ((x & 0x80) == 0) {
x <<= 1;
j++;
@@ -182,7 +170,7 @@ public:
}
/** Determine whether id1 or id2 is closer to this */
constexpr int xorCmp(const Hash& id1, const Hash& id2) const
int xorCmp(const Hash& id1, const Hash& id2) const
{
for (unsigned i = 0; i < N; i++) {
if (id1.data_[i] == id2.data_[i])
@@ -194,7 +182,7 @@ public:
return 0;
}
constexpr bool getBit(unsigned nbit) const
bool getBit(unsigned nbit) const
{
auto& num = *(data_.cbegin() + (nbit / 8));
unsigned bit = 7 - (nbit % 8);
@@ -269,9 +257,8 @@ public:
}
private:
alignas(alignof(uint32_t)) T data_;
static constexpr T fromBytes(const uint8_t*, size_t);
static constexpr T fromString(std::string_view);
T data_;
void fromString(const char*);
};
#define HASH_LEN 20u
@@ -294,51 +281,30 @@ operator>>(std::istream& s, Hash<N>& h)
std::array<char, h.size() * 2> dat;
s.exceptions(std::istream::eofbit | std::istream::failbit);
s.read(&(*dat.begin()), dat.size());
h = Hash<N>(std::string_view(dat.data(), dat.size()));
fromString(dat.data());
return s;
}
template<size_t N>
constexpr typename Hash<N>::T
Hash<N>::fromBytes(const uint8_t* in, size_t size)
void
Hash<N>::fromString(const char* in)
{
T data {};
if (size < N)
return data;
for (size_t i = 0; i < N; ++i)
data[i] = in[i];
return data;
}
template<size_t N>
constexpr typename Hash<N>::T
Hash<N>::fromString(std::string_view in)
{
T data {};
if (in.size() < 2 * N)
return data;
constexpr auto hex2bin = [](char c, bool& valid) -> uint8_t {
auto hex2bin = [](char c) -> uint8_t {
if (c >= 'a' and c <= 'f')
return 10 + c - 'a';
else if (c >= 'A' and c <= 'F')
return 10 + c - 'A';
else if (c >= '0' and c <= '9')
return c - '0';
else {
valid = false;
return 0;
}
else
throw std::domain_error("not an hex character");
};
bool valid = true;
for (size_t i = 0; i < N && valid; i++) {
uint8_t high = hex2bin(in[2 * i], valid);
uint8_t low = hex2bin(in[2 * i + 1], valid);
data[i] = (high << 4) | low;
try {
for (size_t i = 0; i < N; i++)
data_[i] = (hex2bin(in[2 * i]) << 4) | hex2bin(in[2 * i + 1]);
} catch (const std::domain_error&) {
data_.fill(0);
}
if (!valid)
return {};
return data;
}
template<size_t N>
@@ -403,19 +369,6 @@ toHex(const std::vector<uint8_t>& data)
return toHex(data.data(), data.size());
}
template<size_t N>
constexpr std::array<char, N * 2>
toHexArray(const Hash<N>& h)
{
std::array<char, N * 2> ret {};
for (size_t i = 0; i < N; i++) {
const auto& m = hex_map[h[i]];
ret[i * 2] = m[0];
ret[i * 2 + 1] = m[1];
}
return ret;
}
template<size_t N>
const char*
Hash<N>::to_c_str() const
+3 -3
View File
@@ -131,7 +131,7 @@ struct OPENDHT_PUBLIC Logger
std::shared_ptr<Logger> createChild(std::string tag)
{
std::lock_guard lock {children_mutex_};
std::lock_guard<std::mutex> lock {children_mutex_};
auto child = std::make_shared<Logger>(*this, std::move(tag));
children_.push_back(child);
return child;
@@ -139,7 +139,7 @@ struct OPENDHT_PUBLIC Logger
void setFilter(std::string_view filter)
{
std::lock_guard lock {children_mutex_};
std::lock_guard<std::mutex> lock {children_mutex_};
enable_ = filter.empty() or tag_.find(filter) != std::string_view::npos;
for (auto it = children_.begin(); it != children_.end();) {
if (auto c = it->lock()) {
@@ -196,7 +196,7 @@ private:
const LogMethod logger_ = {};
const std::string tag_ {};
const std::string prefix_ {};
std::mutex children_mutex_;
std::mutex children_mutex_ {};
std::vector<std::weak_ptr<Logger>> children_ {};
bool enable_ {true};
};
+3 -8
View File
@@ -489,12 +489,8 @@ private:
static constexpr size_t MTU {1280};
static constexpr size_t MAX_PACKET_VALUE_SIZE {600};
static constexpr size_t MAX_MESSAGE_VALUE_SIZE {56 * 1024};
static constexpr size_t RX_MAX_EARLY_BYTES {MAX_MESSAGE_VALUE_SIZE + MAX_VALUE_SIZE + 32};
static constexpr size_t RX_MAX_EARLY_PARTS {RX_MAX_EARLY_BYTES / MTU + 2};
void process(std::unique_ptr<ParsedMessage>&&, const SockAddr& from);
bool isExpectedReply(const ParsedMessage&, const SockAddr& from);
bool tryCompleteMessage(PartialMessage&, const SockAddr& from);
bool rateLimit(const SockAddr& addr);
@@ -531,8 +527,7 @@ private:
{
return packValueHeader(buf, values.begin(), values.end());
}
using PartialMessageKey = std::pair<SockAddr, Tid>;
void maintainRxBuffer(const PartialMessageKey& key);
void maintainRxBuffer(Tid tid);
/*************
* Answers *
@@ -552,7 +547,7 @@ private:
std::pair<Blob, Blob> bufferNodes(
sa_family_t af, const InfoHash& id, want_t want, std::vector<Sp<Node>>& nodes, std::vector<Sp<Node>>& nodes6);
/* answer to a listen request */
void sendListenConfirmation(const SockAddr& addr, Tid tid, const Blob& token = {});
void sendListenConfirmation(const SockAddr& addr, Tid tid);
/* answer to put request */
void sendValueAnnounced(const SockAddr& addr, Tid, Value::Id);
/* answer in case of error */
@@ -578,7 +573,7 @@ private:
// requests handling
std::map<Tid, Sp<Request>> requests {};
std::map<PartialMessageKey, PartialMessage> partial_messages;
std::map<Tid, PartialMessage> partial_messages;
MessageStats in_stats {}, out_stats {};
std::set<SockAddr> blacklist {};
+6 -6
View File
@@ -58,7 +58,7 @@ public:
inline void setOnReceive(OnReceive&& cb)
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
rx_callback = std::move(cb);
}
@@ -67,12 +67,12 @@ public:
SockAddr getBound(sa_family_t family = AF_UNSPEC) const
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
return getBoundRef(family);
}
in_port_t getPort(sa_family_t family = AF_UNSPEC) const
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
return getBoundRef(family).getPort();
}
@@ -102,7 +102,7 @@ protected:
inline void onReceived(PacketList&& packets)
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
if (rx_callback) {
auto r = rx_callback(std::move(packets));
if (not r.empty() and toRecycle_.size() < RX_QUEUE_MAX_SIZE)
@@ -134,12 +134,12 @@ public:
bool hasIPv4() const override
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
return s4 != -1;
}
bool hasIPv6() const override
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
return s6 != -1;
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace dht {
namespace proxy {
constexpr const std::chrono::hours OP_TIMEOUT {24}; // one day
constexpr const std::chrono::hours OP_MARGIN {8}; // eight hours
constexpr const std::chrono::hours OP_MARGIN {2}; // two hours
using ListenToken = uint64_t;
+25 -2
View File
@@ -113,6 +113,14 @@ public:
* and put it in the DHT.
* The operation will be immediate if the recipient' public key is known (otherwise it will be retrived first).
*/
void putEncrypted(
const InfoHash& hash, const InfoHash& to, Sp<Value> val, DoneCallback callback, bool permanent = false);
[[deprecated("Use the shared_ptr version instead")]]
void putEncrypted(const InfoHash& hash, const InfoHash& to, Value&& v, DoneCallback callback, bool permanent = false)
{
putEncrypted(hash, to, std::make_shared<Value>(std::move(v)), callback, permanent);
}
void putEncrypted(const InfoHash& hash,
const crypto::PublicKey& to,
Sp<Value> val,
@@ -137,11 +145,19 @@ public:
Value decrypt(const Value& v);
[[deprecated("Use PkId version")]] void findCertificate(
const InfoHash& node, const std::function<void(const Sp<crypto::Certificate>)>& cb);
[[deprecated("Use PkId version")]] void findPublicKey(const InfoHash& node,
const std::function<void(const Sp<crypto::PublicKey>)>& cb);
void findCertificate(const PkId& id, const std::function<void(const Sp<crypto::Certificate>)>& cb);
void findPublicKey(const PkId& id, const std::function<void(const Sp<crypto::PublicKey>)>& cb);
void registerCertificate(const Sp<crypto::Certificate>& cert);
[[deprecated("Use PkId version")]] Sp<crypto::Certificate> getCertificate(const InfoHash& node) const;
[[deprecated("Use PkId version")]] Sp<crypto::PublicKey> getPublicKey(const InfoHash& node) const;
Sp<crypto::Certificate> getCertificate(const PkId& node) const;
Sp<crypto::PublicKey> getPublicKey(const PkId& node) const;
@@ -150,6 +166,10 @@ public:
* The search key used is the public key ID, so there may be multiple certificates retured, signed with
* the same private key.
*/
void setLocalCertificateStore(CertificateStoreQueryLegacy&& query_method)
{
localQueryMethodLegacy_ = std::move(query_method);
}
void setLocalCertificateStore(CertificateStoreQuery&& query_method) { localQueryMethod_ = std::move(query_method); }
void setOnPublicAddressChanged(PublicAddressChangedCb cb) override { dht_->setOnPublicAddressChanged(cb); }
@@ -327,10 +347,13 @@ private:
// method to query the local certificate store
CertificateStoreQuery localQueryMethod_ {};
CertificateStoreQueryLegacy localQueryMethodLegacy_ {};
// our certificate cache
std::map<PkId, Sp<crypto::Certificate>> nodesCertificates_ {};
std::map<PkId, Sp<crypto::PublicKey>> nodesPubKeys_ {};
std::map<InfoHash, Sp<crypto::Certificate>> nodesCertificates_ {};
std::map<InfoHash, Sp<crypto::PublicKey>> nodesPubKeys_ {};
std::map<PkId, Sp<crypto::Certificate>> nodesCertificatesLong_ {};
std::map<PkId, Sp<crypto::PublicKey>> nodesPubKeysLong_ {};
std::atomic_bool forward_all_ {false};
bool enableCache_ {false};
+8 -12
View File
@@ -10,9 +10,7 @@
#include <future>
#include <functional>
#if __cplusplus < 202002L
#include <ciso646>
#endif
#include <ciso646> // fix windows compiler bug
namespace dht {
@@ -55,14 +53,12 @@ public:
void detach();
private:
std::mutex lock_;
std::mutex lock_ {};
std::condition_variable cv_ {};
std::condition_variable stopCv_ {};
std::queue<std::function<void()>> tasks_ {};
std::vector<std::unique_ptr<std::thread>> threads_;
unsigned readyThreads_ {0};
bool running_ {true};
bool stopping_ {false};
unsigned minThreads_;
const unsigned maxThreads_;
@@ -85,7 +81,7 @@ public:
private:
std::reference_wrapper<ThreadPool> threadPool_;
const unsigned maxConcurrent_ {1};
std::mutex lock_;
std::mutex lock_ {};
unsigned current_ {0};
std::queue<std::function<void()>> tasks_ {};
@@ -108,7 +104,7 @@ public:
void run(std::function<void()>&& task)
{
std::lock_guard lock(state_->mtx);
std::lock_guard<std::mutex> lock(state_->mtx);
if (state_->shutdown_)
return;
state_->pendingTasks++;
@@ -118,7 +114,7 @@ public:
private:
struct SharedState
{
std::mutex mtx;
std::mutex mtx {};
std::condition_variable cv {};
unsigned pendingTasks {0};
unsigned ongoingTasks {0};
@@ -129,7 +125,7 @@ private:
void destroy(bool wait = true)
{
std::unique_lock lock(mtx);
std::unique_lock<std::mutex> lock(mtx);
if (destroyed)
return;
if (wait) {
@@ -145,7 +141,7 @@ private:
void run(const std::function<void()>& task)
{
{
std::lock_guard lock(mtx);
std::lock_guard<std::mutex> lock(mtx);
pendingTasks--;
ongoingTasks++;
}
@@ -153,7 +149,7 @@ private:
return;
task();
{
std::lock_guard lock(mtx);
std::lock_guard<std::mutex> lock(mtx);
ongoingTasks--;
cv.notify_all();
}
+3 -13
View File
@@ -85,16 +85,8 @@ using system_clock = std::chrono::system_clock;
using time_point = clock::time_point;
using duration = clock::duration;
OPENDHT_PUBLIC time_point from_system_time(system_clock::time_point t,
time_point steady_now,
system_clock::time_point system_now);
OPENDHT_PUBLIC time_point from_system_time(system_clock::time_point t);
OPENDHT_PUBLIC system_clock::time_point to_system_time(time_point t,
time_point steady_now,
system_clock::time_point system_now);
OPENDHT_PUBLIC system_clock::time_point to_system_time(time_point t);
OPENDHT_PUBLIC time_point from_time_t(std::time_t t);
OPENDHT_PUBLIC std::time_t to_time_t(time_point t);
time_point from_time_t(std::time_t t);
std::time_t to_time_t(time_point t);
template<class DT>
static std::string
@@ -134,8 +126,6 @@ print_time_relative(TimePoint now, TimePoint d)
return (d > now) ? concat("in "sv, print_duration(d - now)) : concat(print_duration(now - d), " ago"sv);
}
OPENDHT_PUBLIC std::string printByteCount(size_t bytes);
template<typename Duration = duration>
class uniform_duration_distribution : public std::uniform_int_distribution<typename Duration::rep>
{
@@ -194,7 +184,7 @@ unpackMsg(const Blob& b)
return msgpack::unpack((const char*) b.data(), b.size());
}
OPENDHT_PUBLIC msgpack::object* findMapValue(const msgpack::object& map, const char* key, size_t length);
msgpack::object* findMapValue(const msgpack::object& map, const char* key, size_t length);
inline msgpack::object*
findMapValue(const msgpack::object& map, const char* key)
+1 -14
View File
@@ -31,7 +31,6 @@ using namespace std::literals;
static constexpr auto VALUE_KEY_ID("id");
static const std::string VALUE_KEY_DAT("dat");
static const std::string VALUE_KEY_PRIO("p");
static const std::string VALUE_KEY_PUSHTYPE("pt");
static const std::string VALUE_KEY_SIGNATURE("sig");
static const std::string VALUE_KEY_SEQ("seq");
@@ -452,7 +451,6 @@ struct OPENDHT_PUBLIC Value
, signature(std::move(o.signature))
, cypher(std::move(o.cypher))
, priority(o.priority)
, pushType(std::move(o.pushType))
{}
template<typename Type>
@@ -576,7 +574,7 @@ struct OPENDHT_PUBLIC Value
template<typename Packer>
void msgpack_pack(Packer& pk) const
{
pk.pack_map(2 + (priority ? 1 : 0) + (!pushType.empty() ? 1 : 0));
pk.pack_map(2 + (priority ? 1 : 0));
pk.pack(VALUE_KEY_ID);
pk.pack(id);
pk.pack(VALUE_KEY_DAT);
@@ -585,10 +583,6 @@ struct OPENDHT_PUBLIC Value
pk.pack(VALUE_KEY_PRIO);
pk.pack(priority);
}
if (!pushType.empty()) {
pk.pack(VALUE_KEY_PUSHTYPE);
pk.pack(pushType);
}
}
template<typename Packer>
@@ -678,13 +672,6 @@ struct OPENDHT_PUBLIC Value
*/
unsigned priority {0};
/**
* Push notification type — set by the sender so that the proxy server
* can include it in the FCM/APNs payload without decrypting the value.
* Example: "audioCall", "videoCall"
*/
std::string pushType {};
inline bool isSignatureChecked() const { return signatureChecked; }
inline bool isDecrypted() const { return decrypted; }
bool checkSignature();
+53 -76
View File
@@ -5,7 +5,7 @@ project(
'cython',
license: 'MIT',
license_files: 'LICENSE',
version: '4.3.0',
version: '3.7.2',
meson_version: '>=1.1.0',
default_options: ['cpp_std=c++20', 'warning_level=3'],
)
@@ -15,23 +15,10 @@ nettle = dependency('nettle')
msgpack = dependency('msgpack-cxx', required: false)
argon2 = dependency('libargon2')
fmt = dependency('fmt')
proxy_enabled = get_option('proxy_client').enabled() or get_option('proxy_server').enabled()
openssl = dependency('openssl', required: proxy_enabled)
jsoncpp = dependency('jsoncpp', required: proxy_enabled)
llhttp = dependency('llhttp', 'libllhttp', required: proxy_enabled)
simdutf = dependency('simdutf', required: proxy_enabled)
# restinio is a header-only library that may not ship a pkg-config file
restinio_dep = dependency('restinio', required: false)
if not restinio_dep.found()
cpp = meson.get_compiler('cpp')
if cpp.has_header('restinio/all.hpp', required: proxy_enabled)
restinio_dep = declare_dependency()
endif
endif
openssl = dependency('openssl', required: get_option('proxy_client'))
jsoncpp = dependency('jsoncpp', required: get_option('proxy_client'))
llhttp = dependency('llhttp', 'libllhttp', required: get_option('proxy_client'))
simdutf = dependency('simdutf', required: get_option('proxy_client'))
io_uring = host_machine.system() == 'linux' ? dependency('liburing', required: false) : disabler()
deps = [
@@ -46,10 +33,6 @@ deps = [
llhttp,
]
if restinio_dep.found()
deps += restinio_dep
endif
if host_machine.system() == 'linux' and io_uring.found()
deps += io_uring
endif
@@ -96,7 +79,7 @@ if get_option('indexation').enabled()
opendht_src += 'src/indexation/pht.cpp'
add_project_arguments('-DOPENDHT_INDEXATION', language: 'cpp')
endif
if jsoncpp.found() and simdutf.found()
if jsoncpp.found()
opendht_src += ['src/base64.cpp']
add_project_arguments('-DOPENDHT_JSONCPP', language: 'cpp')
conf_data.set('jsoncpp_lib', ', jsoncpp')
@@ -135,17 +118,6 @@ opendht = library(
install: true,
)
opendht_dep_args = []
if get_option('default_library') != 'static'
opendht_dep_args += '-Dopendht_EXPORTS'
endif
opendht_dep = declare_dependency(
link_with: opendht,
include_directories: opendht_interface_inc,
compile_args: opendht_dep_args,
)
# Install library headers.
install_subdir('include', install_dir: get_option('includedir'), strip_directory: true)
@@ -156,13 +128,6 @@ conf_data.set('libdir', join_paths(get_option('prefix'), get_option('libdir')))
conf_data.set('includedir', join_paths(get_option('prefix'), get_option('includedir')))
conf_data.set('argon2_lib', ', libargon2')
conf_data.set('simdutf_lib', ', simdutf')
conf_data.set('opendht_public_cflags', '')
conf_data.set('opendht_c_public_cflags', '')
if get_option('default_library') != 'static'
conf_data.set('opendht_public_cflags', ' -Dopendht_EXPORTS')
conf_data.set('opendht_c_public_cflags', ' -Dopendht_c_EXPORTS')
endif
configure_file(
input: 'opendht.pc.in',
@@ -178,7 +143,9 @@ if get_option('c').enabled()
opendht_c = library(
'opendht-c',
['c/opendht.cpp', 'c/opendht_c.h'],
dependencies: [opendht_dep, msgpack, jsoncpp, fmt],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [msgpack, jsoncpp, fmt],
cpp_args: ['-DOPENDHT_C_BUILD', '-Dopendht_c_EXPORTS'],
install: true,
)
@@ -214,8 +181,10 @@ if get_option('python').require(
py.extension_module(
'opendht',
'python/opendht.pyx',
include_directories: opendht_interface_inc,
override_options: ['cython_language=cpp'],
dependencies: [opendht_dep, msgpack, jsoncpp, fmt, dep_py],
link_with: [opendht],
dependencies: [msgpack, jsoncpp, fmt, dep_py],
install: true,
)
endif
@@ -224,19 +193,25 @@ if get_option('tools').enabled()
dhtnode = executable(
'dhtnode',
'tools/dhtnode.cpp',
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
install: true,
)
dhtchat = executable(
'dhtchat',
'tools/dhtchat.cpp',
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
install: true,
)
dhtscanner = executable(
'dhtscanner',
'tools/dhtscanner.cpp',
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
install: true,
)
if llhttp.found()
@@ -249,7 +224,9 @@ if get_option('tools').enabled()
durl = executable(
'durl',
'tools/durl.cpp',
dependencies: [opendht_dep] + durl_deps,
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: durl_deps,
)
endif
endif
@@ -262,7 +239,9 @@ if get_option('tests').enabled()
'test_infohash',
'tests/test_infohash.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('Infohash', test_infohash)
@@ -270,49 +249,39 @@ if get_option('tests').enabled()
'test_value',
'tests/test_value.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('Value', test_value)
test_parsedmessage = executable(
'test_parsedmessage',
'tests/test_parsedmessage.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_inc,
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('ParsedMessage', test_parsedmessage)
test_networkengine = executable(
'test_networkengine',
'tests/test_networkengine.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_inc,
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('NetworkEngine', test_networkengine)
test_crypto = executable(
'test_crypto',
'tests/test_crypto.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack, gnutls],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack, gnutls],
)
test('Crypto', test_crypto, timeout: 60)
test('Crypto', test_crypto)
test_dhtrunner = executable(
'test_dhtrunner',
'tests/test_dhtrunner.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('DhtRunner', test_dhtrunner, timeout: 60)
test('DhtRunner', test_dhtrunner)
test_threadpool = executable(
'test_threadpool',
'tests/test_threadpool.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('ThreadPool', test_threadpool)
@@ -321,7 +290,9 @@ if get_option('tests').enabled()
'test_http',
'tests/test_http.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('Http', test_http)
endif
@@ -331,7 +302,9 @@ if get_option('tests').enabled()
'test_peerdiscovery',
'tests/test_peerdiscovery.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('PeerDiscovery', test_peerdiscovery)
endif
@@ -341,7 +314,9 @@ if get_option('tests').enabled()
'test_dhtproxy',
'tests/test_dhtproxy.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('DhtProxy', test_proxy)
@@ -350,7 +325,9 @@ if get_option('tests').enabled()
'test_dhtproxystress',
'tests/dhtproxy_stress_tester.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('DhtProxy Stress', test_proxystress)
endif
+1 -1
View File
@@ -7,4 +7,4 @@ Description: Distributed Hash Table library (C Wrapper)
Version: @VERSION@
Libs: -L${libdir} -lopendht-c
Requires.private: opendht gnutls >= 3.1 @argon2_lib@
Cflags: -I${includedir}@opendht_c_public_cflags@
Cflags: -I${includedir}
+1 -1
View File
@@ -9,4 +9,4 @@ Libs: -L${libdir} -lopendht -lfmt
Libs.private: @http_lib@ -pthread
Requires: gnutls >= 3.3@jsoncpp_lib@@openssl_lib@
Requires.private: nettle >= 2.4@argon2_lib@@llhttp_lib@@iouring_lib@@simdutf_lib@
Cflags: -I${includedir}@opendht_public_cflags@
Cflags: -I${includedir}
+6 -1
View File
@@ -820,8 +820,13 @@ cdef class DhtRunner(_WithID):
key._infohash, (<PkId>to)._pkid, val._value,
cpp.bindDoneCb(done_callback, <void*>cb_obj), permanent
)
elif isinstance(to, InfoHash):
self.thisptr.get().putEncrypted(
key._infohash, (<InfoHash>to)._infohash, val._value,
cpp.bindDoneCb(done_callback, <void*>cb_obj), permanent
)
else:
raise TypeError("'to' must be PublicKey or PkId")
raise TypeError("'to' must be PublicKey, PkId, or InfoHash")
except:
ref.Py_DECREF(cb_obj)
raise
+1
View File
@@ -282,6 +282,7 @@ cdef extern from "opendht/dhtrunner.h" namespace "dht":
void get(InfoHash key, GetCallback get_cb, DoneCallback done_cb, nullptr_t f, Where w)
void put(InfoHash key, shared_ptr[Value] val, DoneCallback done_cb, time_point created, bool permanent)
void putSigned(InfoHash key, shared_ptr[Value] val, DoneCallback done_cb, bool permanent)
void putEncrypted(InfoHash key, InfoHash to, shared_ptr[Value] val, DoneCallback done_cb, bool permanent)
void putEncrypted(InfoHash key, PkId to, shared_ptr[Value] val, DoneCallback done_cb, bool permanent)
void putEncrypted(InfoHash key, shared_ptr[PublicKey] to, shared_ptr[Value] val, DoneCallback done_cb, bool permanent)
void cancelPut(InfoHash key, shared_ptr[Value] val)
+5 -5
View File
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
[tool.cibuildwheel]
build = [
'cp3{9,10,11,12,13,14}-manylinux_x86_64',
'cp3{10,11,12,13,14}-manylinux_aarch64',
'cp3{11,12,13,14}-macosx_x86_64',
'cp3{11,12,13,14}-macosx_arm64',
'cp3{11,12,13,14}-win_amd64',
'cp3{9,10,11,12,13}-manylinux_x86_64',
'cp3{10,11,12,13}-manylinux_aarch64',
'cp3{11,12,13}-macosx_x86_64',
'cp3{11,12,13}-macosx_arm64',
'cp3{11,12,13}-win_amd64',
]
[tool.cibuildwheel.linux]
+1 -1
View File
@@ -226,7 +226,7 @@ impl DhtRunner {
pub fn put_encrypted<'a>(
&mut self,
h: &InfoHash,
to: &PkId,
to: &InfoHash,
v: Box<Value>,
done_cb: &'a mut (dyn FnMut(bool)),
permanent: bool,
+1 -1
View File
@@ -190,7 +190,7 @@ extern {
cb_user_data: *mut c_void,
permanent: bool);
pub fn dht_runner_put_encrypted(dht: *mut DhtRunner, h: *const InfoHash,
to: *const PkId, v: *const Value,
to: *const InfoHash, v: *const Value,
done_cb: extern fn(bool, *mut c_void),
cb_user_data: *mut c_void,
permanent: bool);
-15
View File
@@ -36,19 +36,4 @@ base64_decode(std::string_view str)
return buffer;
}
std::vector<unsigned char>
base64_default_or_url_decode(std::string_view str)
{
std::vector<unsigned char> buffer(simdutf::maximal_binary_length_from_base64(str.data(), str.size()));
simdutf::result r = simdutf::base64_to_binary(str.data(),
str.size(),
(char*) buffer.data(),
simdutf::base64_options::base64_default_or_url);
if (r.error) {
throw std::invalid_argument(concat("Invalid base64 input: "sv, simdutf::error_to_string(r.error)));
} else {
buffer.resize(r.count);
}
return buffer;
}
} // namespace dht
+1 -8
View File
@@ -21,12 +21,5 @@ std::string base64_encode(const std::vector<unsigned char>& str);
* @return a base64-decoded buffer
*/
std::vector<unsigned char> base64_decode(std::string_view str);
/**
* Decode a buffer in base64 or url style base64 (including '-' '_' ).
*
* @param str the input buffer
* @return a base64-decoded buffer
*/
std::vector<unsigned char> base64_default_or_url_decode(std::string_view str);
} // namespace dht
} // namespace dht
+54 -89
View File
@@ -11,7 +11,6 @@ extern "C" {
#include <nettle/gcm.h>
#include <nettle/aes.h>
#include <nettle/hmac.h>
#include <nettle/version.h>
#include <gnutls/crypto.h>
#include <argon2.h>
@@ -99,46 +98,6 @@ private:
#define GCM_DIGEST_SIZE GCM_BLOCK_SIZE
#endif
static void
gcmAes128Digest(gcm_aes128_ctx* ctx, uint8_t* digest)
{
#if NETTLE_VERSION_MAJOR >= 4
gcm_aes128_digest(ctx, digest);
#else
gcm_aes128_digest(ctx, GCM_DIGEST_SIZE, digest);
#endif
}
static void
gcmAes192Digest(gcm_aes192_ctx* ctx, uint8_t* digest)
{
#if NETTLE_VERSION_MAJOR >= 4
gcm_aes192_digest(ctx, digest);
#else
gcm_aes192_digest(ctx, GCM_DIGEST_SIZE, digest);
#endif
}
static void
gcmAes256Digest(gcm_aes256_ctx* ctx, uint8_t* digest)
{
#if NETTLE_VERSION_MAJOR >= 4
gcm_aes256_digest(ctx, digest);
#else
gcm_aes256_digest(ctx, GCM_DIGEST_SIZE, digest);
#endif
}
static void
hmacSha256Digest(hmac_sha256_ctx* ctx, uint8_t* digest)
{
#if NETTLE_VERSION_MAJOR >= 4
hmac_sha256_digest(ctx, digest);
#else
hmac_sha256_digest(ctx, SHA256_DIGEST_SIZE, digest);
#endif
}
Blob
aesEncrypt(const uint8_t* data, size_t data_length, const Blob& key)
{
@@ -156,19 +115,19 @@ aesEncrypt(const uint8_t* data, size_t data_length, const Blob& key)
gcm_aes128_set_key(&aes, key.data());
gcm_aes128_set_iv(&aes, GCM_IV_SIZE, ret.data());
gcm_aes128_encrypt(&aes, data_length, ret.data() + GCM_IV_SIZE, data);
gcmAes128Digest(&aes, ret.data() + GCM_IV_SIZE + data_length);
gcm_aes128_digest(&aes, GCM_DIGEST_SIZE, ret.data() + GCM_IV_SIZE + data_length);
} else if (key.size() == AES_LENGTHS[1]) {
struct gcm_aes192_ctx aes;
gcm_aes192_set_key(&aes, key.data());
gcm_aes192_set_iv(&aes, GCM_IV_SIZE, ret.data());
gcm_aes192_encrypt(&aes, data_length, ret.data() + GCM_IV_SIZE, data);
gcmAes192Digest(&aes, ret.data() + GCM_IV_SIZE + data_length);
gcm_aes192_digest(&aes, GCM_DIGEST_SIZE, ret.data() + GCM_IV_SIZE + data_length);
} else if (key.size() == AES_LENGTHS[2]) {
struct gcm_aes256_ctx aes;
gcm_aes256_set_key(&aes, key.data());
gcm_aes256_set_iv(&aes, GCM_IV_SIZE, ret.data());
gcm_aes256_encrypt(&aes, data_length, ret.data() + GCM_IV_SIZE, data);
gcmAes256Digest(&aes, ret.data() + GCM_IV_SIZE + data_length);
gcm_aes256_digest(&aes, GCM_DIGEST_SIZE, ret.data() + GCM_IV_SIZE + data_length);
}
return ret;
@@ -201,19 +160,19 @@ aesDecrypt(const uint8_t* data, size_t data_length, const Blob& key)
gcm_aes128_set_key(&aes, key.data());
gcm_aes128_set_iv(&aes, GCM_IV_SIZE, data);
gcm_aes128_decrypt(&aes, data_sz, ret.data(), data + GCM_IV_SIZE);
gcmAes128Digest(&aes, digest.data());
gcm_aes128_digest(&aes, GCM_DIGEST_SIZE, digest.data());
} else if (key.size() == AES_LENGTHS[1]) {
struct gcm_aes192_ctx aes;
gcm_aes192_set_key(&aes, key.data());
gcm_aes192_set_iv(&aes, GCM_IV_SIZE, data);
gcm_aes192_decrypt(&aes, data_sz, ret.data(), data + GCM_IV_SIZE);
gcmAes192Digest(&aes, digest.data());
gcm_aes192_digest(&aes, GCM_DIGEST_SIZE, digest.data());
} else if (key.size() == AES_LENGTHS[2]) {
struct gcm_aes256_ctx aes;
gcm_aes256_set_key(&aes, key.data());
gcm_aes256_set_iv(&aes, GCM_IV_SIZE, data);
gcm_aes256_decrypt(&aes, data_sz, ret.data(), data + GCM_IV_SIZE);
gcmAes256Digest(&aes, digest.data());
gcm_aes256_digest(&aes, GCM_DIGEST_SIZE, digest.data());
}
if (not std::equal(digest.begin(), digest.end(), data + data_length - GCM_DIGEST_SIZE)) {
@@ -487,7 +446,7 @@ PrivateKey::getPublicKey() const
const std::shared_ptr<PublicKey>&
PrivateKey::getSharedPublicKey() const
{
std::lock_guard lock(publicKeyMutex_);
std::lock_guard<std::mutex> lock(publicKeyMutex_);
if (not publicKey_) {
auto pk = std::make_shared<PublicKey>();
if (auto err = gnutls_pubkey_import_privkey(pk->pk, key, GNUTLS_KEY_KEY_CERT_SIGN | GNUTLS_KEY_CRL_SIGN, 0))
@@ -571,12 +530,6 @@ PublicKey::toString() const
}
if (err != GNUTLS_E_SUCCESS)
throw CryptoException(std::string("Could not print public key: ") + gnutls_strerror(err));
// GnuTLS reports the PEM size including the terminating null byte, which
// does not belong to the text and would end up embedded in whatever the
// string is copied into, such as the "owner" field of a serialized value.
ret.resize(sz);
if (not ret.empty() and ret.back() == '\0')
ret.pop_back();
return ret;
}
@@ -1006,7 +959,7 @@ Certificate::getPublicKey() const
const std::shared_ptr<PublicKey>&
Certificate::getSharedPublicKey() const
{
std::lock_guard lock(publicKeyMutex_);
std::lock_guard<std::mutex> lock(publicKeyMutex_);
if (not publicKey_) {
auto pk = std::make_shared<PublicKey>();
if (auto err = gnutls_pubkey_import_x509(pk->pk, cert, 0))
@@ -1294,16 +1247,14 @@ PrivateKey::generate(unsigned key_length, gnutls_pk_algorithm_t algo)
}
PrivateKey
PrivateKey::generateEC(gnutls_ecc_curve_t curve)
PrivateKey::generateEC()
{
gnutls_x509_privkey_t key;
if (gnutls_x509_privkey_init(&key) != GNUTLS_E_SUCCESS)
throw CryptoException("Unable to initialize private key.");
int err = gnutls_x509_privkey_generate(key,
GNUTLS_PK_EC,
curve == GNUTLS_ECC_CURVE_INVALID
? gnutls_sec_param_to_pk_bits(GNUTLS_PK_EC, GNUTLS_SEC_PARAM_ULTRA)
: GNUTLS_CURVE_TO_BITS(curve),
gnutls_sec_param_to_pk_bits(GNUTLS_PK_EC, GNUTLS_SEC_PARAM_ULTRA),
0);
if (err != GNUTLS_E_SUCCESS) {
gnutls_x509_privkey_deinit(key);
@@ -1404,11 +1355,8 @@ setRandomSerial(gnutls_x509_crt_t cert)
{
std::random_device rdev;
std::uniform_int_distribution<int64_t> dist {1};
const auto cert_serial = dist(rdev);
std::array<uint8_t, sizeof(cert_serial)> packed_serial;
for (size_t i = 0; i < packed_serial.size(); i++)
packed_serial[i] = cert_serial >> (8 * (packed_serial.size() - 1 - i));
gnutls_x509_crt_set_serial(cert, packed_serial.data(), packed_serial.size());
int64_t cert_serial = dist(rdev);
gnutls_x509_crt_set_serial(cert, &cert_serial, sizeof(cert_serial));
}
Certificate
@@ -1884,8 +1832,34 @@ RevocationList::getUpdateTime() const
return std::chrono::system_clock::from_time_t(t);
}
enum class Endian : uint32_t { LITTLE = 0, BIG = 1 };
template<typename T>
T
endian(T w, Endian endian = Endian::BIG)
{
// this gets optimized out into if (endian == host_endian) return w;
union {
uint64_t quad;
uint32_t islittle;
} t;
t.quad = 1;
if (t.islittle ^ (uint32_t) endian)
return w;
T r = 0;
// decent compilers will unroll this (gcc)
// or even convert straight into single bswap (clang)
for (size_t i = 0; i < sizeof(r); i++) {
r <<= 8;
r |= w & 0xff;
w >>= 8;
}
return r;
}
void
RevocationList::sign(const PrivateKey& key, const Certificate& ca, duration validity, uint64_t crlNumber)
RevocationList::sign(const PrivateKey& key, const Certificate& ca, duration validity)
{
if (auto err = gnutls_x509_crl_set_version(crl, 2))
throw CryptoException(std::string("Unable to set CRL version: ") + gnutls_strerror(err));
@@ -1895,28 +1869,19 @@ RevocationList::sign(const PrivateKey& key, const Certificate& ca, duration vali
throw CryptoException(std::string("Unable to set CRL update time: ") + gnutls_strerror(err));
if (auto err = gnutls_x509_crl_set_next_update(crl, std::chrono::system_clock::to_time_t(next_update)))
throw CryptoException(std::string("Unable to set CRL next update time: ") + gnutls_strerror(err));
uint64_t number {crlNumber};
uint64_t number {0};
size_t number_sz {sizeof(number)};
unsigned critical {0};
gnutls_x509_crl_get_number(crl, &number, &number_sz, &critical);
if (number == 0) {
// The CRL Number extension holds a DER integer of variable length, so it must be
// read as a big-endian byte string rather than straight into an uint64_t.
auto current = getNumber();
for (uint8_t byte : current)
number = (number << 8) | byte;
if (number == 0) {
// Initialize to a random number.
std::random_device rdev;
do {
std::generate_n((uint8_t*) &number, sizeof(number), std::bind(rand_byte, std::ref(rdev)));
} while (number == 0);
} else
number++;
}
// Store the number in network byte order.
uint8_t packedNumber[sizeof(number)];
for (size_t i = 0; i < sizeof(packedNumber); i++)
packedNumber[i] = (uint8_t) ((number >> (8 * (sizeof(packedNumber) - 1 - i))) & 0xff);
if (auto err = gnutls_x509_crl_set_number(crl, packedNumber, sizeof(packedNumber)))
throw CryptoException(std::string("Unable to set CRL number: ") + gnutls_strerror(err));
// initialize to a random number
number_sz = sizeof(number);
std::random_device rdev;
std::generate_n((uint8_t*) &number, sizeof(number), std::bind(rand_byte, std::ref(rdev)));
} else
number = endian(endian(number) + 1);
if (auto err = gnutls_x509_crl_set_number(crl, &number, sizeof(number)))
throw CryptoException(std::string("Unable to set CRL update time: ") + gnutls_strerror(err));
if (auto err = gnutls_x509_crl_sign2(crl, ca.cert, key.x509_key, GNUTLS_DIG_SHA512, 0))
throw CryptoException(std::string("Unable to sign certificate revocation list: ") + gnutls_strerror(err));
// to be able to actually use the CRL we need to serialize/deserialize it
@@ -2080,7 +2045,7 @@ hkdf_extract(const Blob& salt, const Blob& ikm)
hmac_sha256_set_key(&ctx, salt.size(), salt.data());
hmac_sha256_update(&ctx, ikm.size(), ikm.data());
Blob prk(SHA256_DIGEST_SIZE);
hmacSha256Digest(&ctx, prk.data());
hmac_sha256_digest(&ctx, SHA256_DIGEST_SIZE, prk.data());
return prk;
}
@@ -2098,7 +2063,7 @@ hkdf_expand(const Blob& prk, const uint8_t* info, size_t info_len, size_t len)
hmac_sha256_update(&ctx, info_len, info);
hmac_sha256_update(&ctx, 1, &i);
T.resize(SHA256_DIGEST_SIZE);
hmacSha256Digest(&ctx, T.data());
hmac_sha256_digest(&ctx, SHA256_DIGEST_SIZE, T.data());
okm.insert(okm.end(), T.begin(), T.end());
i++;
}
@@ -2258,7 +2223,7 @@ webPushEncrypt(const Blob& p256dh,
Blob ciphertext(plaintext.size() + GCM_DIGEST_SIZE);
gcm_aes128_encrypt(&aes, plaintext.size(), ciphertext.data(), plaintext.data());
gcmAes128Digest(&aes, ciphertext.data() + plaintext.size());
gcm_aes128_digest(&aes, GCM_DIGEST_SIZE, ciphertext.data() + plaintext.size());
// 6. Construct Output
Blob output;
+38 -151
View File
@@ -35,8 +35,6 @@ Dht::updateStatus(sa_family_t af)
auto& d = dht(af);
auto old = d.status;
d.status = d.getStatus(scheduler.time());
if (d.status == NodeStatus::Disconnected && bootstrap_pending && bootstrapJob && !bootstrap_nodes.empty())
d.status = NodeStatus::Connecting;
if (d.status != old) {
auto& other = dht(af == AF_INET ? AF_INET6 : AF_INET);
if (other.status == NodeStatus::Disconnected && d.status == NodeStatus::Disconnected) {
@@ -387,7 +385,7 @@ Dht::paginate(std::weak_ptr<Search> ws, Sp<Query> query, SearchNode* n)
n->pagination_queries[query].push_back(select_q);
if (logger_)
logger_->debug("[search {}] [node {}] Sending {}", sr->id.toString(), n->node->toString(), select_q->toString());
logger_->debug("[search {}] [node {}] Sending {}", sr->id.to_view(), n->node->toString(), select_q->toString());
n->getStatus[select_q] = network_engine.sendGetValues(
n->node, sr->id, *select_q, -1, onSelectDone, std::bind(&Dht::searchNodeGetExpired, this, _1, _2, ws, select_q));
}
@@ -656,20 +654,6 @@ Dht::searchSynchedNodeListen(const Sp<Search>& sr, SearchNode& n)
for (const auto& l : sr->listeners) {
const auto& query = l.second.query;
auto openListenSocket = [this, ws, query, node = n.node] {
return node->openSocket([this, ws, query](const Sp<Node>& node, net::RequestAnswer&& answer) mutable {
/* on new values */
if (auto sr = ws.lock()) {
scheduler.edit(sr->nextSearchStep, scheduler.time());
if (auto sn = sr->getNode(node)) {
if (not answer.ntoken.empty())
sn->refreshToken(answer.ntoken);
sn->onValues(query, std::move(answer), types, scheduler);
}
}
});
};
auto r = n.listenStatus.find(query);
if (n.getListenTime(r, listenExp) > scheduler.time())
continue;
@@ -681,7 +665,21 @@ Dht::searchSynchedNodeListen(const Sp<Search>& sr, SearchNode& n)
r = n.listenStatus
.emplace(std::piecewise_construct,
std::forward_as_tuple(query),
std::forward_as_tuple(l.second.get_cb, l.second.sync_cb, openListenSocket()))
std::forward_as_tuple(l.second.get_cb,
l.second.sync_cb,
n.node->openSocket([this,
ws,
query](const Sp<Node>& node,
net::RequestAnswer&& answer) mutable {
/* on new values */
if (auto sr = ws.lock()) {
scheduler.edit(sr->nextSearchStep, scheduler.time());
sr->insertNode(node, scheduler.time(), answer.ntoken);
if (auto sn = sr->getNode(node)) {
sn->onValues(query, std::move(answer), types, scheduler);
}
}
})))
.first;
r->second.cacheExpirationJob = scheduler.add(time_point::max(), [this, ws, query, node = n.node] {
if (auto sr = ws.lock()) {
@@ -690,8 +688,6 @@ Dht::searchSynchedNodeListen(const Sp<Search>& sr, SearchNode& n)
}
}
});
} else if (not n.node->getSocket(r->second.socketId)) {
r->second.socketId = openListenSocket();
}
auto new_req = network_engine.sendListen(
n.node,
@@ -1421,19 +1417,11 @@ Dht::storageChanged(const InfoHash& id, Storage& st, const Sp<Value>& v, bool ne
}
bool
Dht::storageStore(const InfoHash& id,
const Sp<Value>& value,
time_point created,
const SockAddr& sa,
bool permanent,
time_point expiration)
Dht::storageStore(const InfoHash& id, const Sp<Value>& value, time_point created, const SockAddr& sa, bool permanent)
{
const auto& now = scheduler.time();
created = std::min(created, now);
if (expiration == time_point::min())
expiration = permanent ? time_point::max() : created + getType(value->type).expiration;
else if (not permanent)
expiration = std::min(expiration, created + getType(value->type).expiration);
auto expiration = permanent ? time_point::max() : created + getType(value->type).expiration;
if (expiration < now)
return false;
@@ -1450,7 +1438,7 @@ Dht::storageStore(const InfoHash& id,
StorageBucket* store_bucket {nullptr};
bool is_local = !sa;
if (sa)
store_bucket = &store_quota.try_emplace(sa, sa).first->second;
store_bucket = &store_quota[sa];
else
store_bucket = local_store_quota.get();
@@ -1856,18 +1844,15 @@ std::string
Dht::getStorageLog() const
{
std::ostringstream out;
for (const auto& s : store) {
const auto& st = s.second;
out << "Storage " << s.first << " " << st.listeners.size() << " list., " << st.valueCount() << " values, "
<< printByteCount(st.totalSize()) << "\n";
}
for (const auto& s : store)
out << printStorageLog(s);
out << std::endl << std::endl;
std::multimap<size_t, const SockAddr*> q_map;
for (const auto& ip : store_quota)
if (ip.second.size())
q_map.emplace(ip.second.size(), &ip.first);
for (auto ip = q_map.rbegin(); ip != q_map.rend(); ++ip)
out << "IP " << ip->second->toString() << " uses " << printByteCount(ip->first) << std::endl;
out << "IP " << ip->second->toString() << " uses " << ip->first << " bytes" << std::endl;
out << std::endl;
out << "Total " << store.size() << " storages, " << total_values << " values (";
if (total_store_size < 1024)
@@ -1890,8 +1875,11 @@ std::string
Dht::getStorageLog(const InfoHash& h) const
{
auto s = store.find(h);
if (s == store.end())
return fmt::format("Storage {} empty\n", h);
if (s == store.end()) {
std::ostringstream out;
out << "Storage " << h << " empty" << std::endl;
return out.str();
}
return printStorageLog(*s);
}
@@ -1901,14 +1889,8 @@ Dht::printStorageLog(const decltype(store)::value_type& s) const
std::ostringstream out;
using namespace std::chrono;
const auto& st = s.second;
const auto& now = scheduler.time();
out << "Storage " << s.first << " " << st.listeners.size() << " list., " << st.valueCount() << " values, "
<< printByteCount(st.totalSize()) << std::endl;
for (const auto& v : st.getValues()) {
out << " Value " << v.data->id << " size: " << printByteCount(v.data->size())
<< " created: " << print_time_relative(now, v.created)
<< " expires: " << print_time_relative(now, v.expiration) << std::endl;
}
out << "Storage " << s.first << " " << st.listeners.size() << " list., " << st.valueCount() << " values ("
<< st.totalSize() << " bytes)" << std::endl;
if (not st.local_listeners.empty())
out << " " << st.local_listeners.size() << " local listeners" << std::endl;
for (const auto& node_listeners : st.listeners) {
@@ -2241,18 +2223,6 @@ void
Dht::onConnected()
{
stopBootstrap();
auto restartSearches = [this](std::map<InfoHash, Sp<Search>>& searches) {
const auto& now = scheduler.time();
for (auto& srp : searches) {
auto& sr = *srp.second;
if (sr.done)
continue;
scheduler.edit(sr.nextSearchStep, now);
}
};
restartSearches(dht4.searches);
restartSearches(dht6.searches);
auto callbacks = std::move(onConnectCallbacks_);
while (not callbacks.empty()) {
callbacks.front()();
@@ -2270,19 +2240,10 @@ Dht::onDisconnected()
void
Dht::bootstrap()
{
if (dht4.status != NodeStatus::Disconnected || dht6.status != NodeStatus::Disconnected) {
// The job that led here has already run, and the scheduler does not
// clear the handle to a job it has executed. onDisconnected() reads a
// set handle as "a bootstrap is already scheduled" and does nothing,
// so leaving it set here means the node never tries to rejoin the
// network once it has lost it. Clearing it is what says that no
// bootstrap is pending any more.
bootstrapJob.reset();
if (dht4.status != NodeStatus::Disconnected || dht6.status != NodeStatus::Disconnected)
return;
}
if (logger_)
logger_->debug("[{}] Bootstraping", myid.to_view());
bootstrap_pending = false;
for (const auto& boootstrap : bootstrap_nodes) {
try {
auto ips = network_engine.getSocket()->resolve(boootstrap.first, boootstrap.second);
@@ -2309,7 +2270,6 @@ void
Dht::startBootstrap()
{
stopBootstrap();
bootstrap_pending = !bootstrap_nodes.empty();
bootstrapJob = scheduler.add(scheduler.time(), std::bind(&Dht::bootstrap, this));
}
@@ -2317,7 +2277,6 @@ void
Dht::stopBootstrap()
{
scheduler.cancel(bootstrapJob);
bootstrap_pending = false;
bootstrap_period = BOOTSTRAP_PERIOD;
}
@@ -2363,9 +2322,6 @@ Dht::confirmNodes()
std::vector<ValuesExport>
Dht::exportValues() const
{
const auto& now = scheduler.time();
const auto system_now = system_clock::now();
std::vector<ValuesExport> e {};
e.reserve(store.size());
for (const auto& h : store) {
@@ -2377,16 +2333,9 @@ Dht::exportValues() const
const auto& vals = h.second.getValues();
pk.pack_array(vals.size());
for (const auto& v : vals) {
pk.pack_array(4);
pk.pack(to_system_time(v.created, now, system_now));
pk.pack_array(2);
pk.pack(v.created.time_since_epoch().count());
v.data->msgpack_pack(pk);
if (const auto& store_addr = v.store_bucket ? v.store_bucket->getAddr() : SockAddr {}) {
pk.pack_bin(store_addr.getLength());
pk.pack_bin_body(reinterpret_cast<const char*>(store_addr.get()), store_addr.getLength());
} else {
pk.pack_bin(0);
}
pk.pack(to_system_time(v.expiration, now, system_now));
}
ve.second = {buffer.data(), buffer.data() + buffer.size()};
e.push_back(std::move(ve));
@@ -2398,15 +2347,10 @@ void
Dht::importValues(const std::vector<ValuesExport>& import)
{
const auto& now = scheduler.time();
const auto system_now = system_clock::now();
unsigned imported = 0, ignored = 0;
size_t imported_size = 0;
for (const auto& value : import) {
if (value.second.empty()) {
ignored++;
if (value.second.empty())
continue;
}
try {
msgpack::unpacked msg;
@@ -2419,66 +2363,25 @@ Dht::importValues(const std::vector<ValuesExport>& import)
if (valel.type != msgpack::type::ARRAY or valel.via.array.size < 2)
throw msgpack::type_error();
time_point val_time;
time_point expiration = time_point::min();
Value tmp_val;
SockAddr store_addr;
try {
const bool uses_system_time = valel.via.array.ptr[0].type == msgpack::type::EXT;
if (uses_system_time) {
val_time = from_system_time(valel.via.array.ptr[0].as<system_clock::time_point>(),
now,
system_now);
} else {
val_time = time_point {
time_point::duration {valel.via.array.ptr[0].as<time_point::duration::rep>()}};
}
val_time = time_point {
time_point::duration {valel.via.array.ptr[0].as<time_point::duration::rep>()}};
tmp_val.msgpack_unpack(valel.via.array.ptr[1]);
if (valel.via.array.size >= 3) {
auto addr_blob = valel.via.array.ptr[2].as<Blob>();
if (not addr_blob.empty()) {
store_addr = SockAddr(reinterpret_cast<const sockaddr*>(addr_blob.data()),
static_cast<socklen_t>(addr_blob.size()));
}
}
if (valel.via.array.size >= 4) {
if (uses_system_time) {
expiration = from_system_time(valel.via.array.ptr[3].as<system_clock::time_point>(),
now,
system_now);
} else {
expiration = time_point {
time_point::duration {valel.via.array.ptr[3].as<time_point::duration::rep>()}};
}
}
} catch (const std::exception&) {
if (logger_)
logger_->error("Error reading value at {}", value.first.to_view());
ignored++;
continue;
}
val_time = std::min(val_time, now);
auto val_size = tmp_val.size();
if (storageStore(value.first,
std::make_shared<Value>(std::move(tmp_val)),
val_time,
store_addr,
false,
expiration)) {
imported++;
imported_size += val_size;
} else {
ignored++;
}
storageStore(value.first, std::make_shared<Value>(std::move(tmp_val)), val_time);
}
} catch (const std::exception&) {
ignored++;
if (logger_)
logger_->error("Error reading values at {}", value.first.to_view());
continue;
}
}
if (logger_)
logger_->debug("Imported {} values, {}, ignored {}", imported, dht::printByteCount(imported_size), ignored);
}
std::vector<NodeExport>
@@ -2563,17 +2466,6 @@ Dht::onError(Sp<net::Request> req, net::DhtProtocolException e)
continue;
n->token.clear();
n->last_get_reply = time_point::min();
for (auto& listen : n->listenStatus) {
if (listen.second.req and listen.second.req->pending())
n->node->cancelRequest(listen.second.req);
listen.second.req = {};
n->node->closeSocket(listen.second.socketId);
if (listen.second.refresh) {
listen.second.refresh->cancel();
listen.second.refresh = {};
}
listen.second.cache.onSynced(false);
}
searchSendGetValues(sr);
scheduler.edit(sr->nextSearchStep, scheduler.time());
break;
@@ -2730,13 +2622,11 @@ Dht::onListen(Sp<Node> node, const InfoHash& hash, const Blob& token, size_t soc
}
Query q = query;
storageAddListener(hash, node, socket_id, std::move(q), version);
net::RequestAnswer answer {};
answer.ntoken = makeToken(node->getAddr(), false);
return answer;
return {};
}
void
Dht::onListenDone(const Sp<Node>& node, net::RequestAnswer& answer, Sp<Search>& sr)
Dht::onListenDone(const Sp<Node>& /* node */, net::RequestAnswer& /* answer */, Sp<Search>& sr)
{
// if (logger_)
// logger_->d(sr->id, node->id, "[search %s] [node %s] Got listen confirmation",
@@ -2744,9 +2634,6 @@ Dht::onListenDone(const Sp<Node>& node, net::RequestAnswer& answer, Sp<Search>&
if (not sr->done) {
const auto& now = scheduler.time();
if (not answer.ntoken.empty())
if (auto sn = sr->getNode(node))
sn->refreshToken(answer.ntoken);
searchSendGetValues(sr);
scheduler.edit(sr->nextSearchStep, now);
}
+75 -92
View File
@@ -25,10 +25,8 @@ struct DhtProxyClient::OperationState
struct DhtProxyClient::Listener
{
// The proxy server is a single logical source that re-delivers values
// (push refresh, listen restart): don't count duplicates.
Listener(ValueCallback&& cb)
: cache(std::move(cb), false)
Listener(OpValueCache&& c)
: cache(std::move(c))
{}
OpValueCache cache;
@@ -36,26 +34,26 @@ struct DhtProxyClient::Listener
Sp<OperationState> opstate;
std::shared_ptr<http::Request> request;
#ifdef OPENDHT_PUSH_NOTIFICATIONS
std::unique_ptr<asio::system_timer> refreshSubscriberTimer;
std::unique_ptr<asio::steady_timer> refreshSubscriberTimer;
#endif
};
struct PermanentPut
{
PermanentPut(const Sp<Value>& v, std::unique_ptr<asio::system_timer>&& j, const Sp<std::atomic_bool>& o)
PermanentPut(const Sp<Value>& v, std::unique_ptr<asio::steady_timer>&& j, const Sp<std::atomic_bool>& o)
: value(v)
, refreshPutTimer(std::move(j))
, ok(o)
{}
Sp<Value> value;
std::unique_ptr<asio::system_timer> refreshPutTimer;
std::unique_ptr<asio::steady_timer> refreshPutTimer;
Sp<std::atomic_bool> ok;
};
struct DhtProxyClient::ProxySearch
{
SearchCache ops {false};
SearchCache ops {};
std::unique_ptr<asio::steady_timer> opExpirationTimer;
std::map<size_t, Listener> listeners {};
std::map<Value::Id, PermanentPut> puts {};
@@ -197,7 +195,7 @@ DhtProxyClient::stop()
if (not isDestroying_.exchange(true)) {
std::shared_ptr<http::Resolver> resolver;
{
std::lock_guard l(resolverLock_);
std::lock_guard<std::mutex> l(resolverLock_);
resolver = std::move(resolver_);
}
if (resolver)
@@ -206,7 +204,7 @@ DhtProxyClient::stop()
if (infoState_)
infoState_->cancel = true;
{
std::lock_guard lock(requestLock_);
std::lock_guard<std::mutex> lock(requestLock_);
for (auto& request : requests_)
request.second->cancel();
}
@@ -221,7 +219,7 @@ DhtProxyClient::stop()
std::vector<Sp<Value>>
DhtProxyClient::getLocal(const InfoHash& k, const Value::Filter& filter) const
{
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(k);
if (s == searches_.end())
return {};
@@ -231,7 +229,7 @@ DhtProxyClient::getLocal(const InfoHash& k, const Value::Filter& filter) const
Sp<Value>
DhtProxyClient::getLocalById(const InfoHash& k, Value::Id id) const
{
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(k);
if (s == searches_.end())
return {};
@@ -241,7 +239,7 @@ DhtProxyClient::getLocalById(const InfoHash& k, Value::Id id) const
void
DhtProxyClient::cancelAllListeners()
{
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
if (logger_)
logger_->debug("[proxy:client] [listeners] [{} searches] cancel all", searches_.size());
for (auto& s : searches_) {
@@ -268,7 +266,7 @@ DhtProxyClient::shutdown(ShutdownCallback cb, bool)
NodeStatus
DhtProxyClient::getStatus(sa_family_t af) const
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
switch (af) {
case AF_INET:
return statusIpv4_;
@@ -282,7 +280,7 @@ DhtProxyClient::getStatus(sa_family_t af) const
bool
DhtProxyClient::isRunning(sa_family_t af) const
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
switch (af) {
case AF_INET:
return statusIpv4_ != NodeStatus::Disconnected;
@@ -299,7 +297,7 @@ DhtProxyClient::periodic(const uint8_t*, size_t, SockAddr, const time_point& /*n
// Exec all currently stored callbacks
decltype(callbacks_) callbacks;
{
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks = std::move(callbacks_);
}
for (auto& callback : callbacks)
@@ -359,7 +357,7 @@ DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb, Va
}
if (not values.empty() and cb) {
{
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([opstate, cb, values = std::move(values)]() {
if (not opstate->stop.load() and not cb(values)) {
opstate->stop.store(true);
@@ -384,7 +382,7 @@ DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb, Va
}
if (donecb) {
{
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([donecb, opstate]() {
donecb(opstate->ok, {});
opstate->stop.store(true);
@@ -393,12 +391,12 @@ DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb, Va
loopSignal_();
}
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -421,12 +419,12 @@ DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_po
std::shared_ptr<std::atomic_bool> ok;
if (permanent) {
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
ok = std::make_shared<std::atomic_bool>(true);
auto& search = searches_[key];
if (val->id) {
auto id = val->id;
auto refreshPutTimer = std::make_unique<asio::system_timer>(httpContext_,
auto refreshPutTimer = std::make_unique<asio::steady_timer>(httpContext_,
proxy::OP_TIMEOUT - proxy::OP_MARGIN);
refreshPutTimer->async_wait(
std::bind(&DhtProxyClient::handleRefreshPut, this, std::placeholders::_1, key, id));
@@ -445,7 +443,7 @@ DhtProxyClient::put(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_po
if (ok)
*ok = result;
if (cb) {
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([cb, result]() { cb(result, {}); });
}
loopSignal_();
@@ -466,7 +464,7 @@ DhtProxyClient::handleRefreshPut(const asio::error_code& ec, InfoHash key, Value
}
if (logger_)
logger_->debug("[proxy:client] [put] [refresh {}]", key.to_view());
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto search = searches_.find(key);
if (search != searches_.end()) {
auto p = search->second.puts.find(id);
@@ -482,7 +480,7 @@ DhtProxyClient::handleRefreshPut(const asio::error_code& ec, InfoHash key, Value
std::shared_ptr<http::Request>
DhtProxyClient::buildRequest(const std::string& target)
{
std::unique_lock l(resolverLock_);
std::unique_lock<std::mutex> l(resolverLock_);
auto resolver = resolver_;
l.unlock();
if (not resolver)
@@ -537,12 +535,12 @@ DhtProxyClient::doPut(const InfoHash& key, Sp<Value> val, DoneCallbackSimple cb,
auto id = dht::Value(parsedValue).id;
val->id = id;
if (permanent) {
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto& search = searches_[key];
auto it = search.pendingPuts.find(val);
if (it != search.pendingPuts.end()) {
auto sok = std::make_shared<std::atomic_bool>(ok);
auto refreshPutTimer = std::make_unique<asio::system_timer>(httpContext_,
auto refreshPutTimer = std::make_unique<asio::steady_timer>(httpContext_,
proxy::OP_TIMEOUT
- proxy::OP_MARGIN);
refreshPutTimer->async_wait(
@@ -567,12 +565,12 @@ DhtProxyClient::doPut(const InfoHash& key, Sp<Value> val, DoneCallbackSimple cb,
if (cb)
cb(ok);
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -642,7 +640,7 @@ DhtProxyClient::getProxyInfos()
logger_->debug("[proxy:client] [info] requesting proxy server node information");
auto infoState = std::make_shared<InfoState>();
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
if (infoState_)
infoState_->cancel = true;
infoState_ = infoState;
@@ -657,7 +655,7 @@ DhtProxyClient::getProxyInfos()
auto resolver = std::make_shared<http::Resolver>(httpContext_, proxyUrl_, logger_);
queryProxyInfo(infoState, resolver, AF_INET);
queryProxyInfo(infoState, resolver, AF_INET6);
std::lock_guard l(resolverLock_);
std::lock_guard<std::mutex> l(resolverLock_);
resolver_ = resolver;
}
@@ -703,7 +701,7 @@ DhtProxyClient::queryProxyInfo(const Sp<InfoState>& infoState, const Sp<http::Re
}
}
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
@@ -711,7 +709,7 @@ DhtProxyClient::queryProxyInfo(const Sp<InfoState>& infoState, const Sp<http::Re
if (infoState->cancel.load())
return;
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -726,7 +724,7 @@ DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos, const sa_family_t fa
{
if (isDestroying_)
return;
std::unique_lock l(lockCurrentProxyInfos_);
std::unique_lock<std::mutex> l(lockCurrentProxyInfos_);
auto oldStatus = std::max(statusIpv4_, statusIpv6_);
auto& status = family == AF_INET ? statusIpv4_ : statusIpv6_;
auto& pubAddress = family == AF_INET ? publicAddressV4_ : publicAddressV6_;
@@ -763,7 +761,7 @@ DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos, const sa_family_t fa
addresses.emplace_back(publicAddressV4_);
if (publicAddressV6_)
addresses.emplace_back(publicAddressV6_);
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back(
[cb = publicAddressChangedCb_, addresses = std::move(addresses)]() { cb(std::move(addresses)); });
}
@@ -787,7 +785,7 @@ DhtProxyClient::onProxyInfos(const Json::Value& proxyInfos, const sa_family_t fa
listenerRestartTimer_->expires_at(std::chrono::steady_clock::now());
listenerRestartTimer_->async_wait(std::bind(&DhtProxyClient::restartListeners, this, std::placeholders::_1));
if (not onConnectCallbacks_.empty()) {
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([cbs = std::move(onConnectCallbacks_)]() mutable {
while (not cbs.empty()) {
cbs.front()();
@@ -822,7 +820,7 @@ DhtProxyClient::parsePublicAddress(const Json::Value& val)
std::vector<SockAddr>
DhtProxyClient::getPublicAddress(sa_family_t family)
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
std::vector<SockAddr> result;
if (publicAddressV6_ && family != AF_INET)
result.emplace_back(publicAddressV6_);
@@ -839,7 +837,7 @@ DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filt
if (isDestroying_)
return 0;
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto& search = searches_[key];
auto query = std::make_shared<Query>(Select {}, std::move(where));
return search.ops
@@ -876,7 +874,7 @@ DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filt
system_clock::time_point t) {
if (opstate->stop)
return false;
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(key);
if (s != searches_.end()) {
auto l = s->second.listeners.find(token);
@@ -893,8 +891,8 @@ DhtProxyClient::listen(const InfoHash& key, ValueCallback cb, Value::Filter filt
* (if the proxy crash for any reason)
*/
if (!l->second.refreshSubscriberTimer)
l->second.refreshSubscriberTimer = std::make_unique<asio::system_timer>(httpContext_);
l->second.refreshSubscriberTimer->expires_at(std::chrono::system_clock::now() + proxy::OP_TIMEOUT
l->second.refreshSubscriberTimer = std::make_unique<asio::steady_timer>(httpContext_);
l->second.refreshSubscriberTimer->expires_at(std::chrono::steady_clock::now() + proxy::OP_TIMEOUT
- proxy::OP_MARGIN);
l->second.refreshSubscriberTimer->async_wait(
std::bind(&DhtProxyClient::handleResubscribe, this, std::placeholders::_1, key, token, opstate));
@@ -931,7 +929,7 @@ DhtProxyClient::handleResubscribe(const asio::error_code& ec,
}
if (opstate->stop)
return;
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(key);
if (s != searches_.end()) {
auto l = s->second.listeners.find(token);
@@ -950,7 +948,7 @@ DhtProxyClient::cancelListen(const InfoHash& key, size_t gtoken)
if (logger_)
logger_->debug("[proxy:client] [search {}] cancel listen {}", key.to_view(), gtoken);
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
// find the listener in cache
auto it = searches_.find(key);
if (it == searches_.end())
@@ -981,7 +979,7 @@ DhtProxyClient::handleExpireListener(const asio::error_code& ec, const InfoHash&
if (logger_)
logger_->debug("[proxy:client] [listen {}] expire listener", key.to_view());
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto search = searches_.find(key);
if (search == searches_.end())
return;
@@ -1017,12 +1015,12 @@ DhtProxyClient::handleExpireListener(const asio::error_code& ec, const InfoHash&
opFailed();
}
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -1099,7 +1097,7 @@ DhtProxyClient::sendListen(const restinio::http_request_header_t& header,
if (cb) {
auto expired = json.get("expired", Json::Value(false)).asBool();
{
std::lock_guard lock(lockCallbacks_);
std::lock_guard<std::mutex> lock(lockCallbacks_);
callbacks_.emplace_back([cb, value, opstate, expired]() {
if (not opstate->stop.load()
and not cb({value}, expired, system_clock::time_point::min()))
@@ -1126,12 +1124,12 @@ DhtProxyClient::sendListen(const restinio::http_request_header_t& header,
opFailed();
}
if (not isDestroying_) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->add_on_status_callback([request, seconds = this->listenKeepIdle()](unsigned status_code) {
@@ -1155,7 +1153,7 @@ DhtProxyClient::opFailed()
if (logger_)
logger_->error("[proxy:client] proxy request failed");
{
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
statusIpv4_ = NodeStatus::Disconnected;
statusIpv6_ = NodeStatus::Disconnected;
}
@@ -1188,15 +1186,15 @@ DhtProxyClient::restartListeners(const asio::error_code& ec)
if (logger_)
logger_->d("[proxy:client] [listeners] refresh permanent puts");
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
for (auto& search : searches_) {
const auto& key = search.first;
for (auto& put : search.second.puts) {
doPut(key, put.second.value, [ok = put.second.ok](bool result) { *ok = result; }, time_point::max(), true);
if (!put.second.refreshPutTimer) {
put.second.refreshPutTimer = std::make_unique<asio::system_timer>(httpContext_);
put.second.refreshPutTimer = std::make_unique<asio::steady_timer>(httpContext_);
}
put.second.refreshPutTimer->expires_at(std::chrono::system_clock::now() + proxy::OP_TIMEOUT
put.second.refreshPutTimer->expires_at(std::chrono::steady_clock::now() + proxy::OP_TIMEOUT
- proxy::OP_MARGIN);
put.second.refreshPutTimer->async_wait(
std::bind(&DhtProxyClient::handleRefreshPut, this, std::placeholders::_1, key, put.first));
@@ -1251,7 +1249,7 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
#ifdef OPENDHT_PUSH_NOTIFICATIONS
{
// If a push notification is received, the proxy is up and running
std::lock_guard l(lockCurrentProxyInfos_);
std::lock_guard<std::mutex> l(lockCurrentProxyInfos_);
auto oldStatus = std::max(statusIpv4_, statusIpv6_);
if (oldStatus != NodeStatus::Connected)
launchConnectedCbs_ = true;
@@ -1266,7 +1264,7 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
logger_->debug("[proxy:client] [push] ignoring push for other session");
return PushNotificationResult::IgnoredWrongSession;
}
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto timeout = notification.find("timeout");
if (timeout != notification.cend()) {
InfoHash key(timeout->second);
@@ -1277,10 +1275,10 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
auto vid = std::stoull(vidIt->second);
auto& put = search.puts.at(vid);
if (!put.refreshPutTimer)
put.refreshPutTimer = std::make_unique<asio::system_timer>(httpContext_,
std::chrono::system_clock::now());
put.refreshPutTimer = std::make_unique<asio::steady_timer>(httpContext_,
std::chrono::steady_clock::now());
else
put.refreshPutTimer->expires_at(std::chrono::system_clock::now());
put.refreshPutTimer->expires_at(std::chrono::steady_clock::now());
put.refreshPutTimer->async_wait(
std::bind(&DhtProxyClient::handleRefreshPut, this, std::placeholders::_1, key, vid));
ret = PushNotificationResult::PutRefresh;
@@ -1292,12 +1290,12 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
: PushNotificationResult::ListenRefresh;
}
} else {
auto key = InfoHash(notification.at("key"));
system_clock::time_point sendTime = system_clock::time_point::min();
try {
sendTime = system_clock::time_point(std::chrono::milliseconds(std::stoull(notification.at("t"))));
} catch (...) {
}
auto key = InfoHash(notification.at("key"));
auto& search = searches_.at(key);
auto expired = notification.find("exp");
for (auto& list : search.listeners) {
@@ -1310,25 +1308,13 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
if (expired == notification.end()) {
auto cb = list.second.cb;
auto oldValues = list.second.cache.getValues();
auto receivedIds = std::make_shared<std::set<Value::Id>>();
get(
key,
[cb, sendTime, receivedIds](const std::vector<Sp<Value>>& vals) {
for (const auto& v : vals)
receivedIds->insert(v->id);
return cb(vals, false, sendTime);
},
[cb, oldValues, sendTime, receivedIds](bool ok) {
if (!ok)
return;
[cb, sendTime](const std::vector<Sp<Value>>& vals) { return cb(vals, false, sendTime); },
[cb, oldValues, sendTime](bool /*ok*/) {
// Decrement old values refcount to expire values not
// present in the new list
std::vector<Sp<Value>> toExpire;
for (const auto& v : oldValues)
if (receivedIds->find(v->id) == receivedIds->end())
toExpire.emplace_back(v);
if (!toExpire.empty())
cb(toExpire, true, sendTime);
cb(oldValues, true, sendTime);
});
ret = PushNotificationResult::Values;
} else {
@@ -1340,11 +1326,11 @@ DhtProxyClient::pushNotificationReceived([[maybe_unused]] const std::map<std::st
ids.emplace_back(std::stoull(substr));
}
{
std::lock_guard lockCb(lockCallbacks_);
std::lock_guard<std::mutex> lockCb(lockCallbacks_);
callbacks_.emplace_back([this, key, token, opstate, ids, sendTime]() {
if (opstate->stop)
return;
std::lock_guard lock(searchLock_);
std::lock_guard<std::mutex> lock(searchLock_);
auto s = searches_.find(key);
if (s == searches_.end())
return;
@@ -1390,11 +1376,11 @@ DhtProxyClient::resubscribe([[maybe_unused]] const InfoHash& key,
restinio::http_request_header_t header;
header.method(restinio::http_method_subscribe());
header.request_target("/key/" + key.toString());
header.request_target("/" + key.toString());
if (!listener.refreshSubscriberTimer) {
listener.refreshSubscriberTimer = std::make_unique<asio::system_timer>(httpContext_);
listener.refreshSubscriberTimer = std::make_unique<asio::steady_timer>(httpContext_);
}
listener.refreshSubscriberTimer->expires_at(std::chrono::system_clock::now() + proxy::OP_TIMEOUT - proxy::OP_MARGIN);
listener.refreshSubscriberTimer->expires_at(std::chrono::steady_clock::now() + proxy::OP_TIMEOUT - proxy::OP_MARGIN);
listener.refreshSubscriberTimer->async_wait(
std::bind(&DhtProxyClient::handleResubscribe, this, std::placeholders::_1, key, token, opstate));
auto vcb = listener.cb;
@@ -1436,20 +1422,17 @@ void
DhtProxyClient::setPushNotificationToken([[maybe_unused]] const std::string& token)
{
#ifdef OPENDHT_PUSH_NOTIFICATIONS
{
std::unique_lock l(lockCurrentProxyInfos_);
if (deviceKey_ == token)
return;
std::unique_lock<std::mutex> l(lockCurrentProxyInfos_);
if (deviceKey_ != token) {
deviceKey_ = token;
if (!(statusIpv4_ == NodeStatus::Connected || statusIpv6_ == NodeStatus::Connected))
return;
}
if (logger_)
logger_->debug("[proxy:client] [push] token changed, resubscribing");
std::lock_guard lock(searchLock_);
for (auto& search : searches_) {
for (auto& listener : search.second.listeners) {
resubscribe(search.first, listener.first, listener.second);
if (statusIpv4_ == NodeStatus::Connected || statusIpv6_ == NodeStatus::Connected) {
if (logger_)
logger_->debug("[proxy:client] [push] token changed, resubscribing");
for (auto& search : searches_) {
for (auto& listener : search.second.listeners) {
resubscribe(search.first, listener.first, listener.second);
}
}
}
}
#endif
+48 -59
View File
@@ -176,7 +176,7 @@ DhtProxyServer::ConnectionListener::state_changed(const restinio::connection_sta
void
DhtProxyServer::onConnectionClosed(restinio::connection_id_t id)
{
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
auto it = listeners_.find(id);
if (it != listeners_.end()) {
dht_->cancelListen(it->second.hash, std::move(it->second.token));
@@ -210,7 +210,7 @@ DhtProxyServer::PermanentPut::msgpack_unpack(const msgpack::object& o)
clientId = cid->as<std::string>();
}
if (auto exp = findMapValue(o, "exp"sv)) {
expiration = sys_clock::from_time_t(exp->as<time_t>());
expiration = from_time_t(exp->as<time_t>());
}
if (auto token = findMapValue(o, "token"sv)) {
pushToken = token->as<std::string>();
@@ -240,7 +240,7 @@ DhtProxyServer::Listener::msgpack_unpack(const msgpack::object& o)
clientId = cid->as<std::string>();
}
if (auto exp = findMapValue(o, "exp"sv)) {
expiration = sys_clock::from_time_t(exp->as<time_t>());
expiration = from_time_t(exp->as<time_t>());
}
if (auto sid = findMapValue(o, "sid"sv)) {
if (not sessionCtx)
@@ -381,13 +381,13 @@ DhtProxyServer::saveState(Os& stream)
msgpack::packer<Os> pk(&stream);
pk.pack_map(2);
{
std::lock_guard lock(lockSearchPuts_);
std::lock_guard<std::mutex> lock(lockSearchPuts_);
pk.pack("puts");
pk.pack(puts_);
}
#ifdef OPENDHT_PUSH_NOTIFICATIONS
{
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
pk.pack("pushListeners");
pk.pack(pushListeners_);
}
@@ -408,13 +408,13 @@ DhtProxyServer::loadState(Is& is, size_t size)
if (oh.get().type != msgpack::type::MAP)
continue;
if (auto puts = findMapValue(oh.get(), "puts"sv)) {
std::lock_guard lock(lockSearchPuts_);
std::lock_guard<std::mutex> lock(lockSearchPuts_);
puts_ = puts->as<decltype(puts_)>();
if (logger_)
logger_->debug("[proxy:server] Loading {} persistent puts", puts_.size());
for (auto& put : puts_) {
for (auto& pput : put.second.puts) {
pput.second.expireTimer = std::make_unique<asio::system_timer>(io_context(),
pput.second.expireTimer = std::make_unique<asio::steady_timer>(io_context(),
pput.second.expiration);
pput.second.expireTimer->async_wait(std::bind(&DhtProxyServer::handleCancelPermamentPut,
this,
@@ -432,12 +432,12 @@ DhtProxyServer::loadState(Is& is, size_t size)
json["to"] = clientId;
json["vid"] = std::to_string(vid);
if (sessionCtx) {
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
}
return json;
};
pput.second.expireNotifyTimer = std::make_unique<asio::system_timer>(io_context(),
pput.second.expireNotifyTimer = std::make_unique<asio::steady_timer>(io_context(),
pput.second.expiration
- proxy::OP_MARGIN);
pput.second.expireNotifyTimer->async_wait(
@@ -450,7 +450,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
pput.second.topic));
}
#endif
dht_->put(put.first, pput.second.value, DoneCallbackSimple {}, dht::time_point::max(), true);
dht_->put(put.first, pput.second.value, DoneCallbackSimple {}, time_point::max(), true);
}
}
} else {
@@ -459,7 +459,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
}
#ifdef OPENDHT_PUSH_NOTIFICATIONS
if (auto pushListeners = findMapValue(oh.get(), "pushListeners"sv)) {
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
pushListeners_ = pushListeners->as<decltype(pushListeners_)>();
if (logger_)
logger_->debug("[proxy:server] Loading {} push listeners", pushListeners_.size());
@@ -487,7 +487,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
expired);
});
// expire notify
listener.expireNotifyTimer = std::make_unique<asio::system_timer>(io_context(),
listener.expireNotifyTimer = std::make_unique<asio::steady_timer>(io_context(),
listener.expiration
- proxy::OP_MARGIN);
auto jsonProvider = [infoHash = listeners.first.toString(),
@@ -496,7 +496,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
Json::Value json;
json["timeout"] = infoHash;
json["to"] = clientId;
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
return json;
};
@@ -509,7 +509,7 @@ DhtProxyServer::loadState(Is& is, size_t size)
listener.type,
listener.topic));
// cancel push listen
listener.expireTimer = std::make_unique<asio::system_timer>(io_context(),
listener.expireTimer = std::make_unique<asio::steady_timer>(io_context(),
listener.expiration);
listener.expireTimer->async_wait(std::bind(&DhtProxyServer::handleCancelPushListen,
this,
@@ -546,7 +546,7 @@ DhtProxyServer::~DhtProxyServer()
saveState(stateFile);
}
if (dht_) {
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
for (auto& l : listeners_) {
dht_->cancelListen(l.second.hash, std::move(l.second.token));
if (l.second.response)
@@ -571,18 +571,6 @@ DhtProxyServer::~DhtProxyServer()
ioContext_->stop();
if (serverThread_.joinable())
serverThread_.join();
{
// ~Request notifies DONE and its handler erases from requests_:
// detach pending push requests so nothing calls back into this object.
decltype(requests_) pending;
{
std::lock_guard l(requestLock_);
pending = std::move(requests_);
}
for (auto& r : pending)
r.second->add_on_state_change_callback({});
pending.clear();
}
if (logger_)
logger_->debug("[proxy:server] http server closed");
}
@@ -829,7 +817,7 @@ DhtProxyServer::listen(restinio::request_handle_t request, restinio::router::rou
auto response = std::make_shared<ResponseByPartsBuilder>(
initHttpResponse(request->create_response<ResponseByParts>()));
response->flush();
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
// save the listener to handle a disconnect
auto& session = listeners_[request->connection_id()];
session.hash = infoHash;
@@ -919,7 +907,6 @@ DhtProxyServer::subscribe(restinio::request_handle_t request, restinio::router::
Json::Value r;
auto* char_data = reinterpret_cast<const char*>(request->body().data());
auto reader = std::unique_ptr<Json::CharReader>(jsonReaderBuilder_.newCharReader());
if (!reader->parse(char_data, char_data + request->body().size(), &r, &err)) {
auto response = initHttpResponse(request->create_response(restinio::status_bad_request()));
response.set_body(RESP_MSG_JSON_INCORRECT);
@@ -944,7 +931,7 @@ DhtProxyServer::subscribe(restinio::request_handle_t request, restinio::router::
logger_->debug("[proxy:server] [subscribe {}] [client {}] [session {}]", infoHash, clientId, sessionId);
// Insert new or return existing push listeners of a token
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
auto& pushListener = pushListeners_[pushToken];
auto& pushListeners = pushListener.listeners[infoHash];
@@ -958,25 +945,25 @@ DhtProxyServer::subscribe(restinio::request_handle_t request, restinio::router::
listIt->clientId = clientId;
listIt->sessionCtx = std::make_shared<PushSessionContext>(sessionId);
} else {
std::lock_guard l(listIt->sessionCtx->lock);
std::lock_guard<std::mutex> l(listIt->sessionCtx->lock);
listIt->sessionCtx->sessionId = sessionId;
}
auto& listener = *listIt;
// Expiration (wall-clock: persisted as epoch time, survives restarts)
auto timeout = sys_clock::now() + proxy::OP_TIMEOUT;
// Expiration
auto timeout = std::chrono::steady_clock::now() + proxy::OP_TIMEOUT;
listener.expiration = timeout;
listener.type = type;
listener.topic = topic;
if (listener.expireNotifyTimer)
listener.expireNotifyTimer->expires_at(timeout - proxy::OP_MARGIN);
else
listener.expireNotifyTimer = std::make_unique<asio::system_timer>(io_context(), timeout - proxy::OP_MARGIN);
listener.expireNotifyTimer = std::make_unique<asio::steady_timer>(io_context(), timeout - proxy::OP_MARGIN);
auto jsonProvider = [h = infoHash.toString(), clientId, sessionCtx = listener.sessionCtx]() {
Json::Value json;
json["timeout"] = h;
json["to"] = clientId;
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
return json;
};
@@ -988,7 +975,7 @@ DhtProxyServer::subscribe(restinio::request_handle_t request, restinio::router::
listener.type,
listener.topic));
if (!listener.expireTimer)
listener.expireTimer = std::make_unique<asio::system_timer>(io_context(), timeout);
listener.expireTimer = std::make_unique<asio::steady_timer>(io_context(), timeout);
else
listener.expireTimer->expires_at(timeout);
listener.expireTimer->async_wait(std::bind(&DhtProxyServer::handleCancelPushListen,
@@ -1123,7 +1110,7 @@ DhtProxyServer::handleCancelPushListen(const asio::error_code& ec,
}
if (logger_)
logger_->debug("[proxy:server] [listen:push {}] cancelled for {}", key, clientId);
std::lock_guard lock(lockListener_);
std::lock_guard<std::mutex> lock(lockListener_);
auto pushListener = pushListeners_.find(pushToken);
if (pushListener == pushListeners_.end())
@@ -1164,24 +1151,19 @@ DhtProxyServer::handlePushListen(const InfoHash& infoHash,
json["t"] = Json::Value::Int64(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count());
{
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
}
// Build a comma-separated list of ids from the values. This is intended to be used when
// streaming from the iOS notification extension in order to filter out unwanted values.
std::string ids;
std::string pushTypes;
for (const auto& value : values) {
if (!ids.empty()) {
if (!ids.empty())
ids += ",";
pushTypes += ",";
}
pushTypes += value->pushType;
ids += std::to_string(value->id);
}
json["ids"] = ids;
json["pt"] = pushTypes;
// If message is expired copy the value ID to the `exp` field.
// This is presumably used by the Android notification system.
@@ -1218,7 +1200,14 @@ DhtProxyServer::sendPushNotification(
try {
std::shared_ptr<http::Request> request;
if (type == PushType::UnifiedPush) {
request = std::make_shared<http::Request>(io_context(), token, logger_);
http::Url tokenUrl(token);
request = std::make_shared<http::Request>(io_context(),
concat(tokenUrl.protocol, "://"sv, tokenUrl.host),
tokenUrl.service,
tokenUrl.protocol.find("https") == 0,
logger_);
request->set_target(tokenUrl.target);
request->set_header_field(restinio::http_field_t::host, std::string(tokenUrl.host));
} else {
request = std::make_shared<http::Request>(io_context(),
pushHostPort_.first,
@@ -1237,8 +1226,8 @@ DhtProxyServer::sendPushNotification(
Blob auth; ///< Auth secret
auto delim = topicView.find('|');
if (delim != std::string_view::npos) {
pubKey = base64_default_or_url_decode(topicView.substr(0, delim));
auth = base64_default_or_url_decode(topicView.substr(delim + 1));
pubKey = base64_decode(topicView.substr(0, delim));
auth = base64_decode(topicView.substr(delim + 1));
}
request->set_header_field(restinio::http_field_t::ttl, "86400");
@@ -1313,12 +1302,12 @@ DhtProxyServer::sendPushNotification(
if (state == http::Request::State::DONE) {
if (logger_ and response.status_code != 200)
logger_->error("[proxy:server] [notification] push failed: {}", response.status_code);
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
});
{
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_[reqid] = request;
}
request->send();
@@ -1341,7 +1330,7 @@ DhtProxyServer::sendPushNotification(
if (logger_)
logger_->error("[proxy:server] [notification] error send push: {}", e.what());
if (reqid) {
std::lock_guard l(requestLock_);
std::lock_guard<std::mutex> l(requestLock_);
requests_.erase(reqid);
}
}
@@ -1360,7 +1349,7 @@ DhtProxyServer::handleCancelPermamentPut(const asio::error_code& ec, const InfoH
}
if (logger_)
logger_->debug("[proxy:server] [put {}] cancel permament put {}", key, vid);
std::lock_guard lock(lockSearchPuts_);
std::lock_guard<std::mutex> lock(lockSearchPuts_);
auto sPuts = puts_.find(key);
if (sPuts == puts_.end())
return;
@@ -1417,8 +1406,8 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
sessionId = pVal["session_id"].asString();
topic = pVal["topic"].asString();
}
std::lock_guard lock(lockSearchPuts_);
auto timeout = sys_clock::now() + proxy::OP_TIMEOUT;
std::lock_guard<std::mutex> lock(lockSearchPuts_);
auto timeout = std::chrono::steady_clock::now() + proxy::OP_TIMEOUT;
auto& sPuts = puts_[infoHash];
if (value->id == Value::INVALID_ID) {
for (auto& pp : sPuts.puts) {
@@ -1434,7 +1423,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
if (not pp.second.sessionCtx)
pp.second.sessionCtx = std::make_shared<PushSessionContext>(sessionId);
else {
std::lock_guard l(pp.second.sessionCtx->lock);
std::lock_guard<std::mutex> l(pp.second.sessionCtx->lock);
pp.second.sessionCtx->sessionId = sessionId;
}
}
@@ -1452,7 +1441,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
pput.expiration = timeout;
if (not pput.expireTimer) {
// cancel permanent put
pput.expireTimer = std::make_unique<asio::system_timer>(io_context(), timeout);
pput.expireTimer = std::make_unique<asio::steady_timer>(io_context(), timeout);
#ifdef OPENDHT_PUSH_NOTIFICATIONS
if (not pushToken.empty()) {
pput.pushToken = pushToken;
@@ -1469,7 +1458,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
if (not pput.sessionCtx)
pput.sessionCtx = std::make_shared<PushSessionContext>(sessionId);
else {
std::lock_guard l(pput.sessionCtx->lock);
std::lock_guard<std::mutex> l(pput.sessionCtx->lock);
pput.sessionCtx->sessionId = sessionId;
}
}
@@ -1486,12 +1475,12 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
json["timeout"] = infoHash.toString();
json["to"] = clientId;
json["vid"] = std::to_string(vid);
std::lock_guard l(sessionCtx->lock);
std::lock_guard<std::mutex> l(sessionCtx->lock);
json["s"] = sessionCtx->sessionId;
return json;
};
if (!pput.expireNotifyTimer)
pput.expireNotifyTimer = std::make_unique<asio::system_timer>(io_context(),
pput.expireNotifyTimer = std::make_unique<asio::steady_timer>(io_context(),
timeout - proxy::OP_MARGIN);
else
pput.expireNotifyTimer->expires_at(timeout - proxy::OP_MARGIN);
@@ -1520,7 +1509,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
response.done();
}
},
dht::time_point::max(),
time_point::max(),
permanent);
return restinio::request_handling_status_t::accepted;
} else {
+136 -100
View File
@@ -76,7 +76,7 @@ DhtRunner::run(const char* ip4, const char* ip6, const char* service, Config& co
void
DhtRunner::run(const Config& config, Context&& context)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
auto expected = State::Idle;
if (not running.compare_exchange_strong(expected, State::Running)) {
if (context.logger)
@@ -99,11 +99,6 @@ DhtRunner::run(const Config& config, Context&& context)
auto state_path = config.dht_config.node_config.persist_path;
if (not state_path.empty())
state_path += "_port.txt";
// Ports loaded from the persisted state are a best-effort hint to keep a
// stable identity across restarts; remember which ones we set so that we
// can fall back to random ports if they are already in use (e.g. a fast
// restart while the previous instance still holds them).
bool triedSavedPort = false;
if (not state_path.empty() && (local4.getPort() == 0 || local6.getPort() == 0)) {
std::ifstream inConfig(state_path);
if (inConfig.is_open()) {
@@ -115,7 +110,6 @@ DhtRunner::run(const Config& config, Context&& context)
fmt::ptr(this),
port);
local4.setPort(port);
triedSavedPort = true;
}
}
if (inConfig >> port) {
@@ -125,7 +119,6 @@ DhtRunner::run(const Config& config, Context&& context)
fmt::ptr(this),
port);
local6.setPort(port);
triedSavedPort = true;
}
}
}
@@ -143,25 +136,12 @@ DhtRunner::run(const Config& config, Context&& context)
if (config.proxy_server.empty()) {
if (not context.sock) {
try {
context.sock.reset(new net::UdpSocket(local4, local6, context.logger));
} catch (const DhtException&) {
if (not triedSavedPort)
throw;
// The saved ports are unavailable (typically still held by a
// previous instance): fall back to OS-assigned ports.
if (context.logger)
context.logger->w("[runner %p] Saved ports unavailable, falling back to random ports",
fmt::ptr(this));
local4.setPort(0);
local6.setPort(0);
context.sock.reset(new net::UdpSocket(local4, local6, context.logger));
}
context.sock.reset(new net::UdpSocket(local4, local6, context.logger));
}
context.sock->setOnReceive([&](net::PacketList&& pkts) {
net::PacketList ret;
{
std::lock_guard lck(sock_mtx);
std::lock_guard<std::mutex> lck(sock_mtx);
rcv.splice(rcv.end(), std::move(pkts));
size_t dropped = 0;
while (rcv.size() > net::RX_QUEUE_MAX_SIZE) {
@@ -206,6 +186,9 @@ DhtRunner::run(const Config& config, Context&& context)
if (context.certificateStore) {
dht_->setLocalCertificateStore(std::move(context.certificateStore));
}
if (context.certificateStorePkId) {
dht_->setLocalCertificateStore(std::move(context.certificateStorePkId));
}
if (context.publicAddressChangedCb) {
dht_->setOnPublicAddressChanged(std::move(context.publicAddressChangedCb));
}
@@ -214,19 +197,19 @@ DhtRunner::run(const Config& config, Context&& context)
return;
dht_thread = std::thread([this]() {
while (running != State::Idle) {
std::unique_lock lk(dht_mtx);
std::unique_lock<std::mutex> lk(dht_mtx);
time_point wakeup = loop_();
auto hasJobToDo = [this]() {
if (running == State::Idle)
return true;
{
std::lock_guard lck(sock_mtx);
std::lock_guard<std::mutex> lck(sock_mtx);
if (not rcv.empty())
return true;
}
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (not pending_ops_prio.empty())
return true;
auto s = getStatus();
@@ -301,7 +284,7 @@ DhtRunner::run(const Config& config, Context&& context)
void
DhtRunner::shutdown(ShutdownCallback cb, bool stop)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
auto expected = State::Running;
if (not running.compare_exchange_strong(expected, State::Stopping)) {
if (expected == State::Stopping and ongoing_ops) {
@@ -361,7 +344,7 @@ DhtRunner::checkShutdown()
{
decltype(shutdownCallbacks_) cbs;
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Stopping or ongoing_ops)
return false;
cbs = std::move(shutdownCallbacks_);
@@ -376,7 +359,7 @@ void
DhtRunner::join()
{
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (running.exchange(State::Idle) == State::Idle)
return;
cv.notify_all();
@@ -395,7 +378,7 @@ DhtRunner::join()
dht_thread.join();
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (ongoing_ops and logger_) {
logger_->warn("[runner {:p}] stopping with {:d} remaining ops", fmt::ptr(this), ongoing_ops.load());
}
@@ -405,7 +388,7 @@ DhtRunner::join()
shutdownCallbacks_.clear();
}
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
resetDht();
status4 = NodeStatus::Disconnected;
status6 = NodeStatus::Disconnected;
@@ -415,7 +398,7 @@ DhtRunner::join()
SockAddr
DhtRunner::getBound(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
if (auto sock = dht_->getSocket())
return sock->getBound(af);
@@ -425,7 +408,7 @@ DhtRunner::getBound(sa_family_t af) const
in_port_t
DhtRunner::getBoundPort(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
if (auto sock = dht_->getSocket())
return sock->getPort(af);
@@ -435,7 +418,7 @@ DhtRunner::getBoundPort(sa_family_t af) const
void
DhtRunner::dumpTables() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->dumpTables();
}
@@ -460,7 +443,7 @@ DhtRunner::getNodeId() const
std::pair<size_t, size_t>
DhtRunner::getStoreSize() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
return {};
return dht_->getStoreSize();
@@ -469,7 +452,7 @@ DhtRunner::getStoreSize() const
void
DhtRunner::setStorageLimit(size_t limit)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
throw std::runtime_error("dht is not running");
return dht_->setStorageLimit(limit);
@@ -478,7 +461,7 @@ DhtRunner::setStorageLimit(size_t limit)
size_t
DhtRunner::getStorageLimit() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
throw std::runtime_error("dht is not running");
return dht_->getStorageLimit();
@@ -487,7 +470,7 @@ DhtRunner::getStorageLimit() const
void
DhtRunner::setLocalStorageLimit(size_t limit)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
throw std::runtime_error("dht is not running");
return dht_->setLocalStorageLimit(limit);
@@ -496,7 +479,7 @@ DhtRunner::setLocalStorageLimit(size_t limit)
size_t
DhtRunner::getLocalStorageLimit() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
throw std::runtime_error("dht is not running");
return dht_->getLocalStorageLimit();
@@ -505,7 +488,7 @@ DhtRunner::getLocalStorageLimit() const
std::vector<NodeExport>
DhtRunner::exportNodes() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
return {};
return dht_->exportNodes();
@@ -514,7 +497,7 @@ DhtRunner::exportNodes() const
std::vector<ValuesExport>
DhtRunner::exportValues() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (!dht_)
return {};
return dht_->exportValues();
@@ -523,7 +506,7 @@ DhtRunner::exportValues() const
void
DhtRunner::setLogger(const Sp<Logger>& logger)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
logger_ = logger;
if (dht_)
dht_->setLogger(logger);
@@ -532,7 +515,7 @@ DhtRunner::setLogger(const Sp<Logger>& logger)
void
DhtRunner::setLogFilter(const InfoHash& f)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
dht_->setLogFilter(f);
}
@@ -540,28 +523,22 @@ DhtRunner::setLogFilter(const InfoHash& f)
void
DhtRunner::registerType(const ValueType& type)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->registerType(type);
}
void
DhtRunner::registerInsecureType(const ValueType& type)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->registerInsecureType(type);
}
void
DhtRunner::importValues(const std::vector<ValuesExport>& values)
{
{
std::lock_guard lck(dht_mtx);
dht_->importValues(values);
}
// Recompute the runner wakeup after import schedules value expirations.
std::lock_guard lck(storage_mtx);
pending_ops_prio.emplace([](SecureDht&) {});
cv.notify_all();
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->importValues(values);
}
unsigned
@@ -571,7 +548,7 @@ DhtRunner::getNodesStats(sa_family_t af,
unsigned* cached_return,
unsigned* incoming_return) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
const auto stats = dht_->getNodesStats(af);
if (good_return)
*good_return = stats.good_nodes;
@@ -587,7 +564,7 @@ DhtRunner::getNodesStats(sa_family_t af,
NodeStats
DhtRunner::getNodesStats(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getNodesStats(af);
}
@@ -595,7 +572,7 @@ NodeInfo
DhtRunner::getNodeInfo() const
{
NodeInfo info {};
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
info = dht_->getNodeInfo();
info.ongoing_ops = ongoing_ops;
@@ -605,7 +582,7 @@ DhtRunner::getNodeInfo() const
void
DhtRunner::getNodeInfo(std::function<void(std::shared_ptr<NodeInfo>)> cb)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
ongoing_ops++;
pending_ops_prio.emplace([cb = std::move(cb), this](SecureDht& dht) {
auto sinfo = std::make_shared<NodeInfo>();
@@ -620,44 +597,44 @@ DhtRunner::getNodeInfo(std::function<void(std::shared_ptr<NodeInfo>)> cb)
std::vector<unsigned>
DhtRunner::getNodeMessageStats(bool in) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getNodeMessageStats(in);
}
std::string
DhtRunner::getStorageLog() const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getStorageLog();
}
std::string
DhtRunner::getStorageLog(const InfoHash& f) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getStorageLog(f);
}
std::string
DhtRunner::getRoutingTablesLog(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getRoutingTablesLog(af);
}
std::string
DhtRunner::getSearchesLog(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getSearchesLog(af);
}
std::string
DhtRunner::getSearchLog(const InfoHash& f, sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return dht_->getSearchLog(f, af);
}
std::vector<SockAddr>
DhtRunner::getPublicAddress(sa_family_t af) const
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
return dht_->getPublicAddress(af);
return {};
@@ -674,7 +651,7 @@ DhtRunner::getPublicAddressStr(sa_family_t af) const
void
DhtRunner::getPublicAddress(std::function<void(std::vector<SockAddr>&&)> cb, sa_family_t af)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
ongoing_ops++;
pending_ops_prio.emplace([cb = std::move(cb), this, af](SecureDht& dht) {
cb(dht.getPublicAddress(af));
@@ -686,14 +663,22 @@ DhtRunner::getPublicAddress(std::function<void(std::vector<SockAddr>&&)> cb, sa_
void
DhtRunner::registerCertificate(const std::shared_ptr<crypto::Certificate>& cert)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
dht_->registerCertificate(cert);
}
void
DhtRunner::setLocalCertificateStore(CertificateStoreQueryLegacy&& query_method)
{
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
dht_->setLocalCertificateStore(std::forward<CertificateStoreQueryLegacy>(query_method));
}
void
DhtRunner::setLocalCertificateStore(CertificateStoreQuery&& query_method)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
dht_->setLocalCertificateStore(std::forward<CertificateStoreQuery>(query_method));
}
@@ -706,7 +691,7 @@ DhtRunner::loop_()
decltype(pending_ops) ops {};
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
auto s = getStatus();
ops = (pending_ops_prio.empty()
&& (s == NodeStatus::Connected or s == NodeStatus::Disconnected or running == State::Stopping))
@@ -722,7 +707,7 @@ DhtRunner::loop_()
decltype(rcv) received {};
decltype(rcv) received_treated {};
{
std::lock_guard lck(sock_mtx);
std::lock_guard<std::mutex> lck(sock_mtx);
// move to stack
received = std::move(rcv);
}
@@ -757,7 +742,7 @@ DhtRunner::loop_()
}
if (not received_treated.empty()) {
std::lock_guard lck(sock_mtx);
std::lock_guard<std::mutex> lck(sock_mtx);
if (rcv_free.size() < net::RX_QUEUE_MAX_SIZE)
rcv_free.splice(rcv_free.end(), std::move(received_treated));
}
@@ -781,7 +766,7 @@ DhtRunner::loop_()
void
DhtRunner::get(InfoHash hash, GetCallback vcb, DoneCallback dcb, Value::Filter f, Where w)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (dcb)
@@ -803,7 +788,7 @@ DhtRunner::get(const std::string& key, GetCallback vcb, DoneCallbackSimple dcb,
void
DhtRunner::query(const InfoHash& hash, QueryCallback cb, DoneCallback done_cb, Query q)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (done_cb)
@@ -821,7 +806,7 @@ std::future<size_t>
DhtRunner::listen(InfoHash hash, ValueCallback vcb, Value::Filter f, Where w)
{
auto ret_token = std::make_shared<std::promise<size_t>>();
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
ret_token->set_value(0);
@@ -843,7 +828,7 @@ DhtRunner::listen(const std::string& key, GetCallback vcb, Value::Filter f, Wher
void
DhtRunner::cancelListen(InfoHash h, size_t token)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Running)
return;
ongoing_ops++;
@@ -857,7 +842,7 @@ DhtRunner::cancelListen(InfoHash h, size_t token)
void
DhtRunner::cancelListen(InfoHash h, std::shared_future<size_t> ftoken)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Running)
return;
ongoing_ops++;
@@ -871,7 +856,7 @@ DhtRunner::cancelListen(InfoHash h, std::shared_future<size_t> ftoken)
void
DhtRunner::put(InfoHash hash, Value&& value, DoneCallback cb, time_point created, bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -888,7 +873,7 @@ DhtRunner::put(InfoHash hash, Value&& value, DoneCallback cb, time_point created
void
DhtRunner::put(InfoHash hash, std::shared_ptr<Value> value, DoneCallback cb, time_point created, bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -911,7 +896,7 @@ DhtRunner::put(const std::string& key, Value&& value, DoneCallbackSimple cb, tim
void
DhtRunner::cancelPut(const InfoHash& h, Value::Id id)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops.emplace([=](SecureDht& dht) { dht.cancelPut(h, id); });
cv.notify_all();
}
@@ -919,7 +904,7 @@ DhtRunner::cancelPut(const InfoHash& h, Value::Id id)
void
DhtRunner::cancelPut(const InfoHash& h, const std::shared_ptr<Value>& value)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops.emplace([=](SecureDht& dht) { dht.cancelPut(h, value->id); });
cv.notify_all();
}
@@ -927,7 +912,7 @@ DhtRunner::cancelPut(const InfoHash& h, const std::shared_ptr<Value>& value)
void
DhtRunner::putSigned(InfoHash hash, std::shared_ptr<Value> value, DoneCallback cb, bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -956,7 +941,7 @@ DhtRunner::putSigned(const std::string& key, Value&& value, DoneCallbackSimple c
void
DhtRunner::putEncrypted(InfoHash hash, const PkId& to, std::shared_ptr<Value> value, DoneCallback cb, bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -970,6 +955,35 @@ DhtRunner::putEncrypted(InfoHash hash, const PkId& to, std::shared_ptr<Value> va
cv.notify_all();
}
void
DhtRunner::putEncrypted(InfoHash hash, InfoHash to, std::shared_ptr<Value> value, DoneCallback cb, bool permanent)
{
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
cb(false, {});
return;
}
ongoing_ops++;
pending_ops.emplace([=, cb = std::move(cb), value = std::move(value)](SecureDht& dht) mutable {
dht.putEncrypted(hash, to, value, bindOpDoneCallback(std::move(cb)), permanent);
});
cv.notify_all();
}
void
DhtRunner::putEncrypted(InfoHash hash, InfoHash to, Value&& value, DoneCallback cb, bool permanent)
{
putEncrypted(hash, to, std::make_shared<Value>(std::move(value)), std::move(cb), permanent);
}
void
DhtRunner::putEncrypted(const std::string& key, InfoHash to, Value&& value, DoneCallback cb, bool permanent)
{
putEncrypted(InfoHash::get(key), to, std::forward<Value>(value), std::move(cb), permanent);
}
void
DhtRunner::putEncrypted(InfoHash hash,
const std::shared_ptr<crypto::PublicKey>& to,
@@ -977,7 +991,7 @@ DhtRunner::putEncrypted(InfoHash hash,
DoneCallback cb,
bool permanent)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -1001,7 +1015,7 @@ DhtRunner::putEncrypted(
void
DhtRunner::bootstrap(const std::string& host, const std::string& service)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([host, service](SecureDht& dht) mutable { dht.addBootstrap(host, service); });
cv.notify_all();
}
@@ -1009,7 +1023,7 @@ DhtRunner::bootstrap(const std::string& host, const std::string& service)
void
DhtRunner::bootstrap(const std::string& hostService)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
auto [h, s] = splitPort(hostService);
pending_ops_prio.emplace(
[host = std::string(h), service = std::string(s)](SecureDht& dht) { dht.addBootstrap(host, service); });
@@ -1019,7 +1033,7 @@ DhtRunner::bootstrap(const std::string& hostService)
void
DhtRunner::clearBootstrap()
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([](SecureDht& dht) mutable { dht.clearBootstrap(); });
cv.notify_all();
}
@@ -1031,7 +1045,7 @@ DhtRunner::bootstrap(std::vector<SockAddr> nodes, DoneCallbackSimple cb)
cb(false);
return;
}
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
ongoing_ops++;
pending_ops_prio.emplace([cb = bindOpDoneCallback(std::move(cb)), nodes = std::move(nodes)](SecureDht& dht) mutable {
auto rem = cb ? std::make_shared<std::pair<size_t, bool>>(nodes.size(), false) : nullptr;
@@ -1054,7 +1068,7 @@ DhtRunner::bootstrap(std::vector<SockAddr> nodes, DoneCallbackSimple cb)
void
DhtRunner::bootstrap(SockAddr addr, DoneCallbackSimple cb)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
if (cb)
@@ -1071,7 +1085,7 @@ DhtRunner::bootstrap(SockAddr addr, DoneCallbackSimple cb)
void
DhtRunner::bootstrap(const InfoHash& id, const SockAddr& address)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Running)
return;
pending_ops_prio.emplace([id, address](SecureDht& dht) mutable { dht.insertNode(id, address); });
@@ -1081,7 +1095,7 @@ DhtRunner::bootstrap(const InfoHash& id, const SockAddr& address)
void
DhtRunner::bootstrap(std::vector<NodeExport> nodes)
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
if (running != State::Running)
return;
pending_ops_prio.emplace([nodes = std::move(nodes)](SecureDht& dht) {
@@ -1094,7 +1108,7 @@ DhtRunner::bootstrap(std::vector<NodeExport> nodes)
void
DhtRunner::connectivityChanged()
{
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([=](SecureDht& dht) {
dht.connectivityChanged();
#ifdef OPENDHT_PEER_DISCOVERY
@@ -1105,10 +1119,29 @@ DhtRunner::connectivityChanged()
cv.notify_all();
}
void
DhtRunner::findCertificate(InfoHash hash, std::function<void(const Sp<crypto::Certificate>&)> cb)
{
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
cb({});
return;
}
ongoing_ops++;
pending_ops.emplace([this, hash, cb = std::move(cb)](SecureDht& dht) {
dht.findCertificate(hash, [this, cb = std::move(cb)](const Sp<crypto::Certificate>& crt) {
cb(crt);
opEnded();
});
});
cv.notify_all();
}
void
DhtRunner::findCertificate(PkId hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)> cb)
{
std::unique_lock lck(storage_mtx);
std::unique_lock<std::mutex> lck(storage_mtx);
if (running != State::Running) {
lck.unlock();
return;
@@ -1134,7 +1167,7 @@ void
DhtRunner::setProxyServer(const std::string& proxy, const std::string& pushNodeId)
{
#ifdef OPENDHT_PROXY_CLIENT
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (config_.proxy_server == proxy and config_.push_node_id == pushNodeId)
return;
config_.proxy_server = proxy;
@@ -1161,8 +1194,8 @@ DhtRunner::enableProxy(bool proxify)
config_.client_identity,
[this] {
if (config_.threaded) {
std::lock_guard lck(storage_mtx);
pending_ops_prio.emplace([](SecureDht&) {});
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([=](SecureDht&) mutable {});
cv.notify_all();
}
},
@@ -1174,8 +1207,11 @@ DhtRunner::enableProxy(bool proxify)
config_.push_platform,
logger_);
dht_ = std::make_unique<SecureDht>(std::move(dht_via_proxy), config_.dht_config, identityAnnouncedCb_, logger_);
// and use it
use_proxy = proxify;
} else {
use_proxy = proxify;
}
use_proxy = proxify;
#else
if (proxify)
throw std::runtime_error("DHT proxy requested but OpenDHT built without proxy support.");
@@ -1185,7 +1221,7 @@ DhtRunner::enableProxy(bool proxify)
void
DhtRunner::forwardAllMessages(bool forward)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
if (dht_)
dht_->forwardAllMessages(forward);
}
@@ -1196,7 +1232,7 @@ DhtRunner::forwardAllMessages(bool forward)
void
DhtRunner::setPushNotificationToken(const std::string& token)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
#if defined(OPENDHT_PROXY_CLIENT) && defined(OPENDHT_PUSH_NOTIFICATIONS)
config_.push_token = token;
if (dht_)
@@ -1209,7 +1245,7 @@ DhtRunner::setPushNotificationToken(const std::string& token)
void
DhtRunner::setPushNotificationTopic(const std::string& topic)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
#if defined(OPENDHT_PROXY_CLIENT) && defined(OPENDHT_PUSH_NOTIFICATIONS)
config_.push_topic = topic;
if (dht_)
@@ -1222,7 +1258,7 @@ DhtRunner::setPushNotificationTopic(const std::string& topic)
void
DhtRunner::setPushNotificationPlatform(const std::string& platform)
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
#if defined(OPENDHT_PROXY_CLIENT) && defined(OPENDHT_PUSH_NOTIFICATIONS)
config_.push_platform = platform;
if (dht_)
@@ -1238,7 +1274,7 @@ DhtRunner::pushNotificationReceived([[maybe_unused]] const std::map<std::string,
#if defined(OPENDHT_PROXY_CLIENT) && defined(OPENDHT_PUSH_NOTIFICATIONS)
auto ret_token = std::make_shared<std::promise<PushNotificationResult>>();
auto future = ret_token->get_future();
std::lock_guard lck(storage_mtx);
std::lock_guard<std::mutex> lck(storage_mtx);
pending_ops_prio.emplace([ret_token, this, data](SecureDht&) {
if (dht_)
ret_token->set_value(dht_->pushNotificationReceived(data));
+41 -70
View File
@@ -42,8 +42,8 @@ constexpr std::string_view ORIGIN_PROTOCOL {"//"sv};
constexpr const char HTTP_HEADER_DELIM[] = "\r\n\r\n";
constexpr unsigned MAX_REDIRECTS {5};
Url::Url(std::string url_str)
: url(std::move(url_str))
Url::Url(std::string_view url_str)
: url(url_str)
{
std::string_view u = url;
if (u.empty())
@@ -233,34 +233,13 @@ Connection::Connection(asio::io_context& ctx,
Connection::~Connection()
{
// No asynchronous operation can be pending here: their handlers keep
// the connection alive (see wrapCallback), so closing synchronously
// is safe regardless of the calling thread.
doClose();
close();
}
void
Connection::close()
{
// asio sockets are not thread-safe. Closing from a thread other than the
// one running the io_context races with in-flight operations on the same
// socket (e.g. async_connect closing and reopening it to try the next
// endpoint), corrupting the reactor's descriptor state.
// Always perform the close on the io_context thread.
if (ctx_.get_executor().running_in_this_thread()) {
doClose();
return;
}
asio::post(ctx_, [w = weak_from_this()] {
if (auto sthis = w.lock())
sthis->doClose();
});
}
void
Connection::doClose()
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
asio::error_code ec;
if (ssl_socket_) {
if (ssl_socket_->is_open())
@@ -323,13 +302,13 @@ static X509*
issuer_from_chain(STACK_OF(X509) * fullchain)
{
X509 *cert, *issuer;
const X509_NAME* issuer_name;
X509_NAME* issuer_name;
cert = cert_from_chain(fullchain);
if ((issuer_name = X509_get_issuer_name(cert)) == nullptr)
return nullptr;
issuer = X509_find_by_subject(fullchain, const_cast<X509_NAME*>(issuer_name));
issuer = X509_find_by_subject(fullchain, issuer_name);
return issuer;
}
@@ -511,7 +490,7 @@ ocspValidateResponse(const OscpRequestInfo& info,
void
Connection::set_ssl_verification(const std::string& hostname, const asio::ssl::verify_mode verify_mode)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (ssl_socket_) {
// Set SNI Hostname (many hosts need this to handshake successfully)
SSL_set_tlsext_host_name(ssl_socket_->asio_ssl_stream().native_handle(), hostname.c_str());
@@ -597,7 +576,7 @@ Connection::input()
std::string
Connection::read_bytes(size_t bytes)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (bytes == 0)
bytes = read_buf_.in_avail();
std::string content;
@@ -618,7 +597,7 @@ Connection::read_until(const char delim)
void
Connection::async_connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, ConnectHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!ssl_socket_ && !socket_) {
cb(asio::error::operation_aborted, {});
return;
@@ -650,15 +629,14 @@ Connection::async_connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, Conn
void
Connection::async_handshake(HandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (ssl_socket_) {
// Keep the connection alive while the handshake is in flight, like
// every other asynchronous operation (see wrapCallback).
std::weak_ptr<Connection> wthis = shared_from_this();
ssl_socket_
->async_handshake(asio::ssl::stream<asio::ip::tcp::socket>::client, [sthis = shared_from_this(), cb](const asio::error_code& ec) {
->async_handshake(asio::ssl::stream<asio::ip::tcp::socket>::client, [wthis, cb](const asio::error_code& ec) {
if (ec == asio::error::operation_aborted)
return;
{
if (auto sthis = wthis.lock()) {
auto& this_ = *sthis;
auto verify_ec = SSL_get_verify_result(this_.ssl_socket_->asio_ssl_stream().native_handle());
if (this_.logger_) {
@@ -685,7 +663,7 @@ Connection::async_handshake(HandlerCb cb)
void
Connection::async_write(BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -702,7 +680,7 @@ Connection::async_write(BytesHandlerCb cb)
void
Connection::async_read_until(const char* delim, BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -719,7 +697,7 @@ Connection::async_read_until(const char* delim, BytesHandlerCb cb)
void
Connection::async_read_until(char delim, BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -736,7 +714,7 @@ Connection::async_read_until(char delim, BytesHandlerCb cb)
void
Connection::async_read(size_t bytes, BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -753,7 +731,7 @@ Connection::async_read(size_t bytes, BytesHandlerCb cb)
void
Connection::async_read_some(size_t bytes, BytesHandlerCb cb)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!is_open()) {
if (cb)
asio::post(ctx_, [cb]() { cb(asio::error::broken_pipe, 0); });
@@ -773,7 +751,7 @@ Connection::async_read_some(size_t bytes, BytesHandlerCb cb)
void
Connection::set_keepalive(uint32_t seconds)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!ssl_socket_ && !socket_)
return;
@@ -852,8 +830,8 @@ Connection::timeout(const std::chrono::seconds& timeout, HandlerCb cb)
// Resolver
Resolver::Resolver(asio::io_context& ctx, std::string url, std::shared_ptr<dht::Logger> logger)
: url_(std::move(url))
Resolver::Resolver(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger)
: url_(url)
, resolver_(ctx)
, destroyed_(std::make_shared<bool>(false))
, logger_(logger)
@@ -893,7 +871,7 @@ Resolver::~Resolver()
{
decltype(cbs_) cbs;
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
cbs = std::move(cbs_);
}
while (not cbs.empty()) {
@@ -929,7 +907,7 @@ filter(const std::vector<asio::ip::tcp::endpoint>& epts, sa_family_t family)
void
Resolver::add_callback(ResolverCb cb, sa_family_t family)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (!completed_)
cbs_.emplace(family == AF_UNSPEC ? std::move(cb)
: [cb, family](const asio::error_code& ec,
@@ -939,16 +917,8 @@ Resolver::add_callback(ResolverCb cb, sa_family_t family)
else
cb(ec, filter(endpoints, family));
});
else {
// Always dispatch the callback on the io_context thread. Invoking it
// synchronously would run the caller's continuation (e.g. the whole
// connect chain of Request::send) on the calling thread, mutating asio
// objects concurrently with the io thread running the io_context.
asio::post(resolver_.get_executor(),
[cb = std::move(cb),
ec = ec_,
endpoints = family == AF_UNSPEC ? endpoints_ : filter(endpoints_, family)] { cb(ec, endpoints); });
}
else
cb(ec_, family == AF_UNSPEC ? endpoints_ : filter(endpoints_, family));
}
void
@@ -976,7 +946,7 @@ Resolver::resolve(std::string_view host, std::string_view service)
}
decltype(cbs_) cbs;
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
ec_ = ec;
endpoints_ = std::vector<asio::ip::tcp::endpoint> {endpoints.begin(),
endpoints.end()};
@@ -997,14 +967,14 @@ Resolver::resolve(std::string_view host, std::string_view service)
std::atomic_uint Request::ids_ {1};
Request::Request(asio::io_context& ctx,
std::string url,
const std::string& url,
const Json::Value& json,
OnJsonCb jsoncb,
std::shared_ptr<dht::Logger> logger)
: logger_(std::move(logger))
, id_(Request::ids_++)
, ctx_(ctx)
, resolver_(std::make_shared<Resolver>(ctx, std::move(url), logger))
, resolver_(std::make_shared<Resolver>(ctx, url, logger))
{
init_default_headers();
set_header_field(restinio::http_field_t::content_type, std::string(HTTP_HEADER_CONTENT_TYPE_JSON));
@@ -1027,11 +997,11 @@ Request::Request(asio::io_context& ctx,
});
}
Request::Request(asio::io_context& ctx, std::string url, OnJsonCb jsoncb, std::shared_ptr<dht::Logger> logger)
Request::Request(asio::io_context& ctx, const std::string& url, OnJsonCb jsoncb, std::shared_ptr<dht::Logger> logger)
: logger_(std::move(logger))
, id_(Request::ids_++)
, ctx_(ctx)
, resolver_(std::make_shared<Resolver>(ctx, std::move(url), logger))
, resolver_(std::make_shared<Resolver>(ctx, url, logger))
{
init_default_headers();
set_header_field(restinio::http_field_t::accept, std::string(HTTP_HEADER_CONTENT_TYPE_JSON));
@@ -1052,20 +1022,20 @@ Request::Request(asio::io_context& ctx, std::string url, OnJsonCb jsoncb, std::s
});
}
Request::Request(asio::io_context& ctx, std::string url, std::shared_ptr<dht::Logger> logger)
Request::Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger)
: logger_(logger)
, id_(Request::ids_++)
, ctx_(ctx)
, resolver_(std::make_shared<Resolver>(ctx, std::move(url), logger))
, resolver_(std::make_shared<Resolver>(ctx, url, logger))
{
init_default_headers();
}
Request::Request(asio::io_context& ctx, std::string url, OnDoneCb onDone, std::shared_ptr<dht::Logger> logger)
Request::Request(asio::io_context& ctx, const std::string& url, OnDoneCb onDone, std::shared_ptr<dht::Logger> logger)
: logger_(logger)
, id_(Request::ids_++)
, ctx_(ctx)
, resolver_(std::make_shared<Resolver>(ctx, std::move(url), logger))
, resolver_(std::make_shared<Resolver>(ctx, url, logger))
{
init_default_headers();
add_on_done_callback(std::move(onDone));
@@ -1116,6 +1086,7 @@ Request::Request(asio::io_context& ctx,
, family_(family)
, resolver_(resolver)
{
set_header_field(restinio::http_field_t::host, concat(get_url().host, ":", get_url().service));
set_target(Url(target).target);
}
@@ -1419,7 +1390,7 @@ Request::connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, HandlerCb cb)
if (not sthis)
return;
auto& this_ = *sthis;
std::lock_guard lock(this_.mutex_);
std::lock_guard<std::mutex> lock(this_.mutex_);
if (ec == asio::error::operation_aborted) {
this_.terminate(ec);
return;
@@ -1472,7 +1443,7 @@ Request::send()
[wthis](const asio::error_code& ec, std::vector<asio::ip::tcp::endpoint> endpoints) {
if (auto sthis = wthis.lock()) {
auto& this_ = *sthis;
std::lock_guard lock(this_.mutex_);
std::lock_guard<std::mutex> lock(this_.mutex_);
if (ec) {
if (this_.logger_)
this_.logger_->error("[http:request:{:d}] resolve error: {:s}", this_.id_, ec.message());
@@ -1547,7 +1518,7 @@ Request::terminate(const asio::error_code& ec)
void
Request::handle_request(const asio::error_code& ec)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (ec and ec != asio::error::eof) {
terminate(ec);
return;
@@ -1571,7 +1542,7 @@ Request::handle_request(const asio::error_code& ec)
void
Request::handle_response(const asio::error_code& ec, size_t /* n_bytes */)
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
if (ec && ec != asio::error::eof) {
terminate(ec);
return;
@@ -1689,11 +1660,11 @@ const Response&
Request::await()
{
std::mutex mtx;
std::unique_lock lock(mtx);
std::unique_lock<std::mutex> lock(mtx);
std::condition_variable cv;
bool ok {false};
add_on_done_callback([&](const Response&) {
std::lock_guard lk(mtx);
std::lock_guard<std::mutex> lk(mtx);
ok = true;
cv.notify_all();
});
+45 -137
View File
@@ -40,44 +40,8 @@ struct NetworkEngine::PartialMessage
time_point start;
time_point last_part;
std::unique_ptr<ParsedMessage> msg;
std::vector<std::unique_ptr<ParsedMessage>> early_parts;
size_t early_bytes {};
bool request_quota_consumed {};
};
bool
NetworkEngine::isExpectedReply(const ParsedMessage& msg, const SockAddr& from)
{
auto request = requests.find(msg.tid);
if (request != requests.end() && request->second->pending() && !request->second->node->id
&& request->second->node->getAddr() == from)
return true;
auto node = cache.getNode(msg.id, from.getFamily());
if (!node || node->getAddr() != from)
return false;
auto pending = node->getRequest(msg.tid);
return (pending && pending->pending()) || node->getSocket(msg.tid);
}
bool
NetworkEngine::tryCompleteMessage(PartialMessage& partial, const SockAddr& from)
{
try {
if (!partial.msg->complete())
return false;
process(std::move(partial.msg), from);
} catch (const std::exception& e) {
if (logger_)
logger_->warn("Error while processing partial message: {}", e.what());
} catch (...) {
if (logger_)
logger_->warn("Unknown error while processing partial message");
}
return true;
}
std::vector<Blob>
serializeValues(const std::vector<Sp<Value>>& st)
{
@@ -334,7 +298,6 @@ NetworkEngine::clear()
request.second->node->setExpired();
}
requests.clear();
partial_messages.clear();
}
void
@@ -502,58 +465,34 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f)
// partial value data
if (msg->type == MessageType::ValueData) {
if (msg->fragment_parts.empty()
|| std::any_of(msg->fragment_parts.begin(), msg->fragment_parts.end(), [](const auto& fragment) {
return fragment.second.second.empty();
})) {
auto pmsg_it = partial_messages.find(msg->tid);
if (pmsg_it == partial_messages.end()) {
if (logIncoming_)
if (logger_)
logger_->debug("Unable to find partial message");
rateLimit(from);
return;
}
PartialMessageKey key {from, msg->tid};
auto pmsg_it = partial_messages.find(key);
if (pmsg_it == partial_messages.end()) {
if (!rateLimit(from))
return;
auto inserted = partial_messages.emplace(key, PartialMessage {});
pmsg_it = inserted.first;
pmsg_it->second.from = from;
pmsg_it->second.start = now;
pmsg_it->second.last_part = now;
pmsg_it->second.request_quota_consumed = true;
scheduler.add(now + RX_MAX_PACKET_TIME, std::bind(&NetworkEngine::maintainRxBuffer, this, key));
}
auto& pmsg = pmsg_it->second;
if (!pmsg.msg) {
if (pmsg.early_parts.size() >= RX_MAX_EARLY_PARTS) {
partial_messages.erase(pmsg_it);
return;
}
size_t fragment_bytes {};
for (const auto& [index, fragment] : msg->fragment_parts) {
(void) index;
if (fragment.second.size() > RX_MAX_EARLY_BYTES - fragment_bytes) {
partial_messages.erase(pmsg_it);
return;
}
fragment_bytes += fragment.second.size();
}
if (fragment_bytes > RX_MAX_EARLY_BYTES - pmsg.early_bytes) {
partial_messages.erase(pmsg_it);
return;
}
pmsg.early_bytes += fragment_bytes;
pmsg.early_parts.emplace_back(std::move(msg));
pmsg.last_part = now;
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, key));
if (!pmsg_it->second.from.equals(from)) {
if (logger_)
logger_->debug("Received partial message data from unexpected IP address");
rateLimit(from);
return;
}
// append data block
if (pmsg.msg->append(*msg)) {
pmsg.last_part = now;
if (tryCompleteMessage(pmsg, from)) {
partial_messages.erase(pmsg_it);
if (pmsg_it->second.msg->append(*msg)) {
pmsg_it->second.last_part = now;
// check data completion
if (pmsg_it->second.msg->complete()) {
try {
// process the full message
process(std::move(pmsg_it->second.msg), from);
partial_messages.erase(pmsg_it);
} catch (...) {
return;
}
} else
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, key));
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, msg->tid));
}
return;
}
@@ -564,32 +503,7 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f)
return;
}
if (msg->value_parts.empty()) {
if (msg->type > MessageType::Reply) {
/* Rate limit requests. */
if (!rateLimit(from)) {
if (logger_)
logger_->warn("Dropping request due to rate limiting");
return;
}
}
try {
process(std::move(msg), from);
} catch (const std::exception& e) {
if (logger_)
logger_->warn("Error while processing message: {}", e.what());
}
return;
}
PartialMessageKey partial_key {from, msg->tid};
if (msg->type == MessageType::Reply && !isExpectedReply(*msg, from))
return;
const auto partial = partial_messages.find(partial_key);
const bool request_quota_consumed = partial != partial_messages.end() && !partial->second.msg
&& partial->second.request_quota_consumed;
if (msg->type > MessageType::Reply && !request_quota_consumed) {
if (msg->type > MessageType::Reply) {
/* Rate limit requests. */
if (!rateLimit(from)) {
if (logger_)
@@ -598,28 +512,26 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f)
}
}
// starting partial message session
auto inserted = partial_messages.emplace(partial_key, PartialMessage {});
auto& pmsg = inserted.first->second;
if (not pmsg.msg) {
if (inserted.second) {
if (msg->value_parts.empty()) {
try {
process(std::move(msg), from);
} catch (...) {
return;
}
} else {
// starting partial message session
auto k = msg->tid;
auto& pmsg = partial_messages[k];
if (not pmsg.msg) {
pmsg.from = from;
pmsg.msg = std::move(msg);
pmsg.start = now;
scheduler.add(now + RX_MAX_PACKET_TIME, std::bind(&NetworkEngine::maintainRxBuffer, this, partial_key));
}
pmsg.msg = std::move(msg);
pmsg.last_part = now;
for (const auto& part : pmsg.early_parts)
pmsg.msg->append(*part);
pmsg.early_parts.clear();
pmsg.early_bytes = 0;
if (tryCompleteMessage(pmsg, from)) {
partial_messages.erase(inserted.first);
} else {
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, partial_key));
}
} else if (logger_)
logger_->error("Partial message from {} with given TID {} already exists", from.toString(), msg->tid);
pmsg.last_part = now;
scheduler.add(now + RX_MAX_PACKET_TIME, std::bind(&NetworkEngine::maintainRxBuffer, this, k));
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, k));
} else if (logger_)
logger_->error("Partial message with given TID {} already exists", k);
}
}
void
@@ -787,7 +699,7 @@ NetworkEngine::process(std::unique_ptr<ParsedMessage>&& msg, const SockAddr& fro
RequestAnswer answer
= onListen(node, msg->info_hash, msg->token, msg->socket_id, std::move(msg->query), msg->version);
auto nnodes = bufferNodes(from.getFamily(), msg->info_hash, msg->want, answer.nodes4, answer.nodes6);
sendListenConfirmation(from, msg->tid, answer.ntoken);
sendListenConfirmation(from, msg->tid);
break;
}
case MessageType::UpdateValue: {
@@ -1366,21 +1278,17 @@ NetworkEngine::sendListen(const Sp<Node>& n,
}
void
NetworkEngine::sendListenConfirmation(const SockAddr& addr, Tid tid, const Blob& token)
NetworkEngine::sendListenConfirmation(const SockAddr& addr, Tid tid)
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0));
pk.pack(KEY_R);
pk.pack_map(2 + (not token.empty() ? 1 : 0));
pk.pack_map(2);
pk.pack(KEY_REQ_ID);
pk.pack(myid);
insertAddr(pk, addr);
if (not token.empty()) {
pk.pack(KEY_REQ_TOKEN);
packToken(pk, token);
}
pk.pack(KEY_TID);
pk.pack(tid);
@@ -1698,9 +1606,9 @@ NetworkEngine::sendError(const SockAddr& addr, Tid tid, uint16_t code, const std
}
void
NetworkEngine::maintainRxBuffer(const PartialMessageKey& key)
NetworkEngine::maintainRxBuffer(Tid tid)
{
auto msg = partial_messages.find(key);
auto msg = partial_messages.find(tid);
if (msg != partial_messages.end()) {
const auto& now = scheduler.time();
if (msg->second.start + RX_MAX_PACKET_TIME < now || msg->second.last_part + RX_TIMEOUT < now) {
+7 -7
View File
@@ -107,14 +107,14 @@ UdpSocket::UdpSocket(in_port_t port, const std::shared_ptr<Logger>& l)
SockAddr bind6;
bind6.setFamily(AF_INET6);
bind6.setPort(port);
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
openSockets(bind4, bind6);
}
UdpSocket::UdpSocket(const SockAddr& bind4, const SockAddr& bind6, const std::shared_ptr<Logger>& l)
: logger(l)
{
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
openSockets(bind4, bind6);
}
@@ -161,7 +161,7 @@ UdpSocket::sendTo(const SockAddr& dest, const uint8_t* data, size_t size, [[mayb
if (logger)
logger->d("Can't send message to %s: %s", dest.toString().c_str(), strerror(err));
if (err == EPIPE || err == ENOTCONN || err == ECONNRESET) {
std::lock_guard lk(lock);
std::lock_guard<std::mutex> lk(lock);
auto bind4 = std::move(bound4), bind6 = std::move(bound6);
openSockets(bind4, bind6);
return sendTo(dest, data, size, false);
@@ -286,13 +286,13 @@ UdpSocket::openSockets(const SockAddr& bind4, const SockAddr& bind6)
pkt.received = clock::now();
onReceived(std::move(pkts));
} else if (rc == -1) {
int err = errno;
if (logger)
logger->e("Error receiving packet: %s", strerror(err));
logger->e("Error receiving packet: %s", strerror(errno));
int err = errno;
if (err == EPIPE || err == ENOTCONN || err == ECONNRESET) {
if (not running)
break;
std::unique_lock lk(lock, std::try_to_lock);
std::unique_lock<std::mutex> lk(lock, std::try_to_lock);
if (lk.owns_lock()) {
if (not running)
break;
@@ -337,7 +337,7 @@ UdpSocket::openSockets(const SockAddr& bind4, const SockAddr& bind6)
close(stop_readfd);
if (stopfd != -1)
close(stopfd);
std::unique_lock lk(lock, std::try_to_lock);
std::unique_lock<std::mutex> lk(lock, std::try_to_lock);
if (lk.owns_lock()) {
s4 = -1;
s6 = -1;
+19 -25
View File
@@ -12,20 +12,18 @@ OpValueCache::onValuesAdded(const std::vector<Sp<Value>>& vals, const system_clo
std::vector<Sp<Value>> newValues;
for (const auto& v : vals) {
auto viop = values.emplace(v->id, v);
auto& stored = viop.first->second;
if (viop.second) {
newValues.emplace_back(v);
} else if (*stored.data != *v) {
if (v->seq <= stored.data->seq)
continue; // older version from a lagging source
// Edited: only the reporting source is known to hold this version
stored.data = v;
stored.refCount = 1;
newValues.emplace_back(v);
} else if (countSources) {
stored.refCount++;
} else if (*viop.first->second.data != *v) {
// Special case for edition
if (v->seq > viop.first->second.data->seq) {
viop.first->second.data = v;
newValues.emplace_back(v);
}
} else {
viop.first->second.refCount++;
}
stored.updated = t;
viop.first->second.updated = t;
}
return newValues.empty() ? true : callback(newValues, false);
}
@@ -39,8 +37,6 @@ OpValueCache::onValuesExpired(const std::vector<Sp<Value>>& vals, const system_c
if (vit != values.end()) {
if (vit->second.updated > t)
continue;
if (v->seq < vit->second.data->seq)
continue; // source held an older version, not counted
vit->second.updated = t;
vit->second.refCount--;
@@ -178,7 +174,7 @@ SearchCache::listen(const ValueCallback& get_cb, const Sp<Query>& q, Value::Filt
auto op = getOp(q);
if (op == ops.end()) {
// New query
op = ops.emplace(q, std::make_unique<OpCache>(countSources_)).first;
op = ops.emplace(q, std::make_unique<OpCache>()).first;
auto& cache = *op->second;
cache.searchToken = onListen(
q,
@@ -228,7 +224,7 @@ SearchCache::expire(const time_point& now, const std::function<void(size_t)>& on
auto ret = nextExpiration_;
for (auto it = ops.begin(); it != ops.end();) {
auto expiration = it->second->getExpiration();
if (expiration <= now) {
if (expiration < now) {
auto cache = std::move(it->second);
it = ops.erase(it);
onCancel(cache->searchToken);
@@ -244,17 +240,15 @@ SearchCache::expire(const time_point& now, const std::function<void(size_t)>& on
bool
SearchCache::get(const Value::Filter& f, const Sp<Query>& q, const GetCallback& gcb, const DoneCallback& dcb) const
{
if (not gcb)
return false;
auto op = getOp(q);
if (op == ops.end() or not op->second->isSynced())
return false;
auto vals = op->second->get(f);
if (not vals.empty())
gcb(vals); // a consumer stopping early is not a failure
if (dcb)
dcb(true, {});
return true;
if (op != ops.end()) {
auto vals = op->second->get(f);
if ((not vals.empty() and not gcb(vals)) or op->second->isSynced()) {
dcb(true, {});
return true;
}
}
return false;
}
std::vector<Sp<Value>>
+12 -28
View File
@@ -21,29 +21,19 @@ struct OpCacheValueStorage
class OpValueCache
{
public:
/**
* @param countSources When true, a value is kept until every source
* that reported it has expired it (per-node caches,
* IPv4/IPv6 searches, local storage). When false,
* re-deliveries of the same value are not counted
* and a single expiration removes it (single
* logical source, e.g. a proxy server).
*/
OpValueCache(ValueCallback&& cb, bool countSources = true) noexcept
OpValueCache(ValueCallback&& cb) noexcept
: callback(std::forward<ValueCallback>(cb))
, countSources(countSources)
{}
explicit OpValueCache(OpValueCache&& o) noexcept
: values(std::move(o.values))
, callback(std::move(o.callback))
, countSources(o.countSources)
{
o.callback = {};
}
static ValueCallback cacheCallback(ValueCallback&& cb, std::function<void()>&& onCancel, bool countSources = true)
static ValueCallback cacheCallback(ValueCallback&& cb, std::function<void()>&& onCancel)
{
return [cache = std::make_shared<OpValueCache>(std::forward<ValueCallback>(cb), countSources),
return [cache = std::make_shared<OpValueCache>(std::forward<ValueCallback>(cb)),
onCancel = std::move(onCancel)](const std::vector<Sp<Value>>& vals, bool expired) {
auto ret = cache->onValue(vals, expired);
if (not ret)
@@ -99,22 +89,19 @@ private:
size_t syncedNodes {0};
std::map<Value::Id, OpCacheValueStorage> values {};
ValueCallback callback;
bool countSources {true};
};
class OpCache
{
public:
explicit OpCache(bool countSources = true)
: cache(
[this](const std::vector<Sp<Value>>& vals, bool expired) {
if (expired)
onValuesExpired(vals);
else
onValuesAdded(vals);
return true;
},
countSources)
OpCache()
: cache([this](const std::vector<Sp<Value>>& vals, bool expired) {
if (expired)
onValuesExpired(vals);
else
onValuesAdded(vals);
return true;
})
{}
bool onValue(const std::vector<Sp<Value>>& vals, bool expired) { return cache.onValue(vals, expired); }
@@ -169,9 +156,7 @@ private:
class SearchCache
{
public:
explicit SearchCache(bool countSources = true)
: countSources_(countSources)
{}
SearchCache() {}
SearchCache(SearchCache&&) = default;
using OnListen = std::function<size_t(Sp<Query>, ValueCallback, SyncCallback)>;
@@ -207,7 +192,6 @@ private:
size_t nextToken_ {1};
time_point nextExpiration_ {time_point::max()};
bool countSources_ {true};
};
} // namespace dht
+25 -69
View File
@@ -6,7 +6,6 @@
#include "sockaddr.h"
#include "net.h"
#include <algorithm>
#include <map>
#include <string_view>
@@ -59,12 +58,8 @@ unpackTid(const msgpack::object& o)
switch (o.type) {
case msgpack::type::POSITIVE_INTEGER:
return o.as<Tid>();
default: {
auto arr = o.as<std::array<char, 4>>();
uint32_t val;
std::memcpy(&val, arr.data(), sizeof(val));
return ntohl(val);
}
default:
return ntohl(*reinterpret_cast<const uint32_t*>(o.as<std::array<char, 4>>().data()));
}
}
@@ -100,25 +95,9 @@ struct ParsedMessage
std::vector<Value::Id> expired_values {};
/* index for fields values */
std::vector<Sp<FieldValueIndex>> fields;
/** Tracks partial value reassembly for fragmented messages.
* Populated from message header with total expected sizes. */
struct PartialValue
{
size_t total_size {0};
size_t received_bytes {0};
std::map<size_t, Blob> fragments {}; // offset -> fragment data
bool isComplete() const { return total_size > 0 && received_bytes == total_size; }
Blob reassemble() const
{
Blob result(total_size);
for (const auto& [offset, data] : fragments)
std::copy(data.begin(), data.end(), result.begin() + offset);
return result;
}
};
std::map<unsigned, PartialValue> value_parts;
/** When part of partial value data: {index -> (offset, part_data)} */
std::map<unsigned, std::pair<size_t, Blob>> fragment_parts;
/** When part of the message header: {index -> (total size, {})}
* When part of partial value data: {index -> (offset, part_data)} */
std::map<unsigned, std::pair<unsigned, Blob>> value_parts;
/* query describing a filter to apply on values. */
Query query;
/* states if ipv4 or ipv6 request */
@@ -139,32 +118,17 @@ bool
ParsedMessage::append(const ParsedMessage& block)
{
bool ret(false);
for (const auto& [index, fragment] : block.fragment_parts) {
auto part_it = value_parts.find(index);
if (part_it == value_parts.end())
for (const auto& ve : block.value_parts) {
auto part_val = value_parts.find(ve.first);
if (part_val == value_parts.end() || part_val->second.second.size() >= part_val->second.first)
continue;
auto& pv = part_it->second;
if (pv.isComplete())
// TODO: handle out-of-order packets
if (ve.second.first != part_val->second.second.size()) {
// std::cout << "skipping out-of-order packet" << std::endl;
continue;
auto offset = fragment.first;
const auto& data = fragment.second;
if (data.empty() || offset > pv.total_size || data.size() > pv.total_size - offset)
continue;
// Check for overlap with existing fragments
auto it = pv.fragments.lower_bound(offset);
if (it != pv.fragments.end()) {
auto distance = it->first - offset;
if (distance < data.size())
continue;
}
if (it != pv.fragments.begin()) {
auto prev = std::prev(it);
if (prev->second.size() > offset - prev->first)
continue;
}
pv.fragments.emplace(offset, data);
pv.received_bytes += data.size();
ret = true;
part_val->second.second.insert(part_val->second.second.end(), ve.second.second.begin(), ve.second.second.end());
}
return ret;
}
@@ -172,14 +136,16 @@ ParsedMessage::append(const ParsedMessage& block)
bool
ParsedMessage::complete()
{
for (const auto& [index, pv] : value_parts) {
if (!pv.isComplete())
for (auto& e : value_parts) {
if (e.second.first > e.second.second.size()) {
// std::cout << "uncomplete part " << e.first << ": " << e.second.second.size() << "/" << e.second.first <<
// std::endl;
return false;
}
}
for (auto& [index, pv] : value_parts) {
auto assembled = pv.reassemble();
for (auto& e : value_parts) {
msgpack::unpacked msg;
msgpack::unpack(msg, (const char*) assembled.data(), assembled.size());
msgpack::unpack(msg, (const char*) e.second.second.data(), e.second.second.size());
values.emplace_back(std::make_shared<Value>(msg.get()));
}
return true;
@@ -267,28 +233,21 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
auto d = findMapValue(vdat.val, "d"sv);
if (not o or not d)
continue;
fragment_parts.emplace(vdat.key.as<unsigned>(), std::pair<size_t, Blob>(o->as<size_t>(), unpackBlob(*d)));
value_parts.emplace(vdat.key.as<unsigned>(), std::pair<size_t, Blob>(o->as<size_t>(), unpackBlob(*d)));
}
return;
}
if (!parsed.a && !parsed.r && !parsed.e && !parsed.u)
throw msgpack::type_error();
auto& req = parsed.a ? *parsed.a : (parsed.r ? *parsed.r : (parsed.u ? *parsed.u : *parsed.e));
if (parsed.e) {
if (parsed.e->type != msgpack::type::ARRAY or parsed.e->via.array.size < 1)
if (parsed.e->type != msgpack::type::ARRAY)
throw msgpack::type_error();
error_code = parsed.e->via.array.ptr[0].as<uint16_t>();
}
/* An error-only message carries no request map. The error array must never
be reached through the map view of the msgpack object union, and a
request field that is not a map must be rejected rather than
reinterpreted. */
const msgpack::object* req = parsed.a ? parsed.a : (parsed.r ? parsed.r : parsed.u);
if (req and req->type != msgpack::type::MAP)
throw msgpack::type_error();
struct ParsedReq
{
msgpack::object* values;
@@ -297,9 +256,8 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
msgpack::object* want;
} parsedReq {};
const uint32_t req_size = req ? req->via.map.size : 0;
for (unsigned i = 0; i < req_size; i++) {
auto& o = req->via.map.ptr[i];
for (unsigned i = 0; i < req.via.map.size; i++) {
auto& o = req.via.map.ptr[i];
if (o.key.type != msgpack::type::STR)
continue;
auto key = o.key.as<std::string_view>();
@@ -366,7 +324,7 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
// Skip oversize values with a small margin for header overhead
if (packed_v.via.u64 > MAX_VALUE_SIZE + 32)
continue;
value_parts[i].total_size = packed_v.via.u64;
value_parts.emplace(i, std::make_pair(packed_v.via.u64, Blob {}));
} else {
try {
values.emplace_back(std::make_shared<Value>(parsedReq.values->via.array.ptr[i]));
@@ -378,8 +336,6 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
} else if (parsedReq.fields) {
if (auto rfields = findMapValue(*parsedReq.fields, "f"sv)) {
auto vfields = rfields->as<std::set<Value::Field>>();
if (vfields.empty())
throw msgpack::type_error();
if (auto rvalues = findMapValue(*parsedReq.fields, "v"sv)) {
if (rvalues->type != msgpack::type::ARRAY)
throw msgpack::type_error();
+10 -10
View File
@@ -123,7 +123,7 @@ PeerDiscovery::DomainPeerDiscovery::~DomainPeerDiscovery()
void
PeerDiscovery::DomainPeerDiscovery::startDiscovery(std::string_view type, ServiceDiscoveredCallback callback)
{
std::lock_guard lck(dmtx_);
std::lock_guard<std::mutex> lck(dmtx_);
callbackmap_[std::string(type)] = callback;
if (not drunning_) {
drunning_ = true;
@@ -137,7 +137,7 @@ PeerDiscovery::DomainPeerDiscovery::startDiscovery(std::string_view type, Servic
void
PeerDiscovery::DomainPeerDiscovery::loopListener()
{
std::lock_guard lck(dmtx_);
std::lock_guard<std::mutex> lck(dmtx_);
if (not drunning_)
return;
sockFd_.async_receive_from(asio::buffer(receiveBuf_),
@@ -163,7 +163,7 @@ PeerDiscovery::DomainPeerDiscovery::loopListener()
continue;
ServiceDiscoveredCallback cb;
{
std::lock_guard lck(dmtx_);
std::lock_guard<std::mutex> lck(dmtx_);
if (drunning_) {
auto callback = callbackmap_.find(o.key.as<std::string_view>());
if (callback != callbackmap_.end())
@@ -189,7 +189,7 @@ PeerDiscovery::DomainPeerDiscovery::loopListener()
void
PeerDiscovery::DomainPeerDiscovery::query(const asio::ip::udp::endpoint& peer)
{
std::lock_guard lck(dmtx_);
std::lock_guard<std::mutex> lck(dmtx_);
if (not drunning_)
return;
@@ -209,7 +209,7 @@ PeerDiscovery::DomainPeerDiscovery::query(const asio::ip::udp::endpoint& peer)
void
PeerDiscovery::DomainPeerDiscovery::publish(const asio::ip::udp::endpoint& peer)
{
std::lock_guard lck(mtx_);
std::lock_guard<std::mutex> lck(mtx_);
if (not lrunning_)
return;
@@ -229,7 +229,7 @@ PeerDiscovery::DomainPeerDiscovery::startPublish(std::string_view type, const ms
msgpack::sbuffer pack_buf_c(pack_buf.size());
pack_buf_c.write(pack_buf.data(), pack_buf.size());
std::lock_guard lck(mtx_);
std::lock_guard<std::mutex> lck(mtx_);
messages_[std::string(type)] = std::move(pack_buf_c);
reloadMessages();
lrunning_ = true;
@@ -239,7 +239,7 @@ PeerDiscovery::DomainPeerDiscovery::startPublish(std::string_view type, const ms
bool
PeerDiscovery::DomainPeerDiscovery::stopDiscovery(std::string_view type)
{
std::lock_guard lck(dmtx_);
std::lock_guard<std::mutex> lck(dmtx_);
auto it = callbackmap_.find(type);
if (it != callbackmap_.end()) {
callbackmap_.erase(it);
@@ -253,7 +253,7 @@ PeerDiscovery::DomainPeerDiscovery::stopDiscovery(std::string_view type)
bool
PeerDiscovery::DomainPeerDiscovery::stopPublish(std::string_view type)
{
std::lock_guard lck(mtx_);
std::lock_guard<std::mutex> lck(mtx_);
auto it = messages_.find(type);
if (it != messages_.end()) {
messages_.erase(it);
@@ -282,11 +282,11 @@ void
PeerDiscovery::DomainPeerDiscovery::stop()
{
{
std::lock_guard lck(dmtx_);
std::lock_guard<std::mutex> lck(dmtx_);
stopDiscovery();
}
{
std::lock_guard lck(mtx_);
std::lock_guard<std::mutex> lck(mtx_);
stopPublish();
}
}
-10
View File
@@ -133,16 +133,6 @@ struct Dht::SearchNode
return last_get_reply + Node::NODE_EXPIRE_TIME;
}
/**
* Update the token without refreshing last_get_reply,
* so that periodic find_node/get requests still occur.
*/
void refreshToken(const Blob& tok)
{
if (not tok.empty() and tok.size() <= 64)
token = tok;
}
friend std::ostream& operator<<(std::ostream& s, const SearchNode& node)
{
s << "getStatus:" << node.getStatus.size() << " listenStatus:" << node.listenStatus.size()
+141 -9
View File
@@ -96,13 +96,37 @@ SecureDht::secureType(ValueType&& type)
return type;
}
Sp<crypto::Certificate>
SecureDht::getCertificate(const InfoHash& node) const
{
if (node == getId())
return certificate_;
auto it = nodesCertificates_.find(node);
if (it == nodesCertificates_.end())
return nullptr;
else
return it->second;
}
Sp<crypto::PublicKey>
SecureDht::getPublicKey(const InfoHash& node) const
{
if (node == getId())
return certificate_->getSharedPublicKey();
auto it = nodesPubKeys_.find(node);
if (it == nodesPubKeys_.end())
return nullptr;
else
return it->second;
}
Sp<crypto::Certificate>
SecureDht::getCertificate(const PkId& node) const
{
if (node == getLongId())
return certificate_;
auto it = nodesCertificates_.find(node);
if (it == nodesCertificates_.end())
auto it = nodesCertificatesLong_.find(node);
if (it == nodesCertificatesLong_.end())
return nullptr;
else
return it->second;
@@ -113,8 +137,8 @@ SecureDht::getPublicKey(const PkId& node) const
{
if (node == getLongId())
return certificate_->getSharedPublicKey();
auto it = nodesPubKeys_.find(node);
if (it == nodesPubKeys_.end())
auto it = nodesPubKeysLong_.find(node);
if (it == nodesPubKeysLong_.end())
return nullptr;
else
return it->second;
@@ -167,10 +191,85 @@ void
SecureDht::registerCertificate(const Sp<crypto::Certificate>& cert)
{
if (cert) {
nodesCertificates_[cert->getLongId()] = cert;
nodesCertificates_[cert->getId()] = cert;
nodesCertificatesLong_[cert->getLongId()] = cert;
}
}
void
SecureDht::findCertificate(const InfoHash& node, const std::function<void(const Sp<crypto::Certificate>)>& cb)
{
Sp<crypto::Certificate> b = getCertificate(node);
if (b && *b) {
if (logger_)
logger_->debug("Using certificate from cache for {}", node.toString());
if (cb)
cb(b);
return;
}
if (localQueryMethodLegacy_) {
auto res = localQueryMethodLegacy_(node);
if (not res.empty()) {
if (logger_)
logger_->debug("Registering certificate from local store for {}", node.toString());
nodesCertificates_.emplace(node, res.front());
nodesCertificatesLong_.emplace(res.front()->getLongId(), res.front());
if (cb)
cb(res.front());
return;
}
}
auto found = std::make_shared<bool>(false);
dht_->get(
node,
[cb, node, found, this](const std::vector<Sp<Value>>& vals) {
for (const auto& v : vals) {
if (auto cert = registerCertificate(node, v->data)) {
*found = true;
if (logger_)
logger_->debug("Found certificate for {}", node.toString());
if (cb)
cb(cert);
return false;
}
}
return !*found;
},
[cb, found](bool) {
if (!*found and cb)
cb(nullptr);
},
Value::TypeFilter(CERTIFICATE_TYPE));
}
void
SecureDht::findPublicKey(const InfoHash& node, const std::function<void(const Sp<crypto::PublicKey>)>& cb)
{
auto pk = getPublicKey(node);
if (pk && *pk) {
if (logger_)
logger_->debug("Found public key from cache for {}", node.toString());
if (cb)
cb(pk);
return;
}
findCertificate(node, [=](const Sp<crypto::Certificate>& crt) {
if (crt && *crt) {
auto pk = crt->getSharedPublicKey();
if (*pk) {
nodesPubKeys_[pk->getId()] = pk;
nodesPubKeysLong_[pk->getLongId()] = pk;
if (cb)
cb(pk);
return;
}
}
if (cb)
cb(nullptr);
});
}
void
SecureDht::findCertificate(const PkId& node, const std::function<void(const Sp<crypto::Certificate>)>& cb)
{
@@ -188,7 +287,8 @@ SecureDht::findCertificate(const PkId& node, const std::function<void(const Sp<c
if (not res.empty()) {
if (logger_)
logger_->debug("Registering certificate from local store for {}", node.to_c_str());
nodesCertificates_.emplace(node, res.front());
nodesCertificates_.emplace(res.front()->getId(), res.front());
nodesCertificatesLong_.emplace(node, res.front());
if (cb)
cb(res.front());
return;
@@ -233,7 +333,8 @@ SecureDht::findPublicKey(const PkId& node, const std::function<void(const Sp<cry
if (crt && *crt) {
auto pk = crt->getSharedPublicKey();
if (*pk) {
nodesPubKeys_[pk->getLongId()] = pk;
nodesPubKeys_[pk->getId()] = pk;
nodesPubKeysLong_[pk->getLongId()] = pk;
if (cb)
cb(pk);
return;
@@ -261,7 +362,8 @@ SecureDht::checkValue(const Sp<Value>& v)
if (auto decrypted_val = v->decrypt(*key_)) {
auto cacheValue = not isDecrypted and decrypted_val->owner;
if (cacheValue) {
nodesPubKeys_[decrypted_val->owner->getLongId()] = decrypted_val->owner;
nodesPubKeys_[decrypted_val->owner->getId()] = decrypted_val->owner;
nodesPubKeysLong_[decrypted_val->owner->getLongId()] = decrypted_val->owner;
}
return decrypted_val;
}
@@ -275,7 +377,8 @@ SecureDht::checkValue(const Sp<Value>& v)
auto cacheValue = not v->isSignatureChecked() and enableCache_ and v->owner;
if (v->checkSignature()) {
if (cacheValue) {
nodesPubKeys_[v->owner->getLongId()] = v->owner;
nodesPubKeys_[v->owner->getId()] = v->owner;
nodesPubKeysLong_[v->owner->getLongId()] = v->owner;
}
return v;
} else if (logger_)
@@ -389,6 +492,35 @@ SecureDht::putSigned(const InfoHash& hash, Sp<Value> val, DoneCallback callback,
Where().id(val->id));
}
void
SecureDht::putEncrypted(const InfoHash& hash, const InfoHash& to, Sp<Value> val, DoneCallback callback, bool permanent)
{
if (not key_) {
if (callback)
callback(false, {});
return;
}
findPublicKey(to,
[this, hash, val = std::move(val), callback = std::move(callback), permanent](
const Sp<crypto::PublicKey>& pk) {
if (!pk || !*pk) {
if (callback)
callback(false, {});
return;
}
if (logger_)
logger_->warn("Encrypting data for PK: {}", pk->getId().toString());
try {
dht_->put(hash, encrypt(*val, *pk), callback, time_point::max(), permanent);
} catch (const std::exception& e) {
if (logger_)
logger_->error("Error putting encrypted data: {}", e.what());
if (callback)
callback(false, {});
}
});
}
void
SecureDht::putEncrypted(const InfoHash& hash, const PkId& to, Sp<Value> val, DoneCallback callback, bool permanent)
{
+2 -12
View File
@@ -3,9 +3,8 @@
#pragma once
#include "infohash.h"
#include "listener.h"
#include "sockaddr.h"
#include "value.h"
#include "listener.h"
#include <map>
#include <utility>
@@ -18,11 +17,6 @@ namespace dht {
class StorageBucket
{
public:
StorageBucket() = default;
explicit StorageBucket(SockAddr addr)
: addr_(std::move(addr))
{}
void insert(const InfoHash& id, const Value& value, time_point expiration)
{
totalSize_ += value.size();
@@ -62,10 +56,8 @@ public:
{
return storedValues_.empty() ? std::pair<InfoHash, Value::Id> {} : storedValues_.begin()->second;
}
const SockAddr& getAddr() const { return addr_; }
private:
SockAddr addr_ {};
std::multimap<time_point, std::pair<InfoHash, Value::Id>> storedValues_;
size_t totalSize_ {0};
};
@@ -232,8 +224,6 @@ Storage::store(const InfoHash& id, const Sp<Value>& value, time_point created, t
if (it->data != value) {
size_t size_old = it->data->size();
ssize_t size_diff = size_new - (ssize_t) size_old;
// Re-storing identical content is a refresh, not an edition
size_t edited = (*it->data != *value) ? 1 : 0;
// DHT_LOG.DEBUG("Updating %s -> %s", id.toString().c_str(), value->toString().c_str());
// clear quota for previous value
if (it->store_bucket)
@@ -245,7 +235,7 @@ Storage::store(const InfoHash& id, const Sp<Value>& value, time_point created, t
sb->insert(id, *value, expiration);
it->data = value;
total_size += size_diff;
return std::make_pair(&(*it), StoreDiff {size_diff, 0, 0, edited});
return std::make_pair(&(*it), StoreDiff {size_diff, 0, 0, 1});
}
} else {
// DHT_LOG.DEBUG("Storing %s -> %s", id.toString().c_str(), value->toString().c_str());
+3 -9
View File
@@ -6,10 +6,8 @@
#include <atomic>
#include <thread>
#include <iostream>
#include <cmath> // std::pow
#if __cplusplus < 202002L
#include <ciso646>
#endif
#include <ciso646> // fix windows compiler bug
#include <cmath> // std::pow
namespace dht {
@@ -80,8 +78,6 @@ ThreadPool::run(std::function<void()>&& cb)
break;
task = std::move(tasks_.front());
tasks_.pop();
if (tasks_.empty() && stopping_)
stopCv_.notify_one();
}
// run task
@@ -139,9 +135,7 @@ ThreadPool::stop(bool wait)
{
std::unique_lock l(lock_);
if (wait) {
stopping_ = true;
stopCv_.wait(l, [&]() { return tasks_.empty(); });
stopping_ = false;
cv_.wait(l, [&]() { return tasks_.empty(); });
}
running_ = false;
tasks_ = {};
+14 -68
View File
@@ -238,67 +238,28 @@ operator==(const SockAddr& a, const SockAddr& b)
return a.equals(b);
}
time_point
from_system_time(system_clock::time_point t, time_point steady_now, system_clock::time_point system_now)
{
if (t == system_clock::time_point::min())
return time_point::min();
if (t == system_clock::time_point::max())
return time_point::max();
// Saturate before the cast: the steady clock duration may hold a narrower range than the system one.
constexpr auto max_dt = std::chrono::duration_cast<system_clock::duration>(duration::max());
constexpr auto min_dt = std::chrono::duration_cast<system_clock::duration>(duration::min());
auto system_dt = t - system_now;
if (system_dt >= max_dt)
return time_point::max();
if (system_dt <= min_dt)
return time_point::min();
auto dt = std::chrono::duration_cast<duration>(system_dt);
if (dt > duration(0) and steady_now > time_point::max() - dt)
return time_point::max();
else if (dt < duration(0) and steady_now < time_point::min() - dt)
return time_point::min();
return steady_now + dt;
}
time_point
from_system_time(system_clock::time_point t)
{
return from_system_time(t, clock::now(), system_clock::now());
}
system_clock::time_point
to_system_time(time_point t, time_point steady_now, system_clock::time_point system_now)
{
if (t == time_point::min())
return system_clock::time_point::min();
if (t == time_point::max())
return system_clock::time_point::max();
// Cast first: mixing clock durations of different periods would overflow the bound computations below.
auto dt = std::chrono::duration_cast<system_clock::duration>(t - steady_now);
if (dt > system_clock::duration(0) and system_now >= system_clock::time_point::max() - dt)
return system_clock::time_point::max();
else if (dt < system_clock::duration(0) and system_now <= system_clock::time_point::min() - dt)
return system_clock::time_point::min();
return system_now + dt;
}
system_clock::time_point
to_system_time(time_point t)
{
return to_system_time(t, clock::now(), system_clock::now());
}
time_point
from_time_t(std::time_t t)
{
return from_system_time(system_clock::from_time_t(t));
auto dt = system_clock::from_time_t(t) - system_clock::now();
auto now = clock::now();
if (dt > system_clock::duration(0) and now > time_point::max() - dt)
return time_point::max();
else if (dt < system_clock::duration(0) and now < time_point::min() - dt)
return time_point::min();
return now + dt;
}
std::time_t
to_time_t(time_point t)
{
return system_clock::to_time_t(to_system_time(t));
auto dt = t - clock::now();
auto now = system_clock::now();
if (dt > duration(0) and now >= system_clock::time_point::max() - dt)
return system_clock::to_time_t(system_clock::time_point::max());
else if (dt < duration(0) and now <= system_clock::time_point::min() - dt)
return system_clock::to_time_t(system_clock::time_point::min());
return system_clock::to_time_t(now + std::chrono::duration_cast<system_clock::duration>(dt));
}
Blob
@@ -335,19 +296,4 @@ findMapValue(const msgpack::object& map, const char* key, size_t key_length)
return nullptr;
}
std::string
printByteCount(size_t bytes)
{
static constexpr std::string_view suffixes[] = {"B", "KB", "MB", "GB", "TB"};
size_t suffixIndex = 0;
double count = static_cast<double>(bytes);
while (count >= 1024 && suffixIndex < std::size(suffixes) - 1) {
count /= 1024;
suffixIndex++;
}
if (suffixIndex == 0)
return fmt::format("{} {}", bytes, suffixes[suffixIndex]);
return fmt::format("{:.2f} {} ({} bytes)", count, suffixes[suffixIndex], bytes);
}
} // namespace dht
+1 -12
View File
@@ -85,7 +85,7 @@ ValueType::DEFAULT_STORE_POLICY(InfoHash, const std::shared_ptr<Value>& v, const
size_t
Value::size() const
{
return cypher.size() + data.size() + signature.size() + user_type.size() + pushType.size();
return cypher.size() + data.size() + signature.size() + user_type.size();
}
void
@@ -104,14 +104,9 @@ Value::msgpack_unpack(const msgpack::object& o)
} else
throw msgpack::type_error();
priority = 0;
pushType.clear();
if (auto rprio = findMapValue(o, VALUE_KEY_PRIO)) {
priority = rprio->as<unsigned>();
}
if (auto rpt = findMapValue(o, VALUE_KEY_PUSHTYPE)) {
pushType = rpt->as<std::string>();
}
}
void
@@ -202,9 +197,6 @@ Value::Value(const Json::Value& json)
const auto& jprio = json["prio"];
if (jprio.isIntegral())
priority = jprio.asUInt();
const auto& jpt = json[VALUE_KEY_PUSHTYPE];
if (jpt.isString())
pushType = jpt.asString();
}
Json::Value
@@ -231,8 +223,6 @@ Value::toJson() const
}
if (priority)
val["prio"] = priority;
if (not pushType.empty())
val[VALUE_KEY_PUSHTYPE] = pushType;
return val;
}
@@ -271,7 +261,6 @@ Value::encrypt(const crypto::PrivateKey& from, const crypto::PublicKey& to)
sign(from);
Value nv {id};
nv.priority = priority;
nv.pushType = pushType;
nv.setCypher(to.encrypt(getToEncrypt()));
return nv;
}
+6 -27
View File
@@ -71,21 +71,11 @@ public:
{
std::vector<Sp<Value>> expired_values;
for (auto it = values.begin(); it != values.end();) {
auto effectiveExpiration = it->second.expiration;
if (status == ListenSyncStatus::SYNCED)
// While the listen is synced, the remote node pushes
// explicit expirations and periodic refreshes. Add a
// grace period proportional to the value's type
// expiration so that a single lost UDP refresh
// notification does not cause a premature local drop.
// A grace of one type-expiration covers exactly one
// missed re-announcement cycle.
effectiveExpiration += it->second.typeExpiration;
if (effectiveExpiration <= now) {
if (it->second.expiration <= now) {
expired_values.emplace_back(std::move(it->second.data));
it = values.erase(it);
} else {
next = std::min(next, effectiveExpiration);
next = std::min(next, it->second.expiration);
++it;
}
}
@@ -158,14 +148,12 @@ private:
Sp<Value> data {};
time_point created {};
time_point expiration {};
duration typeExpiration {}; /* value type's expiration period, used as grace when synced */
CacheValueStorage() {}
CacheValueStorage(const Sp<Value>& v, time_point t, time_point e, duration te)
CacheValueStorage(const Sp<Value>& v, time_point t, time_point e)
: data(v)
, created(t)
, expiration(e)
, typeExpiration(te)
{}
};
@@ -178,22 +166,15 @@ private:
{
std::vector<Sp<Value>> nvals;
for (const auto& value : new_values) {
auto te = types.getType(value->type).expiration;
auto v = values.find(value->id);
if (v == values.end()) {
// new value
nvals.emplace_back(value);
values.emplace(value->id, CacheValueStorage(value, now, now + te, te));
values.emplace(value->id, CacheValueStorage(value, now, now + types.getType(value->type).expiration));
} else {
// refreshed value
v->second.created = now;
v->second.expiration = now + te;
v->second.typeExpiration = te;
if (value->seq > v->second.data->seq) {
// edited value (same rule as OpValueCache): emit as add
v->second.data = value;
nvals.emplace_back(value);
}
v->second.expiration = now + types.getType(v->second.data->type).expiration;
}
}
CallbackQueue ret;
@@ -220,10 +201,8 @@ private:
auto v = values.find(vid);
if (v == values.end())
return;
auto te = types.getType(v->second.data->type).expiration;
v->second.created = now;
v->second.expiration = now + te;
v->second.typeExpiration = te;
v->second.expiration = now + types.getType(v->second.data->type).expiration;
}
};
+2 -188
View File
@@ -59,24 +59,6 @@ CryptoTester::testSignatureEncryption()
}
}
void
CryptoTester::testPublicKeyPemIsPlainText()
{
auto key = dht::crypto::PrivateKey::generate();
auto pem = key.getPublicKey().toString();
// The exported key is PEM text: it must not carry the terminating null
// byte GnuTLS counts in the exported size, which would otherwise be
// embedded in anything the string is serialized into.
CPPUNIT_ASSERT(not pem.empty());
CPPUNIT_ASSERT_EQUAL(std::string::npos, pem.find('\0'));
CPPUNIT_ASSERT_EQUAL('\n', pem.back());
// The exported text still reads back as the same key.
dht::crypto::PublicKey reloaded(dht::Blob(pem.begin(), pem.end()));
CPPUNIT_ASSERT_EQUAL(key.getPublicKey().getId(), reloaded.getId());
}
void
CryptoTester::testCertificateRevocation()
{
@@ -113,159 +95,10 @@ CryptoTester::testCertificateRevocation()
CPPUNIT_ASSERT_MESSAGE(v.toString(), v);
}
namespace {
/** Read a CRL Number extension blob (big-endian, variable length) as an integer. */
uint64_t
crlNumberValue(const dht::Blob& number)
{
uint64_t value {0};
for (uint8_t byte : number)
value = (value << 8) | byte;
return value;
}
} // namespace
void
CryptoTester::testRevocationListNumber()
{
auto ca = dht::crypto::generateIdentity("ca");
auto device = dht::crypto::generateIdentity("dev", ca);
dht::crypto::RevocationList crl;
CPPUNIT_ASSERT_EQUAL(uint64_t(0), crlNumberValue(crl.getNumber()));
crl.revoke(*device.second);
crl.sign(ca);
auto first = crl.getNumber();
// An unnumbered list gets a random number on its first signature.
CPPUNIT_ASSERT(not first.empty());
CPPUNIT_ASSERT(crlNumberValue(first) != 0);
// Re-signing keeps the same lineage and moves the number forward.
crl.sign(ca);
auto second = crl.getNumber();
CPPUNIT_ASSERT_EQUAL(crlNumberValue(first) + 1, crlNumberValue(second));
crl.sign(ca);
CPPUNIT_ASSERT_EQUAL(crlNumberValue(first) + 2, crlNumberValue(crl.getNumber()));
// The number is a DER integer, so it is stored with leading zeros stripped.
// Incrementing must still work for numbers shorter than 8 bytes.
for (uint64_t start : {uint64_t(1), uint64_t(0x7f), uint64_t(0x80), uint64_t(0xffff)}) {
dht::crypto::RevocationList small;
small.revoke(*device.second);
small.sign(ca, {}, start);
CPPUNIT_ASSERT_EQUAL(start, crlNumberValue(small.getNumber()));
small.sign(ca);
CPPUNIT_ASSERT_EQUAL(start + 1, crlNumberValue(small.getNumber()));
small.sign(ca);
CPPUNIT_ASSERT_EQUAL(start + 2, crlNumberValue(small.getNumber()));
}
}
void
CryptoTester::testRevocationListExplicitNumber()
{
auto ca = dht::crypto::generateIdentity("ca");
auto device = dht::crypto::generateIdentity("dev", ca);
// Values chosen to cover leading zero bytes, a full-width number and the largest
// value that still encodes as a positive 8-byte DER integer.
const std::vector<uint64_t> numbers {1,
42,
0xff,
0x100,
0x0000'0001'0000'0000ull,
0x0000'02a1'0000'0001ull,
0x7fff'ffff'ffff'ffffull};
for (auto number : numbers) {
dht::crypto::RevocationList crl;
crl.revoke(*device.second);
crl.sign(ca, {}, number);
CPPUNIT_ASSERT_EQUAL(number, crlNumberValue(crl.getNumber()));
CPPUNIT_ASSERT(crl.isSignedBy(*ca.second));
CPPUNIT_ASSERT(crl.isRevoked(*device.second));
// The number must survive a pack/unpack round-trip.
dht::crypto::RevocationList unpacked(crl.getPacked());
CPPUNIT_ASSERT_EQUAL(number, crlNumberValue(unpacked.getNumber()));
}
// An explicit number overrides the increment, in both directions.
dht::crypto::RevocationList crl;
crl.revoke(*device.second);
crl.sign(ca, {}, 1000);
CPPUNIT_ASSERT_EQUAL(uint64_t(1000), crlNumberValue(crl.getNumber()));
crl.sign(ca, {}, 5);
CPPUNIT_ASSERT_EQUAL(uint64_t(5), crlNumberValue(crl.getNumber()));
// Passing 0 falls back to the default behavior: increment the current number.
crl.sign(ca, {}, 0);
CPPUNIT_ASSERT_EQUAL(uint64_t(6), crlNumberValue(crl.getNumber()));
}
void
CryptoTester::testRevocationListPartitionedNumber()
{
// Two devices holding the same authority issue revocation lists independently.
// RFC 5280 5.2.3 requires CRL numbers to increase monotonically for a given CRL
// scope and issuer; partitioning the low bits by device keeps each sequence
// monotonic while making collisions between devices impossible.
constexpr unsigned TAG_BITS = 24;
constexpr uint64_t TAG_A = 0x0a0b0c;
constexpr uint64_t TAG_B = 0x0a0b0d;
auto ca = dht::crypto::generateIdentity("ca");
auto account = dht::crypto::generateIdentity("acc", ca, 4096, true);
auto deviceA = dht::crypto::generateIdentity("devA", account);
auto deviceB = dht::crypto::generateIdentity("devB", account);
uint64_t previous {0};
dht::crypto::RevocationList crlA;
for (unsigned i = 1; i <= 4; i++) {
crlA.revoke(*deviceA.second);
crlA.sign(account, {}, (uint64_t(i) << TAG_BITS) | TAG_A);
auto number = crlNumberValue(crlA.getNumber());
CPPUNIT_ASSERT_EQUAL(TAG_A, number & ((uint64_t(1) << TAG_BITS) - 1));
CPPUNIT_ASSERT(number > previous);
CPPUNIT_ASSERT_EQUAL(uint64_t(0), number >> 63);
previous = number;
}
// Both devices branch from the same counter but never mint the same number.
dht::crypto::RevocationList branchA;
branchA.revoke(*deviceA.second);
branchA.sign(account, {}, (uint64_t(9) << TAG_BITS) | TAG_A);
dht::crypto::RevocationList branchB;
branchB.revoke(*deviceB.second);
branchB.sign(account, {}, (uint64_t(9) << TAG_BITS) | TAG_B);
CPPUNIT_ASSERT(crlNumberValue(branchA.getNumber()) != crlNumberValue(branchB.getNumber()));
// Both are valid and revoke only their own target.
CPPUNIT_ASSERT(branchA.isSignedBy(*account.second));
CPPUNIT_ASSERT(branchB.isSignedBy(*account.second));
CPPUNIT_ASSERT(branchA.isRevoked(*deviceA.second));
CPPUNIT_ASSERT(not branchA.isRevoked(*deviceB.second));
CPPUNIT_ASSERT(branchB.isRevoked(*deviceB.second));
CPPUNIT_ASSERT(not branchB.isRevoked(*deviceA.second));
// A number minted by an older client is fully random, so bit 63 may be set. Masking
// it out and taking the counter part still yields a strictly greater number.
constexpr uint64_t legacy = 0xdead'beef'0123'4567ull;
dht::crypto::RevocationList legacyCrl;
legacyCrl.revoke(*deviceA.second);
legacyCrl.sign(account, {}, legacy);
auto counter = ((crlNumberValue(legacyCrl.getNumber()) & ~(uint64_t(1) << 63)) >> TAG_BITS) + 1;
legacyCrl.sign(account, {}, (counter << TAG_BITS) | TAG_A);
CPPUNIT_ASSERT(crlNumberValue(legacyCrl.getNumber()) > (legacy & ~(uint64_t(1) << 63)));
CPPUNIT_ASSERT_EQUAL(uint64_t(0), crlNumberValue(legacyCrl.getNumber()) >> 63);
}
void
CryptoTester::testCertificateRequest()
{ // Generate CA
{
// Generate CA
auto ca = dht::crypto::generateIdentity("Test CA");
// Generate signed request
@@ -316,25 +149,6 @@ CryptoTester::testCertificateSerialNumber()
};
auto serial = dht::crypto::Certificate(cert_pem).getSerialNumber();
CPPUNIT_ASSERT(std::equal(SERIAL.begin(), SERIAL.end(), serial.begin(), serial.end()));
auto key = dht::crypto::PrivateKey::generateEC();
for (unsigned i = 0; i < 32; i++) {
auto generated = dht::crypto::Certificate::generate(key, "generated", {});
auto generatedSerial = generated.getSerialNumber();
CPPUNIT_ASSERT(not generatedSerial.empty());
CPPUNIT_ASSERT_EQUAL(uint8_t {0}, uint8_t(generatedSerial.front() & 0x80));
CPPUNIT_ASSERT(std::any_of(generatedSerial.begin(), generatedSerial.end(), [](uint8_t byte) { return byte != 0; }));
}
}
void
CryptoTester::testEcKeyCurve()
{
auto key = dht::crypto::PrivateKey::generateEC(GNUTLS_ECC_CURVE_SECP256R1);
unsigned int bits = 0;
CPPUNIT_ASSERT_EQUAL(static_cast<int>(GNUTLS_PK_EC),
gnutls_x509_privkey_get_pk_algorithm2(key.x509_key, &bits));
CPPUNIT_ASSERT_EQUAL(256u, bits);
}
void
-25
View File
@@ -12,14 +12,9 @@ class CryptoTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(CryptoTester);
CPPUNIT_TEST(testSignatureEncryption);
CPPUNIT_TEST(testPublicKeyPemIsPlainText);
CPPUNIT_TEST(testCertificateRevocation);
CPPUNIT_TEST(testRevocationListNumber);
CPPUNIT_TEST(testRevocationListExplicitNumber);
CPPUNIT_TEST(testRevocationListPartitionedNumber);
CPPUNIT_TEST(testCertificateRequest);
CPPUNIT_TEST(testCertificateSerialNumber);
CPPUNIT_TEST(testEcKeyCurve);
CPPUNIT_TEST(testOcsp);
CPPUNIT_TEST(testAesEncryption);
CPPUNIT_TEST(testAesEncryptionWithMultipleKeySizes);
@@ -41,26 +36,10 @@ public:
* Test data signature, encryption and decryption
*/
void testSignatureEncryption();
/**
* Test that an exported public key is text only, with no embedded null byte
*/
void testPublicKeyPemIsPlainText();
/**
* Test certificate generation, validation and revocation
*/
void testCertificateRevocation();
/**
* Test the default CRL Number behavior: random on first signature, incremented after
*/
void testRevocationListNumber();
/**
* Test signing a revocation list with a caller-provided CRL Number
*/
void testRevocationListExplicitNumber();
/**
* Test that issuers sharing an authority can partition the CRL Number space
*/
void testRevocationListPartitionedNumber();
/**
* Test certificate requests
*/
@@ -69,10 +48,6 @@ public:
* Test certificate serial number extraction
*/
void testCertificateSerialNumber();
/**
* Test EC private key generation with an explicit curve
*/
void testEcKeyCurve();
/**
* Test OCSP
*/
+16 -1498
View File
File diff suppressed because it is too large. Load diff
+1 -35
View File
@@ -8,6 +8,7 @@
#include <opendht/dhtrunner.h>
#include <opendht/dht_proxy_server.h>
#include <opendht/log.h>
namespace test {
@@ -16,21 +17,10 @@ class DhtProxyTester : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE(DhtProxyTester);
CPPUNIT_TEST(testGetPut);
CPPUNIT_TEST(testListen);
CPPUNIT_TEST(testListenValueEdit);
CPPUNIT_TEST(testPushListenValueEdit);
CPPUNIT_TEST(testResubscribeGetValues);
CPPUNIT_TEST(testPushNotification);
CPPUNIT_TEST(testProxyServerClientFullChain);
CPPUNIT_TEST(testPutGet40KChars);
CPPUNIT_TEST(testFuzzy);
CPPUNIT_TEST(testShutdownStop);
CPPUNIT_TEST(testGetAfterListen);
CPPUNIT_TEST(testListenDuplicatePut);
CPPUNIT_TEST(testPushMultiValueEditExpire);
CPPUNIT_TEST(testPushRefreshNoSpuriousExpire);
CPPUNIT_TEST(testProxyListenEditChain);
CPPUNIT_TEST(testPushRapidEditsNoPhantom);
CPPUNIT_TEST(testPushRefreshAfterEditKeepsLatest);
CPPUNIT_TEST_SUITE_END();
public:
@@ -51,28 +41,11 @@ public:
* Test listen
*/
void testListen();
/**
* Test that editing a value only triggers an add callback via proxy, no expire
*/
void testListenValueEdit();
/**
* Test that editing a value only triggers an add callback via push,
* no phantom expire, and that real expiration is received.
*/
void testPushListenValueEdit();
/**
* When a proxy redo a subscribe on the proxy
* it should retrieve existant values
*/
void testResubscribeGetValues();
/**
* Test push notification mechanism and OpValueCache
*/
void testPushNotification();
/**
* Test end-to-end chain through proxy server and proxy client
*/
void testProxyServerClientFullChain();
/**
* Test MTU put/get on dht
*/
@@ -81,13 +54,6 @@ public:
void testFuzzy();
void testShutdownStop();
void testGetAfterListen();
void testListenDuplicatePut();
void testPushMultiValueEditExpire();
void testPushRefreshNoSpuriousExpire();
void testProxyListenEditChain();
void testPushRapidEditsNoPhantom();
void testPushRefreshAfterEditKeepsLatest();
private:
dht::DhtRunner::Config clientConfig {};
-93
View File
@@ -1,93 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_dhtproxy_client.h"
#include <any>
#include <mutex>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wkeyword-macro"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
#define private public
#include "opendht/http.h"
#include "opendht/dht_proxy_client.h"
#undef private
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#include "../src/op_cache.cpp"
#include "../src/dht_proxy_client.cpp"
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(DhtProxyClientTester);
using namespace dht;
void
DhtProxyClientTester::testResubscribeUsesKeyRoute()
{
#ifdef OPENDHT_PUSH_NOTIFICATIONS
DhtProxyClient client({}, {}, [] {}, "http://127.0.0.1:8080", "OpenDHT-Test", "client-id", "push-token");
DhtProxyClient::Listener listener {ValueCallback([](const std::vector<Sp<Value>>&, bool) { return true; })};
listener.opstate = std::make_shared<DhtProxyClient::OperationState>();
listener.cb = [](const std::vector<Sp<Value>>&, bool, system_clock::time_point) {
return true;
};
auto key = InfoHash::get("proxy-client-resubscribe");
client.resubscribe(key, 1, listener);
CPPUNIT_ASSERT(listener.request);
CPPUNIT_ASSERT_EQUAL(std::string("/key/") + key.toString(), std::string(listener.request->header_.request_target()));
CPPUNIT_ASSERT(listener.request->body_.find("\"refresh\"") != std::string::npos);
CPPUNIT_ASSERT(listener.request->body_.find("true") != std::string::npos);
#else
CPPUNIT_ASSERT(true);
#endif
}
void
DhtProxyClientTester::testSetPushNotificationTokenResubscribesWithNewToken()
{
#ifdef OPENDHT_PUSH_NOTIFICATIONS
DhtProxyClient client({}, {}, [] {}, "http://127.0.0.1:8080", "OpenDHT-Test", "client-id", "old-token");
client.statusIpv4_ = NodeStatus::Connected;
auto key = InfoHash::get("proxy-client-token-rotation");
auto& search = client.searches_[key];
auto [it, inserted] = search.listeners.emplace(std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(ValueCallback(
[](const std::vector<Sp<Value>>&, bool) { return true; })));
CPPUNIT_ASSERT(inserted);
auto& listener = it->second;
listener.opstate = std::make_shared<DhtProxyClient::OperationState>();
listener.cb = [](const std::vector<Sp<Value>>&, bool, system_clock::time_point) {
return true;
};
client.setPushNotificationToken("new-token");
CPPUNIT_ASSERT(listener.request);
CPPUNIT_ASSERT_EQUAL(std::string("/key/") + key.toString(), std::string(listener.request->header_.request_target()));
CPPUNIT_ASSERT(listener.request->body_.find("new-token") != std::string::npos);
CPPUNIT_ASSERT(listener.request->body_.find("old-token") == std::string::npos);
CPPUNIT_ASSERT(listener.request->body_.find("\"refresh\"") != std::string::npos);
CPPUNIT_ASSERT(listener.request->body_.find("true") != std::string::npos);
#else
CPPUNIT_ASSERT(true);
#endif
}
} // namespace test
-22
View File
@@ -1,22 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
namespace test {
class DhtProxyClientTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(DhtProxyClientTester);
CPPUNIT_TEST(testResubscribeUsesKeyRoute);
CPPUNIT_TEST(testSetPushNotificationTokenResubscribesWithNewToken);
CPPUNIT_TEST_SUITE_END();
public:
void testResubscribeUsesKeyRoute();
void testSetPushNotificationTokenResubscribesWithNewToken();
};
} // namespace test
+7 -7
View File
@@ -51,11 +51,11 @@ DhtProxyStressTester::tearDown()
std::condition_variable cv;
std::mutex cv_m;
nodeProxy->shutdown([&] {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&] { return done; }));
serverProxy.reset();
nodeProxy.reset();
@@ -70,7 +70,7 @@ DhtProxyStressTester::testRepeatValues()
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
bool done = false;
@@ -80,7 +80,7 @@ DhtProxyStressTester::testRepeatValues()
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
@@ -90,7 +90,7 @@ DhtProxyStressTester::testRepeatValues()
std::vector<dht::Blob> values;
auto token = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
if (not expired) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
for (const auto& value : v)
values.emplace_back(value->data);
done = true;
@@ -132,7 +132,7 @@ DhtProxyStressTester::testRepeatValues()
val->id = id++;
nodePeer.put(key, val, [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
@@ -145,7 +145,7 @@ DhtProxyStressTester::testRepeatValues()
if (not expired) {
for (const auto& value : v) {
if (value->id == val->id) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
break;
+15 -1072
View File
File diff suppressed because it is too large. Load diff
-38
View File
@@ -18,24 +18,8 @@ class DhtRunnerTester : public CppUnit::TestFixture
CPPUNIT_TEST(testPutDuplicate);
CPPUNIT_TEST(testPutOverride);
CPPUNIT_TEST(testListen);
CPPUNIT_TEST(testListenValueEdit);
CPPUNIT_TEST(testListenLotOfBytes);
CPPUNIT_TEST(testStoreEmptyValue);
CPPUNIT_TEST(testIdOps);
CPPUNIT_TEST(testImportValuesPreservesRemoteQuota);
CPPUNIT_TEST(testExportValuesUsesSystemTime);
CPPUNIT_TEST(testImportValuesPreservesStoredExpiration);
CPPUNIT_TEST(testImportPermanentValueExpires);
CPPUNIT_TEST(testBootstrapSetsConnectingState);
CPPUNIT_TEST(testBootstrapThenPutNoRace);
CPPUNIT_TEST(testBootstrapMissingNodeThenPutFails);
CPPUNIT_TEST(testShutdownCompletesWithPendingPut);
CPPUNIT_TEST(testSavedPortFallback);
CPPUNIT_TEST(testGetAfterListen);
CPPUNIT_TEST(testListenDuplicatePut);
CPPUNIT_TEST(testListenMultiSourceExpire);
CPPUNIT_TEST(testListenEditChainThenExpire);
CPPUNIT_TEST(testListenMultiValuePartialExpire);
CPPUNIT_TEST_SUITE_END();
dht::DhtRunner node1 {};
@@ -74,32 +58,10 @@ public:
* Test methods requiring a node identity
*/
void testIdOps();
void testImportValuesPreservesRemoteQuota();
void testExportValuesUsesSystemTime();
void testImportValuesPreservesStoredExpiration();
void testImportPermanentValueExpires();
void testBootstrapSetsConnectingState();
void testBootstrapThenPutNoRace();
void testBootstrapMissingNodeThenPutFails();
void testShutdownCompletesWithPendingPut();
void testSavedPortFallback();
void testGetAfterListen();
void testListenDuplicatePut();
void testListenMultiSourceExpire();
void testListenEditChainThenExpire();
void testListenMultiValuePartialExpire();
/**
* Test that editing a value only triggers an add callback, no expire
*/
void testListenValueEdit();
/**
* Test listen method with lot of datas
*/
void testListenLotOfBytes();
/**
* Test storing an empty value
*/
void testStoreEmptyValue();
/**
* Test multithread
*/
+5 -36
View File
@@ -8,7 +8,6 @@
#include <iostream>
#include <string>
#include <atomic>
#include <chrono>
#include <condition_variable>
@@ -333,43 +332,13 @@ HttpTester::test_parse_url_just_slash()
CPPUNIT_ASSERT_EQUAL(std::string("/"), parsed.target);
}
void
HttpTester::test_completed_resolver_callback_uses_io_thread()
{
asio::io_context ioContext;
const asio::ip::tcp::endpoint endpoint(asio::ip::address_v4::loopback(), 80);
dht::http::Resolver resolver(ioContext, {endpoint});
const auto callerThread = std::this_thread::get_id();
std::thread::id callbackThread;
asio::error_code callbackError;
std::vector<asio::ip::tcp::endpoint> resolvedEndpoints;
std::atomic_bool callbackCalled {false};
resolver.add_callback([&](const asio::error_code& ec, const auto& endpoints) {
callbackError = ec;
callbackThread = std::this_thread::get_id();
resolvedEndpoints = endpoints;
callbackCalled = true;
});
CPPUNIT_ASSERT(!callbackCalled);
std::thread ioThread([&] { ioContext.run(); });
ioThread.join();
CPPUNIT_ASSERT(callbackCalled);
CPPUNIT_ASSERT(!callbackError);
CPPUNIT_ASSERT(callbackThread != callerThread);
CPPUNIT_ASSERT_EQUAL(size_t(1), resolvedEndpoints.size());
CPPUNIT_ASSERT_EQUAL(endpoint, resolvedEndpoints.front());
}
void
HttpTester::test_send_json()
{
// Arrange
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
std::unique_lock<std::mutex> lk(cv_m);
bool done = false;
unsigned int status = 0;
@@ -381,7 +350,7 @@ HttpTester::test_send_json()
"http://127.0.0.1:8080/key/key",
json,
[&](Json::Value value, const dht::http::Response& response) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
resp_val = std::move(value);
status = response.status_code;
done = true;
@@ -399,7 +368,7 @@ HttpTester::test_send_json()
request = std::make_shared<dht::http::Request>(serverProxy->io_context(),
"http://google.ca",
[&](const dht::http::Response& response) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
status = response.status_code;
done = true;
cv.notify_all();
@@ -413,7 +382,7 @@ HttpTester::test_send_json()
request = std::make_shared<dht::http::Request>(serverProxy->io_context(),
"https://google.ca",
[&](const dht::http::Response& response) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
status = response.status_code;
done = true;
cv.notify_all();
@@ -427,7 +396,7 @@ HttpTester::test_send_json()
request = std::make_shared<dht::http::Request>(serverProxy->io_context(),
"https://google.ca/sdbjklwGBIP",
[&](const dht::http::Response& response) {
std::lock_guard lk(cv_m);
std::lock_guard<std::mutex> lk(cv_m);
status = response.status_code;
done = true;
cv.notify_all();
-2
View File
@@ -39,7 +39,6 @@ class HttpTester : public CppUnit::TestFixture
CPPUNIT_TEST(test_parse_url_complex_path_query_fragment);
CPPUNIT_TEST(test_parse_url_empty);
CPPUNIT_TEST(test_parse_url_just_slash);
CPPUNIT_TEST(test_completed_resolver_callback_uses_io_thread);
// send
CPPUNIT_TEST(test_send_json);
CPPUNIT_TEST_SUITE_END();
@@ -83,7 +82,6 @@ public:
void test_parse_url_complex_path_query_fragment();
void test_parse_url_empty();
void test_parse_url_just_slash();
void test_completed_resolver_callback_uses_io_thread();
/**
* Test send(json)
*/
+14 -39
View File
@@ -6,7 +6,6 @@
// std
#include <iostream>
#include <string>
#include <string_view>
// opendht
#include "opendht/infohash.h"
@@ -22,60 +21,47 @@ void
InfoHashTester::testConstructors()
{
// Default constructor creates a null infohash
constexpr auto nullHash = dht::InfoHash();
auto nullHash = dht::InfoHash();
CPPUNIT_ASSERT_EQUAL((size_t) 20u, nullHash.size());
CPPUNIT_ASSERT(!nullHash);
// Build from a uint8_t. if length to short, should get a null infohash
constexpr uint8_t too_short[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
auto infohash = dht::InfoHash(too_short, 8);
uint8_t to_short[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
auto infohash = dht::InfoHash(to_short, 8);
CPPUNIT_ASSERT_EQUAL((size_t) 20u, infohash.size());
CPPUNIT_ASSERT_EQUAL(std::string("0000000000000000000000000000000000000000"), infohash.toString());
// Build from a uint8_t. if length is enough, data should contains the uint8_t
constexpr uint8_t enough[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa};
uint8_t enough[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa};
infohash = dht::InfoHash(enough, 20);
static_assert(dht::InfoHash(enough, 20) != nullHash);
CPPUNIT_ASSERT_EQUAL((size_t) 20u, infohash.size());
CPPUNIT_ASSERT(infohash.size() == 20);
const auto* data = infohash.data();
for (auto i = 0; i < 20; ++i) {
CPPUNIT_ASSERT_EQUAL(enough[i], data[i]);
}
// if too long, should be cutted to 20
constexpr uint8_t tooLong[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x1,
0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb0};
uint8_t tooLong[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x1,
0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb0};
infohash = dht::InfoHash(tooLong, 21);
CPPUNIT_ASSERT(infohash.size() == 20);
const auto* data2 = infohash.data();
for (auto i = 0; i < 20; ++i) {
CPPUNIT_ASSERT_EQUAL(enough[i], data2[i]);
}
// Build from string
constexpr auto infohashFromStr = dht::InfoHash("0102030405060708090A0102030405060708090A");
auto infohashFromStr = dht::InfoHash("0102030405060708090A0102030405060708090A");
CPPUNIT_ASSERT_EQUAL((size_t) 20u, infohashFromStr.size());
const auto* dataStr = infohashFromStr.data();
for (auto i = 0; i < 20; ++i) {
CPPUNIT_ASSERT_EQUAL((int) dataStr[i], (int) data[i]);
}
// invalid strings
constexpr auto invalidHash = dht::InfoHash("invalid_string0102030405060708090A010203");
CPPUNIT_ASSERT_EQUAL((size_t) 20u, invalidHash.size());
CPPUNIT_ASSERT(!invalidHash);
CPPUNIT_ASSERT_EQUAL(nullHash, invalidHash);
static_assert(invalidHash == nullHash, "Invalid hash string should result in null hash");
constexpr auto tooShortHash = dht::InfoHash("0123456789abcdef0123456789abcdef012345");
CPPUNIT_ASSERT_EQUAL((size_t) 20u, tooShortHash.size());
CPPUNIT_ASSERT(!tooShortHash);
CPPUNIT_ASSERT_EQUAL(nullHash, tooShortHash);
static_assert(tooShortHash == nullHash, "Too short hash string should result in null hash");
}
void
InfoHashTester::testComparators()
{
constexpr auto nullHash = dht::InfoHash();
constexpr auto minHash = dht::InfoHash("0000000000000000000000000000000000111110");
constexpr auto maxHash = dht::InfoHash("0111110000000000000000000000000000000000");
auto nullHash = dht::InfoHash();
auto minHash = dht::InfoHash("0000000000000000000000000000000000111110");
auto maxHash = dht::InfoHash("0111110000000000000000000000000000000000");
// operator ==
CPPUNIT_ASSERT_EQUAL(minHash, minHash);
CPPUNIT_ASSERT_EQUAL(minHash, dht::InfoHash("0000000000000000000000000000000000111110"));
@@ -140,21 +126,10 @@ InfoHashTester::testXorCmp()
void
InfoHashTester::testHex()
{
using namespace std::literals;
static constexpr auto H = "0123456789abcdef0123456789abcdef01234567"sv;
const std::string TEST_HASH_STR(H);
const std::string TEST_HASH_STR("01b20304d5060708090a010203e05060708090ae");
dht::InfoHash TEST_HASH(TEST_HASH_STR);
static constexpr dht::InfoHash TEST_HASH_CONST(H);
CPPUNIT_ASSERT_EQUAL(TEST_HASH_STR, TEST_HASH.toString());
CPPUNIT_ASSERT_EQUAL(H, TEST_HASH.to_view());
CPPUNIT_ASSERT_EQUAL(TEST_HASH_STR, dht::toHex(TEST_HASH.data(), TEST_HASH.size()));
CPPUNIT_ASSERT_EQUAL(TEST_HASH_CONST.to_view(), H);
static constexpr auto hexArray = dht::toHexArray(TEST_HASH_CONST);
static constexpr auto hexArrayView = std::string_view(hexArray.data(), hexArray.size());
static_assert(hexArrayView == H, "Hex array does not match");
CPPUNIT_ASSERT_EQUAL(std::string_view(TEST_HASH_STR), hexArrayView);
}
void
-672
View File
@@ -1,672 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_networkengine.h"
#ifdef _MSC_VER
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(NetworkEngineTester);
void
NetworkEngineTester::setUp()
{}
void
NetworkEngineTester::tearDown()
{}
void
NetworkEngineTester::testDisabledOnMsvc()
{
CPPUNIT_ASSERT(true);
}
} // namespace test
#else
#include <any>
#include <mutex>
#ifdef _WIN32
#ifdef opendht_EXPORTS
#undef opendht_EXPORTS
#endif
#endif
#include "opendht/node.h"
#include "opendht/network_engine.h"
#include "opendht/network_utils.h"
#include "opendht/utils.h"
#include "opendht/value.h"
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wkeyword-macro"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
#define private public
#include "opendht/dht.h"
#undef private
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#include "../src/parsed_message.h"
#include "../src/search.h"
// Hack to test internal classes
#include "../src/op_cache.cpp"
#include "../src/routing_table.cpp"
#include "../src/dht.cpp"
#include "../src/node.cpp"
#include "../src/node_cache.cpp"
#include "../src/network_engine.cpp"
#include <string>
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(NetworkEngineTester);
using namespace dht;
using namespace dht::net;
static constexpr size_t TEST_MTU {1280};
class TestDatagramSocket : public net::DatagramSocket
{
public:
TestDatagramSocket()
{
bound_.setFamily(AF_INET);
bound_.setLoopback();
bound_.setPort(4222);
}
int sendTo(const SockAddr& dest, const uint8_t* data, size_t size, bool replied) override
{
sends.emplace_back(SentPacket {dest, Blob(data, data + size), replied});
return 0;
}
bool hasIPv4() const override { return true; }
bool hasIPv6() const override { return false; }
const SockAddr& getBoundRef(sa_family_t family = AF_UNSPEC) const override
{
(void) family;
return bound_;
}
void stop() override {}
struct SentPacket
{
SockAddr dest;
Blob data;
bool replied;
};
SockAddr bound_;
std::vector<SentPacket> sends {};
};
static Blob
serializeValue(const std::string& data)
{
auto val = std::make_shared<Value>((const uint8_t*) data.data(), data.size());
return packMsg(val);
}
static Blob
makeReplyHeaderPacketBlob(const InfoHash& id, Tid tid, const std::vector<size_t>& valueSizes)
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_R);
pk.pack_map(2);
pk.pack(KEY_REQ_ID);
pk.pack(id);
pk.pack(KEY_REQ_VALUES);
pk.pack_array(valueSizes.size());
for (const auto& size : valueSizes)
pk.pack(size);
pk.pack(KEY_TID);
pk.pack(tid);
pk.pack(KEY_Y);
pk.pack(KEY_R);
return {buffer.data(), buffer.data() + buffer.size()};
}
static Blob
makeValueDataPacketBlob(Tid tid, unsigned index, size_t offset, const Blob& data)
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_Y);
pk.pack(KEY_V);
pk.pack(KEY_TID);
pk.pack(tid);
pk.pack(KEY_V);
pk.pack_map(1);
pk.pack(index);
pk.pack_map(2);
pk.pack("o");
pk.pack(offset);
pk.pack("d");
pk.pack_bin(data.size());
pk.pack_bin_body((const char*) data.data(), data.size());
return {buffer.data(), buffer.data() + buffer.size()};
}
static Blob
makeListenPacketBlob(const InfoHash& id, const InfoHash& hash, Tid tid, Tid socketId, const Blob& token)
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4);
pk.pack(KEY_A);
pk.pack_map(5);
pk.pack(KEY_REQ_ID);
pk.pack(id);
pk.pack(KEY_VERSION);
pk.pack(1);
pk.pack(KEY_REQ_H);
pk.pack(hash);
pk.pack(KEY_REQ_TOKEN);
packToken(pk, token);
pk.pack(KEY_REQ_SID);
pk.pack(socketId);
pk.pack(KEY_Q);
pk.pack(QUERY_LISTEN);
pk.pack(KEY_TID);
pk.pack(tid);
pk.pack(KEY_Y);
pk.pack(KEY_Q);
return {buffer.data(), buffer.data() + buffer.size()};
}
static SockAddr
makeIPv4(const char* ip, in_port_t port)
{
SockAddr addr;
addr.setFamily(AF_INET);
addr.setAddress(ip);
addr.setPort(port);
return addr;
}
static net::NetworkEngine
makeEngine(std::unique_ptr<TestDatagramSocket>&& socket,
Scheduler& scheduler,
InfoHash& myid,
std::mt19937_64& rd,
int& onNewNodeCalls)
{
net::NetworkConfig config {};
config.max_req_per_sec = -1;
config.max_peer_req_per_sec = -1;
return net::NetworkEngine(
myid,
config,
std::move(socket),
{},
rd,
scheduler,
[](Sp<net::Request>, net::DhtProtocolException) {},
[&onNewNodeCalls](const Sp<Node>&, int) { ++onNewNodeCalls; },
[](const InfoHash&, const SockAddr&) {},
[](Sp<Node>) { return net::RequestAnswer {}; },
[](Sp<Node>, const InfoHash&, want_t) { return net::RequestAnswer {}; },
[](Sp<Node>, const InfoHash&, want_t, const Query&) { return net::RequestAnswer {}; },
[](Sp<Node>, const InfoHash&, const Blob&, Tid, const Query&, int) { return net::RequestAnswer {}; },
[](Sp<Node>, const InfoHash&, const Blob&, const std::vector<Sp<Value>>&, const time_point&) {
return net::RequestAnswer {};
},
[](Sp<Node>, const InfoHash&, const Blob&, const Value::Id&) { return net::RequestAnswer {}; });
}
static Sp<Node>
addPendingGetRequest(net::NetworkEngine& engine, const InfoHash& id, const SockAddr& addr, Tid tid)
{
auto node = engine.insertNode(id, addr);
auto request = std::make_shared<net::Request>(
MessageType::GetValues,
tid,
node,
Blob {},
[](const net::Request&, ParsedMessage&&) {},
[](const net::Request&, bool) {});
node->requested(request);
return node;
}
void
NetworkEngineTester::setUp()
{}
void
NetworkEngineTester::tearDown()
{}
void
NetworkEngineTester::testCompletesPartialSessionWithDataBeforeHeader()
{
Scheduler scheduler;
std::mt19937_64 rd(0);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto serialized = serializeValue("fragment before header");
auto from = makeIPv4("127.0.0.2", 5000);
addPendingGetRequest(engine, remoteId, from, 99);
auto initialOnNewNodeCalls = onNewNodeCalls;
auto fragment = makeValueDataPacketBlob(99, 0, 0, serialized);
auto header = makeReplyHeaderPacketBlob(remoteId, 99, {serialized.size()});
engine.processMessage(fragment.data(), fragment.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL(initialOnNewNodeCalls, onNewNodeCalls);
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls);
}
void
NetworkEngineTester::testBuffersLargeValueBeforeHeader()
{
Scheduler scheduler;
std::mt19937_64 rd(8);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto serialized = serializeValue(std::string(57 * 1024, 'l'));
auto from = makeIPv4("127.0.0.2", 5008);
addPendingGetRequest(engine, remoteId, from, 101);
auto initialOnNewNodeCalls = onNewNodeCalls;
for (size_t offset = 0; offset < serialized.size(); offset += TEST_MTU) {
auto end = std::min(offset + TEST_MTU, serialized.size());
Blob data(serialized.begin() + offset, serialized.begin() + end);
auto fragment = makeValueDataPacketBlob(101, 0, offset, data);
engine.processMessage(fragment.data(), fragment.size(), from);
}
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
auto header = makeReplyHeaderPacketBlob(remoteId, 101, {serialized.size()});
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls);
}
void
NetworkEngineTester::testIgnoresEmptyPartialData()
{
Scheduler scheduler;
std::mt19937_64 rd(7);
InfoHash myid = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto packet = makeValueDataPacketBlob(100, 0, 0, {});
auto from = makeIPv4("127.0.0.2", 5007);
engine.processMessage(packet.data(), packet.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL(0, onNewNodeCalls);
}
void
NetworkEngineTester::testRejectsUnsolicitedPartialReply()
{
Scheduler scheduler;
std::mt19937_64 rd(9);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto from = makeIPv4("127.0.0.2", 5009);
auto header = makeReplyHeaderPacketBlob(remoteId, 102, {TEST_MTU});
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getNodeCacheSize());
}
void
NetworkEngineTester::testDropsMalformedCompletedPartialMessage()
{
Scheduler scheduler;
std::mt19937_64 rd(10);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto from = makeIPv4("127.0.0.2", 5010);
addPendingGetRequest(engine, remoteId, from, 103);
Blob malformed {0xc1};
auto header = makeReplyHeaderPacketBlob(remoteId, 103, {malformed.size()});
auto fragment = makeValueDataPacketBlob(103, 0, 0, malformed);
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
engine.processMessage(fragment.data(), fragment.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
auto earlyFrom = makeIPv4("127.0.0.2", 5011);
addPendingGetRequest(engine, remoteId, earlyFrom, 104);
auto earlyHeader = makeReplyHeaderPacketBlob(remoteId, 104, {malformed.size()});
auto earlyFragment = makeValueDataPacketBlob(104, 0, 0, malformed);
engine.processMessage(earlyFragment.data(), earlyFragment.size(), earlyFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
engine.processMessage(earlyHeader.data(), earlyHeader.size(), earlyFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
}
void
NetworkEngineTester::testCompletesPartialSessionFromSameSource()
{
Scheduler scheduler;
std::mt19937_64 rd(1);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
std::string data(TEST_MTU * 2 + 64, 'z');
auto serialized = serializeValue(data);
auto from = makeIPv4("127.0.0.2", 5001);
addPendingGetRequest(engine, remoteId, from, 7);
auto initialOnNewNodeCalls = onNewNodeCalls;
auto header = makeReplyHeaderPacketBlob(remoteId, 7, {serialized.size()});
engine.processMessage(header.data(), header.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
for (size_t offset = 0; offset < serialized.size(); offset += TEST_MTU) {
auto end = std::min(offset + TEST_MTU, serialized.size());
Blob fragment(serialized.begin() + offset, serialized.begin() + end);
auto packet = makeValueDataPacketBlob(7, 0, offset, fragment);
engine.processMessage(packet.data(), packet.size(), from);
}
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls);
}
void
NetworkEngineTester::testSeparatesPartialSessionsBySource()
{
Scheduler scheduler;
std::mt19937_64 rd(2);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash firstRemoteId = InfoHash::getRandom(rd);
InfoHash secondRemoteId = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
auto serialized = serializeValue("same transaction, different peers");
auto firstFrom = makeIPv4("127.0.0.2", 5002);
auto secondFrom = makeIPv4("127.0.0.3", 5002);
addPendingGetRequest(engine, firstRemoteId, firstFrom, 11);
addPendingGetRequest(engine, secondRemoteId, secondFrom, 11);
auto initialOnNewNodeCalls = onNewNodeCalls;
auto firstHeader = makeReplyHeaderPacketBlob(firstRemoteId, 11, {serialized.size()});
auto secondHeader = makeReplyHeaderPacketBlob(secondRemoteId, 11, {serialized.size()});
engine.processMessage(firstHeader.data(), firstHeader.size(), firstFrom);
engine.processMessage(secondHeader.data(), secondHeader.size(), secondFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 2, engine.getPartialCount());
auto fragment = makeValueDataPacketBlob(11, 0, 0, serialized);
engine.processMessage(fragment.data(), fragment.size(), firstFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
engine.processMessage(fragment.data(), fragment.size(), secondFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > initialOnNewNodeCalls);
}
void
NetworkEngineTester::testListenConfirmationCarriesToken()
{
Scheduler scheduler;
std::mt19937_64 rd(3);
InfoHash myid = InfoHash::getRandom(rd);
InfoHash remoteId = InfoHash::getRandom(rd);
InfoHash hash = InfoHash::getRandom(rd);
Blob requestToken {0x01, 0x02, 0x03, 0x04};
Blob responseToken {0xaa, 0xbb, 0xcc, 0xdd, 0xee};
int onNewNodeCalls = 0;
net::NetworkConfig config {};
config.max_req_per_sec = -1;
config.max_peer_req_per_sec = -1;
auto socket = std::make_unique<TestDatagramSocket>();
auto* socketPtr = socket.get();
auto engine = net::NetworkEngine(
myid,
config,
std::move(socket),
{},
rd,
scheduler,
[](Sp<net::Request>, net::DhtProtocolException) {},
[&onNewNodeCalls](const Sp<Node>&, int) { ++onNewNodeCalls; },
[](const InfoHash&, const SockAddr&) {},
[](Sp<Node>) { return net::RequestAnswer {}; },
[](Sp<Node>, const InfoHash&, want_t) { return net::RequestAnswer {}; },
[](Sp<Node>, const InfoHash&, want_t, const Query&) { return net::RequestAnswer {}; },
[responseToken](Sp<Node>, const InfoHash&, const Blob&, Tid, const Query&, int) {
net::RequestAnswer answer {};
answer.ntoken = responseToken;
return answer;
},
[](Sp<Node>, const InfoHash&, const Blob&, const std::vector<Sp<Value>>&, const time_point&) {
return net::RequestAnswer {};
},
[](Sp<Node>, const InfoHash&, const Blob&, const Value::Id&) { return net::RequestAnswer {}; });
auto from = makeIPv4("127.0.0.2", 5003);
auto packet = makeListenPacketBlob(remoteId, hash, 17, 23, requestToken);
engine.processMessage(packet.data(), packet.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 1, socketPtr->sends.size());
const auto& sent = socketPtr->sends.front();
CPPUNIT_ASSERT(sent.dest == from);
auto replyObject = msgpack::unpack((const char*) sent.data.data(), sent.data.size());
ParsedMessage reply;
reply.msgpack_unpack(replyObject.get());
CPPUNIT_ASSERT(reply.type == MessageType::Reply);
CPPUNIT_ASSERT_EQUAL(myid, reply.id);
CPPUNIT_ASSERT(reply.token == responseToken);
}
void
NetworkEngineTester::testListenConfirmationUpdatesSearchNodeToken()
{
Config config {};
config.max_req_per_sec = -1;
config.max_peer_req_per_sec = -1;
auto rd = std::make_unique<std::mt19937_64>(6);
Dht localDht(std::make_unique<TestDatagramSocket>(), config, {}, std::move(rd));
auto node = std::make_shared<Node>(InfoHash::getRandom(localDht.rd),
makeIPv4("127.0.0.2", 5006),
localDht.rd,
false);
auto sr = std::make_shared<Dht::Search>();
auto query = std::make_shared<Query>();
ValueCallback valueCallback = [](const std::vector<std::shared_ptr<Value>>&, bool) {
return true;
};
sr->id = InfoHash::getRandom(localDht.rd);
sr->af = AF_INET;
sr->nextSearchStep = localDht.scheduler.add(time_point::max(), [] {});
sr->listeners.emplace(1, Dht::Search::SearchListener {query, valueCallback, {}});
auto searchNode = std::make_unique<Dht::SearchNode>(node);
auto socketId = node->openSocket([](const Sp<Node>&, net::RequestAnswer&&) {});
searchNode->listenStatus.emplace(std::piecewise_construct,
std::forward_as_tuple(query),
std::forward_as_tuple(valueCallback, dht::SyncCallback {}, socketId));
auto* searchNodePtr = sr->nodes.emplace_back(std::move(searchNode)).get();
CPPUNIT_ASSERT(searchNodePtr->token.empty());
CPPUNIT_ASSERT(!searchNodePtr->isSynced(localDht.scheduler.time()));
net::RequestAnswer answer {};
answer.ntoken = Blob {0xaa, 0xbb, 0xcc, 0xdd};
localDht.onListenDone(node, answer, sr);
CPPUNIT_ASSERT(searchNodePtr->token == answer.ntoken);
/* last_get_reply should NOT be updated by listen confirmation,
so that periodic find_node requests still refresh the search table. */
CPPUNIT_ASSERT(searchNodePtr->last_get_reply == time_point::min());
CPPUNIT_ASSERT(!searchNodePtr->isSynced(localDht.scheduler.time()));
}
void
NetworkEngineTester::testListenReopensSocketAfterNodeExpiration()
{
Config config {};
config.max_req_per_sec = -1;
config.max_peer_req_per_sec = -1;
auto rd = std::make_unique<std::mt19937_64>(4);
Dht localDht(std::make_unique<TestDatagramSocket>(), config, {}, std::move(rd));
auto node = std::make_shared<Node>(InfoHash::getRandom(localDht.rd),
makeIPv4("127.0.0.2", 5004),
localDht.rd,
false);
auto sr = std::make_shared<Dht::Search>();
auto query = std::make_shared<Query>();
ValueCallback valueCallback = [](const std::vector<std::shared_ptr<Value>>&, bool) {
return true;
};
dht::SyncCallback syncCallback {};
sr->id = InfoHash::getRandom(localDht.rd);
sr->af = AF_INET;
sr->listeners.emplace(1, Dht::Search::SearchListener {query, valueCallback, {}});
auto searchNode = std::make_unique<Dht::SearchNode>(node);
auto staleSocketId = node->openSocket([](const Sp<Node>&, net::RequestAnswer&&) {});
auto status = searchNode->listenStatus
.emplace(std::piecewise_construct,
std::forward_as_tuple(query),
std::forward_as_tuple(valueCallback, syncCallback, staleSocketId))
.first;
status->second.req = std::make_shared<net::Request>(net::Request::State::COMPLETED);
status->second.req->reply_time = time_point::min();
node->setExpired();
CPPUNIT_ASSERT(!node->getSocket(staleSocketId));
auto* searchNodePtr = sr->nodes.emplace_back(std::move(searchNode)).get();
searchNodePtr->token = Blob {0x42};
searchNodePtr->last_get_reply = localDht.scheduler.time();
localDht.searchSynchedNodeListen(sr, *searchNodePtr);
auto listenIt = searchNodePtr->listenStatus.find(query);
CPPUNIT_ASSERT(listenIt != searchNodePtr->listenStatus.end());
CPPUNIT_ASSERT(listenIt->second.req);
CPPUNIT_ASSERT(listenIt->second.socketId);
CPPUNIT_ASSERT(listenIt->second.socketId != staleSocketId);
CPPUNIT_ASSERT(node->getSocket(listenIt->second.socketId));
}
void
NetworkEngineTester::testUnauthorizedListenFlushClearsListenState()
{
Config config {};
config.max_req_per_sec = -1;
config.max_peer_req_per_sec = -1;
auto rd = std::make_unique<std::mt19937_64>(5);
Dht localDht(std::make_unique<TestDatagramSocket>(), config, {}, std::move(rd));
auto node = std::make_shared<Node>(InfoHash::getRandom(localDht.rd),
makeIPv4("127.0.0.2", 5005),
localDht.rd,
false);
auto sr = std::make_shared<Dht::Search>();
auto query = std::make_shared<Query>();
ValueCallback valueCallback = [](const std::vector<std::shared_ptr<Value>>&, bool) {
return true;
};
dht::SyncCallback syncCallback {};
sr->id = InfoHash::getRandom(localDht.rd);
sr->af = AF_INET;
auto searchNode = std::make_unique<Dht::SearchNode>(node);
auto socketId = node->openSocket([](const Sp<Node>&, net::RequestAnswer&&) {});
auto listenIt = searchNode->listenStatus
.emplace(std::piecewise_construct,
std::forward_as_tuple(query),
std::forward_as_tuple(valueCallback, syncCallback, socketId))
.first;
listenIt->second.req = std::make_shared<net::Request>();
listenIt->second.refresh = localDht.scheduler.add(localDht.scheduler.time() + std::chrono::seconds(1), [] {});
searchNode->token = Blob {0x10, 0x11, 0x12};
searchNode->last_get_reply = localDht.scheduler.time();
auto* searchNodePtr = sr->nodes.emplace_back(std::move(searchNode)).get();
localDht.searches(AF_INET).emplace(sr->id, sr);
auto failingReq = std::make_shared<net::Request>(
MessageType::Listen,
node->getNewTid(),
node,
Blob {},
[](const net::Request&, ParsedMessage&&) {},
[](const net::Request&, bool) {});
localDht.onError(failingReq,
net::DhtProtocolException {net::DhtProtocolException::UNAUTHORIZED,
net::DhtProtocolException::LISTEN_WRONG_TOKEN});
CPPUNIT_ASSERT(searchNodePtr->token.empty());
CPPUNIT_ASSERT(searchNodePtr->last_get_reply == time_point::min());
auto listenStatusIt = searchNodePtr->listenStatus.find(query);
CPPUNIT_ASSERT(listenStatusIt != searchNodePtr->listenStatus.end());
CPPUNIT_ASSERT(!listenStatusIt->second.req);
CPPUNIT_ASSERT(!listenStatusIt->second.refresh);
}
} // namespace test
#endif
-51
View File
@@ -1,51 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
namespace test {
class NetworkEngineTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(NetworkEngineTester);
#ifdef _MSC_VER
CPPUNIT_TEST(testDisabledOnMsvc);
#else
CPPUNIT_TEST(testCompletesPartialSessionWithDataBeforeHeader);
CPPUNIT_TEST(testBuffersLargeValueBeforeHeader);
CPPUNIT_TEST(testIgnoresEmptyPartialData);
CPPUNIT_TEST(testRejectsUnsolicitedPartialReply);
CPPUNIT_TEST(testDropsMalformedCompletedPartialMessage);
CPPUNIT_TEST(testCompletesPartialSessionFromSameSource);
CPPUNIT_TEST(testSeparatesPartialSessionsBySource);
CPPUNIT_TEST(testListenConfirmationCarriesToken);
CPPUNIT_TEST(testListenConfirmationUpdatesSearchNodeToken);
CPPUNIT_TEST(testListenReopensSocketAfterNodeExpiration);
CPPUNIT_TEST(testUnauthorizedListenFlushClearsListenState);
#endif
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
#ifdef _MSC_VER
void testDisabledOnMsvc();
#else
void testCompletesPartialSessionWithDataBeforeHeader();
void testBuffersLargeValueBeforeHeader();
void testIgnoresEmptyPartialData();
void testRejectsUnsolicitedPartialReply();
void testDropsMalformedCompletedPartialMessage();
void testCompletesPartialSessionFromSameSource();
void testSeparatesPartialSessionsBySource();
void testListenConfirmationCarriesToken();
void testListenConfirmationUpdatesSearchNodeToken();
void testListenReopensSocketAfterNodeExpiration();
void testUnauthorizedListenFlushClearsListenState();
#endif
};
} // namespace test
-674
View File
@@ -1,674 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_opcache.h"
// Hack to test internal class
#include "../src/op_cache.cpp"
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(OpCacheTester);
namespace {
using Values = std::vector<std::shared_ptr<dht::Value>>;
struct Event
{
dht::Value::Id id;
uint16_t seq;
bool expired;
};
std::shared_ptr<dht::Value>
makeValue(dht::Value::Id id, uint16_t seq, std::string data)
{
auto v = std::make_shared<dht::Value>();
v->id = id;
v->seq = seq;
v->data = {data.begin(), data.end()};
return v;
}
dht::ValueCallback
recordEvents(std::vector<Event>& events)
{
return [&events](const Values& vals, bool expired) {
for (const auto& v : vals)
events.push_back({v->id, v->seq, expired});
return true;
};
}
unsigned
count(const std::vector<Event>& events, bool expired)
{
unsigned n = 0;
for (const auto& e : events)
if (e.expired == expired)
n++;
return n;
}
} // namespace
void
OpCacheTester::setUp()
{}
void
OpCacheTester::tearDown()
{}
void
OpCacheTester::testBasicAddExpire()
{
dht::OpValueCache cache([](const Values&, bool) { return true; });
auto v1 = makeValue(10, 0, "A");
cache.onValuesAdded({v1});
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
CPPUNIT_ASSERT(cache.get(10));
std::vector<dht::Value::Id> ids = {v1->id};
cache.onValuesExpired(ids);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
CPPUNIT_ASSERT(!cache.get(10));
}
void
OpCacheTester::testMultipleSources()
{
// Two sources report the same value: it must survive until both have
// expired it, and the user must see exactly one add and one expire.
std::vector<Event> events;
dht::OpValueCache cache(recordEvents(events));
auto v = makeValue(20, 0, "B");
cache.onValuesAdded({v}); // source A
cache.onValuesAdded({v}); // source B
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
CPPUNIT_ASSERT_EQUAL(1u, count(events, false));
cache.onValuesExpired({v}); // source A goes away
CPPUNIT_ASSERT_EQUAL_MESSAGE("Value still held by source B", (size_t) 1, cache.size());
CPPUNIT_ASSERT_EQUAL(0u, count(events, true));
cache.onValuesExpired({v}); // source B goes away
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
}
void
OpCacheTester::testEditResetsRefCount()
{
// A and B hold v1. A reports the edit v2: only A is known to hold v2, so
// the refcount restarts at 1. B then confirms v2 (refcount 2).
std::vector<Event> events;
dht::OpValueCache cache(recordEvents(events));
auto v1 = makeValue(1, 1, "data1");
auto v2 = makeValue(1, 2, "data2");
cache.onValuesAdded({v1}); // A
cache.onValuesAdded({v1}); // B
cache.onValuesAdded({v2}); // A edits
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
CPPUNIT_ASSERT_EQUAL(v2->seq, cache.get(1)->seq);
CPPUNIT_ASSERT_EQUAL(2u, count(events, false));
cache.onValuesExpired({v2}); // A expires v2 -> nobody known to hold it
CPPUNIT_ASSERT_EQUAL_MESSAGE("Edit reset the refcount to the editing source", (size_t) 0, cache.size());
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
// Same scenario, but B confirms v2 before A expires it.
events.clear();
cache.onValuesAdded({v1}); // A
cache.onValuesAdded({v1}); // B
cache.onValuesAdded({v2}); // A edits
cache.onValuesAdded({v2}); // B confirms
cache.onValuesExpired({v2});
CPPUNIT_ASSERT_EQUAL_MESSAGE("Still held by B", (size_t) 1, cache.size());
cache.onValuesExpired({v2});
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
CPPUNIT_ASSERT_EQUAL(2u, count(events, false));
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
}
void
OpCacheTester::testEditChainMultiSource()
{
// Rapid edit chain seen from two sources with interleaving: the user
// must see each version once as an add and a single final expire.
std::vector<Event> events;
dht::OpValueCache cache(recordEvents(events));
auto v1 = makeValue(7, 1, "v1");
auto v2 = makeValue(7, 2, "v2");
auto v3 = makeValue(7, 3, "v3");
cache.onValuesAdded({v1}); // A
cache.onValuesAdded({v1}); // B
cache.onValuesAdded({v2}); // A
cache.onValuesAdded({v3}); // A (B skipped v2)
cache.onValuesAdded({v2}); // B, late and stale: ignored
cache.onValuesAdded({v3}); // B
CPPUNIT_ASSERT_EQUAL(3u, count(events, false));
CPPUNIT_ASSERT_EQUAL((uint16_t) 3, cache.get(7)->seq);
cache.onValuesExpired({v3}); // A
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
cache.onValuesExpired({v3}); // B
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
CPPUNIT_ASSERT_EQUAL((uint16_t) 3, events.back().seq);
}
void
OpCacheTester::testStaleAddIgnored()
{
dht::OpValueCache cache([](const Values&, bool) { return true; });
auto v1 = makeValue(30, 10, "C");
auto v2 = makeValue(30, 20, "D");
auto v3 = makeValue(30, 5, "E");
cache.onValuesAdded({v1});
CPPUNIT_ASSERT_EQUAL((uint16_t) 10, cache.get(30)->seq);
cache.onValuesAdded({v2});
CPPUNIT_ASSERT_EQUAL((uint16_t) 20, cache.get(30)->seq);
// Older version from a lagging source: neither stored nor counted
cache.onValuesAdded({v3});
CPPUNIT_ASSERT_EQUAL((uint16_t) 20, cache.get(30)->seq);
cache.onValuesExpired({v2});
CPPUNIT_ASSERT_MESSAGE("Stale add must not have incremented the refcount", !cache.get(30));
}
void
OpCacheTester::testStaleExpireIgnored()
{
// A source that still holds an older version expiring it must not
// affect the newer version held by others.
std::vector<Event> events;
dht::OpValueCache cache(recordEvents(events));
auto v1 = makeValue(31, 1, "old");
auto v2 = makeValue(31, 2, "new");
cache.onValuesAdded({v1}); // A
cache.onValuesAdded({v1}); // B
cache.onValuesAdded({v2}); // A edits; B never receives v2
cache.onValuesExpired({v1}); // B expires its stale copy
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
CPPUNIT_ASSERT_EQUAL(0u, count(events, true));
cache.onValuesExpired({v2}); // A
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
CPPUNIT_ASSERT_EQUAL((uint16_t) 2, events.back().seq);
}
void
OpCacheTester::testCallbacks()
{
std::vector<Event> events;
dht::OpValueCache cache(recordEvents(events));
auto v1 = makeValue(40, 0, "");
cache.onValuesAdded({v1});
CPPUNIT_ASSERT_EQUAL(1u, count(events, false));
// Duplicate from another source: counted, but not re-delivered
cache.onValuesAdded({v1});
CPPUNIT_ASSERT_EQUAL(1u, count(events, false));
auto v2 = makeValue(40, 100, "MOD");
cache.onValuesAdded({v2});
CPPUNIT_ASSERT_EQUAL(2u, count(events, false));
// The edit reset the refcount: one expire removes it
cache.onValuesExpired({v2});
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
OpCacheTester::testFilters()
{
dht::OpValueCache cache([](const Values&, bool) { return true; });
auto v1 = makeValue(50, 0, "");
v1->type = 1;
auto v2 = makeValue(51, 0, "");
v2->type = 2;
cache.onValuesAdded({v1, v2});
auto res1 = cache.get(dht::Value::TypeFilter(1));
CPPUNIT_ASSERT_EQUAL((size_t) 1, res1.size());
CPPUNIT_ASSERT_EQUAL(v1->id, res1[0]->id);
auto res2 = cache.get(dht::Value::TypeFilter(2));
CPPUNIT_ASSERT_EQUAL((size_t) 1, res2.size());
CPPUNIT_ASSERT_EQUAL(v2->id, res2[0]->id);
CPPUNIT_ASSERT_EQUAL((size_t) 2, cache.getValues().size());
}
void
OpCacheTester::testSyncStatus()
{
dht::OpValueCache cache([](const Values&, bool) { return true; });
CPPUNIT_ASSERT(!cache.isSynced());
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
CPPUNIT_ASSERT(!cache.isSynced());
cache.onNodeChanged(dht::ListenSyncStatus::SYNCED);
CPPUNIT_ASSERT(cache.isSynced());
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
CPPUNIT_ASSERT(!cache.isSynced());
cache.onNodeChanged(dht::ListenSyncStatus::SYNCED);
CPPUNIT_ASSERT(cache.isSynced());
cache.onNodeChanged(dht::ListenSyncStatus::UNSYNCED);
CPPUNIT_ASSERT(!cache.isSynced());
cache.onNodeChanged(dht::ListenSyncStatus::REMOVED);
CPPUNIT_ASSERT(cache.isSynced());
}
void
OpCacheTester::testGetWhileSynced()
{
dht::SearchCache searchCache;
int getCallCount = 0;
bool doneCalled = false;
bool doneSuccess = false;
auto v1 = makeValue(100, 0, "X");
auto v2 = makeValue(101, 0, "Y");
auto query = std::make_shared<dht::Query>();
auto token = searchCache.listen([](const Values&, bool) { return true; },
query,
{},
[&](const std::shared_ptr<dht::Query>&,
dht::ValueCallback vcb,
dht::SyncCallback scb) -> size_t {
scb(dht::ListenSyncStatus::ADDED);
vcb({v1, v2}, false);
scb(dht::ListenSyncStatus::SYNCED);
return 1;
});
CPPUNIT_ASSERT(token != 0);
bool result = searchCache.get(
{},
query,
[&](const Values& vals) {
getCallCount++;
CPPUNIT_ASSERT_EQUAL((size_t) 2, vals.size());
return true;
},
[&](bool success, const std::vector<std::shared_ptr<dht::Node>>&) {
doneCalled = true;
doneSuccess = success;
});
CPPUNIT_ASSERT_MESSAGE("get() should be served from a synced cache", result);
CPPUNIT_ASSERT_EQUAL(1, getCallCount);
CPPUNIT_ASSERT(doneCalled);
CPPUNIT_ASSERT(doneSuccess);
}
void
OpCacheTester::testGetWhileNotSynced()
{
// An unsynced cache must not answer a get: the caller falls back to the
// network and values are delivered once, from the network only.
dht::SearchCache searchCache;
std::vector<dht::Value::Id> deliveries;
auto v1 = makeValue(200, 0, "A");
auto query = std::make_shared<dht::Query>();
dht::SyncCallback storedScb;
auto token = searchCache.listen([](const Values&, bool) { return true; },
query,
{},
[&](const std::shared_ptr<dht::Query>&,
dht::ValueCallback vcb,
dht::SyncCallback scb) -> size_t {
storedScb = scb;
scb(dht::ListenSyncStatus::ADDED);
vcb({v1}, false);
return 1;
});
CPPUNIT_ASSERT(token != 0);
bool doneCalled = false;
auto gcb = [&](const Values& vals) {
for (const auto& v : vals)
deliveries.emplace_back(v->id);
return true;
};
auto dcb = [&](bool, const std::vector<std::shared_ptr<dht::Node>>&) {
doneCalled = true;
};
CPPUNIT_ASSERT(!searchCache.get({}, query, gcb, dcb));
CPPUNIT_ASSERT(deliveries.empty());
CPPUNIT_ASSERT(!doneCalled);
// Simulated network reply
gcb({v1});
CPPUNIT_ASSERT_EQUAL((size_t) 1, deliveries.size());
storedScb(dht::ListenSyncStatus::SYNCED);
deliveries.clear();
CPPUNIT_ASSERT(searchCache.get({}, query, gcb, dcb));
CPPUNIT_ASSERT(doneCalled);
CPPUNIT_ASSERT_EQUAL((size_t) 1, deliveries.size());
}
void
OpCacheTester::testGetEmptySynced()
{
dht::SearchCache searchCache;
int getCallCount = 0;
bool doneCalled = false;
bool doneSuccess = false;
auto query = std::make_shared<dht::Query>();
auto token = searchCache.listen([](const Values&, bool) { return true; },
query,
{},
[&](const std::shared_ptr<dht::Query>&,
dht::ValueCallback,
dht::SyncCallback scb) -> size_t {
scb(dht::ListenSyncStatus::ADDED);
scb(dht::ListenSyncStatus::SYNCED);
return 1;
});
CPPUNIT_ASSERT(token != 0);
bool result = searchCache.get(
{},
query,
[&](const Values&) {
getCallCount++;
return true;
},
[&](bool success, const std::vector<std::shared_ptr<dht::Node>>&) {
doneCalled = true;
doneSuccess = success;
});
CPPUNIT_ASSERT(result);
CPPUNIT_ASSERT_EQUAL(0, getCallCount);
CPPUNIT_ASSERT(doneCalled);
CPPUNIT_ASSERT(doneSuccess);
}
void
OpCacheTester::testGetWithoutCallback()
{
// Dht::query() passes no GetCallback: the cache cannot answer it and
// must let the network path run instead of calling an empty function.
dht::SearchCache searchCache;
auto v1 = makeValue(300, 0, "Q");
auto query = std::make_shared<dht::Query>();
searchCache.listen([](const Values&, bool) { return true; },
query,
{},
[&](const std::shared_ptr<dht::Query>&, dht::ValueCallback vcb, dht::SyncCallback scb) -> size_t {
scb(dht::ListenSyncStatus::ADDED);
vcb({v1}, false);
scb(dht::ListenSyncStatus::SYNCED);
return 1;
});
bool doneCalled = false;
CPPUNIT_ASSERT(!searchCache.get({}, query, {}, [&](bool, const std::vector<std::shared_ptr<dht::Node>>&) {
doneCalled = true;
}));
CPPUNIT_ASSERT(!doneCalled);
}
void
OpCacheTester::testGetCallbackReturnsFalse()
{
// A consumer returning false means "I have enough", not a failure.
dht::SearchCache searchCache;
auto v1 = makeValue(1000, 0, "X");
auto query = std::make_shared<dht::Query>();
searchCache.listen([](const Values&, bool) { return true; },
query,
{},
[&](const std::shared_ptr<dht::Query>&, dht::ValueCallback vcb, dht::SyncCallback scb) -> size_t {
scb(dht::ListenSyncStatus::ADDED);
vcb({v1}, false);
scb(dht::ListenSyncStatus::SYNCED);
return 1;
});
bool doneCalled = false;
bool doneSuccess = false;
int gcbCallCount = 0;
bool result = searchCache.get(
{},
query,
[&](const Values&) {
gcbCallCount++;
return false;
},
[&](bool success, const std::vector<std::shared_ptr<dht::Node>>&) {
doneCalled = true;
doneSuccess = success;
});
CPPUNIT_ASSERT(result);
CPPUNIT_ASSERT_EQUAL(1, gcbCallCount);
CPPUNIT_ASSERT(doneCalled);
CPPUNIT_ASSERT_MESSAGE("Stopping early is reported as success, like the network path", doneSuccess);
}
void
OpCacheTester::testTimestampOrdering()
{
dht::OpValueCache cache([](const Values&, bool) { return true; });
auto v1 = makeValue(400, 0, "T");
auto t1 = std::chrono::system_clock::now();
auto t2 = t1 + std::chrono::seconds(10);
cache.onValue({v1}, false, t2);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
cache.onValue({v1}, true, t1);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Stale expiration should be ignored", (size_t) 1, cache.size());
cache.onValue({v1}, true, t2);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
OpCacheTester::testSourceDropKeepsValue()
{
// Two per-node ValueCaches feed one Search-level OpCache. When one node
// leaves the search (its ValueCache is destroyed, expiring everything it
// held) the value must stay visible as long as the other node holds it.
std::vector<Event> events;
dht::SearchCache searchCache;
auto query = std::make_shared<dht::Query>();
dht::ValueCallback toOpCache;
dht::SyncCallback toOpCacheSync;
searchCache.listen(recordEvents(events),
query,
{},
[&](const std::shared_ptr<dht::Query>&, dht::ValueCallback vcb, dht::SyncCallback scb) -> size_t {
toOpCache = std::move(vcb);
toOpCacheSync = std::move(scb);
return 1;
});
dht::TypeStore types;
auto now = dht::clock::now();
auto v = makeValue(42, 0, "hello");
auto makeNodeCache = [&] {
return std::make_unique<dht::ValueCache>([&](const Values& vals, bool expired) { toOpCache(vals, expired); },
[&](dht::ListenSyncStatus s) { toOpCacheSync(s); });
};
auto nodeA = makeNodeCache();
auto nodeB = makeNodeCache();
nodeA->onValues({v}, {}, {}, types, now);
nodeA->onSynced(true);
nodeB->onValues({v}, {}, {}, types, now);
nodeB->onSynced(true);
CPPUNIT_ASSERT_EQUAL(1u, count(events, false));
CPPUNIT_ASSERT_EQUAL((size_t) 1, searchCache.get(dht::Value::Filter {}).size());
nodeA.reset();
CPPUNIT_ASSERT_EQUAL_MESSAGE("No expire while another node holds the value", 0u, count(events, true));
CPPUNIT_ASSERT_EQUAL((size_t) 1, searchCache.get(dht::Value::Filter {}).size());
nodeB->onValues({v}, {}, {}, types, now + std::chrono::seconds(1));
CPPUNIT_ASSERT_EQUAL_MESSAGE("Refresh from the remaining node is not re-delivered", 1u, count(events, false));
nodeB.reset();
CPPUNIT_ASSERT_EQUAL_MESSAGE("Last holder gone: expire once", 1u, count(events, true));
CPPUNIT_ASSERT_EQUAL((size_t) 0, searchCache.get(dht::Value::Filter {}).size());
}
void
OpCacheTester::testHighChurnRefCountConsistency()
{
dht::OpValueCache cache([](const Values&, bool) { return true; });
auto v = makeValue(800, 0, "CHURN");
for (int i = 0; i < 50; i++) {
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
cache.onValuesAdded({v});
cache.onValuesExpired({v});
cache.onNodeChanged(dht::ListenSyncStatus::REMOVED);
}
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
// Overlapping churn: add A, add B, expire A, expire B
for (int i = 0; i < 20; i++) {
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
cache.onValuesAdded({v});
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
cache.onValuesAdded({v});
cache.onValuesExpired({v});
cache.onNodeChanged(dht::ListenSyncStatus::REMOVED);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Value kept while one source remains", (size_t) 1, cache.size());
cache.onValuesExpired({v});
cache.onNodeChanged(dht::ListenSyncStatus::REMOVED);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
// Extra expirations from sources that never added must be no-ops
cache.onValuesExpired({v});
cache.onValuesExpired({v});
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
OpCacheTester::testValueUpdateSingleSourcePhantom()
{
// Editing a value (same id, higher seq) is delivered as a single add of
// the new version, without a phantom expiration of the old one.
std::vector<Event> events;
dht::OpValueCache cache(recordEvents(events));
auto v1 = makeValue(700, 1, "OLD");
auto v2 = makeValue(700, 2, "NEW");
cache.onValuesAdded({v1});
events.clear();
cache.onValuesAdded({v2});
CPPUNIT_ASSERT_EQUAL((size_t) 1, events.size());
CPPUNIT_ASSERT(!events[0].expired);
CPPUNIT_ASSERT_EQUAL(v2->id, events[0].id);
CPPUNIT_ASSERT_EQUAL(v2->seq, cache.get(700)->seq);
}
void
OpCacheTester::testExpireAfterUpdate()
{
std::vector<Event> events;
dht::OpValueCache cache(recordEvents(events));
auto v1 = makeValue(1200, 1, "V1");
auto v2 = makeValue(1200, 5, "V2");
cache.onValuesAdded({v1});
cache.onValuesAdded({v2});
CPPUNIT_ASSERT_EQUAL(v2->seq, cache.get(1200)->seq);
cache.onValuesExpired({v2});
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
CPPUNIT_ASSERT_EQUAL_MESSAGE("Expired value should be the updated one", v2->seq, events.back().seq);
// Once gone, an older version is accepted again
cache.onValuesAdded({v1});
CPPUNIT_ASSERT_EQUAL(v1->seq, cache.get(1200)->seq);
}
void
OpCacheTester::testSingleSourceMode()
{
// Single-source caches (proxy client) get the same values re-delivered
// on refresh: duplicates must not be counted, one expire removes.
std::vector<Event> events;
dht::OpValueCache cache(recordEvents(events), false);
auto v = makeValue(1100, 0, "DUP");
for (int i = 0; i < 10; i++)
cache.onValuesAdded({v});
CPPUNIT_ASSERT_EQUAL(1u, count(events, false));
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
cache.onValuesExpired({v});
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
cache.onValuesExpired({v});
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
}
void
OpCacheTester::testSingleSourceModeSearchCache()
{
std::vector<Event> events;
dht::SearchCache searchCache(false);
auto query = std::make_shared<dht::Query>();
dht::ValueCallback vcbStored;
searchCache.listen(recordEvents(events),
query,
{},
[&](const std::shared_ptr<dht::Query>&, dht::ValueCallback vcb, dht::SyncCallback) -> size_t {
vcbStored = std::move(vcb);
return 1;
});
auto v = makeValue(1300, 0, "S");
vcbStored({v}, false);
vcbStored({v}, false); // re-delivery after listen restart
CPPUNIT_ASSERT_EQUAL(1u, count(events, false));
vcbStored({v}, true);
CPPUNIT_ASSERT_EQUAL(1u, count(events, true));
CPPUNIT_ASSERT_EQUAL((size_t) 0, searchCache.get(dht::Value::Filter {}).size());
}
} // namespace test
-63
View File
@@ -1,63 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
namespace test {
class OpCacheTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(OpCacheTester);
CPPUNIT_TEST(testBasicAddExpire);
CPPUNIT_TEST(testMultipleSources);
CPPUNIT_TEST(testEditResetsRefCount);
CPPUNIT_TEST(testEditChainMultiSource);
CPPUNIT_TEST(testStaleAddIgnored);
CPPUNIT_TEST(testStaleExpireIgnored);
CPPUNIT_TEST(testCallbacks);
CPPUNIT_TEST(testFilters);
CPPUNIT_TEST(testSyncStatus);
CPPUNIT_TEST(testGetWhileSynced);
CPPUNIT_TEST(testGetWhileNotSynced);
CPPUNIT_TEST(testGetEmptySynced);
CPPUNIT_TEST(testGetWithoutCallback);
CPPUNIT_TEST(testGetCallbackReturnsFalse);
CPPUNIT_TEST(testTimestampOrdering);
CPPUNIT_TEST(testSourceDropKeepsValue);
CPPUNIT_TEST(testHighChurnRefCountConsistency);
CPPUNIT_TEST(testValueUpdateSingleSourcePhantom);
CPPUNIT_TEST(testExpireAfterUpdate);
CPPUNIT_TEST(testSingleSourceMode);
CPPUNIT_TEST(testSingleSourceModeSearchCache);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testBasicAddExpire();
void testMultipleSources();
void testEditResetsRefCount();
void testEditChainMultiSource();
void testStaleAddIgnored();
void testStaleExpireIgnored();
void testCallbacks();
void testFilters();
void testSyncStatus();
void testGetWhileSynced();
void testGetWhileNotSynced();
void testGetEmptySynced();
void testGetWithoutCallback();
void testGetCallbackReturnsFalse();
void testTimestampOrdering();
void testSourceDropKeepsValue();
void testHighChurnRefCountConsistency();
void testValueUpdateSingleSourcePhantom();
void testExpireAfterUpdate();
void testSingleSourceMode();
void testSingleSourceModeSearchCache();
};
} // namespace test
-638
View File
@@ -1,638 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_parsedmessage.h"
#include "opendht/value.h"
#include "opendht/node.h"
#include "opendht/utils.h"
#include "../src/parsed_message.h"
#include <iostream>
#include <limits>
#include <string>
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(ParsedMessageTester);
using namespace dht;
using namespace dht::net;
static constexpr size_t TEST_MTU {1280};
// Helper: create a ParsedMessage with a single value_parts entry of given total_size
static ParsedMessage
makeHeader(unsigned index, size_t total_size)
{
ParsedMessage msg;
msg.value_parts[index].total_size = total_size;
return msg;
}
// Helper: create a ParsedMessage fragment (as if parsed from ValueData)
static ParsedMessage
makeFragment(unsigned index, size_t offset, const Blob& data)
{
ParsedMessage frag;
frag.fragment_parts[index] = {offset, data};
return frag;
}
// Helper: serialize a Value to a Blob
static Blob
serializeValue(const std::string& data)
{
auto val = std::make_shared<Value>((const uint8_t*) data.data(), data.size());
return packMsg(val);
}
static ParsedMessage
parsePackedMessage(msgpack::sbuffer& buffer)
{
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
parsed.msgpack_unpack(msg.get());
return parsed;
}
static ParsedMessage
makeReplyHeaderPacket(Tid tid, const std::vector<size_t>& valueSizes)
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_R);
pk.pack_map(1);
pk.pack(KEY_REQ_VALUES);
pk.pack_array(valueSizes.size());
for (const auto& size : valueSizes)
pk.pack(size);
pk.pack(KEY_TID);
pk.pack(tid);
pk.pack(KEY_Y);
pk.pack(KEY_R);
return parsePackedMessage(buffer);
}
static ParsedMessage
makeValueDataPacket(Tid tid, unsigned index, size_t offset, const Blob& data)
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_Y);
pk.pack(KEY_V);
pk.pack(KEY_TID);
pk.pack(tid);
pk.pack(KEY_V);
pk.pack_map(1);
pk.pack(index);
pk.pack_map(2);
pk.pack("o");
pk.pack(offset);
pk.pack("d");
pk.pack_bin(data.size());
pk.pack_bin_body((const char*) data.data(), data.size());
return parsePackedMessage(buffer);
}
void
ParsedMessageTester::setUp()
{}
void
ParsedMessageTester::tearDown()
{}
// --- append tests ---
void
ParsedMessageTester::testAppendInOrder()
{
Blob full = {1, 2, 3, 4, 5, 6};
auto msg = makeHeader(0, full.size());
// Send first half, then second half
Blob part1(full.begin(), full.begin() + 3);
Blob part2(full.begin() + 3, full.end());
auto frag1 = makeFragment(0, 0, part1);
auto frag2 = makeFragment(0, 3, part2);
CPPUNIT_ASSERT(msg.append(frag1));
CPPUNIT_ASSERT(msg.append(frag2));
auto& pv = msg.value_parts[0];
CPPUNIT_ASSERT(pv.isComplete());
CPPUNIT_ASSERT_EQUAL(full.size(), pv.received_bytes);
auto reassembled = pv.reassemble();
CPPUNIT_ASSERT(reassembled == full);
}
void
ParsedMessageTester::testAppendOutOfOrder()
{
Blob full = {10, 20, 30, 40, 50, 60};
auto msg = makeHeader(0, full.size());
Blob part1(full.begin(), full.begin() + 2); // [0..2)
Blob part2(full.begin() + 2, full.begin() + 4); // [2..4)
Blob part3(full.begin() + 4, full.end()); // [4..6)
// Send in order: part3, part1, part2
CPPUNIT_ASSERT(msg.append(makeFragment(0, 4, part3)));
CPPUNIT_ASSERT(!msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, part1)));
CPPUNIT_ASSERT(!msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.append(makeFragment(0, 2, part2)));
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
auto reassembled = msg.value_parts[0].reassemble();
CPPUNIT_ASSERT(reassembled == full);
}
void
ParsedMessageTester::testAppendReverseOrder()
{
Blob full = {1, 2, 3, 4, 5, 6, 7, 8};
auto msg = makeHeader(0, full.size());
// 4 fragments of 2 bytes each, sent in reverse
for (int i = 3; i >= 0; --i) {
size_t offset = i * 2;
Blob part(full.begin() + offset, full.begin() + offset + 2);
CPPUNIT_ASSERT(msg.append(makeFragment(0, offset, part)));
}
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.value_parts[0].reassemble() == full);
}
void
ParsedMessageTester::testAppendDuplicateFragment()
{
Blob full = {1, 2, 3, 4};
auto msg = makeHeader(0, full.size());
Blob part1(full.begin(), full.begin() + 2);
Blob part2(full.begin() + 2, full.end());
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, part1)));
// Duplicate of first fragment should be rejected
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 0, part1)));
CPPUNIT_ASSERT(msg.append(makeFragment(0, 2, part2)));
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
CPPUNIT_ASSERT_EQUAL((size_t) 4, msg.value_parts[0].received_bytes);
}
void
ParsedMessageTester::testAppendOverlappingFragments()
{
Blob full = {1, 2, 3, 4, 5, 6};
auto msg = makeHeader(0, full.size());
// Fragment [0..3)
Blob part1(full.begin(), full.begin() + 3);
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, part1)));
// Overlapping fragment [2..5) should be rejected
Blob overlap(full.begin() + 2, full.begin() + 5);
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 2, overlap)));
// Non-overlapping fragment [3..6) should succeed
Blob part2(full.begin() + 3, full.end());
CPPUNIT_ASSERT(msg.append(makeFragment(0, 3, part2)));
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.value_parts[0].reassemble() == full);
}
void
ParsedMessageTester::testAppendFragmentBeyondTotal()
{
auto msg = makeHeader(0, 4);
// Fragment at offset 3 with 2 bytes goes beyond total_size of 4
Blob data = {1, 2};
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 3, data)));
CPPUNIT_ASSERT_EQUAL((size_t) 0, msg.value_parts[0].received_bytes);
}
void
ParsedMessageTester::testAppendEmptyFragment()
{
auto msg = makeHeader(0, 4);
Blob empty;
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 0, empty)));
CPPUNIT_ASSERT_EQUAL((size_t) 0, msg.value_parts[0].received_bytes);
}
void
ParsedMessageTester::testAppendUnknownIndex()
{
auto msg = makeHeader(0, 10);
// Fragment for index 1 which doesn't exist in value_parts
Blob data = {1, 2, 3};
CPPUNIT_ASSERT(!msg.append(makeFragment(1, 0, data)));
// Index 0 should be untouched
CPPUNIT_ASSERT_EQUAL((size_t) 0, msg.value_parts[0].received_bytes);
}
void
ParsedMessageTester::testAppendAlreadyComplete()
{
Blob full = {1, 2, 3, 4};
auto msg = makeHeader(0, full.size());
// Complete the value
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, full)));
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
// Further fragment should be rejected
Blob extra = {5, 6};
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 0, extra)));
}
void
ParsedMessageTester::testAppendOverflowingOffset()
{
auto msg = makeHeader(0, 4);
Blob data = {1};
CPPUNIT_ASSERT(!msg.append(makeFragment(0, std::numeric_limits<size_t>::max(), data)));
CPPUNIT_ASSERT_EQUAL((size_t) 0, msg.value_parts[0].received_bytes);
}
void
ParsedMessageTester::testParseRejectsNonMapPacket()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_array(1);
pk.pack(42);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseRejectsInvalidValueDataPayload()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_Y);
pk.pack(KEY_V);
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_V);
pk.pack_array(1);
pk.pack(42);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseRejectsInvalidValuesField()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_R);
pk.pack_map(1);
pk.pack(KEY_REQ_VALUES);
pk.pack("invalid");
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_Y);
pk.pack(KEY_R);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseRejectsEmptyFieldSelection()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_R);
pk.pack_map(1);
pk.pack(KEY_REQ_FIELDS);
pk.pack_map(2);
pk.pack("f");
pk.pack_array(0);
pk.pack("v");
pk.pack_array(0);
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_Y);
pk.pack(KEY_R);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseRejectsEmptyErrorArray()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_E);
pk.pack_array(0);
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_Y);
pk.pack(KEY_E);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseRejectsNonMapRequest()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(4);
pk.pack(KEY_A);
pk.pack_array(2);
pk.pack(1u);
pk.pack(2u);
pk.pack(KEY_Q);
pk.pack(QUERY_PING);
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_Y);
pk.pack("q");
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
}
void
ParsedMessageTester::testParseErrorOnlyMessageKeepsRequestFieldsEmpty()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
// An error message with no request map: the error array must not be walked
// as if it were a sequence of key/value pairs.
pk.pack_map(3);
pk.pack(KEY_E);
pk.pack_array(2);
pk.pack(401u);
pk.pack("unauthorized");
pk.pack(KEY_TID);
pk.pack(9u);
pk.pack(KEY_Y);
pk.pack(KEY_E);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
parsed.msgpack_unpack(msg.get());
CPPUNIT_ASSERT(parsed.type == MessageType::Error);
CPPUNIT_ASSERT_EQUAL((uint16_t) 401, parsed.error_code);
CPPUNIT_ASSERT(not parsed.id);
CPPUNIT_ASSERT(parsed.token.empty());
CPPUNIT_ASSERT(parsed.values.empty());
CPPUNIT_ASSERT(parsed.nodes4_raw.empty());
CPPUNIT_ASSERT(parsed.nodes6_raw.empty());
}
void
ParsedMessageTester::testParseIgnoresIncompleteValueDataEntry()
{
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_map(3);
pk.pack(KEY_Y);
pk.pack(KEY_V);
pk.pack(KEY_TID);
pk.pack(1u);
pk.pack(KEY_V);
pk.pack_map(1);
pk.pack(0u);
pk.pack_map(1);
pk.pack("o");
pk.pack(0u);
auto msg = msgpack::unpack(buffer.data(), buffer.size());
ParsedMessage parsed;
parsed.msgpack_unpack(msg.get());
CPPUNIT_ASSERT(parsed.fragment_parts.empty());
}
// --- complete tests ---
void
ParsedMessageTester::testCompleteEmpty()
{
// No value_parts at all — should be trivially complete
ParsedMessage msg;
CPPUNIT_ASSERT(msg.complete());
CPPUNIT_ASSERT(msg.values.empty());
}
void
ParsedMessageTester::testCompleteIncomplete()
{
auto msg = makeHeader(0, 100);
Blob partial = {1, 2, 3};
msg.append(makeFragment(0, 0, partial));
CPPUNIT_ASSERT(!msg.complete());
CPPUNIT_ASSERT(msg.values.empty());
}
void
ParsedMessageTester::testCompleteZeroSizedValue()
{
auto msg = makeHeader(0, 0);
CPPUNIT_ASSERT(!msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(!msg.complete());
CPPUNIT_ASSERT(msg.values.empty());
}
void
ParsedMessageTester::testCompleteSingleValue()
{
std::string data = "hello world";
auto serialized = serializeValue(data);
auto msg = makeHeader(0, serialized.size());
msg.append(makeFragment(0, 0, serialized));
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.complete());
CPPUNIT_ASSERT_EQUAL((size_t) 1, msg.values.size());
std::string recovered(msg.values[0]->data.begin(), msg.values[0]->data.end());
CPPUNIT_ASSERT_EQUAL(data, recovered);
}
void
ParsedMessageTester::testCompleteMultipleValues()
{
std::string data1 = "first value";
std::string data2 = "second value";
auto ser1 = serializeValue(data1);
auto ser2 = serializeValue(data2);
ParsedMessage msg;
msg.value_parts[0].total_size = ser1.size();
msg.value_parts[1].total_size = ser2.size();
msg.append(makeFragment(0, 0, ser1));
msg.append(makeFragment(1, 0, ser2));
CPPUNIT_ASSERT(msg.complete());
CPPUNIT_ASSERT_EQUAL((size_t) 2, msg.values.size());
std::string r1(msg.values[0]->data.begin(), msg.values[0]->data.end());
std::string r2(msg.values[1]->data.begin(), msg.values[1]->data.end());
CPPUNIT_ASSERT_EQUAL(data1, r1);
CPPUNIT_ASSERT_EQUAL(data2, r2);
}
void
ParsedMessageTester::testCompleteReassemblyOrder()
{
// Verify that out-of-order fragments produce correct reassembly
std::string data = "test data for fragmentation";
auto serialized = serializeValue(data);
auto msg = makeHeader(0, serialized.size());
// Split into 3 fragments and send out of order
size_t chunk = serialized.size() / 3;
Blob frag1(serialized.begin(), serialized.begin() + chunk);
Blob frag2(serialized.begin() + chunk, serialized.begin() + 2 * chunk);
Blob frag3(serialized.begin() + 2 * chunk, serialized.end());
// Send: third, first, second
CPPUNIT_ASSERT(msg.append(makeFragment(0, 2 * chunk, frag3)));
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, frag1)));
CPPUNIT_ASSERT(msg.append(makeFragment(0, chunk, frag2)));
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.complete());
CPPUNIT_ASSERT_EQUAL((size_t) 1, msg.values.size());
std::string recovered(msg.values[0]->data.begin(), msg.values[0]->data.end());
CPPUNIT_ASSERT_EQUAL(data, recovered);
}
void
ParsedMessageTester::testAppendMultipleValuesOutOfOrder()
{
// Two values, both fragmented, fragments interleaved and out of order
Blob val1 = {1, 2, 3, 4, 5, 6};
Blob val2 = {10, 20, 30, 40};
ParsedMessage msg;
msg.value_parts[0].total_size = val1.size();
msg.value_parts[1].total_size = val2.size();
// val2 second half
CPPUNIT_ASSERT(msg.append(makeFragment(1, 2, Blob {30, 40})));
// val1 last third
CPPUNIT_ASSERT(msg.append(makeFragment(0, 4, Blob {5, 6})));
// val2 first half
CPPUNIT_ASSERT(msg.append(makeFragment(1, 0, Blob {10, 20})));
// val1 first third
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, Blob {1, 2})));
// val1 middle third
CPPUNIT_ASSERT(msg.append(makeFragment(0, 2, Blob {3, 4})));
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.value_parts[1].isComplete());
CPPUNIT_ASSERT(msg.value_parts[0].reassemble() == val1);
CPPUNIT_ASSERT(msg.value_parts[1].reassemble() == val2);
}
void
ParsedMessageTester::testReceiveLargeFragmentedValue()
{
std::string data(TEST_MTU * 3, 'x');
auto serialized = serializeValue(data);
CPPUNIT_ASSERT(serialized.size() > TEST_MTU);
auto msg = makeReplyHeaderPacket(1, {serialized.size()});
CPPUNIT_ASSERT_EQUAL((size_t) 1, msg.value_parts.size());
CPPUNIT_ASSERT_EQUAL(serialized.size(), msg.value_parts[0].total_size);
for (size_t offset = 0; offset < serialized.size(); offset += TEST_MTU) {
auto end = std::min(offset + TEST_MTU, serialized.size());
Blob fragment(serialized.begin() + offset, serialized.begin() + end);
auto packet = makeValueDataPacket(1, 0, offset, fragment);
CPPUNIT_ASSERT(msg.append(packet));
}
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.complete());
CPPUNIT_ASSERT_EQUAL((size_t) 1, msg.values.size());
std::string recovered(msg.values[0]->data.begin(), msg.values[0]->data.end());
CPPUNIT_ASSERT_EQUAL(data, recovered);
}
void
ParsedMessageTester::testReceiveLargeFragmentedValueOutOfOrder()
{
std::string data(TEST_MTU * 3 + 117, 'y');
auto serialized = serializeValue(data);
CPPUNIT_ASSERT(serialized.size() > TEST_MTU);
auto msg = makeReplyHeaderPacket(7, {serialized.size()});
std::vector<ParsedMessage> packets;
for (size_t offset = 0; offset < serialized.size(); offset += TEST_MTU) {
auto end = std::min(offset + TEST_MTU, serialized.size());
Blob fragment(serialized.begin() + offset, serialized.begin() + end);
packets.emplace_back(makeValueDataPacket(7, 0, offset, fragment));
}
CPPUNIT_ASSERT(packets.size() >= 3);
CPPUNIT_ASSERT(msg.append(packets.back()));
CPPUNIT_ASSERT(msg.append(packets.front()));
for (size_t i = 1; i + 1 < packets.size(); ++i)
CPPUNIT_ASSERT(msg.append(packets[i]));
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
CPPUNIT_ASSERT(msg.complete());
CPPUNIT_ASSERT_EQUAL((size_t) 1, msg.values.size());
std::string recovered(msg.values[0]->data.begin(), msg.values[0]->data.end());
CPPUNIT_ASSERT_EQUAL(data, recovered);
}
} // namespace test
-76
View File
@@ -1,76 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
// cppunit
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
namespace test {
class ParsedMessageTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(ParsedMessageTester);
CPPUNIT_TEST(testAppendInOrder);
CPPUNIT_TEST(testAppendOutOfOrder);
CPPUNIT_TEST(testAppendReverseOrder);
CPPUNIT_TEST(testAppendDuplicateFragment);
CPPUNIT_TEST(testAppendOverlappingFragments);
CPPUNIT_TEST(testAppendFragmentBeyondTotal);
CPPUNIT_TEST(testAppendEmptyFragment);
CPPUNIT_TEST(testAppendUnknownIndex);
CPPUNIT_TEST(testAppendAlreadyComplete);
CPPUNIT_TEST(testAppendOverflowingOffset);
CPPUNIT_TEST(testParseRejectsNonMapPacket);
CPPUNIT_TEST(testParseRejectsInvalidValueDataPayload);
CPPUNIT_TEST(testParseRejectsInvalidValuesField);
CPPUNIT_TEST(testParseRejectsEmptyFieldSelection);
CPPUNIT_TEST(testParseRejectsEmptyErrorArray);
CPPUNIT_TEST(testParseRejectsNonMapRequest);
CPPUNIT_TEST(testParseErrorOnlyMessageKeepsRequestFieldsEmpty);
CPPUNIT_TEST(testParseIgnoresIncompleteValueDataEntry);
CPPUNIT_TEST(testCompleteEmpty);
CPPUNIT_TEST(testCompleteIncomplete);
CPPUNIT_TEST(testCompleteZeroSizedValue);
CPPUNIT_TEST(testCompleteSingleValue);
CPPUNIT_TEST(testCompleteMultipleValues);
CPPUNIT_TEST(testCompleteReassemblyOrder);
CPPUNIT_TEST(testAppendMultipleValuesOutOfOrder);
CPPUNIT_TEST(testReceiveLargeFragmentedValue);
CPPUNIT_TEST(testReceiveLargeFragmentedValueOutOfOrder);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testAppendInOrder();
void testAppendOutOfOrder();
void testAppendReverseOrder();
void testAppendDuplicateFragment();
void testAppendOverlappingFragments();
void testAppendFragmentBeyondTotal();
void testAppendEmptyFragment();
void testAppendUnknownIndex();
void testAppendAlreadyComplete();
void testAppendOverflowingOffset();
void testParseRejectsNonMapPacket();
void testParseRejectsInvalidValueDataPayload();
void testParseRejectsInvalidValuesField();
void testParseRejectsEmptyFieldSelection();
void testParseRejectsEmptyErrorArray();
void testParseRejectsNonMapRequest();
void testParseErrorOnlyMessageKeepsRequestFieldsEmpty();
void testParseIgnoresIncompleteValueDataEntry();
void testCompleteEmpty();
void testCompleteIncomplete();
void testCompleteZeroSizedValue();
void testCompleteSingleValue();
void testCompleteMultipleValues();
void testCompleteReassemblyOrder();
void testAppendMultipleValuesOutOfOrder();
void testReceiveLargeFragmentedValue();
void testReceiveLargeFragmentedValueOutOfOrder();
};
} // namespace test
+3 -3
View File
@@ -54,7 +54,7 @@ PeerDiscoveryTester::testMulticastToTwoNodes()
unsigned countDht {0};
unsigned countJami {0};
{
std::unique_lock l(lock);
std::unique_lock<std::mutex> l(lock);
dht::PeerDiscovery testDht(MULTICAST_PORT);
dht::PeerDiscovery testJami(MULTICAST_PORT);
@@ -63,7 +63,7 @@ PeerDiscoveryTester::testMulticastToTwoNodes()
CPPUNIT_ASSERT_EQUAL(dhtNode.nodeid, v.nodeid);
CPPUNIT_ASSERT_EQUAL(dhtNode.nid, v.nid);
{
std::lock_guard l(lock);
std::lock_guard<std::mutex> l(lock);
countDht++;
}
cv.notify_all();
@@ -75,7 +75,7 @@ PeerDiscoveryTester::testMulticastToTwoNodes()
CPPUNIT_ASSERT_EQUAL(jamiNode.cha, v.cha);
CPPUNIT_ASSERT_EQUAL(jamiNode.str, v.str);
{
std::lock_guard l(lock);
std::lock_guard<std::mutex> l(lock);
countJami++;
}
cv.notify_all();
-40
View File
@@ -80,46 +80,6 @@ ThreadPoolTester::testContext()
CPPUNIT_ASSERT_EQUAL(N, count.load());
}
void
ThreadPoolTester::testJoinNonEmptyTaskQueue()
{
dht::ThreadPool pool(2);
constexpr unsigned N = 128;
std::atomic_uint count {0};
// Schedule enough work so that the task queue is non-empty when calling
// pool.join() below.
for (unsigned i = 0; i < N; i++)
pool.run([&] {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
count++;
});
std::atomic_bool joined {false};
std::condition_variable cv;
std::thread joinThread([&] {
CPPUNIT_ASSERT(count.load() < N);
pool.join();
joined = true;
cv.notify_one();
});
// After calling join(), all pending tasks should complete, and then all
// threads in the pool should join.
std::mutex mtx;
std::unique_lock lock(mtx);
bool poolJoined = cv.wait_for(lock, std::chrono::seconds(10), [&] { return joined.load(); });
if (!poolJoined) {
// Detach joinThread if we timed out, otherwise the test can crash.
joinThread.detach();
} else {
joinThread.join();
}
CPPUNIT_ASSERT_EQUAL_MESSAGE("Some tasks were not completed", N, count.load());
CPPUNIT_ASSERT_MESSAGE("Thread pool failed to join before timeout", poolJoined);
}
void
ThreadPoolTester::tearDown()
{}
-2
View File
@@ -14,7 +14,6 @@ class ThreadPoolTester : public CppUnit::TestFixture
CPPUNIT_TEST(testThreadPool);
CPPUNIT_TEST(testExecutor);
CPPUNIT_TEST(testContext);
CPPUNIT_TEST(testJoinNonEmptyTaskQueue);
CPPUNIT_TEST_SUITE_END();
public:
@@ -30,7 +29,6 @@ public:
void testThreadPool();
void testExecutor();
void testContext();
void testJoinNonEmptyTaskQueue();
};
} // namespace test
-103
View File
@@ -8,8 +8,6 @@
// opendht
#include "opendht/value.h"
#include "opendht/crypto.h"
#include <msgpack.hpp>
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(ValueTester);
@@ -61,105 +59,4 @@ ValueTester::testFilter()
void
ValueTester::tearDown()
{}
void
ValueTester::testPushTypeMsgpackRoundTrip()
{
dht::Value original {(const uint8_t*) "hello", 5};
original.id = 42;
original.priority = 3;
original.pushType = "audioCall";
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
original.msgpack_pack(pk);
msgpack::unpacked msg;
msgpack::unpack(msg, buffer.data(), buffer.size());
dht::Value restored(msg.get());
CPPUNIT_ASSERT_EQUAL(original.id, restored.id);
CPPUNIT_ASSERT_EQUAL(original.priority, restored.priority);
CPPUNIT_ASSERT_EQUAL(original.pushType, restored.pushType);
}
void
ValueTester::testPushTypeAbsentAfterUnpack()
{
dht::Value withPt {(const uint8_t*) "data", 4};
withPt.id = 1;
withPt.pushType = "videoCall";
withPt.priority = 2;
msgpack::sbuffer buf1;
msgpack::packer<msgpack::sbuffer> pk1(&buf1);
withPt.msgpack_pack(pk1);
msgpack::unpacked msg1;
msgpack::unpack(msg1, buf1.data(), buf1.size());
dht::Value reused;
reused.msgpack_unpack(msg1.get());
CPPUNIT_ASSERT_EQUAL(std::string("videoCall"), reused.pushType);
CPPUNIT_ASSERT_EQUAL(2u, reused.priority);
dht::Value plain {(const uint8_t*) "data", 4};
plain.id = 2;
msgpack::sbuffer buf2;
msgpack::packer<msgpack::sbuffer> pk2(&buf2);
plain.msgpack_pack(pk2);
msgpack::unpacked msg2;
msgpack::unpack(msg2, buf2.data(), buf2.size());
reused.msgpack_unpack(msg2.get());
CPPUNIT_ASSERT_EQUAL(std::string(), reused.pushType);
CPPUNIT_ASSERT_EQUAL(0u, reused.priority);
}
void
ValueTester::testPushTypePreservedAfterEncrypt()
{
auto key = dht::crypto::PrivateKey::generate();
const auto& pubkey = key.getPublicKey();
dht::Value original {(const uint8_t*) "secret", 6};
original.id = 99;
original.priority = 1;
original.pushType = "audioCall";
auto encrypted = original.encrypt(key, pubkey);
CPPUNIT_ASSERT_EQUAL(original.pushType, encrypted.pushType);
CPPUNIT_ASSERT_EQUAL(original.priority, encrypted.priority);
CPPUNIT_ASSERT_EQUAL(original.id, encrypted.id);
CPPUNIT_ASSERT(encrypted.isEncrypted());
}
void
ValueTester::testPushTypeJsonRoundTrip()
{
#ifdef OPENDHT_JSONCPP
dht::Value original {(const uint8_t*) "hello", 5};
original.id = 42;
original.priority = 1;
original.pushType = "videoCall";
dht::Value restored(original.toJson());
CPPUNIT_ASSERT_EQUAL(original.id, restored.id);
CPPUNIT_ASSERT_EQUAL(original.priority, restored.priority);
CPPUNIT_ASSERT_EQUAL(original.pushType, restored.pushType);
// No pushType: the field must stay absent from the JSON and empty after parse.
dht::Value plain {(const uint8_t*) "data", 4};
plain.id = 7;
auto json = plain.toJson();
CPPUNIT_ASSERT(!json.isMember("pt"));
dht::Value plainRestored(json);
CPPUNIT_ASSERT_EQUAL(std::string(), plainRestored.pushType);
#endif
}
} // namespace test
-8
View File
@@ -13,10 +13,6 @@ class ValueTester : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE(ValueTester);
CPPUNIT_TEST(testConstructors);
CPPUNIT_TEST(testFilter);
CPPUNIT_TEST(testPushTypeMsgpackRoundTrip);
CPPUNIT_TEST(testPushTypeAbsentAfterUnpack);
CPPUNIT_TEST(testPushTypePreservedAfterEncrypt);
CPPUNIT_TEST(testPushTypeJsonRoundTrip);
CPPUNIT_TEST_SUITE_END();
public:
@@ -36,10 +32,6 @@ public:
* Test compare operators
*/
void testFilter();
void testPushTypeMsgpackRoundTrip();
void testPushTypeAbsentAfterUnpack();
void testPushTypePreservedAfterEncrypt();
void testPushTypeJsonRoundTrip();
};
} // namespace test
-560
View File
@@ -1,560 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_valuecache.h"
#include "../src/value_cache.h"
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(ValueCacheTester);
void
ValueCacheTester::setUp()
{}
void
ValueCacheTester::tearDown()
{}
void
ValueCacheTester::testUpdate()
{
int addCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (!expired)
addCount += vals.size();
});
dht::TypeStore types;
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
v1->seq = 1;
v1->data = {'A'};
auto v2 = std::make_shared<dht::Value>();
v2->id = 1;
v2->seq = 2;
v2->data = {'B'};
auto now = std::chrono::steady_clock::now();
// Add v1
cache.onValues({v1}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(1, addCount);
// Update v1 -> v2
cache.onValues({v2}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Callback should be called for update", 2, addCount);
}
void
ValueCacheTester::testUpdateExpiration()
{
int addCount = 0;
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
else
addCount += vals.size();
});
dht::TypeStore types;
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
v1->seq = 1;
v1->data = {'A'};
auto v2 = std::make_shared<dht::Value>();
v2->id = 1;
v2->seq = 2;
v2->data = {'B'};
auto now = std::chrono::steady_clock::now();
// Add v1
cache.onValues({v1}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(1, addCount);
CPPUNIT_ASSERT_EQUAL(0, expireCount);
// Update v1 -> v2: only the new value is emitted as an add,
// no expire callback for the old value.
cache.onValues({v2}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(2, addCount);
CPPUNIT_ASSERT_EQUAL_MESSAGE("No expire callback on edit", 0, expireCount);
}
void
ValueCacheTester::testExpiration()
{
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
});
dht::TypeStore types;
auto v1 = std::make_shared<dht::Value>();
v1->id = 10;
v1->data = {'A'};
// Default expiration is 10 minutes
auto now = std::chrono::steady_clock::now();
cache.onValues({v1}, {}, {}, types, now);
// Advance time past expiration
auto future = now + std::chrono::minutes(11);
// expireValues returns next expiration time
cache.expireValues(future);
CPPUNIT_ASSERT_EQUAL(1, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
ValueCacheTester::testRefresh()
{
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
});
dht::TypeStore types;
auto v1 = std::make_shared<dht::Value>();
v1->id = 20;
v1->data = {'B'};
auto now = std::chrono::steady_clock::now();
cache.onValues({v1}, {}, {}, types, now);
// Refresh at +5 mins
auto later = now + std::chrono::minutes(5);
cache.onValues({}, {v1->id}, {}, types, later);
// Should not expire at +11 mins (original expiration was +10, so +10 from start)
// But refresh should extend it to +10 from 'later' = +15 from start.
auto checkTime = now + std::chrono::minutes(11);
cache.expireValues(checkTime);
CPPUNIT_ASSERT_EQUAL(0, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
// Should expire at +16 mins
auto future = now + std::chrono::minutes(16);
cache.expireValues(future);
CPPUNIT_ASSERT_EQUAL(1, expireCount);
}
void
ValueCacheTester::testExplicitExpiration()
{
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
});
dht::TypeStore types;
auto v1 = std::make_shared<dht::Value>();
v1->id = 30;
v1->data = {'C'};
auto now = std::chrono::steady_clock::now();
cache.onValues({v1}, {}, {}, types, now);
cache.onValues({}, {}, {v1->id}, types, now);
CPPUNIT_ASSERT_EQUAL(1, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
ValueCacheTester::testClear()
{
dht::ValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool) {});
dht::TypeStore types;
auto now = std::chrono::steady_clock::now();
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
auto v2 = std::make_shared<dht::Value>();
v2->id = 2;
cache.onValues({v1, v2}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL((size_t) 2, cache.size());
auto cbs = cache.clear();
// Execute callbacks
for (auto& cb : cbs)
cb();
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
ValueCacheTester::testSyncStatus()
{
std::vector<dht::ListenSyncStatus> statuses;
{
dht::ValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool) {},
[&](dht::ListenSyncStatus status) { statuses.push_back(status); });
CPPUNIT_ASSERT_EQUAL((size_t) 1, statuses.size());
CPPUNIT_ASSERT(statuses.back() == dht::ListenSyncStatus::ADDED);
cache.onSynced(true);
CPPUNIT_ASSERT_EQUAL((size_t) 2, statuses.size());
CPPUNIT_ASSERT(statuses.back() == dht::ListenSyncStatus::SYNCED);
// Calling onSynced(true) again should not trigger the callback
cache.onSynced(true);
CPPUNIT_ASSERT_EQUAL((size_t) 2, statuses.size());
cache.onSynced(false);
CPPUNIT_ASSERT_EQUAL((size_t) 3, statuses.size());
CPPUNIT_ASSERT(statuses.back() == dht::ListenSyncStatus::UNSYNCED);
// Calling onSynced(false) again should not trigger the callback
cache.onSynced(false);
CPPUNIT_ASSERT_EQUAL((size_t) 3, statuses.size());
// Set to synced before destruction to test UNSYNCED on destruction
cache.onSynced(true);
CPPUNIT_ASSERT_EQUAL((size_t) 4, statuses.size());
CPPUNIT_ASSERT(statuses.back() == dht::ListenSyncStatus::SYNCED);
}
// Destructor should have been called
CPPUNIT_ASSERT_EQUAL((size_t) 6, statuses.size());
CPPUNIT_ASSERT(statuses[4] == dht::ListenSyncStatus::UNSYNCED);
CPPUNIT_ASSERT(statuses[5] == dht::ListenSyncStatus::REMOVED);
}
void
ValueCacheTester::testMaxValues()
{
dht::ValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool) {});
dht::TypeStore types;
auto now = std::chrono::steady_clock::now();
// MAX_VALUES is 4096
for (int i = 0; i < 4100; ++i) {
auto v = std::make_shared<dht::Value>();
v->id = (dht::Value::Id) i + 1;
cache.onValues({v}, {}, {}, types, now + std::chrono::milliseconds(i));
}
// Should be capped at 4096
CPPUNIT_ASSERT_EQUAL((size_t) 4096, cache.size());
}
void
ValueCacheTester::testUpdateTypeExpiration()
{
// Test that when a value is updated with a different type, the expiration
// uses the new type's expiration period, not the old one.
int addCount = 0;
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
else
addCount += vals.size();
});
// Register a custom type with a short expiration (2 minutes)
dht::TypeStore types;
dht::ValueType shortType(42, "short", std::chrono::minutes(2));
types.registerType(shortType);
// Register a custom type with a long expiration (30 minutes)
dht::ValueType longType(99, "long", std::chrono::minutes(30));
types.registerType(longType);
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
v1->type = 42; // short type: 2 min expiration
v1->data = {'A'};
auto now = std::chrono::steady_clock::now();
// Add v1 with short type
cache.onValues({v1}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(1, addCount);
// Update with new type (long expiration)
auto v2 = std::make_shared<dht::Value>();
v2->id = 1;
v2->seq = 1;
v2->type = 99; // long type: 30 min expiration
v2->data = {'B'};
cache.onValues({v2}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(2, addCount);
// At +3 minutes: should NOT be expired (long type has 30 min expiration)
// Before the fix, the old type (2 min) would be used, causing expiration here
expireCount = 0;
auto at3min = now + std::chrono::minutes(3);
cache.expireValues(at3min);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Value updated to long-expiry type should NOT expire at 3 min", 0, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
// At +31 minutes: should be expired
auto at31min = now + std::chrono::minutes(31);
cache.expireValues(at31min);
CPPUNIT_ASSERT_EQUAL(1, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
ValueCacheTester::testSameSeqEditIgnored()
{
// Like OpValueCache, an edit is only recognized by an increasing seq:
// a different payload with the same seq is a refresh, not an edit.
int addCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (!expired)
addCount += vals.size();
});
dht::TypeStore types;
auto now = std::chrono::steady_clock::now();
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
v1->seq = 3;
v1->data = {'A'};
cache.onValues({v1}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(1, addCount);
auto sameSeq = std::make_shared<dht::Value>();
sameSeq->id = 1;
sameSeq->seq = 3;
sameSeq->data = {'B'};
cache.onValues({sameSeq}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(1, addCount);
auto older = std::make_shared<dht::Value>();
older->id = 1;
older->seq = 2;
older->data = {'C'};
cache.onValues({older}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(1, addCount);
auto newer = std::make_shared<dht::Value>();
newer->id = 1;
newer->seq = 4;
newer->data = {'D'};
cache.onValues({newer}, {}, {}, types, now);
CPPUNIT_ASSERT_EQUAL(2, addCount);
}
void
ValueCacheTester::testShortExpirationRefreshLost()
{
// Demonstrates that a value with a short expiration type survives
// past its nominal expiration when the listen is synced, thanks to
// a proportional grace period (= typeExpiration).
//
// For a 5s type: effective expiration when synced = expiration + 5s = 10s
//
// Scenario:
// t=0 : Value V added → nominal expiration t+5s, effective t+10s
// t=6 : Past nominal but within grace → alive
// t=8 : Refresh arrives → V refreshed → expiration reset to t8+5s, effective t8+10s=t18s
// t=14: Still alive (within refreshed effective range)
// t=20: No more refreshes → past t8+10s=t18s → expired
int addCount = 0;
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
else
addCount += vals.size();
});
// Register a type with a very short expiration (5 seconds)
dht::TypeStore types;
dht::ValueType shortType(100, "short_lived", std::chrono::seconds(5));
types.registerType(shortType);
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
v1->type = 100;
v1->data = {'V'};
auto t0 = std::chrono::steady_clock::now();
// t=0: Listen response arrives with V
cache.onValues({v1}, {}, {}, types, t0);
CPPUNIT_ASSERT_EQUAL(1, addCount);
CPPUNIT_ASSERT_EQUAL(0, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
// Mark cache as synced (listen is active)
cache.onSynced(true);
// t=6: Past nominal 5s expiration, but grace (5s) gives effective 10s
auto t6 = t0 + std::chrono::seconds(6);
cache.expireValues(t6);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Value should survive past nominal expiration thanks to proportional grace",
0,
expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
// t=8: Value refresh arrives → expiration reset to t8+5s=t13s, effective t8+10s=t18s
auto t8 = t0 + std::chrono::seconds(8);
cache.onValues({}, {v1->id}, {}, types, t8);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
// t=14: Within refreshed effective range (t18s) → alive
auto t14 = t0 + std::chrono::seconds(14);
cache.expireValues(t14);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Value should survive past short expiration after refresh", 0, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
// t=20: No more refreshes → past effective t18s → must expire
auto t20 = t0 + std::chrono::seconds(20);
cache.expireValues(t20);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Value must eventually expire even while synced if server stops refreshing",
1,
expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
ValueCacheTester::testShortExpirationHighChurn()
{
// Simulates high churn: multiple rapid add/expire/refresh cycles with
// short-lived values, verifying the cache stays consistent.
int addCount = 0;
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
else
addCount += vals.size();
});
dht::TypeStore types;
dht::ValueType shortType(101, "fast_churn", std::chrono::seconds(3));
types.registerType(shortType);
auto now = std::chrono::steady_clock::now();
// Simulate 10 churn cycles: add value, advance time, refresh, advance, expire
for (int cycle = 0; cycle < 10; cycle++) {
auto v = std::make_shared<dht::Value>();
v->id = static_cast<dht::Value::Id>(500 + cycle);
v->type = 101;
v->data = {static_cast<uint8_t>('A' + cycle)};
auto t = now + std::chrono::seconds(cycle * 10);
// Add the value
cache.onValues({v}, {}, {}, types, t);
// Refresh halfway through
auto tRefresh = t + std::chrono::seconds(2);
cache.onValues({}, {v->id}, {}, types, tRefresh);
// Explicit expire from server
auto tExpire = t + std::chrono::seconds(8);
cache.onValues({}, {}, {v->id}, types, tExpire);
}
// All 10 values were added and explicitly expired
CPPUNIT_ASSERT_EQUAL(10, addCount);
CPPUNIT_ASSERT_EQUAL(10, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
ValueCacheTester::testGracePeriodWhileSynced()
{
// Verify that when the listen is synced, the proportional grace period
// (= typeExpiration) delays time-based expiration but doesn't prevent
// it entirely. For a 2s type: effective expiration = 2s + 2s = 4s.
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
});
dht::TypeStore types;
dht::ValueType shortType(110, "short", std::chrono::seconds(2));
types.registerType(shortType);
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
v1->type = 110;
v1->data = {'X'};
auto t0 = std::chrono::steady_clock::now();
cache.onValues({v1}, {}, {}, types, t0);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
// Mark as synced
cache.onSynced(true);
// t=3: Past nominal 2s, within effective 4s → grace protects
auto t3 = t0 + std::chrono::seconds(3);
cache.expireValues(t3);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Synced cache: grace period should prevent early expiry", 0, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 1, cache.size());
// t=5: Past effective 4s (2s + 2s grace) → must expire
auto t5 = t0 + std::chrono::seconds(5);
cache.expireValues(t5);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Synced cache: value must eventually expire after grace period", 1, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
void
ValueCacheTester::testUnsyncExpiresImmediately()
{
// When the cache goes from synced to unsynced, the grace period is
// removed and already-past-due values expire on the next check.
// Type expiration = 5s. Grace when synced = 5s → effective = 10s.
int expireCount = 0;
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired) {
if (expired)
expireCount += vals.size();
});
dht::TypeStore types;
dht::ValueType shortType(120, "short", std::chrono::seconds(5));
types.registerType(shortType);
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
v1->type = 120;
v1->data = {'U'};
auto t0 = std::chrono::steady_clock::now();
cache.onValues({v1}, {}, {}, types, t0);
cache.onSynced(true);
// t=7: past nominal 5s, within effective 10s while synced → alive
auto t7 = t0 + std::chrono::seconds(7);
cache.expireValues(t7);
CPPUNIT_ASSERT_EQUAL(0, expireCount);
// Now unsync (node went away / connectivity loss)
cache.onSynced(false);
// Same time point t=7: past nominal 5s, no grace → expires
cache.expireValues(t7);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Unsynced cache should immediately expire past-due values", 1, expireCount);
CPPUNIT_ASSERT_EQUAL((size_t) 0, cache.size());
}
} // namespace test
-49
View File
@@ -1,49 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
namespace test {
class ValueCacheTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(ValueCacheTester);
CPPUNIT_TEST(testUpdate);
CPPUNIT_TEST(testUpdateExpiration);
CPPUNIT_TEST(testExpiration);
CPPUNIT_TEST(testRefresh);
CPPUNIT_TEST(testExplicitExpiration);
CPPUNIT_TEST(testClear);
CPPUNIT_TEST(testSyncStatus);
CPPUNIT_TEST(testMaxValues);
CPPUNIT_TEST(testUpdateTypeExpiration);
CPPUNIT_TEST(testSameSeqEditIgnored);
CPPUNIT_TEST(testShortExpirationRefreshLost);
CPPUNIT_TEST(testShortExpirationHighChurn);
CPPUNIT_TEST(testGracePeriodWhileSynced);
CPPUNIT_TEST(testUnsyncExpiresImmediately);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testUpdate();
void testUpdateExpiration();
void testExpiration();
void testRefresh();
void testExplicitExpiration();
void testClear();
void testSyncStatus();
void testMaxValues();
void testUpdateTypeExpiration();
void testSameSeqEditIgnored();
void testShortExpirationRefreshLost();
void testShortExpirationHighChurn();
void testGracePeriodWhileSynced();
void testUnsyncExpiresImmediately();
};
} // namespace test
+2 -26
View File
@@ -5,29 +5,12 @@
#include <cppunit/CompilerOutputter.h>
#include <iostream>
namespace {
void
listTests(CppUnit::Test* test, const std::string& prefix = {})
{
const auto name = test->getName();
const auto fullName = prefix.empty() ? name : prefix + "/" + name;
if (test->getChildTestCount() == 0) {
std::cout << fullName << std::endl;
return;
}
for (int index = 0; index < test->getChildTestCount(); ++index)
listTests(test->getChildTestAt(index), fullName);
}
} // namespace
extern "C" {
#include <gnutls/gnutls.h>
}
int
main(int argc, char** argv)
main(int /*argc*/, char** /*argv*/)
{
#ifdef _MSC_VER
if (auto err = gnutls_global_init()) {
@@ -42,15 +25,8 @@ main(int argc, char** argv)
std::cout << "No test cases specified for suite" << std::endl;
return 1;
}
if (argc > 1 && std::string(argv[1]) == "--list") {
listTests(suite);
return 0;
}
CppUnit::TextUi::TestRunner runner;
if (argc > 1)
runner.addTest(suite->findTest(argv[1]));
else
runner.addTest(suite);
runner.addTest(suite);
auto result = runner.run() ? 0 : 1;
return result;
}
+14 -20
View File
@@ -12,10 +12,10 @@ extern "C" {
}
#include <ctime>
namespace dht {
namespace tools {
using namespace dht;
static std::mt19937_64 rd {dht::crypto::getSeededRandomEngine<std::mt19937_64>()};
static std::uniform_int_distribution<dht::Value::Id> rand_id;
const std::string
printTime(const std::time_t& now)
@@ -34,18 +34,12 @@ print_usage()
std::cout << "Report bugs to: https://opendht.net" << std::endl;
}
} // namespace tools
} // namespace dht
int
main(int argc, char** argv)
{
using namespace dht;
using IdDist = std::uniform_int_distribution<dht::Value::Id>;
auto params = tools::parseArgs(argc, argv);
auto params = parseArgs(argc, argv);
if (params.help) {
tools::print_usage();
print_usage();
return 0;
}
#ifdef _MSC_VER
@@ -64,14 +58,14 @@ main(int argc, char** argv)
if (not params.bootstrap.empty())
dht.bootstrap(params.bootstrap);
tools::print_node_info(dht.getNodeInfo());
print_node_info(dht.getNodeInfo());
std::cout << " type 'c {hash}' to join a channel" << std::endl << std::endl;
bool connected {false};
InfoHash room;
std::future<size_t> token;
const auto mypk = dht.getPublicKey();
const InfoHash myid = dht.getId();
#ifndef _MSC_VER
// using the GNU History API
@@ -80,7 +74,7 @@ main(int argc, char** argv)
while (true) {
// using the GNU Readline API
std::string line = tools::readLine(connected ? tools::PROMPT : "> ");
std::string line = readLine(connected ? PROMPT : "> ");
if (!line.empty() && line[0] == '\0')
break;
if (line.empty())
@@ -101,12 +95,12 @@ main(int argc, char** argv)
}
token = dht.listen<dht::ImMessage>(room, [&](dht::ImMessage&& msg) {
if (msg.owner && msg.owner->getLongId() != mypk->getLongId())
std::cout << msg.from.toString() << " at " << tools::printTime(msg.date) << " (took "
if (msg.from != myid)
std::cout << msg.from.toString() << " at " << printTime(msg.date) << " (took "
<< print_duration(std::chrono::system_clock::now()
- std::chrono::system_clock::from_time_t(msg.date))
<< ") " << (msg.to == mypk->getId() ? "ENCRYPTED " : "") << ": " << msg.id
<< " - " << msg.msg << std::endl;
<< ") " << (msg.to == myid ? "ENCRYPTED " : "") << ": " << msg.id << " - "
<< msg.msg << std::endl;
return true;
});
connected = true;
@@ -123,15 +117,15 @@ main(int argc, char** argv)
iss >> idstr;
std::getline(iss, line);
dht.putEncrypted(room,
PkId(idstr),
dht::ImMessage(IdDist()(tools::rd), std::move(line), now),
InfoHash(idstr),
dht::ImMessage(rand_id(rd), std::move(line), now),
[](bool ok) {
// dht.cancelPut(room, id);
if (not ok)
std::cout << "Message publishing failed !" << std::endl;
});
} else {
dht.putSigned(room, dht::ImMessage(IdDist()(tools::rd), std::move(line), now), [](bool ok) {
dht.putSigned(room, dht::ImMessage(rand_id(rd), std::move(line), now), [](bool ok) {
// dht.cancelPut(room, id);
if (not ok)
std::cout << "Message publishing failed !" << std::endl;
+3 -9
View File
@@ -16,8 +16,7 @@ extern "C" {
#include <set>
#include <thread> // std::this_thread::sleep_for
namespace dht {
namespace tools {
using namespace dht;
void
print_info()
@@ -481,7 +480,7 @@ cmd_loop(std::shared_ptr<DhtRunner>& node,
auto value = std::make_shared<dht::Value>(dht::ValueType::USER_DATA.id,
std::vector<uint8_t> {v.begin(), v.end()});
value->user_type = "text/plain";
node->putEncrypted(id, PkId(tostr), std::move(value), [start](bool ok) {
node->putEncrypted(id, InfoHash(tostr), std::move(value), [start](bool ok) {
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Put encrypted: " << (ok ? "success" : "failure") << " (took "
<< print_duration(end - start) << ')' << std::endl;
@@ -549,14 +548,9 @@ cmd_loop(std::shared_ptr<DhtRunner>& node,
std::cout << std::endl << "Stopping node..." << std::endl;
}
} // namespace tools
} // namespace dht
int
main(int argc, char** argv)
{
using namespace dht;
using namespace dht::tools;
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
@@ -649,7 +643,7 @@ main(int argc, char** argv)
});
// wait for shutdown
std::unique_lock lk(m);
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&]() { return done; });
node->join();
+3 -8
View File
@@ -15,8 +15,7 @@ extern "C" {
#include <condition_variable>
#include <mutex>
namespace dht {
namespace tools {
using namespace dht;
void
print_usage()
@@ -83,13 +82,9 @@ step(DhtRunner& dht, std::atomic_uint& done, std::shared_ptr<NodeSet> all_nodes,
});
}
} // namespace tools
} // namespace dht
int
main(int argc, char** argv)
{
using namespace dht::tools;
#ifdef _MSC_VER
if (auto err = gnutls_global_init()) {
fmt::print(stderr, "Failed to initialize GnuTLS: {}\n", gnutls_strerror(err));
@@ -102,7 +97,7 @@ main(int argc, char** argv)
return EXIT_SUCCESS;
}
dht::DhtRunner dht;
DhtRunner dht;
try {
auto [config, context] = getDhtConfig(params);
config.dht_config.node_config.client_mode = true;
@@ -126,7 +121,7 @@ main(int argc, char** argv)
{
std::mutex m;
std::unique_lock lk(m);
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&]() { return done.load() == 0; });
}
+17 -20
View File
@@ -14,14 +14,6 @@ extern "C" {
#include <mutex>
#include <atomic>
namespace dht {
namespace tests {
constexpr unsigned PINGPONG_MAX = 2048;
using clock = std::chrono::high_resolution_clock;
using duration = clock::duration;
void
print_usage()
{
@@ -29,6 +21,14 @@ print_usage()
std::cout << "perftest, a simple OpenDHT basic performance tester." << std::endl;
std::cout << "Report bugs to: https://opendht.net" << std::endl;
}
constexpr unsigned PINGPONG_MAX = 2048;
using namespace dht;
namespace tests {
using clock = std::chrono::high_resolution_clock;
using duration = clock::duration;
duration
benchPingPong(unsigned netSize, unsigned n_parallel)
{
@@ -68,7 +68,7 @@ benchPingPong(unsigned netSize, unsigned n_parallel)
unsigned max = PINGPONG_MAX * n_parallel;
auto ping = [&](DhtRunner& node, InfoHash h) {
std::lock_guard lk(m);
std::lock_guard<std::mutex> lk(m);
if (i < max) {
i++;
node.put(h, Value("hey"));
@@ -93,7 +93,7 @@ benchPingPong(unsigned netSize, unsigned n_parallel)
ping(pong_node, locs[i].first);
{
std::unique_lock lk(m);
std::unique_lock<std::mutex> lk(m);
if (not cv.wait_for(lk, std::chrono::minutes(1), [&]() { return i == max; })) {
throw std::runtime_error(std::string("Timeout: ") + std::to_string(i));
}
@@ -115,13 +115,10 @@ benchPingPong(unsigned netSize, unsigned n_parallel)
}
} // namespace tests
} // namespace dht
int
main(int argc, char** argv)
{
using namespace dht::tools;
using namespace dht::tests;
#ifdef _MSC_VER
if (auto err = gnutls_global_init()) {
std::cerr << "Failed to initialize GnuTLS: " << gnutls_strerror(err) << std::endl;
@@ -143,10 +140,10 @@ main(int argc, char** argv)
results.reserve(8);
duration total {0};
for (unsigned i = 2; i < 32; i *= 2) {
auto dt = benchPingPong(i - 2, nparallel);
auto dt = tests::benchPingPong(i - 2, nparallel);
std::cout << "Network size: " << i << std::endl;
std::cout << max << " ping-pong done, took " << dht::print_duration(dt) << std::endl;
std::cout << dht::print_duration(dt / max) << " per rt, " << max / std::chrono::duration<double>(dt).count()
std::cout << max << " ping-pong done, took " << print_duration(dt) << std::endl;
std::cout << print_duration(dt / max) << " per rt, " << max / std::chrono::duration<double>(dt).count()
<< " ping per s" << std::endl
<< std::endl;
total += dt;
@@ -158,14 +155,14 @@ main(int argc, char** argv)
std::cout << "Total for " << nparallel << std::endl;
auto totNum = max * results.size();
std::cout << totNum << " ping-pong done, took " << dht::print_duration(total) << std::endl;
std::cout << dht::print_duration(total / totNum) << " per rt, "
std::cout << totNum << " ping-pong done, took " << print_duration(total) << std::endl;
std::cout << print_duration(total / totNum) << " per rt, "
<< totNum / std::chrono::duration<double>(total).count() << " ping per s" << std::endl
<< std::endl;
}
std::cout << std::endl << "Grand total: " << dht::print_duration(totalTime) << " for " << totalOps << std::endl;
std::cout << dht::print_duration(totalTime / totalOps) << " per rt, "
std::cout << std::endl << "Grand total: " << print_duration(totalTime) << " for " << totalOps << std::endl;
std::cout << print_duration(totalTime / totalOps) << " per rt, "
<< totalOps / std::chrono::duration<double>(totalTime).count() << " ping per s" << std::endl
<< std::endl;
+16 -7
View File
@@ -54,9 +54,6 @@
#include <sstream>
#include <fstream>
namespace dht {
namespace tools {
/*
* The mapString shall have the following format:
*
@@ -122,6 +119,21 @@ loadFile(const std::string& path)
return buffer;
}
std::string
printByteCount(size_t bytes)
{
static constexpr std::string_view suffixes[] = {"B", "KB", "MB", "GB", "TB"};
size_t suffixIndex = 0;
double count = static_cast<double>(bytes);
while (count >= 1024 && suffixIndex < std::size(suffixes) - 1) {
count /= 1024;
suffixIndex++;
}
if (suffixIndex == 0)
return fmt::format("{} {}", bytes, suffixes[suffixIndex]);
return fmt::format("{:.2f} {} ({} bytes)", count, suffixes[suffixIndex], bytes);
}
struct dht_params
{
bool help {false}; // print help and exit
@@ -421,7 +433,7 @@ struct ServiceRunner
{
bool wait()
{
std::unique_lock lock(m);
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [&] { return terminate.load(); });
return !terminate;
}
@@ -492,6 +504,3 @@ daemonize()
close(STDERR_FILENO);
#endif
}
} // namespace tools
} // namespace dht
+1 -1
View File
@@ -1,7 +1,7 @@
{
"default-registry": {
"kind": "git",
"baseline": "f3e10653cc27d62a37a3763cd84b38bca07c6075",
"baseline": "66c0373dc7fca549e5803087b9487edfe3aca0a1",
"repository": "https://github.com/microsoft/vcpkg"
},
"registries": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "opendht",
"version-string": "4.3.0",
"version-string": "3.7.2",
"port-version": 1,
"description": "OpenDHT: A lightweight C++17 Distributed Hash Table library.",
"homepage": "https://opendht.net",