Compare commits

..
10 Commits
Author SHA1 Message Date
Adrien Béraud eda36c5044 tests/dhtrunner: test empty value 2026-02-22 18:45:17 -05:00
Adrien Beraud 659b64bfb2 tests: add more proxy tests 2026-02-22 18:45:17 -05:00
Adrien Béraud 4de6d6cfd9 ValueCacheTester: improve testSyncStatus 2026-02-22 18:45:17 -05:00
Adrien Béraud c2a74045c1 tests: improve push notification proxy tests 2026-02-22 18:45:17 -05:00
Adrien Béraud a9c9b75bee dht: relax storage rules 2026-02-22 18:45:17 -05:00
Adrien Béraud b2ff05407b value cache: keep track of expired values in case of edition 2026-02-22 18:45:17 -05:00
Adrien Béraud 6a45891ee7 op cache: always increase refCount on edit 2026-02-22 18:45:17 -05:00
Adrien Béraud d1f432b6d6 tests: add ValueCacheTester 2026-02-22 18:45:17 -05:00
Adrien Béraud 432b6792ca tests: add OpCacheTester 2026-02-22 18:45:17 -05:00
Adrien Béraud d32f51455a infohash: make core APIs constexpr and non-throwing hex parse
Make Hash default constructor, hex constructor, data(), const
operator[] and fromString constexpr. Change fromString to be
non-throwing: it now uses a validity flag and fills the hash with zeros
on parse error instead of throwing. Add a constexpr toHexArray helper
that returns a std::array<char, N*2> for compile-time hex conversion.
Update tests to include <string_view>, use std::string_view, add
constexpr InfoHash usage and a static_assert for the hex array.
2026-02-22 18:45:17 -05:00
117 changed files with 3945 additions and 7768 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: |
-74
View File
@@ -1,74 +0,0 @@
# OpenDHT – Agent Instructions
## Build
### Primary: CMake (C++17)
```sh
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON -GNinja
ninja
```
### Common CMake options
| Option | Default | Purpose |
|--------|---------|---------|
| `OPENDHT_TOOLS` | ON | CLI tools (dhtnode, dhtchat, …) |
| `OPENDHT_C` | OFF | C language bindings |
| `OPENDHT_PYTHON` | OFF | Python 3 Cython bindings |
| `OPENDHT_PROXY_SERVER` | OFF | REST API proxy server (requires RESTinio + llhttp) |
| `OPENDHT_PROXY_CLIENT` | OFF | REST API proxy client |
| `OPENDHT_PEER_DISCOVERY` | OFF | Multicast local peer discovery |
| `OPENDHT_PUSH_NOTIFICATIONS` | OFF | Push notification support |
| `OPENDHT_SANITIZE` | OFF | AddressSanitizer + stack protector |
### Alternative: Meson (C++20)
```sh
meson setup build . && cd build && ninja
```
### Run tests
```sh
# Full suite
ctest --output-on-failure --test-dir build
# Single test executable
./build/test_testName
```
## 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).
---
## Architecture
OpenDHT is a Kademlia-based DHT library. The implementation is layered:
```
DhtRunner (public API — thread-safe, manages lifecycle & I/O threads)
└── SecureDht (GnuTLS crypto: signing, encryption, identity)
└── Dht (core DHT logic: routing, storage, find/get/put operations)
└── NetworkEngine (raw UDP sockets, message parsing, IPv4/IPv6)
```
- **`DhtRunner`** (`include/opendht/dhtrunner.h`, `src/dhtrunner.cpp`) — entry point for all users of the library. Wraps everything in a thread-pool and exposes async callbacks as well as blocking calls.
- **`Dht`** (`include/opendht/dht.h`, `src/dht.cpp`) — Kademlia protocol: routing table, k-buckets, value replication, expiry.
- **`SecureDht`** (`include/opendht/securedht.h`, `src/securedht.cpp`) — transparent encryption/signing layer on top of `Dht`. Identity is a GnuTLS certificate.
- **`NetworkEngine`** (`include/opendht/network_engine.h`, `src/network_engine.cpp`) — UDP socket layer, serializes/deserializes msgpack messages.
- **`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>`.
Optional components (`DhtProxyServer`, `DhtProxyClient`, `PeerDiscovery`) are compiled in only when the corresponding CMake options are enabled.
---
+12 -23
View File
@@ -21,11 +21,11 @@ Optionally, install RESTinio to enable the OpenDHT proxy client and server:
mkdir /usr/include/nonstd
wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp -O /usr/include/nonstd/expected.hpp
wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.9/restinio-0.7.9.tar.bz2
tar -xjf restinio-0.7.9.tar.bz2
cd restinio-0.7.9/dev
wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.3/restinio-0.7.3.tar.bz2
tar -xjf restinio-0.7.3.tar.bz2
cd restinio-0.7.3/dev
cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system .
make && make install
make -j2 && make install
```
To run openDHT on a different computer from the build computer, the following runtime dependencies are required:
@@ -48,11 +48,11 @@ Optionally, install RESTinio to enable the OpenDHT proxy client and server:
mkdir /usr/include/nonstd
wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp -O /usr/include/nonstd/expected.hpp
wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.9/restinio-0.7.9.tar.bz2
tar -xjf restinio-0.7.9.tar.bz2
cd restinio-0.7.9/dev
wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.3/restinio-0.7.3.tar.bz2
tar -xjf restinio-0.7.3.tar.bz2
cd restinio-0.7.3/dev
cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system .
make && make install
make -j2 && make install
```
### Fedora
@@ -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,21 +100,10 @@ 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
| Option | Default | Purpose |
|--------|---------|---------|
| `OPENDHT_TOOLS` | ON | CLI tools (dhtnode, dhtchat, …) |
| `OPENDHT_C` | OFF | C language bindings |
| `OPENDHT_PYTHON` | OFF | Python 3 Cython bindings |
| `OPENDHT_PROXY_SERVER` | OFF | REST API proxy server (requires RESTinio + llhttp) |
| `OPENDHT_PROXY_CLIENT` | OFF | REST API proxy client |
| `OPENDHT_PEER_DISCOVERY` | OFF | Multicast local peer discovery |
| `OPENDHT_PUSH_NOTIFICATIONS` | OFF | Push notification support |
| `OPENDHT_SANITIZE` | OFF | AddressSanitizer + stack protector |
## Windows/MSVC
Building on Windows with MSVC is supported with VCPKG and CMake.
+28 -42
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.0)
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
@@ -502,11 +501,7 @@ list (APPEND opendht_HEADERS
)
if (OPENDHT_PEER_DISCOVERY)
list (APPEND opendht_SOURCES
src/peer_discovery/mdns.cpp
src/peer_discovery/dns_sd.cpp
src/peer_discovery/mdns_transport.cpp
src/peer_discovery/peer_discovery.cpp)
list (APPEND opendht_SOURCES src/peer_discovery.cpp)
list (APPEND opendht_HEADERS include/opendht/peer_discovery.h)
add_definitions(-DOPENDHT_PEER_DISCOVERY)
endif()
@@ -593,7 +588,6 @@ if (MSVC)
simdutf::simdutf
wsock32
ws2_32
iphlpapi
)
if (OPENDHT_TOOLS)
target_link_libraries(opendht PUBLIC unofficial::readline-win32::readline)
@@ -677,7 +671,7 @@ else()
# System-specific linker pipelines
if (WIN32 AND MINGW) # MSYS2
message(STATUS "Build pipeline: Windows/MSYS2-MinGW")
target_link_libraries(opendht PUBLIC wsock32 ws2_32 iphlpapi)
target_link_libraries(opendht PUBLIC wsock32 ws2_32)
if(OPENDHT_PROXY_OPENSSL)
target_link_libraries(opendht PUBLIC Crypt32)
endif()
@@ -859,44 +853,36 @@ if (BUILD_TESTING)
endif()
# unit testing
list (APPEND test_FILES
tests/test_infohash.h
tests/test_infohash.cpp
tests/test_value.h
tests/test_value.cpp
tests/test_crypto.h
tests/test_crypto.cpp
tests/test_dhtrunner.h
tests/test_dhtrunner.cpp
tests/test_storage.h
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/infohashtester.h
tests/infohashtester.cpp
tests/valuetester.h
tests/valuetester.cpp
tests/cryptotester.h
tests/cryptotester.cpp
tests/dhtrunnertester.h
tests/dhtrunnertester.cpp
tests/storagetester.h
tests/storagetester.cpp
tests/threadpooltester.h
tests/threadpooltester.cpp
tests/opcachetester.h
tests/opcachetester.cpp
tests/valuecachetester.h
tests/valuecachetester.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
tests/test_http.h
tests/test_http.cpp
tests/test_dhtproxy.h
tests/test_dhtproxy.cpp
tests/httptester.h
tests/httptester.cpp
tests/dhtproxytester.h
tests/dhtproxytester.cpp
)
endif()
if (OPENDHT_PEER_DISCOVERY)
list (APPEND test_FILES
tests/test_mdns.h
tests/test_mdns.cpp
tests/test_peerdiscovery.h
tests/test_peerdiscovery.cpp
tests/peerdiscoverytester.h
tests/peerdiscoverytester.cpp
)
endif()
endif()
@@ -904,7 +890,7 @@ if (BUILD_TESTING)
# We keep using the common tests_runner.cpp (CppUnit registry based) and compile it with each test file.
foreach(test_src ${test_FILES})
if (test_src MATCHES ".*\\.cpp$")
get_filename_component(test_name ${test_src} NAME_WE) # e.g. test_infohash
get_filename_component(test_name ${test_src} NAME_WE) # e.g. infohashtester
set(target_name ${test_name})
add_executable(${target_name}
tests/tests_runner.cpp
+7 -9
View File
@@ -140,22 +140,20 @@ asyncio.run(dht_async_demo("unique_key"))
```
## Dependencies
Core dependencies:
- msgpack-c 1.2+, used for data serialization.
- GnuTLS 3.3+, used for cryptographic operations.
- Nettle 2.4+, a GnuTLS dependency for crypto.
- {fmt} 9.0+, for log formatting.
Optional dependencies for the REST API (DHT proxy client/server):
- (optional) restinio
- (optional) llhttp
- (optional) jsoncpp 1.7.4-3+
- (optional) simdutf
Build environment:
- (optional) restinio used for the REST API.
- (optional) llhttp used for the REST API.
- (optional) jsoncpp 1.7.4-3+, used for the REST API.
- Build tested with GCC 9+ (GNU/Linux, Windows with MinGW), Clang/LLVM (GNU/Linux, Android, macOS, iOS).
- Build tested with Microsoft Visual Studio 2019, 2022
## Contact
IRC: join us on Libera.chat at [`#opendht`](https://web.libera.chat/#opendht).
## License
Copyright (c) 2014-2026 Savoir-faire Linux Inc.
+15
View File
@@ -0,0 +1,15 @@
lib_LTLIBRARIES = libopendht-c.la
noinst_HEADERS = opendht_c.h
AM_CPPFLAGS = -DOPENDHT_C_BUILD -isystem @top_srcdir@/include @JsonCpp_CFLAGS@ @MsgPack_CFLAGS@
if OPENDHT_SHARED
AM_CPPFLAGS += -Dopendht_c_EXPORTS
endif
libopendht_c_la_LIBADD = $(LIBOBJS) ../src/.libs/libopendht.la
libopendht_c_la_SOURCES = \
opendht.cpp
nobase_include_HEADERS = \
opendht_c.h
+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,
+16
View File
@@ -0,0 +1,16 @@
dist_man1_MANS = dhtnode.1
noinst_HEADERS = \
Doxyfile
if HAVE_DOXYGEN
doxyfile.stamp: Doxyfile
$(DOXYGEN) Doxyfile
echo stamp > doxyfile.stamp
CLEANFILES = doxyfile.stamp
all-local: doxyfile.stamp
clean-local:
rm -rf $(top_srcdir)/doc/man $(top_srcdir)/doc/html
endif
+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
+6 -4
View File
@@ -23,15 +23,15 @@ struct Node;
*/
enum class NodeStatus {
Disconnected, // 0 nodes
Connecting, // bootstrap pending or 1+ nodes
Connecting, // 1+ nodes
Connected // 1+ good nodes
};
inline constexpr std::string_view
inline constexpr const char*
statusToStr(NodeStatus status)
{
return status == NodeStatus::Connected ? "connected"sv
: (status == NodeStatus::Connecting ? "connecting"sv : "disconnected"sv);
return status == NodeStatus::Connected ? "connected"
: (status == NodeStatus::Connecting ? "connecting" : "disconnected");
}
struct OPENDHT_PUBLIC NodeStats
@@ -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)>;
+4 -9
View File
@@ -214,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_ {};
};
@@ -267,14 +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;
@@ -690,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,
-1
View File
@@ -276,7 +276,6 @@ public:
void insertNode(const InfoHash&, const SockAddr&) override {}
void insertNode(const NodeExport&) override {}
std::pair<size_t, size_t> getStoreSize() const override { return {}; }
std::pair<size_t, size_t> getLocalStoreSize() const override { return {}; }
std::vector<NodeExport> exportNodes() const override { return {}; }
std::vector<ValuesExport> exportValues() const override { return {}; }
void importValues(const std::vector<ValuesExport>&) override {}
+2 -2
View File
@@ -425,7 +425,7 @@ private:
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);
}
@@ -478,7 +478,7 @@ private:
p.pack("exp");
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);
}
+26 -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,12 @@ public:
void getPublicAddress(std::function<void(std::vector<SockAddr>&&)>, sa_family_t af = AF_UNSPEC);
// securedht methods
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 +498,7 @@ public:
*/
time_point loop()
{
std::lock_guard lck(dht_mtx);
std::lock_guard<std::mutex> lck(dht_mtx);
return loop_();
}
@@ -565,16 +578,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 -8
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;
@@ -149,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,
@@ -161,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();
@@ -212,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);
@@ -340,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_;
+42 -71
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,20 +54,27 @@ public:
typedef typename T::iterator iterator;
typedef typename T::const_iterator const_iterator;
constexpr Hash() noexcept
: data_ {}
{}
constexpr Hash() noexcept { data_.fill(0); }
constexpr Hash(const uint8_t* h, size_t data_len)
: data_(fromBytes(h, 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))
{}
: data_()
{
if (hex.size() < 2 * N)
data_.fill(0);
else
fromString(hex.data());
}
Hash(const msgpack::object& o) { msgpack_unpack(o); }
@@ -80,21 +88,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 +100,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 +109,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,13 +118,6 @@ 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]; }
@@ -138,16 +127,15 @@ public:
* 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 +147,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 +159,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 +171,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 +183,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 +258,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_;
constexpr void fromString(const char*);
};
#define HASH_LEN 20u
@@ -294,30 +282,14 @@ 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)
constexpr 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 {
if (c >= 'a' and c <= 'f')
return 10 + c - 'a';
@@ -334,11 +306,10 @@ Hash<N>::fromString(std::string_view in)
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;
data_[i] = (high << 4) | low;
}
if (!valid)
return {};
return data;
data_.fill(0);
}
template<size_t N>
+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;
}
+52 -13
View File
@@ -3,13 +3,12 @@
#pragma once
#include "def.h"
#include "sockaddr.h"
#include "infohash.h"
#include "logger.h"
#include "sockaddr.h"
#include "utils.h"
#include <functional>
#include <memory>
#include <asio/steady_timer.hpp>
#include <thread>
namespace asio {
@@ -21,26 +20,66 @@ namespace dht {
class OPENDHT_PUBLIC PeerDiscovery
{
public:
using PeerDiscoveredCallback = std::function<void(const InfoHash&, SockAddr&&)>;
static constexpr in_port_t DEFAULT_PORT = 8888;
using ServiceDiscoveredCallback = std::function<void(msgpack::object&&, SockAddr&&)>;
PeerDiscovery(std::shared_ptr<asio::io_context> ioContext = {}, std::shared_ptr<Logger> logger = {});
PeerDiscovery(in_port_t port = DEFAULT_PORT,
std::shared_ptr<asio::io_context> ioContext = {},
std::shared_ptr<Logger> logger = {});
~PeerDiscovery();
void startDiscovery(NetId network, PeerDiscoveredCallback callback);
void startPublish(const InfoHash& nodeId, NetId network, in_port_t port);
/**
* startDiscovery - Keep Listening data from the sender until node is joinned
* or stop is called
*/
void startDiscovery(std::string_view type, ServiceDiscoveredCallback callback);
bool stopDiscovery();
bool stopPublish();
template<typename T>
void startDiscovery(std::string_view type, std::function<void(T&&, SockAddr&&)> cb)
{
startDiscovery(type, [cb](msgpack::object&& ob, SockAddr&& addr) { cb(ob.as<T>(), std::move(addr)); });
}
/**
* startPublish - Keeping sending data until node is joinned or stop is called
*/
void startPublish(std::string_view type, const msgpack::sbuffer& pack_buf);
void startPublish(sa_family_t domain, std::string_view type, const msgpack::sbuffer& pack_buf);
template<typename T>
void startPublish(std::string_view type, const T& object)
{
msgpack::sbuffer buf;
msgpack::pack(buf, object);
startPublish(type, buf);
}
/**
* Thread Stopper
*/
void stop();
/**
* Remove possible callBack to discovery
*/
bool stopDiscovery(std::string_view type);
/**
* Remove different serivce message to send
*/
bool stopPublish(std::string_view type);
bool stopPublish(sa_family_t domain, std::string_view type);
void connectivityChanged();
void stopConnectivityChanged();
private:
class Impl;
std::shared_ptr<Impl> impl_;
class DomainPeerDiscovery;
std::unique_ptr<DomainPeerDiscovery> peerDiscovery4_;
std::unique_ptr<DomainPeerDiscovery> peerDiscovery6_;
std::shared_ptr<asio::io_context> ioContext_;
std::thread ioRunner_;
std::thread ioRunnner_;
};
} // namespace dht
} // namespace dht
+23 -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,17 @@ public:
Value decrypt(const Value& v);
void findCertificate(const InfoHash& node, const std::function<void(const Sp<crypto::Certificate>)>& cb);
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);
Sp<crypto::Certificate> getCertificate(const InfoHash& node) const;
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 +164,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 +345,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();
+64 -103
View File
@@ -5,9 +5,9 @@ project(
'cython',
license: 'MIT',
license_files: 'LICENSE',
version: '4.3.0',
version: '3.7.0',
meson_version: '>=1.1.0',
default_options: ['cpp_std=c++20', 'warning_level=3'],
default_options: ['cpp_std=c++17', 'warning_level=3'],
)
gnutls = dependency('gnutls')
@@ -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')
@@ -123,37 +106,18 @@ if get_option('push_notifications').enabled()
add_project_arguments('-DOPENDHT_PUSH_NOTIFICATIONS', language: 'cpp')
endif
if get_option('peer_discovery').enabled()
opendht_src += [
'src/peer_discovery/mdns.cpp',
'src/peer_discovery/dns_sd.cpp',
'src/peer_discovery/mdns_transport.cpp',
'src/peer_discovery/peer_discovery.cpp',
]
opendht_src += 'src/peer_discovery.cpp'
add_project_arguments('-DOPENDHT_PEER_DISCOVERY', language: 'cpp')
if host_machine.system() == 'windows'
deps += meson.get_compiler('cpp').find_library('iphlpapi')
endif
endif
opendht = library(
'opendht',
opendht_src,
include_directories: [opendht_inc, opendht_interface_inc],
include_directories: opendht_inc,
dependencies: deps,
cpp_args: ['-DOPENDHT_BUILD', '-Dopendht_EXPORTS'],
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)
@@ -164,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',
@@ -186,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,
)
@@ -222,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
@@ -232,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()
@@ -257,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
@@ -268,86 +237,74 @@ if get_option('tests').enabled()
test_infohash = executable(
'test_infohash',
'tests/test_infohash.cpp',
'tests/infohashtester.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)
test_value = executable(
'test_value',
'tests/test_value.cpp',
'tests/valuetester.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/cryptotester.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/dhtrunnertester.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/threadpooltester.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)
if get_option('proxy_client').enabled() or get_option('proxy_server').enabled()
test_http = executable(
'test_http',
'tests/test_http.cpp',
'tests/httptester.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
if get_option('peer_discovery').enabled()
test_mdns = executable(
'test_mdns',
'tests/test_mdns.cpp',
'tests/tests_runner.cpp',
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('Mdns', test_mdns)
test_peerdiscovery = executable(
'test_peerdiscovery',
'tests/test_peerdiscovery.cpp',
'tests/peerdiscoverytester.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
@@ -355,9 +312,11 @@ if get_option('tests').enabled()
if get_option('proxy_server').enabled()
test_proxy = executable(
'test_dhtproxy',
'tests/test_dhtproxy.cpp',
'tests/dhtproxytester.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)
@@ -366,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}
+30
View File
@@ -0,0 +1,30 @@
if USE_CYTHON
noinst_HEADERS = \
opendht.pyx \
opendht_cpp.pxd \
pyproject.toml
PYTHON_INSTALL_RECORD = $(builddir)/install_record.txt
pybuild.stamp:
LDFLAGS="-L$(top_srcdir)/src/.libs" $(PYTHON) -m build .
echo stamp > pybuild.stamp
CLEANFILES = pybuild.stamp
all-local: pybuild.stamp
clean-local:
rm -rf $(builddir)/build $(builddir)/*.so $(PYTHON_INSTALL_RECORD)
install-exec-local:
$(PYTHON) -m pip install --no-deps --no-build-isolation . \
--root=$(DESTDIR)/ --prefix=$(prefix)
rm -rf $(builddir)/build
if HAVE_PIP
uninstall-local:
/usr/bin/yes | $(PIP) uninstall $(PACKAGE)
endif
endif
+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);
+82
View File
@@ -0,0 +1,82 @@
lib_LTLIBRARIES = libopendht.la
libopendht_la_CPPFLAGS = @CPPFLAGS@ -I$(top_srcdir)/include/opendht @Argon2_CFLAGS@ @JsonCpp_CFLAGS@ @MsgPack_CFLAGS@ @OpenSSL_CFLAGS@ @Fmt_CFLAGS@ @Llhttp_CFLAGS@ @Liburing_CFLAGS@ @simdutf_CFLAGS@
libopendht_la_LIBADD = @Argon2_LIBS@ @JsonCpp_LIBS@ @GnuTLS_LIBS@ @Nettle_LIBS@ @OpenSSL_LIBS@ @Fmt_LIBS@ @Llhttp_LIBS@ @Liburing_LIBS@ @simdutf_LIBS@
libopendht_la_LDFLAGS = @LDFLAGS@ -version-number @OPENDHT_MAJOR_VERSION@:@OPENDHT_MINOR_VERSION@:@OPENDHT_PATCH_VERSION@
libopendht_la_SOURCES = \
dht.cpp \
storage.h \
listener.h \
request.h \
search.h \
value_cache.h \
op_cache.h \
op_cache.cpp \
net.h \
parsed_message.h \
node_cache.cpp \
callbacks.cpp \
routing_table.cpp \
network_engine.cpp \
utils.cpp \
node.cpp \
value.cpp \
crypto.cpp \
securedht.cpp \
dhtrunner.cpp \
default_types.cpp \
log.cpp \
network_utils.cpp \
thread_pool.cpp
nobase_include_HEADERS = \
../include/opendht.h \
../include/opendht/def.h \
../include/opendht/dht.h \
../include/opendht/callbacks.h \
../include/opendht/node_cache.h \
../include/opendht/node_export.h \
../include/opendht/routing_table.h \
../include/opendht/network_engine.h \
../include/opendht/scheduler.h \
../include/opendht/rate_limiter.h \
../include/opendht/utils.h \
../include/opendht/sockaddr.h \
../include/opendht/infohash.h \
../include/opendht/node.h \
../include/opendht/value.h \
../include/opendht/crypto.h \
../include/opendht/securedht.h \
../include/opendht/dhtrunner.h \
../include/opendht/default_types.h \
../include/opendht/log.h \
../include/opendht/logger.h \
../include/opendht/network_utils.h \
../include/opendht/rng.h \
../include/opendht/thread_pool.h
if ENABLE_PROXY_SERVER
libopendht_la_SOURCES += dht_proxy_server.cpp
nobase_include_HEADERS += ../include/opendht/dht_proxy_server.h
endif
if ENABLE_PROXY_CLIENT
libopendht_la_SOURCES += dht_proxy_client.cpp
nobase_include_HEADERS += ../include/opendht/dht_proxy_client.h ../include/opendht/dht_interface.h
endif
libopendht_la_SOURCES += base64.h base64.cpp
if PROXY_CLIENT_OR_SERVER
libopendht_la_SOURCES += http.cpp compat/os_cert.cpp
nobase_include_HEADERS += ../include/opendht/proxy.h ../include/opendht/http.h compat/os_cert.h
endif
if ENABLE_PEER_DISCOVERY
libopendht_la_SOURCES += peer_discovery.cpp
nobase_include_HEADERS += ../include/opendht/peer_discovery.h
endif
if ENABLE_INDEXATION
libopendht_la_SOURCES += indexation/pht.cpp
nobase_include_HEADERS += ../include/opendht/indexation/pht.h
endif
-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
+52 -85
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))
@@ -1402,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
@@ -1882,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));
@@ -1893,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
@@ -2078,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;
}
@@ -2096,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++;
}
@@ -2256,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;
+44 -170
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,
@@ -893,16 +889,10 @@ Dht::search(const InfoHash& id,
sr = std::make_shared<Search>();
srs.emplace(id, sr);
} else {
for (auto it = srs.begin(); it != srs.end(); ++it) {
for (auto it = srs.begin(); it != srs.end();) {
auto& s = *it->second;
if ((s.done or s.expired) and s.announce.empty() and s.listeners.empty()) {
sr = it->second;
if (sr->nextSearchStep)
scheduler.cancel(sr->nextSearchStep);
if (sr->opExpirationJob)
scheduler.cancel(sr->opExpirationJob);
sr->clear();
srs.erase(it);
break;
}
}
@@ -911,11 +901,8 @@ Dht::search(const InfoHash& id,
logger_->error("[search {} IPv{}] Maximum number of searches reached!",
id.toString(),
(af == AF_INET) ? '4' : '6');
if (dcb)
dcb(false, {});
return {};
}
srs.emplace(id, sr);
}
sr->af = af;
sr->tid = search_id++;
@@ -1421,19 +1408,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 +1429,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 +1835,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 +1866,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 +1880,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 +2214,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 +2231,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 +2261,6 @@ void
Dht::startBootstrap()
{
stopBootstrap();
bootstrap_pending = !bootstrap_nodes.empty();
bootstrapJob = scheduler.add(scheduler.time(), std::bind(&Dht::bootstrap, this));
}
@@ -2317,7 +2268,6 @@ void
Dht::stopBootstrap()
{
scheduler.cancel(bootstrapJob);
bootstrap_pending = false;
bootstrap_period = BOOTSTRAP_PERIOD;
}
@@ -2363,9 +2313,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 +2324,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 +2338,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 +2354,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 +2457,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 +2613,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 +2625,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);
}
@@ -2773,10 +2651,9 @@ Dht::onAnnounce(Sp<Node> n,
net::DhtProtocolException::PUT_WRONG_TOKEN};
}
{
// We store a value only if we think we're part of the
// SEARCH_NODES nodes around the target id.
// We store a value only if we think we're part of the SEARCH_NODES nodes around the target id.
auto closest_nodes = buckets(node.getFamily()).findClosestNodes(hash, scheduler.time(), SEARCH_NODES);
if (closest_nodes.size() >= TARGET_NODES and hash.xorCmp(closest_nodes.back()->id, myid) < 0) {
if (closest_nodes.size() > TARGET_NODES and hash.xorCmp(closest_nodes.back()->id, myid) < 0) {
if (logger_)
logger_->warn("[node {}] Announce too far from the target. Dropping value.", node.toString());
return {};
@@ -2802,8 +2679,7 @@ Dht::onAnnounce(Sp<Node> n,
node.toString(),
v->id);
} else {
const auto& type = getType(lv->type);
if (type.editPolicy(hash, lv, vc, node.id, node.getAddr())) {
if (getType(lv->type).editPolicy(hash, lv, vc, node.id, node.getAddr())) {
if (logger_)
logger_->debug("[store {}] Editing {}", hash.toString(), vc->toString());
storageStore(hash, vc, created, node.getAddr());
@@ -2816,11 +2692,9 @@ Dht::onAnnounce(Sp<Node> n,
}
} else {
// Allow the value to be edited by the storage policy
const auto& type = getType(vc->type);
if (type.storePolicy(hash, vc, node.id, node.getAddr())) {
if (getType(vc->type).storePolicy(hash, vc, node.id, node.getAddr())) {
// if (logger_)
// logger_->d(hash, node.id, "[store %s] Storing %s", hash.toString().c_str(),
// std::to_string(vc->id).c_str());
// logger_->d(hash, node.id, "[store %s] Storing %s", hash.toString().c_str(), vc->id);
storageStore(hash, vc, created, node.getAddr());
} else {
if (logger_)
+54 -57
View File
@@ -195,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)
@@ -204,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();
}
@@ -219,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 {};
@@ -229,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 {};
@@ -239,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_) {
@@ -266,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_;
@@ -280,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;
@@ -297,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)
@@ -357,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);
@@ -382,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);
@@ -391,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();
@@ -419,7 +419,7 @@ 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) {
@@ -443,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_();
@@ -464,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);
@@ -480,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)
@@ -535,7 +535,7 @@ 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()) {
@@ -565,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();
@@ -640,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;
@@ -655,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;
}
@@ -701,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);
}
});
@@ -709,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();
@@ -724,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_;
@@ -761,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)); });
}
@@ -785,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()();
@@ -820,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_);
@@ -837,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
@@ -874,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);
@@ -929,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);
@@ -948,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())
@@ -979,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;
@@ -1015,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();
@@ -1097,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()))
@@ -1124,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) {
@@ -1153,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;
}
@@ -1186,7 +1186,7 @@ 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) {
@@ -1249,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;
@@ -1264,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);
@@ -1326,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;
@@ -1376,7 +1376,7 @@ 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::steady_timer>(httpContext_);
}
@@ -1422,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
+33 -31
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));
@@ -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,7 +408,7 @@ 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());
@@ -432,7 +432,7 @@ 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;
@@ -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());
@@ -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;
};
@@ -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)
@@ -817,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;
@@ -931,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];
@@ -945,7 +945,7 @@ 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;
@@ -963,7 +963,7 @@ DhtProxyServer::subscribe(restinio::request_handle_t request, restinio::router::
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;
};
@@ -1110,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())
@@ -1151,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.
@@ -1205,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,
@@ -1224,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");
@@ -1300,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();
@@ -1328,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);
}
}
@@ -1347,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;
@@ -1404,7 +1406,7 @@ DhtProxyServer::put(restinio::request_handle_t request, restinio::router::route_
sessionId = pVal["session_id"].asString();
topic = pVal["topic"].asString();
}
std::lock_guard lock(lockSearchPuts_);
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) {
@@ -1421,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;
}
}
@@ -1456,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;
}
}
@@ -1473,7 +1475,7 @@ 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;
};
+170 -114
View File
@@ -21,6 +21,16 @@ namespace dht {
using namespace std::literals;
constexpr auto PEER_DISCOVERY_DHT_SERVICE = "dht"sv;
struct NodeInsertionPack
{
dht::InfoHash nodeId;
in_port_t port;
dht::NetId net;
MSGPACK_DEFINE(nodeId, port, net)
};
DhtRunner::DhtRunner()
: dht_()
{
@@ -66,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)
@@ -89,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()) {
@@ -105,7 +110,6 @@ DhtRunner::run(const Config& config, Context&& context)
fmt::ptr(this),
port);
local4.setPort(port);
triedSavedPort = true;
}
}
if (inConfig >> port) {
@@ -115,7 +119,6 @@ DhtRunner::run(const Config& config, Context&& context)
fmt::ptr(this),
port);
local6.setPort(port);
triedSavedPort = true;
}
}
}
@@ -133,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) {
@@ -196,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));
}
@@ -204,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();
@@ -237,7 +230,7 @@ DhtRunner::run(const Config& config, Context&& context)
if (config.peer_discovery or config.peer_publish) {
#ifdef OPENDHT_PEER_DISCOVERY
peerDiscovery_ = context.peerDiscovery ? std::move(context.peerDiscovery)
: std::make_shared<PeerDiscovery>(nullptr, context.logger);
: std::make_shared<PeerDiscovery>();
#else
std::cerr << "Peer discovery requested but OpenDHT built without peer discovery support." << std::endl;
#endif
@@ -246,22 +239,32 @@ DhtRunner::run(const Config& config, Context&& context)
#ifdef OPENDHT_PEER_DISCOVERY
auto netId = config.dht_config.node_config.network;
if (config.peer_discovery) {
peerDiscovery_->startDiscovery(netId, [this](const InfoHash& nodeId, SockAddr&& address) {
if (nodeId != dht_->getNodeId())
bootstrap(nodeId, address);
});
peerDiscovery_->startDiscovery<NodeInsertionPack>(PEER_DISCOVERY_DHT_SERVICE,
[this, netId](NodeInsertionPack&& v, SockAddr&& addr) {
addr.setPort(v.port);
if (v.nodeId != dht_->getNodeId() && netId == v.net) {
bootstrap(v.nodeId, addr);
}
});
}
if (config.peer_publish) {
msgpack::sbuffer sbuf_node;
NodeInsertionPack adc;
adc.net = netId;
adc.nodeId = dht_->getNodeId();
if (auto socket = dht_->getSocket()) {
const auto& bound4 = socket->getBoundRef(AF_INET);
const auto& bound6 = socket->getBoundRef(AF_INET6);
const auto port4 = bound4 ? bound4.getPort() : 0;
const auto port6 = bound6 ? bound6.getPort() : 0;
if (port4 and port6 and port4 != port6) {
if (logger_)
logger_->error("Unable to publish peer discovery with different IPv4 and IPv6 ports");
} else if (const auto port = port4 ? port4 : port6) {
peerDiscovery_->startPublish(dht_->getNodeId(), netId, port);
// IPv4
if (const auto& bound4 = socket->getBoundRef(AF_INET)) {
adc.port = bound4.getPort();
msgpack::pack(sbuf_node, adc);
peerDiscovery_->startPublish(AF_INET, PEER_DISCOVERY_DHT_SERVICE, sbuf_node);
}
// IPv6
if (const auto& bound6 = socket->getBoundRef(AF_INET6)) {
adc.port = bound6.getPort();
sbuf_node.clear();
msgpack::pack(sbuf_node, adc);
peerDiscovery_->startPublish(AF_INET6, PEER_DISCOVERY_DHT_SERVICE, sbuf_node);
}
}
}
@@ -281,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) {
@@ -341,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_);
@@ -356,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();
@@ -375,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());
}
@@ -385,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;
@@ -395,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);
@@ -405,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);
@@ -415,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();
}
@@ -440,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();
@@ -449,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);
@@ -458,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();
@@ -467,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);
@@ -476,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();
@@ -485,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();
@@ -494,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();
@@ -503,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);
@@ -512,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);
}
@@ -520,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
@@ -551,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;
@@ -567,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);
}
@@ -575,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;
@@ -585,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>();
@@ -600,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 {};
@@ -654,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));
@@ -666,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));
}
@@ -686,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))
@@ -702,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);
}
@@ -737,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));
}
@@ -761,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)
@@ -783,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)
@@ -801,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);
@@ -823,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++;
@@ -837,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++;
@@ -851,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)
@@ -868,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)
@@ -891,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();
}
@@ -899,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();
}
@@ -907,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)
@@ -936,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)
@@ -950,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,
@@ -957,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)
@@ -981,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();
}
@@ -989,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); });
@@ -999,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();
}
@@ -1011,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;
@@ -1034,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)
@@ -1051,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); });
@@ -1061,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) {
@@ -1074,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
@@ -1085,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;
@@ -1114,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;
@@ -1141,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();
}
},
@@ -1154,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.");
@@ -1165,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);
}
@@ -1176,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_)
@@ -1189,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_)
@@ -1202,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_)
@@ -1218,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));
+37 -44
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())
@@ -239,7 +239,7 @@ Connection::~Connection()
void
Connection::close()
{
std::lock_guard lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_);
asio::error_code ec;
if (ssl_socket_) {
if (ssl_socket_->is_open())
@@ -302,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;
}
@@ -490,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());
@@ -576,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;
@@ -597,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;
@@ -629,7 +629,7 @@ 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_) {
std::weak_ptr<Connection> wthis = shared_from_this();
ssl_socket_
@@ -663,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); });
@@ -680,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); });
@@ -697,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); });
@@ -714,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); });
@@ -731,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); });
@@ -751,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;
@@ -830,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)
@@ -871,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()) {
@@ -907,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,
@@ -917,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
@@ -954,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()};
@@ -975,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));
@@ -1005,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));
@@ -1030,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));
@@ -1094,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);
}
@@ -1397,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;
@@ -1450,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());
@@ -1525,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;
@@ -1549,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;
@@ -1667,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;
+8 -14
View File
@@ -14,14 +14,15 @@ OpValueCache::onValuesAdded(const std::vector<Sp<Value>>& vals, const system_clo
auto viop = values.emplace(v->id, v);
if (viop.second) {
newValues.emplace_back(v);
} 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++;
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);
}
}
}
viop.first->second.updated = t;
}
@@ -210,13 +211,6 @@ SearchCache::cancelAll(const std::function<void(size_t)>& onCancel)
ops.clear();
}
void
SearchCache::clear()
{
ops.clear();
nextExpiration_ = time_point::max();
}
time_point
SearchCache::expire(const time_point& now, const std::function<void(size_t)>& onCancel)
{
@@ -224,7 +218,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);
-1
View File
@@ -164,7 +164,6 @@ public:
bool cancelListen(size_t gtoken, const time_point& now);
void cancelAll(const std::function<void(size_t)>& onCancel);
void clear();
time_point expire(const time_point& now, const std::function<void(size_t)>& onCancel);
time_point getExpiration() const { return nextExpiration_; }
+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();
+474
View File
@@ -0,0 +1,474 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "peer_discovery.h"
#include "network_utils.h"
#include "utils.h"
#ifdef __ANDROID__
#include "compat/getif_workaround_android.h"
#endif
#include <asio.hpp>
using namespace std::literals;
namespace dht {
// Organization-local Scope multicast
constexpr char MULTICAST_ADDRESS_IPV4[] = "239.192.0.1";
constexpr char MULTICAST_ADDRESS_IPV6[] = "ff08::101";
class PeerDiscovery::DomainPeerDiscovery
{
public:
DomainPeerDiscovery(asio::ip::udp domain,
in_port_t port,
Sp<asio::io_context> ioContext = {},
Sp<Logger> logger = {});
~DomainPeerDiscovery();
void startDiscovery(std::string_view type, ServiceDiscoveredCallback callback);
void startPublish(std::string_view type, const msgpack::sbuffer& pack_buf);
void stop();
bool stopDiscovery(std::string_view type);
bool stopPublish(std::string_view type);
void connectivityChanged();
void stopConnectivityChanged();
private:
Sp<Logger> logger_;
// dmtx_ for callbackmap_ and drunning_ (write)
std::mutex dmtx_;
// mtx_ for messages_ and lrunning (listen)
std::mutex mtx_;
std::shared_ptr<asio::io_context> ioContext_;
static constexpr dht::duration PeerDiscovery_PERIOD_MAX {std::chrono::minutes(1)};
static constexpr std::chrono::seconds PeerDiscovery_PERIOD {10};
asio::steady_timer peerDiscoveryTimer;
std::chrono::steady_clock::duration peerDiscovery_period {PeerDiscovery_PERIOD};
asio::ip::udp::socket sockFd_;
asio::ip::udp::endpoint sockAddrSend_;
std::array<char, 64 * 1024> receiveBuf_;
asio::ip::udp::endpoint receiveFrom_;
msgpack::sbuffer sbuf_;
std::map<std::string, msgpack::sbuffer, std::less<>> messages_;
std::map<std::string, ServiceDiscoveredCallback, std::less<>> callbackmap_;
bool lrunning_ {false};
bool drunning_ {false};
void loopListener();
void query(const asio::ip::udp::endpoint& peer);
void reloadMessages();
void stopDiscovery();
void stopPublish();
void publish(const asio::ip::udp::endpoint& peer);
void reDiscover();
};
PeerDiscovery::DomainPeerDiscovery::DomainPeerDiscovery(asio::ip::udp domain,
in_port_t port,
Sp<asio::io_context> ioContext,
Sp<Logger> logger)
: logger_(logger)
, ioContext_(ioContext)
, peerDiscoveryTimer(*ioContext_)
, sockFd_(*ioContext_, domain)
, sockAddrSend_(asio::ip::make_address(domain.family() == AF_INET ? MULTICAST_ADDRESS_IPV4 : MULTICAST_ADDRESS_IPV6),
port)
{
try {
sockFd_.set_option(asio::ip::multicast::join_group(sockAddrSend_.address()));
sockFd_.set_option(asio::ip::udp::socket::reuse_address(true));
sockFd_.bind({domain, port});
} catch (const std::exception& e) {
#ifdef __ANDROID__
if (domain.family() == AF_INET && strcmp(e.what(), "set_option: No such device") == 0) {
try {
sockFd_.set_option(asio::ip::udp::socket::reuse_address(true));
auto mc_interface = workaround::get_interface();
sockFd_.set_option(asio::ip::multicast::outbound_interface(mc_interface));
sockFd_.set_option(asio::ip::multicast::join_group(sockAddrSend_.address().to_v4(), mc_interface));
sockFd_.bind({domain, port});
} catch (const std::exception& e) {
if (logger_)
logger_->error("Unable to start peer discovery using android workaround: {}", e.what());
}
} else
#endif
if (logger_)
logger_->error("Unable to start peer discovery for {}: {}",
domain.family() == AF_INET ? "IPv4" : "IPv6",
e.what());
}
}
PeerDiscovery::DomainPeerDiscovery::~DomainPeerDiscovery()
{
stop();
sockFd_.close();
}
void
PeerDiscovery::DomainPeerDiscovery::startDiscovery(std::string_view type, ServiceDiscoveredCallback callback)
{
std::lock_guard<std::mutex> lck(dmtx_);
callbackmap_[std::string(type)] = callback;
if (not drunning_) {
drunning_ = true;
asio::post(*ioContext_, [this]() {
loopListener();
query(sockAddrSend_);
});
}
}
void
PeerDiscovery::DomainPeerDiscovery::loopListener()
{
std::lock_guard<std::mutex> lck(dmtx_);
if (not drunning_)
return;
sockFd_.async_receive_from(asio::buffer(receiveBuf_),
receiveFrom_,
[this](const asio::error_code& error, size_t bytes) {
if (error == asio::error::operation_aborted)
return;
if (error) {
if (logger_)
logger_->error("Error receiving message: {}", error.message());
}
try {
auto rcv = msgpack::unpack(receiveBuf_.data(), bytes);
msgpack::object obj = rcv.get();
if (obj.type == msgpack::type::STR) {
if (lrunning_ and obj.as<std::string_view>() == "q"sv)
publish(receiveFrom_);
} else if (obj.type == msgpack::type::MAP) {
for (unsigned i = 0; i < obj.via.map.size; i++) {
auto& o = obj.via.map.ptr[i];
if (o.key.type != msgpack::type::STR)
continue;
ServiceDiscoveredCallback cb;
{
std::lock_guard<std::mutex> lck(dmtx_);
if (drunning_) {
auto callback = callbackmap_.find(o.key.as<std::string_view>());
if (callback != callbackmap_.end())
cb = callback->second;
} else
return;
}
if (cb)
cb(std::move(o.val),
SockAddr {receiveFrom_.data(), (socklen_t) receiveFrom_.size()});
}
} else {
throw msgpack::type_error {};
}
} catch (const std::exception& e) {
if (logger_)
logger_->error("Error receiving packet: {}", e.what());
}
loopListener();
});
}
void
PeerDiscovery::DomainPeerDiscovery::query(const asio::ip::udp::endpoint& peer)
{
std::lock_guard<std::mutex> lck(dmtx_);
if (not drunning_)
return;
msgpack::sbuffer pbuf_request;
msgpack::pack(pbuf_request, "q");
sockFd_.async_send_to(asio::buffer(pbuf_request.data(), pbuf_request.size()),
peer,
[logger = logger_, peer](const asio::error_code& ec, size_t) {
if (ec and (ec != asio::error::operation_aborted) and logger)
logger->warn("Error sending packet to: {} with err: {}",
peer.address().to_string(),
ec.message());
});
}
void
PeerDiscovery::DomainPeerDiscovery::publish(const asio::ip::udp::endpoint& peer)
{
std::lock_guard<std::mutex> lck(mtx_);
if (not lrunning_)
return;
sockFd_.async_send_to(asio::buffer((const void*) sbuf_.data(), sbuf_.size()),
peer,
[logger = logger_, peer](const asio::error_code& ec, size_t) {
if (ec and (ec != asio::error::operation_aborted) and logger)
logger->warn("Error sending packet to: {} with err: {}",
peer.address().to_string(),
ec.message());
});
}
void
PeerDiscovery::DomainPeerDiscovery::startPublish(std::string_view type, const msgpack::sbuffer& pack_buf)
{
msgpack::sbuffer pack_buf_c(pack_buf.size());
pack_buf_c.write(pack_buf.data(), pack_buf.size());
std::lock_guard<std::mutex> lck(mtx_);
messages_[std::string(type)] = std::move(pack_buf_c);
reloadMessages();
lrunning_ = true;
asio::post(*ioContext_, [this]() { publish(sockAddrSend_); });
}
bool
PeerDiscovery::DomainPeerDiscovery::stopDiscovery(std::string_view type)
{
std::lock_guard<std::mutex> lck(dmtx_);
auto it = callbackmap_.find(type);
if (it != callbackmap_.end()) {
callbackmap_.erase(it);
if (callbackmap_.empty())
stopDiscovery();
return true;
}
return false;
}
bool
PeerDiscovery::DomainPeerDiscovery::stopPublish(std::string_view type)
{
std::lock_guard<std::mutex> lck(mtx_);
auto it = messages_.find(type);
if (it != messages_.end()) {
messages_.erase(it);
if (messages_.empty())
stopPublish();
else
reloadMessages();
return true;
}
return false;
}
void
PeerDiscovery::DomainPeerDiscovery::stopDiscovery()
{
drunning_ = false;
}
void
PeerDiscovery::DomainPeerDiscovery::stopPublish()
{
lrunning_ = false;
}
void
PeerDiscovery::DomainPeerDiscovery::stop()
{
{
std::lock_guard<std::mutex> lck(dmtx_);
stopDiscovery();
}
{
std::lock_guard<std::mutex> lck(mtx_);
stopPublish();
}
}
void
PeerDiscovery::DomainPeerDiscovery::reloadMessages()
{
sbuf_.clear();
msgpack::packer<msgpack::sbuffer> pk(&sbuf_);
pk.pack_map(messages_.size());
for (const auto& m : messages_) {
pk.pack(m.first);
sbuf_.write(m.second.data(), m.second.size());
}
}
void
PeerDiscovery::DomainPeerDiscovery::reDiscover()
{
asio::error_code ec;
sockFd_.set_option(asio::ip::multicast::join_group(sockAddrSend_.address()), ec);
if (ec and logger_)
logger_->warn("Unable to multicast on {}: {}", sockAddrSend_.address().to_string(), ec.message());
query(sockAddrSend_);
}
void
PeerDiscovery::DomainPeerDiscovery::connectivityChanged()
{
asio::post(*ioContext_, [this]() {
reDiscover();
publish(sockAddrSend_);
});
if (logger_)
logger_->debug("PeerDiscovery: connectivity changed");
if (peerDiscovery_period == PeerDiscovery_PERIOD_MAX) {
peerDiscovery_period = PeerDiscovery_PERIOD;
} else {
peerDiscoveryTimer.expires_after(peerDiscovery_period);
peerDiscoveryTimer.async_wait([this](const asio::error_code& ec) {
if (ec == asio::error::operation_aborted)
return;
connectivityChanged();
});
peerDiscovery_period = std::min(peerDiscovery_period * 2, PeerDiscovery_PERIOD_MAX);
}
}
void
PeerDiscovery::DomainPeerDiscovery::stopConnectivityChanged()
{
peerDiscoveryTimer.cancel();
peerDiscovery_period = PeerDiscovery_PERIOD;
}
PeerDiscovery::PeerDiscovery(in_port_t port, Sp<asio::io_context> ioContext, Sp<Logger> logger)
{
if (not ioContext) {
ioContext = std::make_shared<asio::io_context>();
ioContext_ = ioContext;
ioRunnner_ = std::thread([logger, ioContext] {
try {
if (logger)
logger->d("[peerdiscovery] starting io_context");
auto work = asio::make_work_guard(*ioContext);
ioContext->run();
if (logger)
logger->d("[peerdiscovery] io_context stopped");
} catch (const std::exception& ex) {
if (logger)
logger->e("[peerdiscovery] run error: %s", ex.what());
}
});
}
try {
peerDiscovery4_.reset(new DomainPeerDiscovery(asio::ip::udp::v4(), port, ioContext, logger));
} catch (const std::exception& e) {
if (logger)
logger->e("[peerdiscovery] Unable to start IPv4: %s", e.what());
}
try {
peerDiscovery6_.reset(new DomainPeerDiscovery(asio::ip::udp::v6(), port, ioContext, logger));
} catch (const std::exception& e) {
if (logger)
logger->e("[peerdiscovery] Unable to start IPv6: %s", e.what());
}
}
PeerDiscovery::~PeerDiscovery()
{
stop();
if (ioContext_)
ioContext_->stop();
if (ioRunnner_.joinable())
ioRunnner_.join();
}
void
PeerDiscovery::startDiscovery(std::string_view type, ServiceDiscoveredCallback callback)
{
if (peerDiscovery4_)
peerDiscovery4_->startDiscovery(type, callback);
if (peerDiscovery6_)
peerDiscovery6_->startDiscovery(type, callback);
}
void
PeerDiscovery::startPublish(std::string_view type, const msgpack::sbuffer& pack_buf)
{
if (peerDiscovery4_)
peerDiscovery4_->startPublish(type, pack_buf);
if (peerDiscovery6_)
peerDiscovery6_->startPublish(type, pack_buf);
}
void
PeerDiscovery::startPublish(sa_family_t domain, std::string_view type, const msgpack::sbuffer& pack_buf)
{
if (domain == AF_INET) {
if (peerDiscovery4_)
peerDiscovery4_->startPublish(type, pack_buf);
} else if (domain == AF_INET6) {
if (peerDiscovery6_)
peerDiscovery6_->startPublish(type, pack_buf);
}
}
void
PeerDiscovery::stop()
{
if (peerDiscovery4_)
peerDiscovery4_->stop();
if (peerDiscovery6_)
peerDiscovery6_->stop();
}
bool
PeerDiscovery::stopDiscovery(std::string_view type)
{
bool stopped4 = peerDiscovery4_ and peerDiscovery4_->stopDiscovery(type);
bool stopped6 = peerDiscovery6_ and peerDiscovery6_->stopDiscovery(type);
return stopped4 or stopped6;
}
bool
PeerDiscovery::stopPublish(std::string_view type)
{
bool stopped4 = peerDiscovery4_ and peerDiscovery4_->stopPublish(type);
bool stopped6 = peerDiscovery6_ and peerDiscovery6_->stopPublish(type);
return stopped4 or stopped6;
}
bool
PeerDiscovery::stopPublish(sa_family_t domain, std::string_view type)
{
if (domain == AF_INET) {
return peerDiscovery4_ and peerDiscovery4_->stopPublish(type);
} else if (domain == AF_INET6) {
return peerDiscovery6_ and peerDiscovery6_->stopPublish(type);
}
return false;
}
void
PeerDiscovery::connectivityChanged()
{
if (peerDiscovery4_)
peerDiscovery4_->connectivityChanged();
if (peerDiscovery6_)
peerDiscovery6_->connectivityChanged();
}
void
PeerDiscovery::stopConnectivityChanged()
{
if (peerDiscovery4_)
peerDiscovery4_->stopConnectivityChanged();
if (peerDiscovery6_)
peerDiscovery6_->stopConnectivityChanged();
}
} /* namespace dht */
-504
View File
@@ -1,504 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "dns_sd.h"
#include <algorithm>
#include <charconv>
#include <map>
namespace dht::mdns::dns_sd {
namespace {
constexpr std::string_view SERVICE_NAME = "_opendht._udp.local.";
constexpr std::string_view SERVICE_ENUMERATION_NAME = "_services._dns-sd._udp.local.";
constexpr std::string_view SERVICE_SUFFIX = "._opendht._udp.local.";
constexpr std::string_view HOST_SUFFIX = ".local.";
std::vector<uint8_t>
txtString(std::string value)
{
return {value.begin(), value.end()};
}
std::string
asciiLower(std::string_view value)
{
std::string lower;
lower.reserve(value.size());
for (const auto character : value) {
if (character >= 'A' and character <= 'Z')
lower.push_back(static_cast<char>(character + ('a' - 'A')));
else
lower.push_back(character);
}
return lower;
}
void
validateService(const Service& service)
{
if (not service.nodeId)
throw ParseError("DNS-SD service requires a node ID");
if (service.port == 0)
throw ParseError("DNS-SD service requires a port");
if (service.addresses.empty())
throw ParseError("DNS-SD service requires an address");
}
Message
makeAdvertisement(const Service& service, uint32_t serviceTtl, uint32_t hostTtl)
{
validateService(service);
const auto instance = instanceName(service.nodeId);
const auto host = hostName(service.nodeId);
Message message;
message.response = true;
message.authoritative = true;
message.answers.push_back({serviceName(), Type::PTR, CLASS_IN, false, serviceTtl, PtrData {instance}});
message.additionals.push_back({
instance, Type::SRV, CLASS_IN, true, hostTtl, SrvData {0, 0, service.port, host}
});
TxtData txt;
txt.strings.emplace_back(txtString("txtvers=1"));
txt.strings.emplace_back(txtString("id=" + service.nodeId.toString()));
txt.strings.emplace_back(txtString("net=" + std::to_string(service.network)));
message.additionals.push_back({instance, Type::TXT, CLASS_IN, true, serviceTtl, std::move(txt)});
for (const auto& address : service.addresses) {
std::visit(
[&](const auto& data) {
using Data = std::decay_t<decltype(data)>;
constexpr auto type = std::is_same_v<Data, AData> ? Type::A : Type::AAAA;
message.additionals.push_back({host, type, CLASS_IN, true, hostTtl, data});
},
address);
}
return message;
}
std::optional<InfoHash>
nodeIdFromInstance(const Name& instance)
{
const auto& labels = instance.labels();
const auto service = serviceName();
const auto& serviceLabels = service.labels();
if (labels.size() != serviceLabels.size() + 1
or not std::equal(labels.begin() + 1, labels.end(), serviceLabels.begin(), serviceLabels.end()))
return {};
if (labels.front().size() != 40)
return {};
const InfoHash nodeId {labels.front()};
if (not nodeId)
return {};
return nodeId;
}
struct TxtValues
{
InfoHash nodeId;
NetId network {};
};
std::optional<TxtValues>
parseTxt(const TxtData& txt)
{
std::map<std::string, std::string> values;
for (const auto& raw : txt.strings) {
const std::string_view string {reinterpret_cast<const char*>(raw.data()), raw.size()};
const auto separator = string.find('=');
if (separator == std::string_view::npos or separator == 0)
continue;
const auto keyView = string.substr(0, separator);
if (std::any_of(keyView.begin(), keyView.end(), [](char value) {
return value < 0x20 or value > 0x7e or value == '=';
}))
continue;
const auto key = asciiLower(keyView);
if ((key == "txtvers" or key == "id" or key == "net") and values.count(key))
return {};
values.emplace(key, std::string(string.substr(separator + 1)));
}
if (values.size() < 3 or values["txtvers"] != "1" or values["id"].size() != 40)
return {};
TxtValues parsed;
parsed.nodeId = InfoHash {values["id"]};
if (not parsed.nodeId)
return {};
const auto& network = values["net"];
const auto result = std::from_chars(network.data(), network.data() + network.size(), parsed.network);
if (result.ec != std::errc {} or result.ptr != network.data() + network.size())
return {};
return parsed;
}
template<typename Callback>
void
forEachRecord(const Message& message, Callback&& callback)
{
for (const auto& record : message.answers)
callback(record);
for (const auto& record : message.authorities)
callback(record);
for (const auto& record : message.additionals)
callback(record);
}
bool
matches(const Question& question, const ResourceRecord& record)
{
return question.name == record.name and (question.type == Type::ANY or question.type == record.type)
and (question.classCode == CLASS_ANY or question.classCode == record.classCode);
}
bool
sameRecord(const ResourceRecord& lhs, const ResourceRecord& rhs)
{
return lhs.name == rhs.name and lhs.type == rhs.type and lhs.classCode == rhs.classCode and lhs.data == rhs.data;
}
bool
isKnownAnswer(const Message& query, const ResourceRecord& record)
{
return std::any_of(query.answers.begin(), query.answers.end(), [&](const ResourceRecord& known) {
return sameRecord(known, record) and known.ttl > record.ttl / 2;
});
}
} // namespace
class Cache::Impl
{
public:
explicit Impl(Now now)
: now_(std::move(now))
{}
struct Entry
{
ResourceRecord record;
Clock::time_point expires;
};
std::vector<Service> update(const Message& message)
{
expire();
std::vector<const ResourceRecord*> records;
forEachRecord(message, [&](const ResourceRecord& record) { records.push_back(&record); });
std::vector<const ResourceRecord*> flushed;
for (const auto* record : records) {
if (not record->cacheFlush)
continue;
const auto alreadyFlushed = std::any_of(flushed.begin(), flushed.end(), [&](const ResourceRecord* other) {
return other->name == record->name and other->type == record->type
and other->classCode == record->classCode;
});
if (not alreadyFlushed) {
flushed.push_back(record);
entries_.erase(std::remove_if(entries_.begin(),
entries_.end(),
[&](const Entry& entry) {
return entry.record.name == record->name
and entry.record.type == record->type
and entry.record.classCode == record->classCode;
}),
entries_.end());
}
}
for (const auto* record : records) {
if (record->ttl == 0) {
entries_.erase(std::remove_if(entries_.begin(),
entries_.end(),
[&](const Entry& entry) { return sameRecord(entry.record, *record); }),
entries_.end());
continue;
}
const auto expires = now_() + std::chrono::seconds(record->ttl);
const auto existing = std::find_if(entries_.begin(), entries_.end(), [&](const Entry& entry) {
return sameRecord(entry.record, *record);
});
if (existing == entries_.end())
entries_.push_back({*record, expires});
else {
existing->record = *record;
existing->expires = expires;
}
}
auto current = collect();
std::vector<Service> discovered;
for (const auto& service : current) {
if (std::find_if(emitted_.begin(),
emitted_.end(),
[&](const Service& previous) { return equal(previous, service); })
== emitted_.end())
discovered.push_back(service);
}
emitted_ = std::move(current);
return discovered;
}
std::vector<Service> services()
{
expire();
auto current = collect();
emitted_ = current;
return current;
}
void clear()
{
entries_.clear();
emitted_.clear();
}
private:
static bool equal(const Service& lhs, const Service& rhs)
{
return lhs.nodeId == rhs.nodeId and lhs.network == rhs.network and lhs.port == rhs.port
and lhs.addresses == rhs.addresses;
}
void expire()
{
const auto now = now_();
entries_.erase(std::remove_if(entries_.begin(),
entries_.end(),
[&](const Entry& entry) { return entry.expires <= now; }),
entries_.end());
}
std::vector<Service> collect() const
{
std::vector<Service> result;
for (const auto& entry : entries_) {
if (entry.record.type != Type::PTR or entry.record.classCode != CLASS_IN
or entry.record.name != serviceName())
continue;
const auto* ptr = std::get_if<PtrData>(&entry.record.data);
if (not ptr)
continue;
Message message;
message.response = true;
message.answers.push_back(entry.record);
for (const auto& additional : entries_) {
if (&additional != &entry and additional.record.type != Type::PTR)
message.additionals.push_back(additional.record);
}
const auto service = resolve(message);
if (service and std::none_of(result.begin(), result.end(), [&](const Service& existing) {
return equal(existing, *service);
}))
result.push_back(*service);
}
return result;
}
Now now_;
std::vector<Entry> entries_;
std::vector<Service> emitted_;
};
Cache::Cache(Now now)
: impl_(std::make_unique<Impl>(std::move(now)))
{}
Cache::~Cache() = default;
Cache::Cache(Cache&&) noexcept = default;
Cache& Cache::operator=(Cache&&) noexcept = default;
std::vector<Service>
Cache::update(const Message& message)
{
return impl_->update(message);
}
std::vector<Service>
Cache::services()
{
return impl_->services();
}
void
Cache::clear()
{
impl_->clear();
}
Name
serviceName()
{
return Name::fromString(SERVICE_NAME);
}
Name
instanceName(const InfoHash& nodeId)
{
if (not nodeId)
throw ParseError("DNS-SD instance requires a node ID");
return Name::fromString(nodeId.toString() + std::string(SERVICE_SUFFIX));
}
Name
hostName(const InfoHash& nodeId)
{
if (not nodeId)
throw ParseError("mDNS host requires a node ID");
return Name::fromString(nodeId.toString() + std::string(HOST_SUFFIX));
}
Message
announcement(const Service& service)
{
return makeAdvertisement(service, SERVICE_RECORD_TTL, HOST_RECORD_TTL);
}
Message
goodbye(const Service& service)
{
return makeAdvertisement(service, 0, 0);
}
Message
browseQuery()
{
Message query;
query.questions.push_back({serviceName(), Type::PTR, CLASS_IN, false});
return query;
}
std::optional<Message>
respond(const Message& query, const Service& service)
{
if (query.response)
return {};
const auto advertisement = announcement(service);
Message response;
response.id = query.id;
response.response = true;
response.authoritative = true;
const ResourceRecord serviceType {
Name::fromString(SERVICE_ENUMERATION_NAME),
Type::PTR,
CLASS_IN,
false,
SERVICE_RECORD_TTL,
PtrData {serviceName()},
};
auto addMatching = [&](const ResourceRecord& record) {
const auto question = std::find_if(query.questions.begin(), query.questions.end(), [&](const Question& q) {
return matches(q, record);
});
if (question == query.questions.end() or (not question->unicastResponse and isKnownAnswer(query, record)))
return;
if (std::find_if(response.answers.begin(),
response.answers.end(),
[&](const ResourceRecord& answer) { return sameRecord(answer, record); })
== response.answers.end())
response.answers.push_back(record);
};
addMatching(serviceType);
for (const auto& record : advertisement.answers)
addMatching(record);
for (const auto& record : advertisement.additionals)
addMatching(record);
if (response.answers.empty())
return {};
const auto metaOnly = std::all_of(response.answers.begin(), response.answers.end(), [&](const auto& answer) {
return sameRecord(answer, serviceType);
});
if (metaOnly)
return response;
response.additionals = advertisement.additionals;
response.additionals.erase(std::remove_if(response.additionals.begin(),
response.additionals.end(),
[&](const ResourceRecord& record) {
return std::any_of(response.answers.begin(),
response.answers.end(),
[&](const ResourceRecord& answer) {
return sameRecord(answer, record);
});
}),
response.additionals.end());
return response;
}
std::optional<Service>
resolve(const Message& message)
{
std::vector<Name> instances;
forEachRecord(message, [&](const ResourceRecord& record) {
if (record.type == Type::PTR and record.classCode == CLASS_IN and record.name == serviceName()) {
if (const auto ptr = std::get_if<PtrData>(&record.data))
instances.push_back(ptr->name);
}
});
if (instances.size() != 1)
return {};
const auto nodeId = nodeIdFromInstance(instances.front());
if (not nodeId)
return {};
const auto expectedHost = hostName(*nodeId);
const SrvData* srv = nullptr;
const TxtData* txt = nullptr;
std::vector<AddressData> addresses;
auto duplicate = false;
forEachRecord(message, [&](const ResourceRecord& record) {
if (record.classCode != CLASS_IN)
return;
if (record.name == instances.front()) {
if (record.type == Type::SRV) {
if (srv)
duplicate = true;
else
srv = std::get_if<SrvData>(&record.data);
} else if (record.type == Type::TXT) {
if (txt)
duplicate = true;
else
txt = std::get_if<TxtData>(&record.data);
}
} else if (record.name == expectedHost) {
if (record.type == Type::A) {
if (const auto data = std::get_if<AData>(&record.data))
addresses.emplace_back(*data);
} else if (record.type == Type::AAAA) {
if (const auto data = std::get_if<AaaaData>(&record.data))
addresses.emplace_back(*data);
}
}
});
if (duplicate or not srv or not txt or srv->priority != 0 or srv->weight != 0 or srv->port == 0
or srv->target != expectedHost or addresses.empty())
return {};
const auto txtValues = parseTxt(*txt);
if (not txtValues or txtValues->nodeId != *nodeId)
return {};
Service service;
service.nodeId = *nodeId;
service.network = txtValues->network;
service.port = srv->port;
for (auto& address : addresses) {
if (std::find(service.addresses.begin(), service.addresses.end(), address) == service.addresses.end())
service.addresses.emplace_back(std::move(address));
}
return service;
}
} // namespace dht::mdns::dns_sd
-61
View File
@@ -1,61 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include "mdns.h"
#include "opendht/infohash.h"
#include "opendht/utils.h"
#include <chrono>
#include <functional>
#include <memory>
#include <optional>
namespace dht::mdns::dns_sd {
constexpr uint32_t HOST_RECORD_TTL = 120;
constexpr uint32_t SERVICE_RECORD_TTL = 4500;
using AddressData = std::variant<AData, AaaaData>;
struct Service
{
InfoHash nodeId;
NetId network {};
in_port_t port {};
std::vector<AddressData> addresses;
};
class OPENDHT_PUBLIC Cache
{
public:
using Clock = std::chrono::steady_clock;
using Now = std::function<Clock::time_point()>;
explicit Cache(Now now = [] { return Clock::now(); });
~Cache();
Cache(Cache&&) noexcept;
Cache& operator=(Cache&&) noexcept;
std::vector<Service> update(const Message& message);
std::vector<Service> services();
void clear();
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
OPENDHT_PUBLIC Name serviceName();
OPENDHT_PUBLIC Name instanceName(const InfoHash& nodeId);
OPENDHT_PUBLIC Name hostName(const InfoHash& nodeId);
OPENDHT_PUBLIC Message announcement(const Service& service);
OPENDHT_PUBLIC Message goodbye(const Service& service);
OPENDHT_PUBLIC Message browseQuery();
OPENDHT_PUBLIC std::optional<Message> respond(const Message& query, const Service& service);
OPENDHT_PUBLIC std::optional<Service> resolve(const Message& message);
} // namespace dht::mdns::dns_sd
-563
View File
@@ -1,563 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "mdns.h"
#include <algorithm>
#include <cctype>
#include <limits>
#include <map>
#include <unordered_map>
namespace dht::mdns {
namespace {
constexpr uint16_t FLAG_RESPONSE = 0x8000;
constexpr uint16_t FLAG_AUTHORITATIVE = 0x0400;
constexpr uint16_t FLAG_TRUNCATED = 0x0200;
constexpr uint16_t CLASS_TOP_BIT = 0x8000;
constexpr uint16_t CLASS_MASK = 0x7fff;
char
asciiLower(char value)
{
const auto byte = static_cast<unsigned char>(value);
return static_cast<char>(std::tolower(byte));
}
void
validateLabels(const std::vector<std::string>& labels)
{
size_t wireSize = 0;
for (const auto& label : labels) {
if (label.empty() or label.size() > 63)
throw ParseError("invalid DNS label length");
wireSize += label.size() + 1;
if (wireSize > 255)
throw ParseError("DNS name is too long");
}
}
std::string
suffixKey(const std::vector<std::string>& labels, size_t first)
{
std::string key;
for (auto index = first; index < labels.size(); ++index) {
key.push_back(static_cast<char>(labels[index].size()));
key.append(labels[index]);
}
key.push_back('\0');
return key;
}
class Writer
{
public:
void write8(uint8_t value)
{
ensure(1);
packet_.push_back(value);
}
void write16(uint16_t value)
{
ensure(2);
packet_.push_back(static_cast<uint8_t>(value >> 8));
packet_.push_back(static_cast<uint8_t>(value));
}
void write32(uint32_t value)
{
write16(static_cast<uint16_t>(value >> 16));
write16(static_cast<uint16_t>(value));
}
template<size_t Size>
void write(const std::array<uint8_t, Size>& bytes)
{
write(bytes.data(), bytes.size());
}
void write(const uint8_t* bytes, size_t size)
{
ensure(size);
packet_.insert(packet_.end(), bytes, bytes + size);
}
void writeName(const Name& name)
{
const auto& labels = name.labels();
for (size_t index = 0; index < labels.size(); ++index) {
const auto key = suffixKey(labels, index);
if (const auto found = suffixes_.find(key); found != suffixes_.end()) {
write16(static_cast<uint16_t>(0xc000 | found->second));
return;
}
if (packet_.size() < 0x4000)
suffixes_.emplace(key, static_cast<uint16_t>(packet_.size()));
write8(static_cast<uint8_t>(labels[index].size()));
write(reinterpret_cast<const uint8_t*>(labels[index].data()), labels[index].size());
}
write8(0);
}
size_t size() const noexcept { return packet_.size(); }
void patch16(size_t offset, uint16_t value)
{
if (offset + 2 > packet_.size())
throw ParseError("invalid DNS packet patch offset");
packet_[offset] = static_cast<uint8_t>(value >> 8);
packet_[offset + 1] = static_cast<uint8_t>(value);
}
Packet take() { return std::move(packet_); }
private:
void ensure(size_t additional) const
{
if (additional > MAX_PACKET_SIZE - packet_.size())
throw ParseError("mDNS packet exceeds maximum size");
}
Packet packet_;
std::unordered_map<std::string, uint16_t> suffixes_;
};
class Reader
{
public:
Reader(const uint8_t* packet, size_t size)
: packet_(packet)
, size_(size)
{}
uint8_t read8()
{
require(1);
return packet_[position_++];
}
uint16_t read16()
{
const auto high = read8();
const auto low = read8();
return static_cast<uint16_t>((high << 8) | low);
}
uint32_t read32() { return (static_cast<uint32_t>(read16()) << 16) | read16(); }
Name readName()
{
std::vector<std::string> labels;
std::vector<size_t> visited;
auto cursor = position_;
auto jumped = false;
size_t wireSize = 0;
while (true) {
if (cursor >= size_)
throw ParseError("truncated DNS name");
if (std::find(visited.begin(), visited.end(), cursor) != visited.end())
throw ParseError("DNS compression pointer loop");
visited.push_back(cursor);
const auto length = packet_[cursor];
if ((length & 0xc0) == 0xc0) {
if (cursor + 1 >= size_)
throw ParseError("truncated DNS compression pointer");
const auto pointer = static_cast<size_t>(((length & 0x3f) << 8) | packet_[cursor + 1]);
if (pointer >= cursor)
throw ParseError("DNS compression pointer does not point backwards");
if (not jumped)
position_ = cursor + 2;
cursor = pointer;
jumped = true;
continue;
}
if (length & 0xc0)
throw ParseError("invalid DNS label prefix");
++cursor;
if (length == 0) {
if (not jumped)
position_ = cursor;
break;
}
if (length > 63 or length > size_ - cursor)
throw ParseError("invalid or truncated DNS label");
wireSize += length + 1;
if (wireSize > 255)
throw ParseError("DNS name is too long");
labels.emplace_back(reinterpret_cast<const char*>(packet_ + cursor), length);
cursor += length;
if (not jumped)
position_ = cursor;
}
return Name {std::move(labels)};
}
std::vector<uint8_t> readBytes(size_t size)
{
require(size);
std::vector<uint8_t> bytes(packet_ + position_, packet_ + position_ + size);
position_ += size;
return bytes;
}
size_t position() const noexcept { return position_; }
void setPosition(size_t position)
{
if (position > size_)
throw ParseError("invalid DNS packet position");
position_ = position;
}
private:
void require(size_t count) const
{
if (count > size_ - position_)
throw ParseError("truncated DNS packet");
}
const uint8_t* packet_;
size_t size_;
size_t position_ {};
};
uint16_t
checkedCount(size_t count)
{
if (count > std::numeric_limits<uint16_t>::max())
throw ParseError("too many DNS records");
return static_cast<uint16_t>(count);
}
void
encodeQuestion(Writer& writer, const Question& question)
{
writer.writeName(question.name);
writer.write16(static_cast<uint16_t>(question.type));
writer.write16(static_cast<uint16_t>(question.classCode | (question.unicastResponse ? CLASS_TOP_BIT : 0)));
}
void
encodeNsec(Writer& writer, const NsecData& nsec)
{
writer.writeName(nsec.nextDomain);
std::map<uint8_t, std::array<uint8_t, 32>> windows;
std::map<uint8_t, uint8_t> lengths;
for (const auto type : nsec.types) {
const auto value = static_cast<uint16_t>(type);
const auto window = static_cast<uint8_t>(value >> 8);
const auto low = static_cast<uint8_t>(value);
const auto byte = static_cast<uint8_t>(low / 8);
const auto bit = static_cast<uint8_t>(low % 8);
windows[window][byte] |= static_cast<uint8_t>(1u << (7 - bit));
lengths[window] = std::max<uint8_t>(lengths[window], byte + 1);
}
for (const auto& [window, bitmap] : windows) {
writer.write8(window);
writer.write8(lengths[window]);
writer.write(bitmap.data(), lengths[window]);
}
}
void
encodeRecord(Writer& writer, const ResourceRecord& record)
{
writer.writeName(record.name);
writer.write16(static_cast<uint16_t>(record.type));
writer.write16(static_cast<uint16_t>(record.classCode | (record.cacheFlush ? CLASS_TOP_BIT : 0)));
writer.write32(record.ttl);
const auto lengthOffset = writer.size();
writer.write16(0);
const auto dataOffset = writer.size();
switch (record.type) {
case Type::A:
writer.write(std::get<AData>(record.data).address);
break;
case Type::AAAA:
writer.write(std::get<AaaaData>(record.data).address);
break;
case Type::PTR:
writer.writeName(std::get<PtrData>(record.data).name);
break;
case Type::TXT:
for (const auto& string : std::get<TxtData>(record.data).strings) {
if (string.size() > 255)
throw ParseError("DNS TXT string is too long");
writer.write8(static_cast<uint8_t>(string.size()));
writer.write(string.data(), string.size());
}
break;
case Type::SRV: {
const auto& srv = std::get<SrvData>(record.data);
writer.write16(srv.priority);
writer.write16(srv.weight);
writer.write16(srv.port);
writer.writeName(srv.target);
break;
}
case Type::NSEC:
encodeNsec(writer, std::get<NsecData>(record.data));
break;
default: {
const auto& bytes = std::get<UnknownData>(record.data).bytes;
writer.write(bytes.data(), bytes.size());
break;
}
}
const auto dataSize = writer.size() - dataOffset;
if (dataSize > std::numeric_limits<uint16_t>::max())
throw ParseError("DNS record data is too large");
writer.patch16(lengthOffset, static_cast<uint16_t>(dataSize));
}
Question
decodeQuestion(Reader& reader)
{
Question question;
question.name = reader.readName();
question.type = static_cast<Type>(reader.read16());
const auto classField = reader.read16();
question.classCode = classField & CLASS_MASK;
question.unicastResponse = classField & CLASS_TOP_BIT;
return question;
}
NsecData
decodeNsec(Reader& reader, size_t end)
{
NsecData nsec;
nsec.nextDomain = reader.readName();
while (reader.position() < end) {
const auto window = reader.read8();
const auto length = reader.read8();
if (length == 0 or length > 32 or length > end - reader.position())
throw ParseError("invalid DNS NSEC bitmap");
const auto bitmap = reader.readBytes(length);
for (size_t byte = 0; byte < bitmap.size(); ++byte) {
for (uint8_t bit = 0; bit < 8; ++bit) {
if (bitmap[byte] & (1u << (7 - bit))) {
const auto value = static_cast<uint16_t>((window << 8) | (byte * 8 + bit));
nsec.types.push_back(static_cast<Type>(value));
}
}
}
}
return nsec;
}
ResourceRecord
decodeRecord(Reader& reader)
{
ResourceRecord record;
record.name = reader.readName();
record.type = static_cast<Type>(reader.read16());
const auto classField = reader.read16();
record.classCode = classField & CLASS_MASK;
record.cacheFlush = classField & CLASS_TOP_BIT;
record.ttl = reader.read32();
const auto dataLength = reader.read16();
const auto dataStart = reader.position();
const auto dataEnd = dataStart + dataLength;
if (dataEnd < dataStart)
throw ParseError("invalid DNS record length");
switch (record.type) {
case Type::A: {
if (dataLength != 4)
throw ParseError("invalid DNS A record length");
AData data;
const auto bytes = reader.readBytes(data.address.size());
std::copy(bytes.begin(), bytes.end(), data.address.begin());
record.data = data;
break;
}
case Type::AAAA: {
if (dataLength != 16)
throw ParseError("invalid DNS AAAA record length");
AaaaData data;
const auto bytes = reader.readBytes(data.address.size());
std::copy(bytes.begin(), bytes.end(), data.address.begin());
record.data = data;
break;
}
case Type::PTR:
record.data = PtrData {reader.readName()};
break;
case Type::TXT: {
TxtData data;
while (reader.position() < dataEnd) {
const auto length = reader.read8();
if (length > dataEnd - reader.position())
throw ParseError("truncated DNS TXT string");
data.strings.push_back(reader.readBytes(length));
}
record.data = std::move(data);
break;
}
case Type::SRV: {
if (dataLength < 7)
throw ParseError("invalid DNS SRV record length");
SrvData data;
data.priority = reader.read16();
data.weight = reader.read16();
data.port = reader.read16();
data.target = reader.readName();
record.data = std::move(data);
break;
}
case Type::NSEC:
record.data = decodeNsec(reader, dataEnd);
break;
default:
record.data = UnknownData {reader.readBytes(dataLength)};
break;
}
if (reader.position() != dataEnd)
throw ParseError("DNS record data length mismatch");
return record;
}
} // namespace
Name::Name(std::vector<std::string> labels)
: labels_(std::move(labels))
{
validateLabels(labels_);
}
Name
Name::fromString(std::string_view name)
{
if (name.empty() or name == ".")
return {};
if (name.back() == '.')
name.remove_suffix(1);
std::vector<std::string> labels;
size_t start = 0;
while (start <= name.size()) {
const auto end = name.find('.', start);
const auto length = (end == std::string_view::npos ? name.size() : end) - start;
if (length == 0)
throw ParseError("empty DNS label");
labels.emplace_back(name.substr(start, length));
if (end == std::string_view::npos)
break;
start = end + 1;
}
return Name {std::move(labels)};
}
std::string
Name::toString() const
{
if (labels_.empty())
return ".";
std::string name;
for (const auto& label : labels_) {
name.append(label);
name.push_back('.');
}
return name;
}
bool
operator==(const Name& lhs, const Name& rhs) noexcept
{
if (lhs.labels_.size() != rhs.labels_.size())
return false;
for (size_t index = 0; index < lhs.labels_.size(); ++index) {
const auto& left = lhs.labels_[index];
const auto& right = rhs.labels_[index];
if (left.size() != right.size())
return false;
if (not std::equal(left.begin(), left.end(), right.begin(), [](char a, char b) {
return asciiLower(a) == asciiLower(b);
}))
return false;
}
return true;
}
Packet
encode(const Message& message)
{
const auto recordCount = message.questions.size() + message.answers.size() + message.authorities.size()
+ message.additionals.size();
if (recordCount > MAX_RECORD_COUNT)
throw ParseError("too many DNS records");
Writer writer;
writer.write16(message.id);
uint16_t flags = 0;
if (message.response)
flags |= FLAG_RESPONSE;
if (message.authoritative)
flags |= FLAG_AUTHORITATIVE;
if (message.truncated)
flags |= FLAG_TRUNCATED;
writer.write16(flags);
writer.write16(checkedCount(message.questions.size()));
writer.write16(checkedCount(message.answers.size()));
writer.write16(checkedCount(message.authorities.size()));
writer.write16(checkedCount(message.additionals.size()));
for (const auto& question : message.questions)
encodeQuestion(writer, question);
for (const auto& record : message.answers)
encodeRecord(writer, record);
for (const auto& record : message.authorities)
encodeRecord(writer, record);
for (const auto& record : message.additionals)
encodeRecord(writer, record);
return writer.take();
}
Message
decode(const uint8_t* packet, size_t size)
{
if (packet == nullptr)
throw ParseError("null DNS packet");
if (size > MAX_PACKET_SIZE)
throw ParseError("mDNS packet exceeds maximum size");
Reader reader(packet, size);
Message message;
message.id = reader.read16();
const auto flags = reader.read16();
message.response = flags & FLAG_RESPONSE;
message.authoritative = flags & FLAG_AUTHORITATIVE;
message.truncated = flags & FLAG_TRUNCATED;
const auto questionCount = reader.read16();
const auto answerCount = reader.read16();
const auto authorityCount = reader.read16();
const auto additionalCount = reader.read16();
const auto recordCount = static_cast<size_t>(questionCount) + answerCount + authorityCount + additionalCount;
if (recordCount > MAX_RECORD_COUNT)
throw ParseError("too many DNS records");
message.questions.reserve(questionCount);
message.answers.reserve(answerCount);
message.authorities.reserve(authorityCount);
message.additionals.reserve(additionalCount);
for (size_t index = 0; index < questionCount; ++index)
message.questions.push_back(decodeQuestion(reader));
for (size_t index = 0; index < answerCount; ++index)
message.answers.push_back(decodeRecord(reader));
for (size_t index = 0; index < authorityCount; ++index)
message.authorities.push_back(decodeRecord(reader));
for (size_t index = 0; index < additionalCount; ++index)
message.additionals.push_back(decodeRecord(reader));
if (reader.position() != size)
throw ParseError("trailing bytes in DNS packet");
return message;
}
} // namespace dht::mdns
-194
View File
@@ -1,194 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include "opendht/def.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
namespace dht::mdns {
constexpr size_t MAX_PACKET_SIZE = 9000;
constexpr size_t MAX_RECORD_COUNT = 1024;
constexpr uint16_t CLASS_IN = 1;
constexpr uint16_t CLASS_ANY = 255;
enum class Type : uint16_t {
A = 1,
PTR = 12,
TXT = 16,
AAAA = 28,
SRV = 33,
NSEC = 47,
ANY = 255,
};
class OPENDHT_PUBLIC ParseError : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
class OPENDHT_PUBLIC Name
{
public:
Name() = default;
explicit Name(std::vector<std::string> labels);
static Name fromString(std::string_view name);
const std::vector<std::string>& labels() const noexcept { return labels_; }
std::string toString() const;
friend bool operator==(const Name& lhs, const Name& rhs) noexcept;
friend bool operator!=(const Name& lhs, const Name& rhs) noexcept { return not(lhs == rhs); }
private:
std::vector<std::string> labels_;
};
OPENDHT_PUBLIC bool operator==(const Name& lhs, const Name& rhs) noexcept;
struct Question
{
Name name;
Type type {Type::ANY};
uint16_t classCode {CLASS_IN};
bool unicastResponse {false};
};
struct AData
{
std::array<uint8_t, 4> address {};
};
struct AaaaData
{
std::array<uint8_t, 16> address {};
};
struct PtrData
{
Name name;
};
struct TxtData
{
std::vector<std::vector<uint8_t>> strings;
};
struct SrvData
{
uint16_t priority {};
uint16_t weight {};
uint16_t port {};
Name target;
};
struct NsecData
{
Name nextDomain;
std::vector<Type> types;
};
struct UnknownData
{
std::vector<uint8_t> bytes;
};
using RData = std::variant<AData, AaaaData, PtrData, TxtData, SrvData, NsecData, UnknownData>;
struct ResourceRecord
{
Name name;
Type type {Type::ANY};
uint16_t classCode {CLASS_IN};
bool cacheFlush {false};
uint32_t ttl {};
RData data {UnknownData {}};
};
struct Message
{
uint16_t id {};
bool response {false};
bool authoritative {false};
bool truncated {false};
std::vector<Question> questions;
std::vector<ResourceRecord> answers;
std::vector<ResourceRecord> authorities;
std::vector<ResourceRecord> additionals;
};
using Packet = std::vector<uint8_t>;
OPENDHT_PUBLIC Packet encode(const Message& message);
OPENDHT_PUBLIC Message decode(const uint8_t* packet, size_t size);
inline bool
operator==(const Question& lhs, const Question& rhs)
{
return lhs.name == rhs.name and lhs.type == rhs.type and lhs.classCode == rhs.classCode
and lhs.unicastResponse == rhs.unicastResponse;
}
inline bool
operator==(const AData& lhs, const AData& rhs)
{
return lhs.address == rhs.address;
}
inline bool
operator==(const AaaaData& lhs, const AaaaData& rhs)
{
return lhs.address == rhs.address;
}
inline bool
operator==(const PtrData& lhs, const PtrData& rhs)
{
return lhs.name == rhs.name;
}
inline bool
operator==(const TxtData& lhs, const TxtData& rhs)
{
return lhs.strings == rhs.strings;
}
inline bool
operator==(const SrvData& lhs, const SrvData& rhs)
{
return lhs.priority == rhs.priority and lhs.weight == rhs.weight and lhs.port == rhs.port
and lhs.target == rhs.target;
}
inline bool
operator==(const NsecData& lhs, const NsecData& rhs)
{
return lhs.nextDomain == rhs.nextDomain and lhs.types == rhs.types;
}
inline bool
operator==(const UnknownData& lhs, const UnknownData& rhs)
{
return lhs.bytes == rhs.bytes;
}
inline bool
operator==(const ResourceRecord& lhs, const ResourceRecord& rhs)
{
return lhs.name == rhs.name and lhs.type == rhs.type and lhs.classCode == rhs.classCode
and lhs.cacheFlush == rhs.cacheFlush and lhs.ttl == rhs.ttl and lhs.data == rhs.data;
}
inline bool
operator==(const Message& lhs, const Message& rhs)
{
return lhs.id == rhs.id and lhs.response == rhs.response and lhs.authoritative == rhs.authoritative
and lhs.truncated == rhs.truncated and lhs.questions == rhs.questions and lhs.answers == rhs.answers
and lhs.authorities == rhs.authorities and lhs.additionals == rhs.additionals;
}
} // namespace dht::mdns
-455
View File
@@ -1,455 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "mdns_transport.h"
#include <asio.hpp>
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstring>
#include <future>
#include <mutex>
#ifdef _WIN32
#include <iphlpapi.h>
#else
#include <ifaddrs.h>
#include <net/if.h>
#endif
namespace dht::mdns {
namespace {
constexpr in_port_t MDNS_PORT = 5353;
constexpr std::string_view MDNS_IPV4 = "224.0.0.251";
constexpr std::string_view MDNS_IPV6 = "ff02::fb";
std::vector<InterfaceAddress>
interfaceAddresses([[maybe_unused]] asio::io_context& ioContext)
{
std::vector<InterfaceAddress> result;
#ifdef _WIN32
ULONG size = 15000;
std::vector<uint8_t> buffer(size);
auto* adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buffer.data());
auto status = GetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
nullptr,
adapters,
&size);
if (status == ERROR_BUFFER_OVERFLOW) {
buffer.resize(size);
adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buffer.data());
status = GetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
nullptr,
adapters,
&size);
}
if (status == NO_ERROR) {
InterfaceId id = 0;
for (auto* adapter = adapters; adapter; adapter = adapter->Next) {
if (adapter->OperStatus != IfOperStatusUp)
continue;
++id;
const auto multicast = not(adapter->Flags & IP_ADAPTER_NO_MULTICAST);
const auto loopback = adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK;
for (auto* current = adapter->FirstUnicastAddress; current; current = current->Next) {
const auto* socketAddress = current->Address.lpSockaddr;
if (socketAddress->sa_family == AF_INET) {
const auto* address = reinterpret_cast<const sockaddr_in*>(socketAddress);
asio::ip::address_v4::bytes_type bytes;
std::memcpy(bytes.data(), &address->sin_addr, bytes.size());
result.push_back({id, adapter->IfIndex, asio::ip::address_v4(bytes), multicast, loopback});
} else if (socketAddress->sa_family == AF_INET6) {
const auto* address = reinterpret_cast<const sockaddr_in6*>(socketAddress);
asio::ip::address_v6::bytes_type bytes;
std::memcpy(bytes.data(), &address->sin6_addr, bytes.size());
result.push_back({id,
adapter->Ipv6IfIndex,
asio::ip::address_v6(bytes, address->sin6_scope_id),
multicast,
loopback});
}
}
}
}
#else
ifaddrs* addresses = nullptr;
if (getifaddrs(&addresses) != 0)
return result;
for (auto* current = addresses; current; current = current->ifa_next) {
if (not current->ifa_addr or not(current->ifa_flags & IFF_UP))
continue;
const auto family = current->ifa_addr->sa_family;
if (family == AF_INET) {
const auto* address = reinterpret_cast<const sockaddr_in*>(current->ifa_addr);
asio::ip::address_v4::bytes_type bytes;
std::memcpy(bytes.data(), &address->sin_addr, bytes.size());
const auto index = if_nametoindex(current->ifa_name);
result.push_back({index,
index,
asio::ip::address_v4(bytes),
bool(current->ifa_flags & IFF_MULTICAST),
bool(current->ifa_flags & IFF_LOOPBACK)});
} else if (family == AF_INET6) {
const auto* address = reinterpret_cast<const sockaddr_in6*>(current->ifa_addr);
asio::ip::address_v6::bytes_type bytes;
std::memcpy(bytes.data(), &address->sin6_addr, bytes.size());
const auto index = if_nametoindex(current->ifa_name);
result.push_back({index,
index,
asio::ip::address_v6(bytes, address->sin6_scope_id),
bool(current->ifa_flags & IFF_MULTICAST),
bool(current->ifa_flags & IFF_LOOPBACK)});
}
}
freeifaddrs(addresses);
#endif
return result;
}
} // namespace
std::vector<NetworkInterface>
groupInterfaces(const std::vector<InterfaceAddress>& addresses)
{
std::vector<NetworkInterface> result;
const auto hasNonLoopback = std::any_of(addresses.begin(), addresses.end(), [](const InterfaceAddress& address) {
return address.multicast and not address.loopback and not address.address.is_unspecified()
and not address.address.is_multicast();
});
for (const auto& address : addresses) {
if (address.id == 0 or not address.multicast or (hasNonLoopback and address.loopback)
or address.address.is_unspecified() or address.address.is_multicast())
continue;
auto scope = std::find_if(result.begin(), result.end(), [&](const NetworkInterface& interface) {
return interface.id == address.id;
});
if (scope == result.end()) {
NetworkInterface interface;
interface.id = address.id;
result.push_back(std::move(interface));
scope = std::prev(result.end());
}
dns_sd::AddressData record;
if (address.address.is_v4()) {
scope->ipv4Index = address.index;
if (not scope->ipv4Outbound)
scope->ipv4Outbound = address.address.to_v4();
record = AData {address.address.to_v4().to_bytes()};
} else {
const auto bytes = address.address.to_v6().to_bytes();
const auto mapped = std::all_of(bytes.begin(), bytes.begin() + 10, [](uint8_t byte) { return byte == 0; })
and bytes[10] == 0xff and bytes[11] == 0xff;
if (mapped)
continue;
scope->ipv6Index = address.index;
record = AaaaData {bytes};
}
if (std::find(scope->addresses.begin(), scope->addresses.end(), record) == scope->addresses.end())
scope->addresses.emplace_back(std::move(record));
}
result.erase(std::remove_if(result.begin(),
result.end(),
[](const NetworkInterface& interface) { return interface.addresses.empty(); }),
result.end());
return result;
}
class MdnsTransport::Impl : public std::enable_shared_from_this<MdnsTransport::Impl>
{
public:
Impl(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger)
: ioContext_(std::move(ioContext))
, logger_(std::move(logger))
, interfaces_(groupInterfaces(interfaceAddresses(*ioContext_)))
{}
struct Socket
{
explicit Socket(asio::io_context& ioContext)
: socket(ioContext)
{}
asio::ip::udp::socket socket;
std::array<uint8_t, MAX_PACKET_SIZE> receiveBuffer {};
asio::ip::udp::endpoint sender;
asio::ip::udp::endpoint multicast;
InterfaceId interface {};
unsigned ipv6Index {};
};
void start(ReceiveCallback callback)
{
callback_ = std::move(callback);
configure();
}
void send(Packet packet) { send(std::move(packet), {}, {}); }
void send(Packet packet, std::optional<asio::ip::udp::endpoint> destination, std::optional<InterfaceId> interface)
{
auto sharedPacket = std::make_shared<Packet>(std::move(packet));
asio::post(*ioContext_, [weak = weak_from_this(), sharedPacket, destination, interface] {
const auto self = weak.lock();
if (not self)
return;
for (const auto& state : self->sockets_) {
if (interface and state->interface != *interface)
continue;
const auto target = destination.value_or(state->multicast);
if (target.address().is_v4() != state->multicast.address().is_v4())
continue;
state->socket.async_send_to(asio::buffer(*sharedPacket),
target,
[logger = self->logger_, sharedPacket](const asio::error_code& error,
size_t) {
if (error and error != asio::error::operation_aborted and logger)
logger->warn("Unable to send mDNS packet: {}", error.message());
});
}
});
}
void sendAndWait(Packet packet, InterfaceId interface)
{
if (ioContext_->get_executor().running_in_this_thread()) {
sendNow(packet, interface);
return;
}
auto completed = std::make_shared<std::promise<void>>();
auto future = completed->get_future();
asio::post(*ioContext_, [weak = weak_from_this(), packet = std::move(packet), interface, completed] {
if (const auto self = weak.lock())
self->sendNow(packet, interface);
completed->set_value();
});
future.wait_for(std::chrono::milliseconds(250));
}
void refresh()
{
{
std::lock_guard lock(interfacesMutex_);
interfaces_ = groupInterfaces(interfaceAddresses(*ioContext_));
}
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->configure();
});
}
void stop()
{
if (ioContext_->get_executor().running_in_this_thread()) {
stopNow();
return;
}
auto completed = std::make_shared<std::promise<void>>();
auto future = completed->get_future();
asio::post(*ioContext_, [weak = weak_from_this(), completed] {
if (const auto self = weak.lock())
self->stopNow();
completed->set_value();
});
future.wait_for(std::chrono::milliseconds(250));
}
std::vector<NetworkInterface> interfaces() const
{
std::lock_guard lock(interfacesMutex_);
return interfaces_;
}
private:
void sendNow(const Packet& packet, InterfaceId interface)
{
for (const auto& state : sockets_) {
if (state->interface != interface)
continue;
asio::error_code error;
state->socket.send_to(asio::buffer(packet), state->multicast, 0, error);
if (error and logger_)
logger_->warn("Unable to send final mDNS packet: {}", error.message());
}
}
void stopNow()
{
callback_ = {};
stopSockets();
}
void configure()
{
stopSockets();
std::vector<NetworkInterface> currentInterfaces;
{
std::lock_guard lock(interfacesMutex_);
currentInterfaces = interfaces_;
}
for (const auto& interface : currentInterfaces) {
if (interface.ipv4Outbound)
configure(interface, asio::ip::udp::v4(), asio::ip::make_address(MDNS_IPV4));
if (interface.ipv6Index)
configure(interface, asio::ip::udp::v6(), asio::ip::make_address(MDNS_IPV6));
}
}
void configure(const NetworkInterface& interface, asio::ip::udp protocol, const asio::ip::address& multicast)
{
auto state = std::make_shared<Socket>(*ioContext_);
asio::error_code error;
state->socket.open(protocol, error);
if (error)
return logSocketError(protocol, error);
state->socket.set_option(asio::ip::udp::socket::reuse_address(true), error);
#if defined(SO_REUSEPORT) && !defined(_WIN32)
if (not error) {
const int enabled = 1;
if (::setsockopt(state->socket.native_handle(), SOL_SOCKET, SO_REUSEPORT, &enabled, sizeof(enabled)) != 0)
error.assign(errno, asio::error::get_system_category());
}
#endif
if (error)
return logSocketError(protocol, error);
if (protocol == asio::ip::udp::v6())
state->socket.set_option(asio::ip::v6_only(true), error);
state->socket.bind({protocol, MDNS_PORT}, error);
if (error)
return logSocketError(protocol, error);
state->socket.set_option(asio::ip::multicast::hops(255), error);
if (error)
return logSocketError(protocol, error);
state->socket.set_option(asio::ip::multicast::enable_loopback(true), error);
if (protocol == asio::ip::udp::v4()) {
state->socket.set_option(asio::ip::multicast::join_group(multicast.to_v4(), *interface.ipv4Outbound), error);
if (not error)
state->socket.set_option(asio::ip::multicast::outbound_interface(*interface.ipv4Outbound), error);
} else {
state->socket.set_option(asio::ip::multicast::join_group(multicast.to_v6(), interface.ipv6Index), error);
if (not error)
state->socket.set_option(asio::ip::multicast::outbound_interface(interface.ipv6Index), error);
}
if (error)
return logSocketError(protocol, error);
if (protocol == asio::ip::udp::v6())
state->multicast = {asio::ip::address_v6(multicast.to_v6().to_bytes(), interface.ipv6Index), MDNS_PORT};
else
state->multicast = {multicast, MDNS_PORT};
state->interface = interface.id;
state->ipv6Index = interface.ipv6Index;
sockets_.push_back(state);
receive(state);
}
void receive(const std::shared_ptr<Socket>& state)
{
state->socket.async_receive_from(asio::buffer(state->receiveBuffer),
state->sender,
[weak = weak_from_this(), state](const asio::error_code& error, size_t size) {
const auto self = weak.lock();
if (not self)
return;
if (not error and self->callback_) {
Packet packet(state->receiveBuffer.begin(),
state->receiveBuffer.begin() + size);
self->callback_(packet,
{state->sender, state->interface, state->ipv6Index});
} else if (error != asio::error::operation_aborted and self->logger_) {
self->logger_->warn("Unable to receive mDNS packet: {}",
error.message());
}
if (state->socket.is_open())
self->receive(state);
});
}
void stopSockets()
{
for (const auto& state : sockets_) {
asio::error_code error;
state->socket.cancel(error);
state->socket.close(error);
}
sockets_.clear();
}
void logSocketError(asio::ip::udp protocol, const asio::error_code& error)
{
if (logger_)
logger_->warn("Unable to start {} mDNS transport: {}",
protocol == asio::ip::udp::v4() ? "IPv4" : "IPv6",
error.message());
}
std::shared_ptr<asio::io_context> ioContext_;
std::shared_ptr<Logger> logger_;
mutable std::mutex interfacesMutex_;
std::vector<NetworkInterface> interfaces_;
std::vector<std::shared_ptr<Socket>> sockets_;
ReceiveCallback callback_;
};
MdnsTransport::MdnsTransport(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger)
: impl_(std::make_shared<Impl>(std::move(ioContext), std::move(logger)))
{}
MdnsTransport::~MdnsTransport() = default;
void
MdnsTransport::start(ReceiveCallback callback)
{
impl_->start(std::move(callback));
}
void
MdnsTransport::send(Packet packet)
{
impl_->send(std::move(packet));
}
void
MdnsTransport::send(Packet packet, InterfaceId interface)
{
impl_->send(std::move(packet), {}, interface);
}
void
MdnsTransport::send(Packet packet, const asio::ip::udp::endpoint& destination, InterfaceId interface)
{
impl_->send(std::move(packet), destination, interface);
}
void
MdnsTransport::sendAndWait(Packet packet, InterfaceId interface)
{
impl_->sendAndWait(std::move(packet), interface);
}
void
MdnsTransport::refresh()
{
impl_->refresh();
}
void
MdnsTransport::stop()
{
if (impl_)
impl_->stop();
}
std::vector<NetworkInterface>
MdnsTransport::interfaces() const
{
return impl_->interfaces();
}
} // namespace dht::mdns
-74
View File
@@ -1,74 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once
#include "dns_sd.h"
#include "opendht/logger.h"
#include <asio/ip/address.hpp>
#include <asio/ip/udp.hpp>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
namespace asio {
class io_context;
}
namespace dht::mdns {
using InterfaceId = uint64_t;
struct InterfaceAddress
{
InterfaceId id {};
unsigned index {};
asio::ip::address address;
bool multicast {true};
bool loopback {false};
};
struct NetworkInterface
{
InterfaceId id {};
unsigned ipv4Index {};
unsigned ipv6Index {};
std::optional<asio::ip::address_v4> ipv4Outbound;
std::vector<dns_sd::AddressData> addresses;
};
OPENDHT_PUBLIC std::vector<NetworkInterface> groupInterfaces(const std::vector<InterfaceAddress>& addresses);
struct ReceiveContext
{
asio::ip::udp::endpoint source;
InterfaceId interface {};
unsigned ipv6Index {};
};
class MdnsTransport
{
public:
using ReceiveCallback = std::function<void(const Packet&, const ReceiveContext&)>;
MdnsTransport(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger = {});
~MdnsTransport();
void start(ReceiveCallback callback);
void send(Packet packet);
void send(Packet packet, InterfaceId interface);
void send(Packet packet, const asio::ip::udp::endpoint& destination, InterfaceId interface);
void sendAndWait(Packet packet, InterfaceId interface);
void refresh();
void stop();
std::vector<NetworkInterface> interfaces() const;
private:
class Impl;
std::shared_ptr<Impl> impl_;
};
} // namespace dht::mdns
-438
View File
@@ -1,438 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "peer_discovery.h"
#include "dns_sd.h"
#include "mdns_transport.h"
#include <asio.hpp>
#include <chrono>
#include <cstring>
#include <map>
#include <mutex>
#include <optional>
#include <random>
namespace dht {
namespace {
constexpr auto BROWSE_INTERVAL = std::chrono::seconds(60);
constexpr auto CONNECTIVITY_RETRY = std::chrono::seconds(10);
constexpr auto CONNECTIVITY_RETRY_MAX = std::chrono::minutes(1);
SockAddr
socketAddress(const mdns::dns_sd::AddressData& data, in_port_t port, const mdns::ReceiveContext& context)
{
SockAddr address;
std::visit(
[&](const auto& record) {
using Record = std::decay_t<decltype(record)>;
if constexpr (std::is_same_v<Record, mdns::AData>) {
address.setFamily(AF_INET);
std::memcpy(&address.getIPv4().sin_addr, record.address.data(), record.address.size());
} else {
address.setFamily(AF_INET6);
std::memcpy(&address.getIPv6().sin6_addr, record.address.data(), record.address.size());
const asio::ip::address_v6 parsed(record.address);
if (parsed.is_link_local())
address.getIPv6().sin6_scope_id = context.ipv6Index;
}
},
data);
address.setPort(port);
return address;
}
} // namespace
class PeerDiscovery::Impl : public std::enable_shared_from_this<PeerDiscovery::Impl>
{
public:
Impl(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger)
: ioContext_(std::move(ioContext))
, logger_(std::move(logger))
, transport_(ioContext_, logger_)
, browseTimer_(*ioContext_)
, announceTimer_(*ioContext_)
, connectivityTimer_(*ioContext_)
{}
void start()
{
transport_.start([weak = weak_from_this()](const mdns::Packet& packet, const mdns::ReceiveContext& context) {
if (const auto self = weak.lock())
self->receive(packet, context);
});
if (logger_)
logger_->debug("[peer discovery {:p}] Started.", fmt::ptr(this));
}
void startDiscovery(NetId network, PeerDiscoveredCallback callback)
{
{
std::lock_guard lock(mutex_);
discoveryNetwork_ = network;
discoveryCallback_ = std::move(callback);
}
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->browse();
});
}
void startPublish(const InfoHash& nodeId, NetId network, in_port_t port)
{
mdns::dns_sd::Service service {nodeId, network, port, {}};
if (transport_.interfaces().empty())
throw mdns::ParseError("DNS-SD service requires a multicast interface");
{
std::lock_guard lock(mutex_);
published_ = std::move(service);
}
announce();
asio::post(*ioContext_, [weak = weak_from_this()] {
const auto self = weak.lock();
if (not self)
return;
self->announceTimer_.expires_after(std::chrono::seconds(1));
self->announceTimer_.async_wait([weak](const asio::error_code& error) {
if (not error) {
if (const auto locked = weak.lock())
locked->announce();
}
});
});
}
bool stopDiscovery()
{
std::lock_guard lock(mutex_);
const auto stopped = bool(discoveryCallback_);
discoveryCallback_ = {};
discoveryNetwork_.reset();
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->browseTimer_.cancel();
});
return stopped;
}
bool stopPublish()
{
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
service = std::exchange(published_, {});
}
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->announceTimer_.cancel();
});
if (service)
advertise(*service, true, false);
return bool(service);
}
void stop()
{
stopConnectivityChanged();
stopDiscovery();
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
service = std::exchange(published_, {});
}
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->announceTimer_.cancel();
});
if (service)
advertise(*service, true, true);
transport_.stop();
}
void connectivityChanged()
{
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock())
self->handleConnectivityChanged();
});
}
void stopConnectivityChanged()
{
asio::post(*ioContext_, [weak = weak_from_this()] {
if (const auto self = weak.lock()) {
self->connectivityTimer_.cancel();
self->connectivityRetry_ = CONNECTIVITY_RETRY;
}
});
}
private:
void handleConnectivityChanged()
{
caches_.clear();
transport_.refresh();
refreshPublication();
browse();
connectivityTimer_.expires_after(connectivityRetry_);
connectivityTimer_.async_wait([weak = weak_from_this()](const asio::error_code& error) {
if (not error) {
if (const auto self = weak.lock())
self->handleConnectivityChanged();
}
});
connectivityRetry_ = std::min(connectivityRetry_ * 2,
std::chrono::duration_cast<std::chrono::steady_clock::duration>(
CONNECTIVITY_RETRY_MAX));
}
void browse()
{
bool active;
{
std::lock_guard lock(mutex_);
active = bool(discoveryCallback_);
}
if (not active)
return;
if (logger_)
logger_->debug("[peer discovery {:p}] Browsing.", fmt::ptr(this));
transport_.send(mdns::encode(mdns::dns_sd::browseQuery()));
browseTimer_.expires_after(BROWSE_INTERVAL);
browseTimer_.async_wait([weak = weak_from_this()](const asio::error_code& error) {
if (not error) {
if (const auto self = weak.lock())
self->browse();
}
});
}
void announce()
{
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
service = published_;
}
if (logger_)
logger_->debug("[peer discovery {:p}] Announcing.", fmt::ptr(this));
if (service)
advertise(*service, false, false);
}
void refreshPublication()
{
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
if (published_)
service = published_;
}
if (service)
advertise(*service, false, false);
}
void advertise(const mdns::dns_sd::Service& service, bool goodbye, bool wait)
{
for (const auto& interface : transport_.interfaces()) {
auto scoped = service;
scoped.addresses = interface.addresses;
auto packet = mdns::encode(goodbye ? mdns::dns_sd::goodbye(scoped) : mdns::dns_sd::announcement(scoped));
if (wait)
transport_.sendAndWait(std::move(packet), interface.id);
else
transport_.send(std::move(packet), interface.id);
}
}
void receive(const mdns::Packet& packet, const mdns::ReceiveContext& context)
{
if (logger_)
logger_->debug("[peer discovery {:p}] Got packet of size {}.", fmt::ptr(this), packet.size());
try {
const auto message = mdns::decode(packet.data(), packet.size());
if (message.response)
discover(message, context);
else
answer(message, context);
} catch (const std::exception& error) {
if (logger_)
logger_->warn("Ignoring invalid mDNS packet: {}", error.what());
}
}
void answer(const mdns::Message& query, const mdns::ReceiveContext& context)
{
std::optional<mdns::dns_sd::Service> service;
{
std::lock_guard lock(mutex_);
service = published_;
}
if (service) {
const auto interfaces = transport_.interfaces();
const auto interface = std::find_if(interfaces.begin(), interfaces.end(), [&](const auto& candidate) {
return candidate.id == context.interface;
});
if (interface == interfaces.end())
return;
service->addresses = interface->addresses;
if (auto response = mdns::dns_sd::respond(query, *service)) {
const auto unicast = context.source.port() != 5353
or std::any_of(query.questions.begin(),
query.questions.end(),
[](const mdns::Question& question) {
return question.unicastResponse;
});
if (context.source.port() != 5353)
response->questions = query.questions;
auto packet = mdns::encode(*response);
const auto sharedAnswer = std::any_of(response->answers.begin(),
response->answers.end(),
[](const mdns::ResourceRecord& record) {
return not record.cacheFlush;
});
if (not sharedAnswer) {
if (unicast)
transport_.send(std::move(packet), context.source, context.interface);
else
transport_.send(std::move(packet), context.interface);
return;
}
static thread_local std::mt19937 random {std::random_device {}()};
const auto delay = std::chrono::milliseconds(std::uniform_int_distribution<int> {20, 120}(random));
auto timer = std::make_shared<asio::steady_timer>(*ioContext_, delay);
timer->async_wait([weak = weak_from_this(), timer, packet = std::move(packet), context, unicast](
const asio::error_code& error) mutable {
if (error)
return;
const auto self = weak.lock();
if (not self)
return;
if (unicast)
self->transport_.send(std::move(packet), context.source, context.interface);
else
self->transport_.send(std::move(packet), context.interface);
});
}
}
}
void discover(const mdns::Message& message, const mdns::ReceiveContext& context)
{
PeerDiscoveredCallback callback;
std::optional<NetId> network;
std::optional<InfoHash> localNode;
{
std::lock_guard lock(mutex_);
callback = discoveryCallback_;
network = discoveryNetwork_;
if (published_)
localNode = published_->nodeId;
}
if (not callback or not network)
return;
const auto cache = caches_.try_emplace(context.interface).first;
for (const auto& service : cache->second.update(message)) {
if (service.network != *network or (localNode and service.nodeId == *localNode))
continue;
for (const auto& address : service.addresses)
callback(service.nodeId, socketAddress(address, service.port, context));
}
}
std::shared_ptr<asio::io_context> ioContext_;
std::shared_ptr<Logger> logger_;
mdns::MdnsTransport transport_;
asio::steady_timer browseTimer_;
asio::steady_timer announceTimer_;
asio::steady_timer connectivityTimer_;
std::chrono::steady_clock::duration connectivityRetry_ {CONNECTIVITY_RETRY};
std::mutex mutex_;
std::optional<NetId> discoveryNetwork_;
PeerDiscoveredCallback discoveryCallback_;
std::optional<mdns::dns_sd::Service> published_;
std::map<mdns::InterfaceId, mdns::dns_sd::Cache> caches_;
};
PeerDiscovery::PeerDiscovery(std::shared_ptr<asio::io_context> ioContext, std::shared_ptr<Logger> logger)
{
if (not ioContext) {
ioContext = std::make_shared<asio::io_context>();
ioContext_ = ioContext;
}
impl_ = std::make_shared<Impl>(ioContext, logger);
impl_->start();
if (ioContext_) {
ioRunner_ = std::thread([logger = std::move(logger), ioContext = std::move(ioContext)] {
try {
auto work = asio::make_work_guard(*ioContext);
ioContext->run();
} catch (const std::exception& error) {
if (logger)
logger->error("Peer discovery event loop failed: {}", error.what());
}
});
}
}
PeerDiscovery::~PeerDiscovery()
{
stop();
impl_.reset();
if (ioContext_)
ioContext_->stop();
if (ioRunner_.joinable())
ioRunner_.join();
}
void
PeerDiscovery::startDiscovery(NetId network, PeerDiscoveredCallback callback)
{
impl_->startDiscovery(network, std::move(callback));
}
void
PeerDiscovery::startPublish(const InfoHash& nodeId, NetId network, in_port_t port)
{
impl_->startPublish(nodeId, network, port);
}
bool
PeerDiscovery::stopDiscovery()
{
return impl_->stopDiscovery();
}
bool
PeerDiscovery::stopPublish()
{
return impl_->stopPublish();
}
void
PeerDiscovery::stop()
{
if (impl_)
impl_->stop();
}
void
PeerDiscovery::connectivityChanged()
{
impl_->connectivityChanged();
}
void
PeerDiscovery::stopConnectivityChanged()
{
impl_->stopConnectivityChanged();
}
} // namespace dht
-11
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()
@@ -846,7 +836,6 @@ struct Dht::Search
listeners.clear();
nodes.clear();
nextSearchStep.reset();
cache.clear();
}
};
+158 -12
View File
@@ -40,12 +40,26 @@ SecureDht::SecureDht(std::unique_ptr<DhtInterface> dht,
registerInsecureType(CERTIFICATE_TYPE);
if (certificate_) {
auto certId = certificate_->getId();
auto certLongId = certificate_->getLongId();
if (key_ and (certLongId != key_->getPublicKey().getLongId()))
if (key_ and (certId != key_->getPublicKey().getId() or certLongId != key_->getPublicKey().getLongId()))
throw DhtException("SecureDht: provided certificate doesn't match private key.");
dht_->addOnConnectedCallback([this, certLongId, cb = std::move(iacb)]() mutable {
dht_->put(InfoHash::get(certLongId), Value {CERTIFICATE_TYPE, *certificate_, 1}, std::move(cb), {}, true);
auto cb = iacb ? [
this,
completedState = std::make_shared<std::pair<unsigned,bool>>(2, true),
iacb = std::move(iacb)
](bool ok) {
if (logger_)
logger_->debug("SecureDht: certificate announcement {}", ok ? "succeeded" : "failed");
completedState->second = completedState->second and ok;
if (--completedState->first == 0)
iacb(completedState->second);
} : iacb;
dht_->addOnConnectedCallback([this, certId, certLongId, cb = std::move(cb)] {
dht_->put(certId, Value {CERTIFICATE_TYPE, *certificate_, 1}, cb, {}, true);
dht_->put(InfoHash::get(certLongId), Value {CERTIFICATE_TYPE, *certificate_, 1}, cb, {}, true);
});
}
}
@@ -96,13 +110,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 +151,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 +205,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 +301,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 +347,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 +376,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 +391,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 +506,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)
{
+1 -9
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};
};
+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 -59
View File
@@ -238,58 +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();
auto dt = t - system_now;
if (dt > system_clock::duration(0) and steady_now > time_point::max() - dt)
return time_point::max();
else if (dt < system_clock::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();
auto dt = t - steady_now;
if (dt > duration(0) and system_now >= system_clock::time_point::max() - dt)
return system_clock::time_point::max();
else if (dt < duration(0) and system_now <= system_clock::time_point::min() - dt)
return system_clock::time_point::min();
return system_now + std::chrono::duration_cast<system_clock::duration>(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
@@ -326,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;
}
+10
View File
@@ -165,6 +165,7 @@ private:
CallbackQueue addValues(const std::vector<Sp<Value>>& new_values, const TypeStore& types, const time_point& now)
{
std::vector<Sp<Value>> nvals;
std::vector<Sp<Value>> expired_vals;
for (const auto& value : new_values) {
auto v = values.find(value->id);
if (v == values.end()) {
@@ -175,9 +176,18 @@ private:
// refreshed value
v->second.created = now;
v->second.expiration = now + types.getType(v->second.data->type).expiration;
if (*v->second.data != *value) {
expired_vals.emplace_back(v->second.data);
v->second.data = value;
nvals.emplace_back(value);
}
}
}
CallbackQueue ret;
if (callback and not expired_vals.empty())
ret.emplace_back([cb = callback, expired_vals = std::move(expired_vals)]{
cb(expired_vals, true);
});
if (callback and not nvals.empty())
ret.emplace_back([cb = callback, nvals = std::move(nvals)] { cb(nvals, false); });
return ret;
+9
View File
@@ -0,0 +1,9 @@
if ENABLE_TESTS
bin_PROGRAMS = opendht_unit_tests
AM_CPPFLAGS = -I../include -DOPENDHT_JSONCPP
nobase_include_HEADERS = infohashtester.h valuetester.h cryptotester.h dhtrunnertester.h httptester.h dhtproxytester.h
opendht_unit_tests_SOURCES = tests_runner.cpp cryptotester.cpp infohashtester.cpp valuetester.cpp dhtrunnertester.cpp httptester.cpp dhtproxytester.cpp
opendht_unit_tests_LDFLAGS = -lopendht -lcppunit -ljsoncpp -L@top_builddir@/src/.libs @GnuTLS_LIBS@
endif
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_crypto.h"
#include "cryptotester.h"
#include <opendht/crypto.h>
@@ -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,15 +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
@@ -12,11 +12,7 @@ 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(testOcsp);
@@ -40,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
*/
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_dhtproxy_stress.h"
#include "dhtproxy_stress_tester.h"
#include <iostream>
#include <string>
@@ -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;
File renamed without changes.
+705
View File
@@ -0,0 +1,705 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "dhtproxytester.h"
// std
#include <iostream>
#include <string>
#include <chrono>
#include <condition_variable>
#include <asio.hpp>
#include <sys/socket.h>
#include <sys/time.h>
using namespace std::chrono_literals;
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(DhtProxyTester);
void
DhtProxyTester::setUp()
{
clientConfig.dht_config.node_config.max_peer_req_per_sec = -1;
clientConfig.dht_config.node_config.max_req_per_sec = -1;
nodePeer.run(0, clientConfig);
nodeProxy = std::make_shared<dht::DhtRunner>();
nodeProxy->run(0, clientConfig);
auto bound = nodePeer.getBound();
if (bound.isUnspecified())
bound.setLoopback();
nodeProxy->bootstrap(bound);
// auto serverCAIdentity = dht::crypto::generateEcIdentity("DHT Node CA");
uint16_t port = 1024 + (std::rand() % (65535 - 1024));
dht::ProxyServerConfig serverConfig;
// serverConfig.identity = dht::crypto::generateIdentity("DHT Node", serverCAIdentity);
serverConfig.port = port;
// serverConfig.pushServer = "127.0.0.1:8090";
serverProxy = std::make_unique<dht::DhtProxyServer>(nodeProxy, serverConfig);
/*clientConfig.server_ca = serverCAIdentity.second;
clientConfig.client_identity = dht::crypto::generateIdentity("DhtProxyTester");
clientConfig.push_node_id = "dhtnode";*/
clientConfig.proxy_server = "http://127.0.0.1:" + std::to_string(port); //"https://127.0.0.1:8080";
}
void
DhtProxyTester::tearDown()
{
nodePeer.join();
nodeClient.join();
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
nodeProxy->shutdown([&] {
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
std::unique_lock<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&] { return done; }));
serverProxy.reset();
nodeProxy.reset();
}
void
DhtProxyTester::testGetPut()
{
nodeClient.run(0, clientConfig);
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
auto key = dht::InfoHash::get("GLaDOs");
dht::Value val {"Hey! It's been a long time. How have you been?"};
auto val_data = val.data;
{
nodePeer.put(key, std::move(val), [&](bool) {
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
std::unique_lock<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
}
auto vals = nodeClient.get(key).get();
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtProxyTester::testListen()
{
nodeClient.run(0, clientConfig);
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock<std::mutex> lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
bool done = false;
// If a peer send a value, the listen operation from the client
// should retrieve this value
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
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<std::mutex> lk(cv_m);
for (const auto& value : v)
values.emplace_back(value->data);
done = true;
cv.notify_all();
}
return true;
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
// Here values should contains 1 values
CPPUNIT_ASSERT_EQUAL(static_cast<int>(values.size()), 1);
CPPUNIT_ASSERT(values.front() == firstVal_data);
// And the listen should retrieve futures values
// All values
dht::Value secondVal {"You're a monster"};
auto secondVal_data = secondVal.data;
nodePeer.put(key, std::move(secondVal));
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
nodeClient.cancelListen(key, std::move(token));
// Here values should contains 2 values
CPPUNIT_ASSERT_EQUAL(static_cast<int>(values.size()), 2);
CPPUNIT_ASSERT(values.back() == secondVal_data);
}
void
DhtProxyTester::testResubscribeGetValues()
{
clientConfig.push_token = "atlas";
nodeClient.run(0, clientConfig);
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock<std::mutex> lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
// If a peer sent a value, the listen operation from the client
// should retrieve this value
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(ok);
done = true;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
// Send a first subscribe, the value is sent via a push notification
// So ignore values here.
nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>&, bool) { return true; });
cv.wait_for(lk, std::chrono::seconds(1));
// Reboot node (to avoid cache)
nodeClient.join();
clientConfig.push_token = "";
nodeClient.run(0, clientConfig);
// For the second subscribe, the proxy will return the value in the body
std::vector<std::shared_ptr<dht::Value>> values;
auto ftoken = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
if (not expired) {
std::lock_guard<std::mutex> lk(cv_m);
values.insert(values.end(), v.begin(), v.end());
done = true;
cv.notify_all();
}
return true;
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
auto token = ftoken.get();
CPPUNIT_ASSERT(token);
nodeClient.cancelListen(key, token);
// Here values should still contains 1 values
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
CPPUNIT_ASSERT(firstVal_data == values.front()->data);
}
void
DhtProxyTester::testPushNotification()
{
#ifndef OPENDHT_PUSH_NOTIFICATIONS
fmt::print(stderr, "Push notifications are not supported in this build, skipping test\n");
return;
#else
static constexpr dht::ValueType::Id PUSH_EDITABLE_TYPE_ID = 4242;
const dht::ValueType pushEditableType {PUSH_EDITABLE_TYPE_ID,
"push-editable",
std::chrono::seconds(1),
dht::ValueType::DEFAULT_STORE_POLICY,
[](dht::InfoHash,
const std::shared_ptr<dht::Value>&,
std::shared_ptr<dht::Value>&,
const dht::InfoHash&,
const dht::SockAddr&) { return true; }};
nodePeer.registerType(pushEditableType);
nodeProxy->registerType(pushEditableType);
clientConfig.push_token = "atlas";
nodeClient.run(0, clientConfig);
nodeClient.registerType(pushEditableType);
bool gotValues = false;
bool gotExpiredValues = false;
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock<std::mutex> lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
auto valueAuthor = dht::crypto::generateIdentity("PushNotificationAuthor");
std::vector<std::shared_ptr<dht::Value>> values;
std::vector<std::shared_ptr<dht::Value>> expiredValues;
auto ftoken = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
std::lock_guard<std::mutex> lk(cv_m);
if (expired) {
expiredValues.insert(expiredValues.end(), v.begin(), v.end());
gotExpiredValues = true;
} else {
values.insert(values.end(), v.begin(), v.end());
gotValues = true;
}
cv.notify_all();
return true;
});
// Wait for listen to be sent to proxy
cv.wait_for(lk, std::chrono::seconds(1));
// Peer puts a value
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
firstVal.type = PUSH_EDITABLE_TYPE_ID;
firstVal.id = 1337;
firstVal.seq = 1;
firstVal.sign(*valueAuthor.first);
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(ok);
gotValues = true;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return gotValues; }));
gotValues = false;
// Simulate push notification
std::map<std::string, std::string> push_data;
push_data["key"] = key.toString();
push_data["t"] = std::to_string(
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
.count());
CPPUNIT_ASSERT(nodeClient.pushNotificationReceived(push_data).get() == dht::PushNotificationResult::Values);
// Wait for client to receive the value
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return gotValues; }));
gotValues = false;
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
CPPUNIT_ASSERT(firstVal_data == values.front()->data);
// Peer edits the existing value (same id, higher seq)
dht::Value secondVal {"You're a monster"};
secondVal.type = PUSH_EDITABLE_TYPE_ID;
secondVal.id = 1337;
secondVal.seq = 2;
secondVal.sign(*valueAuthor.first);
auto secondVal_data = secondVal.data;
nodePeer.put(key, std::move(secondVal), [&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(ok);
gotValues = true;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return gotValues; }));
gotValues = false;
// Simulate push notification again
push_data["t"] = std::to_string(
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
.count());
CPPUNIT_ASSERT(nodeClient.pushNotificationReceived(push_data).get() == dht::PushNotificationResult::Values);
// Wait for client to receive the edited value
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return gotValues; }));
gotValues = false;
// OpValueCache should update the existing id with the new sequence/data
CPPUNIT_ASSERT_EQUAL((size_t) 2u, values.size());
CPPUNIT_ASSERT_EQUAL((dht::Value::Id) 1337, values.back()->id);
CPPUNIT_ASSERT_EQUAL((uint16_t) 2, values.back()->seq);
CPPUNIT_ASSERT(secondVal_data == values.back()->data);
// Simulate expiration push notification for the edited value
push_data["exp"] = std::to_string((dht::Value::Id) 1337);
push_data["t"] = std::to_string(
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
.count());
CPPUNIT_ASSERT(nodeClient.pushNotificationReceived(push_data).get() == dht::PushNotificationResult::ValuesExpired);
cv.wait_for(lk, 500ms, [&] { return gotExpiredValues; });
auto token = ftoken.get();
CPPUNIT_ASSERT(token);
nodeClient.cancelListen(key, token);
#endif
}
void
DhtProxyTester::testProxyServerClientFullChain()
{
#ifndef OPENDHT_PUSH_NOTIFICATIONS
fmt::print(stderr, "Push notifications are not supported in this build, skipping test\n");
return;
#else
static constexpr dht::ValueType::Id CHAIN_EDITABLE_TYPE_ID = 4343;
const dht::ValueType chainEditableType {CHAIN_EDITABLE_TYPE_ID,
"chain-editable",
std::chrono::seconds(30),
dht::ValueType::DEFAULT_STORE_POLICY,
[](dht::InfoHash,
const std::shared_ptr<dht::Value>&,
std::shared_ptr<dht::Value>&,
const dht::InfoHash&,
const dht::SockAddr&) { return true; }};
uint16_t pushPort = 1024 + (std::rand() % (65535 - 1024));
uint16_t proxyPort = 1024 + (std::rand() % (65535 - 1024));
std::atomic_bool relayStop {false};
std::atomic_size_t relayedPushCount {0};
std::thread pushRelay([&] {
try {
asio::io_context io;
asio::ip::tcp::acceptor acceptor(io, asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), pushPort));
acceptor.non_blocking(true);
while (not relayStop.load()) {
asio::ip::tcp::socket socket(io);
std::error_code ec;
acceptor.accept(socket, ec);
if (ec == asio::error::would_block || ec == asio::error::try_again) {
std::this_thread::sleep_for(5ms);
continue;
}
if (ec)
continue;
socket.non_blocking(true);
std::string raw;
raw.reserve(4096);
auto deadline = std::chrono::steady_clock::now() + 2s;
while (raw.find("\r\n\r\n") == std::string::npos && std::chrono::steady_clock::now() < deadline
&& not relayStop.load()) {
std::array<char, 2048> chunk {};
size_t n = socket.read_some(asio::buffer(chunk), ec);
if (!ec) {
raw.append(chunk.data(), n);
} else if (ec == asio::error::would_block || ec == asio::error::try_again) {
std::this_thread::sleep_for(2ms);
} else {
break;
}
}
if (raw.find("\r\n\r\n") == std::string::npos)
continue;
auto sep = raw.find("\r\n\r\n");
std::string head = raw.substr(0, sep + 4);
auto clPos = head.find("Content-Length:");
size_t contentLength = 0;
if (clPos != std::string::npos) {
clPos += std::string("Content-Length:").size();
while (clPos < head.size() && (head[clPos] == ' ' || head[clPos] == '\t'))
++clPos;
size_t clEnd = head.find("\r\n", clPos);
contentLength = std::stoul(head.substr(clPos, clEnd - clPos));
}
std::string body = raw.substr(sep + 4);
while (body.size() < contentLength && std::chrono::steady_clock::now() < deadline
&& not relayStop.load()) {
std::array<char, 2048> chunk {};
size_t n = socket.read_some(asio::buffer(chunk), ec);
if (!ec) {
body.append(chunk.data(), n);
} else if (ec == asio::error::would_block || ec == asio::error::try_again) {
std::this_thread::sleep_for(2ms);
} else {
break;
}
}
if (body.size() > contentLength)
body.resize(contentLength);
Json::Value payload;
std::string err;
auto reader = std::unique_ptr<Json::CharReader>(Json::CharReaderBuilder().newCharReader());
if (reader->parse(body.data(), body.data() + body.size(), &payload, &err)
&& payload.isMember("notifications") && payload["notifications"].isArray()
&& !payload["notifications"].empty() && payload["notifications"][0].isMember("data")) {
const auto& data = payload["notifications"][0]["data"];
std::map<std::string, std::string> notif;
for (const auto& name : data.getMemberNames()) {
const auto& value = data[name];
if (value.isString())
notif[name] = value.asString();
else if (value.isBool())
notif[name] = value.asBool() ? "true" : "false";
else if (value.isUInt64())
notif[name] = std::to_string(value.asUInt64());
else if (value.isInt64())
notif[name] = std::to_string(value.asInt64());
}
nodeClient.pushNotificationReceived(notif);
relayedPushCount++;
}
static const std::string okResp = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK";
asio::write(socket, asio::buffer(okResp), ec);
}
} catch (...) {
}
});
struct RelayGuard
{
std::atomic_bool& stop;
std::thread& th;
~RelayGuard()
{
stop = true;
if (th.joinable())
th.join();
}
} relayGuard {relayStop, pushRelay};
serverProxy.reset();
dht::ProxyServerConfig serverConfig;
serverConfig.port = proxyPort;
serverConfig.pushServer = "127.0.0.1:" + std::to_string(pushPort);
serverProxy = std::make_unique<dht::DhtProxyServer>(nodeProxy, serverConfig);
clientConfig.proxy_server = "http://127.0.0.1:" + std::to_string(proxyPort);
clientConfig.push_token = "relay-token";
clientConfig.push_platform = "android";
nodePeer.registerType(chainEditableType);
nodeProxy->registerType(chainEditableType);
nodeClient.run(0, clientConfig);
nodeClient.registerType(chainEditableType);
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock<std::mutex> lk(cv_m);
auto key = dht::InfoHash::get("proxy-full-chain");
auto valueAuthor = dht::crypto::generateIdentity("ProxyChainAuthor");
std::vector<std::shared_ptr<dht::Value>> values;
auto token = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& incoming, bool expired) {
if (expired)
return true;
std::lock_guard<std::mutex> lk(cv_m);
values.insert(values.end(), incoming.begin(), incoming.end());
done = true;
cv.notify_all();
return true;
});
dht::Value firstVal {"first-version"};
firstVal.type = CHAIN_EDITABLE_TYPE_ID;
firstVal.id = 424242;
firstVal.seq = 1;
firstVal.sign(*valueAuthor.first);
auto firstData = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(ok);
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
CPPUNIT_ASSERT_EQUAL((dht::Value::Id) 424242, values.back()->id);
CPPUNIT_ASSERT_EQUAL((uint16_t) 1, values.back()->seq);
CPPUNIT_ASSERT(firstData == values.back()->data);
dht::Value secondVal {"second-version"};
secondVal.type = CHAIN_EDITABLE_TYPE_ID;
secondVal.id = 424242;
secondVal.seq = 2;
secondVal.sign(*valueAuthor.first);
auto secondData = secondVal.data;
nodePeer.put(key, std::move(secondVal), [&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(ok);
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
CPPUNIT_ASSERT_EQUAL((size_t) 2u, values.size());
CPPUNIT_ASSERT_EQUAL((dht::Value::Id) 424242, values.back()->id);
CPPUNIT_ASSERT_EQUAL((uint16_t) 2, values.back()->seq);
CPPUNIT_ASSERT(secondData == values.back()->data);
CPPUNIT_ASSERT(relayedPushCount.load() >= 2);
// Older sequence should not update current cached value
dht::Value staleVal {"stale-version"};
staleVal.type = CHAIN_EDITABLE_TYPE_ID;
staleVal.id = 424242;
staleVal.seq = 1;
staleVal.sign(*valueAuthor.first);
nodePeer.put(key, std::move(staleVal), [&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(ok);
cv.notify_all();
});
CPPUNIT_ASSERT(not cv.wait_for(lk, 1s, [&] { return done; }));
CPPUNIT_ASSERT_EQUAL((size_t) 2u, values.size());
CPPUNIT_ASSERT_EQUAL((uint16_t) 2, values.back()->seq);
CPPUNIT_ASSERT(secondData == values.back()->data);
// New id should be delivered as an additional value
dht::Value thirdVal {"third-value"};
thirdVal.type = CHAIN_EDITABLE_TYPE_ID;
thirdVal.id = 424243;
thirdVal.seq = 1;
thirdVal.sign(*valueAuthor.first);
auto thirdData = thirdVal.data;
nodePeer.put(key, std::move(thirdVal), [&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(ok);
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
CPPUNIT_ASSERT_EQUAL((size_t) 3u, values.size());
CPPUNIT_ASSERT_EQUAL((dht::Value::Id) 424243, values.back()->id);
CPPUNIT_ASSERT_EQUAL((uint16_t) 1, values.back()->seq);
CPPUNIT_ASSERT(thirdData == values.back()->data);
CPPUNIT_ASSERT(relayedPushCount.load() >= 3);
nodeClient.cancelListen(key, std::move(token));
#endif
}
void
DhtProxyTester::testPutGet40KChars()
{
nodeClient.run(0, clientConfig);
constexpr size_t N = 40000;
// Arrange
auto key = dht::InfoHash::get("testPutGet40KChars");
std::vector<std::shared_ptr<dht::Value>> values;
std::vector<uint8_t> mtu;
mtu.reserve(N);
for (size_t i = 0; i < N; i++)
mtu.emplace_back((i % 2) ? 'T' : 'M');
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock<std::mutex> lk(cv_m);
bool done_put = false;
bool done_get = false;
// Act
dht::Value val {mtu};
nodePeer.put(key, std::move(val), [&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
done_put = ok;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done_put; }));
nodeClient.get(
key,
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
values.insert(values.end(), vals.begin(), vals.end());
return true;
},
[&](bool ok) {
std::lock_guard<std::mutex> lk(cv_m);
done_get = ok;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done_get; }));
// Assert
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
for (const auto& value : values)
CPPUNIT_ASSERT(value->data == mtu);
}
void
DhtProxyTester::testFuzzy()
{
constexpr size_t N = 40000;
// Arrange
auto key = dht::InfoHash::get("testFuzzy");
std::vector<std::shared_ptr<dht::Value>> values;
std::vector<uint8_t> mtu;
mtu.reserve(N);
for (size_t i = 0; i < N; i++)
mtu.emplace_back((i % 2) ? 'T' : 'M');
// Act
for (size_t i = 0; i < 100; i++) {
auto nodeTest = std::make_shared<dht::DhtRunner>();
nodeTest->run(0, clientConfig);
nodeTest->put(key, dht::Value(mtu), [&](bool ok) { CPPUNIT_ASSERT(ok); });
nodeTest->get(
key,
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
values.insert(values.end(), vals.begin(), vals.end());
return true;
},
[&](bool ok) { CPPUNIT_ASSERT(ok); });
std::this_thread::sleep_for(5ms);
}
// Assert
for (const auto& value : values)
CPPUNIT_ASSERT(value->data == mtu);
}
void
DhtProxyTester::testShutdownStop()
{
constexpr size_t N = 40000;
constexpr unsigned C = 100;
// Arrange
auto key = dht::InfoHash::get("testShutdownStop");
std::vector<std::shared_ptr<dht::Value>> values;
std::vector<uint8_t> mtu;
mtu.reserve(N);
for (size_t i = 0; i < N; i++)
mtu.emplace_back((i % 2) ? 'T' : 'M');
std::atomic_uint callback_count {0};
// Act
for (size_t i = 0; i < C; i++) {
auto nodeTest = std::make_shared<dht::DhtRunner>();
nodeTest->run(0, clientConfig);
nodeTest->put(key, dht::Value(mtu), [&](bool /*ok*/) { callback_count++; });
nodeTest->get(
key,
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
values.insert(values.end(), vals.begin(), vals.end());
return true;
},
[&](bool /*ok*/) { callback_count++; });
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
nodeTest->shutdown(
[&] {
std::lock_guard<std::mutex> lk(cv_m);
done = true;
cv.notify_all();
},
true);
std::unique_lock<std::mutex> lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
}
CPPUNIT_ASSERT_EQUAL(2 * C, callback_count.load());
}
} // namespace test
@@ -18,6 +18,8 @@ class DhtProxyTester : public CppUnit::TestFixture
CPPUNIT_TEST(testGetPut);
CPPUNIT_TEST(testListen);
CPPUNIT_TEST(testResubscribeGetValues);
CPPUNIT_TEST(testPushNotification);
CPPUNIT_TEST(testProxyServerClientFullChain);
CPPUNIT_TEST(testPutGet40KChars);
CPPUNIT_TEST(testFuzzy);
CPPUNIT_TEST(testShutdownStop);
@@ -46,6 +48,14 @@ public:
* 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
*/
+509
View File
@@ -0,0 +1,509 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "dhtrunnertester.h"
#include <opendht/thread_pool.h>
#include <chrono>
#include <mutex>
#include <condition_variable>
using namespace std::chrono_literals;
using namespace std::literals;
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(DhtRunnerTester);
void
DhtRunnerTester::setUp()
{
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
node1.run(0, config);
node2.run(0, config);
auto bound = node1.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node2.bootstrap(bound);
}
void
DhtRunnerTester::tearDown()
{
unsigned done {0};
std::condition_variable cv;
std::mutex cv_m;
auto shutdown = [&] {
std::lock_guard lk(cv_m);
done++;
cv.notify_all();
};
node1.shutdown(shutdown);
node2.shutdown(shutdown);
std::unique_lock lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return done == 2u; }));
node1.join();
node2.join();
}
void
DhtRunnerTester::testConstructors()
{
CPPUNIT_ASSERT(node1.getBoundPort());
CPPUNIT_ASSERT_EQUAL(node1.getBoundPort(), node1.getBound().getPort());
CPPUNIT_ASSERT(node2.getBoundPort());
CPPUNIT_ASSERT_EQUAL(node2.getBoundPort(), node2.getBound().getPort());
dht::DhtRunner::Config config {};
dht::DhtRunner::Context context {};
dht::DhtRunner testNode;
testNode.run(config, std::move(context));
CPPUNIT_ASSERT(testNode.getBoundPort());
}
void
DhtRunnerTester::testGetPut()
{
auto key = dht::InfoHash::get("123");
dht::Value val {"hey"};
auto val_data = val.data;
std::promise<bool> p;
node2.put(key, std::move(val), [&](bool ok) { p.set_value(ok); });
CPPUNIT_ASSERT(p.get_future().get());
auto vals = node1.get(key).get();
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtRunnerTester::testStoreEmptyValue()
{
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = 100; // Small size to force expireStore()
config.dht_config.node_config.max_store_keys = -1;
dht::DhtRunner testNode;
testNode.run(0, config);
testNode.bootstrap(node1.getBound());
auto key = testNode.getId();
auto val = std::make_shared<dht::Value>();
val->data.clear(); // Ensure size is 0
CPPUNIT_ASSERT_EQUAL((size_t) 0, val->size());
std::promise<bool> p;
testNode.put(key, val, [&](bool ok) { p.set_value(ok); });
p.get_future().get(); // Wait for put to finish
// Put a large value to exceed max_store_size and trigger expireStore()
auto key2 = dht::InfoHash::get("large_value_test");
auto val2 = std::make_shared<dht::Value>();
val2->data = std::vector<uint8_t>(200, 0); // 200 bytes
std::promise<bool> p2;
testNode.put(key2, val2, [&](bool ok) { p2.set_value(ok); });
p2.get_future().get(); // Wait for put to finish
// Wait a bit to ensure the value is processed
std::this_thread::sleep_for(std::chrono::milliseconds(200));
testNode.join();
}
void
DhtRunnerTester::testPutDuplicate()
{
auto key = dht::InfoHash::get("123");
auto val = std::make_shared<dht::Value>("hey");
val->id = 42;
auto val_data = val->data;
std::promise<bool> p1;
std::promise<bool> p2;
node2.put(key, val, [&](bool ok) { p1.set_value(ok); });
node2.put(key, val, [&](bool ok) { p2.set_value(ok); });
auto p1ret = p1.get_future().get();
auto p2ret = p2.get_future().get();
CPPUNIT_ASSERT(p1ret);
CPPUNIT_ASSERT(p2ret);
auto vals = node1.get(key).get();
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.size() == 1);
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtRunnerTester::testPutOverride()
{
auto key = dht::InfoHash::get("123");
auto val = std::make_shared<dht::Value>("meh");
val->id = 42;
auto val2 = std::make_shared<dht::Value>("hey");
val2->id = 42;
CPPUNIT_ASSERT_EQUAL(val->id, val2->id);
auto val_data = val2->data;
std::promise<bool> p1;
std::promise<bool> p2;
node2.put(key, val, [&](bool ok) { p1.set_value(ok); });
node2.put(key, val2, [&](bool ok) { p2.set_value(ok); });
auto p1ret = p1.get_future().get();
auto p2ret = p2.get_future().get();
CPPUNIT_ASSERT(!p1ret);
CPPUNIT_ASSERT(p2ret);
auto vals = node1.get(key).get();
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.size() == 1);
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtRunnerTester::testListen()
{
std::mutex mutex;
std::condition_variable cv;
std::atomic_uint valueCounta(0);
std::atomic_uint valueCountb(0);
std::atomic_uint valueCountc(0);
std::atomic_uint valueCountd(0);
unsigned putCount(0);
unsigned putOkCount1(0);
unsigned putOkCount2(0);
unsigned putOkCount3(0);
auto a = dht::InfoHash::get("234");
auto b = dht::InfoHash::get("2345");
auto c = dht::InfoHash::get("23456");
auto d = dht::InfoHash::get("234567");
constexpr unsigned N = 256;
constexpr unsigned SZ = 56 * 1024;
auto ftokena = node1.listen(a, [&](const std::vector<std::shared_ptr<dht::Value>>& values, bool expired) {
if (expired)
valueCounta -= values.size();
else
valueCounta += values.size();
return true;
});
auto ftokenb = node1.listen(b, [&](const std::shared_ptr<dht::Value>&) {
/*if (expired)
valueCountb -= values.size();
else
valueCountb += values.size();*/
valueCountb++;
return false;
});
auto ftokenc = node1.listen(c, [&](const std::vector<std::shared_ptr<dht::Value>>& values, bool expired) {
if (expired)
valueCountc -= values.size();
else
valueCountc += values.size();
return true;
});
auto ftokend = node1.listen(d, [&](const std::vector<std::shared_ptr<dht::Value>>& values, bool expired) {
if (expired)
valueCountd -= values.size();
else
valueCountd += values.size();
return true;
});
std::vector<uint8_t> mtu;
mtu.reserve(SZ);
for (size_t i = 0; i < SZ; i++)
mtu.emplace_back((i % 2) ? 'T' : 'M');
for (unsigned i = 0; i < N; i++) {
node2.put(a, dht::Value("v1"), [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount1++;
cv.notify_all();
});
node2.put(b, dht::Value("v2"), [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount2++;
cv.notify_all();
});
auto bigVal = std::make_shared<dht::Value>();
bigVal->data = mtu;
node2.put(c, std::move(bigVal), [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount3++;
cv.notify_all();
});
}
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return putCount == N * 3u; }));
CPPUNIT_ASSERT_EQUAL(N, putOkCount1);
CPPUNIT_ASSERT_EQUAL(N, putOkCount2);
CPPUNIT_ASSERT_EQUAL(N, putOkCount3);
}
CPPUNIT_ASSERT(ftokena.valid());
CPPUNIT_ASSERT(ftokenb.valid());
CPPUNIT_ASSERT(ftokenc.valid());
CPPUNIT_ASSERT(ftokend.valid());
auto tokena = ftokena.get();
auto tokenc = ftokenc.get();
auto tokend = ftokend.get();
// tokenb might be 0 since the callback returns false.
CPPUNIT_ASSERT(tokena);
CPPUNIT_ASSERT(tokenc);
CPPUNIT_ASSERT(tokend);
CPPUNIT_ASSERT_EQUAL(N, valueCounta.load());
CPPUNIT_ASSERT_EQUAL(1u, valueCountb.load());
CPPUNIT_ASSERT_EQUAL(N, valueCountc.load());
CPPUNIT_ASSERT_EQUAL(0u, valueCountd.load());
node1.cancelListen(a, tokena);
node1.cancelListen(b, std::move(ftokenb));
node1.cancelListen(c, tokenc);
node1.cancelListen(d, tokend);
}
void
DhtRunnerTester::testIdOps()
{
std::mutex mutex;
std::condition_variable cv;
unsigned identityCount(0);
unsigned valueCount(0);
unsigned valueCountEdit(0);
dht::DhtRunner::Config config2;
config2.dht_config.node_config.max_peer_req_per_sec = -1;
config2.dht_config.node_config.max_req_per_sec = -1;
config2.dht_config.id = dht::crypto::generateIdentity();
dht::DhtRunner::Context context2;
context2.identityAnnouncedCb = [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
identityCount++;
cv.notify_all();
};
node2.join();
node2.run(42232, config2, std::move(context2));
auto bound = node1.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node2.bootstrap(bound);
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return identityCount == 1; }));
}
node1.findCertificate(node2.getId(), [&](const std::shared_ptr<dht::crypto::Certificate>& crt) {
CPPUNIT_ASSERT(crt);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
});
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCount == 1u; }));
CPPUNIT_ASSERT_EQUAL(1u, identityCount);
}
dht::DhtRunner::Context context1;
context1.identityAnnouncedCb = [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
identityCount++;
cv.notify_all();
};
config2.dht_config.id = dht::crypto::generateIdentity();
node1.join();
node1.run(42222, config2, std::move(context1));
bound = node2.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node1.bootstrap(bound);
auto key = dht::InfoHash::get("key");
node1.putEncrypted(key, node2.getId(), dht::Value("yo"), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
});
node1.putEncrypted(key, node2.getPublicKey(), std::make_shared<dht::Value>("yo"), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
});
node2.listen<std::string>(key, [&](std::string&& value) {
CPPUNIT_ASSERT_EQUAL("yo"s, value);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
return true;
});
auto key2 = dht::InfoHash::get("key2");
auto editValue = std::make_shared<dht::Value>("v1");
node1.putSigned(key2, editValue, [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCountEdit++;
cv.notify_all();
});
node2.listen(key2, [&](const std::vector<std::shared_ptr<dht::Value>>& values, bool /*expired*/) {
for (const auto& v : values) {
if (v->seq == 0)
CPPUNIT_ASSERT_EQUAL("v1"s, dht::unpackMsg<std::string>(v->data));
else if (v->seq == 1)
CPPUNIT_ASSERT_EQUAL("v2"s, dht::unpackMsg<std::string>(v->data));
CPPUNIT_ASSERT_EQUAL(v->owner->getLongId(), node1.getPublicKey()->getLongId());
}
std::lock_guard lk(mutex);
valueCountEdit += values.size();
cv.notify_all();
return true;
});
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return identityCount == 2u; }));
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCount == 5u; }));
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCountEdit == 2u; }));
}
node2.findCertificate(node1.getPublicKey()->getLongId(), [&](const std::shared_ptr<dht::crypto::Certificate>& crt) {
CPPUNIT_ASSERT(crt);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
});
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCount == 6u; }));
}
// editValue->data = dht::packMsg("v2");
editValue = std::make_shared<dht::Value>(editValue->id);
editValue->data = dht::packMsg("v2");
node1.putSigned(key2, editValue, [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCountEdit++;
cv.notify_all();
});
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCountEdit == 4u; }));
}
void
DhtRunnerTester::testListenLotOfBytes()
{
std::mutex mutex;
std::condition_variable cv;
std::atomic_uint valueCount(0);
unsigned putCount(0);
unsigned putOkCount(0);
std::string data(10000, 'a');
auto foo = dht::InfoHash::get("foo");
constexpr unsigned N = 1024;
for (unsigned i = 0; i < N; i++) {
node2.put(foo, data, [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount++;
cv.notify_all();
});
}
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return putCount == N; }));
}
dht::DhtRunner node3 {};
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
node3.run(42242, config);
auto bound = node1.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node3.bootstrap(bound);
auto ftokenfoo = node3.listen(foo, [&](const std::shared_ptr<dht::Value>&) {
valueCount++;
cv.notify_all();
return true;
});
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return valueCount == N; }));
}
node3.cancelListen(foo, ftokenfoo.get());
}
void
DhtRunnerTester::testMultithread()
{
std::mutex mutex;
std::condition_variable cv;
unsigned putCount(0);
unsigned putOkCount(0);
constexpr unsigned N = 2048;
for (unsigned i = 0; i < N; i++) {
dht::ThreadPool::computation().run([&] {
node2.put(dht::InfoHash::get("123" + std::to_string(i)), "hehe", [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount++;
cv.notify_all();
});
node2.get(
dht::InfoHash::get("123" + std::to_string(N - i - 1)),
[](const std::shared_ptr<dht::Value>&) { return true; },
[&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount++;
cv.notify_all();
});
});
}
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return putCount == 2 * N; }));
CPPUNIT_ASSERT_EQUAL(2 * N, putOkCount);
}
} // namespace test
@@ -19,16 +19,8 @@ class DhtRunnerTester : public CppUnit::TestFixture
CPPUNIT_TEST(testPutOverride);
CPPUNIT_TEST(testListen);
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_SUITE_END();
dht::DhtRunner node1 {};
@@ -67,19 +59,14 @@ 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();
/**
* Test listen method with lot of datas
*/
void testListenLotOfBytes();
/**
* Test storing an empty value
*/
void testStoreEmptyValue();
/**
* Test multithread
*/
+6 -37
View File
@@ -1,14 +1,13 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_http.h"
#include "httptester.h"
#include <opendht/value.h>
#include <opendht/dhtrunner.h>
#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)
*/
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_infohash.h"
#include "infohashtester.h"
// std
#include <iostream>
@@ -22,60 +22,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"));
File renamed without changes.
+287
View File
@@ -0,0 +1,287 @@
/*
* Copyright (C) 2025 Savoir-faire Linux Inc.
*
* Author: GitHub Copilot
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "opcachetester.h"
// Hack to test internal class
#include "../src/op_cache.cpp"
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(OpCacheTester);
void
OpCacheTester::setUp() {
}
void
OpCacheTester::tearDown() {
}
void
OpCacheTester::testUpdateRefCount() {
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
auto v1 = std::make_shared<dht::Value>();
v1->id = 1;
v1->seq = 1;
std::string data1 = "data1";
v1->data = {data1.begin(), data1.end()};
auto v2 = std::make_shared<dht::Value>();
v2->id = 1;
v2->seq = 2;
std::string data2 = "data2";
v2->data = {data2.begin(), data2.end()};
// Add v1
cache.onValuesAdded({v1});
// Should have 1 value
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
auto stored = cache.get(1);
CPPUNIT_ASSERT(stored);
CPPUNIT_ASSERT_EQUAL(v1->seq, stored->seq);
// Add v2 (update)
cache.onValuesAdded({v2});
// Should still have 1 value (updated)
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
stored = cache.get(1);
CPPUNIT_ASSERT(stored);
CPPUNIT_ASSERT_EQUAL(v2->seq, stored->seq);
// Now expire v1
// Note: onValuesExpired uses ID to find value.
cache.onValuesExpired({v1});
// If refCount was incremented on update, we should still have the value (refCount=1).
// If not, it will be removed (refCount=0).
// Check if value exists
stored = cache.get(1);
// This assertion will fail if the bug exists
CPPUNIT_ASSERT_MESSAGE("Value should still exist after expiring one source", stored != nullptr);
if (stored) {
CPPUNIT_ASSERT_EQUAL(v2->seq, stored->seq);
}
// Expire v2
cache.onValuesExpired({v2});
stored = cache.get(1);
CPPUNIT_ASSERT_MESSAGE("Value should be removed after expiring all sources", stored == nullptr);
}
void
OpCacheTester::testBasicAddExpire() {
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
auto v1 = std::make_shared<dht::Value>();
v1->id = 10;
v1->data = {'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() {
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
auto v1 = std::make_shared<dht::Value>();
v1->id = 20;
v1->data = {'B'};
// Add same value twice (simulating two sources)
cache.onValuesAdded({v1});
cache.onValuesAdded({v1});
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
// Expire once
cache.onValuesExpired({v1});
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
CPPUNIT_ASSERT(cache.get(20));
// Expire twice
cache.onValuesExpired({v1});
CPPUNIT_ASSERT_EQUAL((size_t)0, cache.size());
}
void
OpCacheTester::testUpdateSequence() {
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
auto v1 = std::make_shared<dht::Value>();
v1->id = 30;
v1->seq = 10;
v1->data = {'C'};
auto v2 = std::make_shared<dht::Value>();
v2->id = 30;
v2->seq = 20;
v2->data = {'D'};
auto v3 = std::make_shared<dht::Value>();
v3->id = 30;
v3->seq = 5;
v3->data = {'E'};
// Add seq 10
cache.onValuesAdded({v1});
auto stored = cache.get(30);
CPPUNIT_ASSERT_EQUAL((uint16_t)10, stored->seq);
// Add seq 20 (update)
cache.onValuesAdded({v2});
stored = cache.get(30);
CPPUNIT_ASSERT_EQUAL((uint16_t)20, stored->seq);
// Add seq 5 (older, should not update content but increment refCount)
cache.onValuesAdded({v3});
stored = cache.get(30);
CPPUNIT_ASSERT_EQUAL((uint16_t)20, stored->seq);
// We added 3 times. Should need 3 expires to remove.
cache.onValuesExpired({v1}); // 2 left
CPPUNIT_ASSERT(cache.get(30));
cache.onValuesExpired({v2}); // 1 left
CPPUNIT_ASSERT(cache.get(30));
cache.onValuesExpired({v3}); // 0 left
CPPUNIT_ASSERT(!cache.get(30));
}
void
OpCacheTester::testCallbacks() {
int addCount = 0;
int expireCount = 0;
dht::OpValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired){
if (expired) expireCount += vals.size();
else addCount += vals.size();
return true;
});
auto v1 = std::make_shared<dht::Value>();
v1->id = 40;
cache.onValuesAdded({v1});
CPPUNIT_ASSERT_EQUAL(1, addCount);
CPPUNIT_ASSERT_EQUAL(0, expireCount);
// Adding same value again should NOT trigger callback (no new value added)
// Wait, looking at code:
// if (viop.second) { newValues.emplace_back(v); } ...
// return newValues.empty() ? true : callback(newValues, false);
// So if it's a duplicate or update, does it callback?
// If update: newValues.emplace_back(v) IS called.
// If duplicate (refCount++ only): newValues is NOT added.
cache.onValuesAdded({v1});
CPPUNIT_ASSERT_EQUAL(1, addCount); // Should still be 1
auto v2 = std::make_shared<dht::Value>();
v2->id = 40;
v2->seq = 100; // Update
v2->data = {'M', 'O', 'D'}; // Change data to ensure inequality
cache.onValuesAdded({v2});
CPPUNIT_ASSERT_EQUAL(2, addCount); // Should be 2 now (update triggers callback)
cache.onValuesExpired({v1});
CPPUNIT_ASSERT_EQUAL(0, expireCount); // RefCount dec, not removed yet
cache.onValuesExpired({v1});
CPPUNIT_ASSERT_EQUAL(0, expireCount); // RefCount dec, not removed yet
cache.onValuesExpired({v1});
CPPUNIT_ASSERT_EQUAL(1, expireCount); // Removed now
}
void
OpCacheTester::testFilters() {
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
auto v1 = std::make_shared<dht::Value>();
v1->id = 50;
v1->type = 1;
auto v2 = std::make_shared<dht::Value>();
v2->id = 51;
v2->type = 2;
cache.onValuesAdded({v1, v2});
auto f1 = dht::Value::TypeFilter(1);
auto res1 = cache.get(f1);
CPPUNIT_ASSERT_EQUAL((size_t)1, res1.size());
CPPUNIT_ASSERT_EQUAL(v1->id, res1[0]->id);
auto f2 = dht::Value::TypeFilter(2);
auto res2 = cache.get(f2);
CPPUNIT_ASSERT_EQUAL((size_t)1, res2.size());
CPPUNIT_ASSERT_EQUAL(v2->id, res2[0]->id);
auto all = cache.getValues();
CPPUNIT_ASSERT_EQUAL((size_t)2, all.size());
}
void
OpCacheTester::testSyncStatus() {
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
// Initial state
CPPUNIT_ASSERT(!cache.isSynced());
// Add a node
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
// 1 node, 0 synced -> not synced
CPPUNIT_ASSERT(!cache.isSynced());
// Sync the node
cache.onNodeChanged(dht::ListenSyncStatus::SYNCED);
// 1 node, 1 synced -> synced
CPPUNIT_ASSERT(cache.isSynced());
// Add another node
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
// 2 nodes, 1 synced -> not synced
CPPUNIT_ASSERT(!cache.isSynced());
// Sync second node
cache.onNodeChanged(dht::ListenSyncStatus::SYNCED);
// 2 nodes, 2 synced -> synced
CPPUNIT_ASSERT(cache.isSynced());
// Unsync one
cache.onNodeChanged(dht::ListenSyncStatus::UNSYNCED);
CPPUNIT_ASSERT(!cache.isSynced());
// Remove one (the unsynced one)
cache.onNodeChanged(dht::ListenSyncStatus::REMOVED);
// 1 node, 1 synced -> synced
CPPUNIT_ASSERT(cache.isSynced());
}
} // namespace test
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2025 Savoir-faire Linux Inc.
*
* Author: GitHub Copilot
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
namespace test {
class OpCacheTester : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(OpCacheTester);
CPPUNIT_TEST(testUpdateRefCount);
CPPUNIT_TEST(testBasicAddExpire);
CPPUNIT_TEST(testMultipleSources);
CPPUNIT_TEST(testUpdateSequence);
CPPUNIT_TEST(testCallbacks);
CPPUNIT_TEST(testFilters);
CPPUNIT_TEST(testSyncStatus);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void testUpdateRefCount();
void testBasicAddExpire();
void testMultipleSources();
void testUpdateSequence();
void testCallbacks();
void testFilters();
void testSyncStatus();
};
} // namespace test
+103
View File
@@ -0,0 +1,103 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "peerdiscoverytester.h"
#include "opendht/value.h"
#include <mutex>
#include <condition_variable>
namespace test {
using namespace std::literals;
constexpr unsigned MULTICAST_PORT = 2222;
constexpr auto DHT_NODE_NAME {"dht"sv};
constexpr auto JAMI_NODE_NAME {"jami"sv};
struct DhtNode
{
dht::InfoHash nodeid;
in_port_t node_port;
dht::NetId nid;
MSGPACK_DEFINE(nodeid, node_port, nid)
};
struct JamiNode
{
int num;
char cha;
std::string str;
MSGPACK_DEFINE(num, cha, str)
};
CPPUNIT_TEST_SUITE_REGISTRATION(PeerDiscoveryTester);
void
PeerDiscoveryTester::setUp()
{}
void
PeerDiscoveryTester::testMulticastToTwoNodes()
{
DhtNode dhtNode;
dhtNode.nid = 10;
dhtNode.node_port = 50000;
dhtNode.nodeid = dht::InfoHash::get("opendht01");
JamiNode jamiNode;
jamiNode.num = 100;
jamiNode.cha = 'a';
jamiNode.str = "jami01";
std::mutex lock;
std::condition_variable cv;
unsigned countDht {0};
unsigned countJami {0};
{
std::unique_lock<std::mutex> l(lock);
dht::PeerDiscovery testDht(MULTICAST_PORT);
dht::PeerDiscovery testJami(MULTICAST_PORT);
testJami.startDiscovery<DhtNode>(DHT_NODE_NAME, [&](DhtNode&& v, dht::SockAddr&&) {
CPPUNIT_ASSERT_EQUAL(dhtNode.node_port, v.node_port);
CPPUNIT_ASSERT_EQUAL(dhtNode.nodeid, v.nodeid);
CPPUNIT_ASSERT_EQUAL(dhtNode.nid, v.nid);
{
std::lock_guard<std::mutex> l(lock);
countDht++;
}
cv.notify_all();
});
testJami.startDiscovery(JAMI_NODE_NAME, [&](msgpack::object&& obj, dht::SockAddr&&) {
auto v = obj.as<JamiNode>();
CPPUNIT_ASSERT_EQUAL(jamiNode.num, v.num);
CPPUNIT_ASSERT_EQUAL(jamiNode.cha, v.cha);
CPPUNIT_ASSERT_EQUAL(jamiNode.str, v.str);
{
std::lock_guard<std::mutex> l(lock);
countJami++;
}
cv.notify_all();
});
testDht.startPublish(DHT_NODE_NAME, dhtNode);
CPPUNIT_ASSERT(cv.wait_for(l, std::chrono::seconds(5), [&] { return countDht > 0; }));
testDht.startPublish(JAMI_NODE_NAME, jamiNode);
CPPUNIT_ASSERT(cv.wait_for(l, std::chrono::seconds(5), [&] { return countDht > 1 and countJami > 0; }));
// we don't verify count values since its a continious multicasting
l.unlock();
testDht.stopPublish(DHT_NODE_NAME);
testDht.stopPublish(JAMI_NODE_NAME);
testJami.stopDiscovery(DHT_NODE_NAME);
testJami.stopDiscovery(JAMI_NODE_NAME);
}
}
void
PeerDiscoveryTester::tearDown()
{}
} // namespace test
File renamed without changes.
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_storage.h"
#include "storagetester.h"
#include <opendht/thread_pool.h>
File renamed without changes.
-330
View File
@@ -1,330 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_dhtproxy.h"
// std
#include <iostream>
#include <string>
#include <chrono>
#include <condition_variable>
using namespace std::chrono_literals;
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(DhtProxyTester);
void
DhtProxyTester::setUp()
{
clientConfig.dht_config.node_config.max_peer_req_per_sec = -1;
clientConfig.dht_config.node_config.max_req_per_sec = -1;
nodePeer.run(0, clientConfig);
nodeProxy = std::make_shared<dht::DhtRunner>();
nodeProxy->run(0, clientConfig);
auto bound = nodePeer.getBound();
if (bound.isUnspecified())
bound.setLoopback();
nodeProxy->bootstrap(bound);
// auto serverCAIdentity = dht::crypto::generateEcIdentity("DHT Node CA");
uint16_t port = 1024 + (std::rand() % (65535 - 1024));
dht::ProxyServerConfig serverConfig;
// serverConfig.identity = dht::crypto::generateIdentity("DHT Node", serverCAIdentity);
serverConfig.port = port;
// serverConfig.pushServer = "127.0.0.1:8090";
serverProxy = std::make_unique<dht::DhtProxyServer>(nodeProxy, serverConfig);
/*clientConfig.server_ca = serverCAIdentity.second;
clientConfig.client_identity = dht::crypto::generateIdentity("DhtProxyTester");
clientConfig.push_node_id = "dhtnode";*/
clientConfig.proxy_server = "http://127.0.0.1:" + std::to_string(port); //"https://127.0.0.1:8080";
}
void
DhtProxyTester::tearDown()
{
nodePeer.join();
nodeClient.join();
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
nodeProxy->shutdown([&] {
std::lock_guard lk(cv_m);
done = true;
cv.notify_all();
});
std::unique_lock lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&] { return done; }));
serverProxy.reset();
nodeProxy.reset();
}
void
DhtProxyTester::testGetPut()
{
nodeClient.run(0, clientConfig);
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
auto key = dht::InfoHash::get("GLaDOs");
dht::Value val {"Hey! It's been a long time. How have you been?"};
auto val_data = val.data;
{
nodePeer.put(key, std::move(val), [&](bool) {
std::lock_guard lk(cv_m);
done = true;
cv.notify_all();
});
std::unique_lock lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
}
auto vals = nodeClient.get(key).get();
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtProxyTester::testListen()
{
nodeClient.run(0, clientConfig);
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
bool done = false;
// If a peer send a value, the listen operation from the client
// should retrieve this value
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(cv_m);
done = true;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
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);
for (const auto& value : v)
values.emplace_back(value->data);
done = true;
cv.notify_all();
}
return true;
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
// Here values should contains 1 values
CPPUNIT_ASSERT_EQUAL(static_cast<int>(values.size()), 1);
CPPUNIT_ASSERT(values.front() == firstVal_data);
// And the listen should retrieve futures values
// All values
dht::Value secondVal {"You're a monster"};
auto secondVal_data = secondVal.data;
nodePeer.put(key, std::move(secondVal));
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
nodeClient.cancelListen(key, std::move(token));
// Here values should contains 2 values
CPPUNIT_ASSERT_EQUAL(static_cast<int>(values.size()), 2);
CPPUNIT_ASSERT(values.back() == secondVal_data);
}
void
DhtProxyTester::testResubscribeGetValues()
{
clientConfig.push_token = "atlas";
nodeClient.run(0, clientConfig);
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
auto key = dht::InfoHash::get("GLaDOs");
// If a peer sent a value, the listen operation from the client
// should retrieve this value
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
auto firstVal_data = firstVal.data;
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
std::lock_guard lk(cv_m);
CPPUNIT_ASSERT(ok);
done = true;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
done = false;
// Send a first subscribe, the value is sent via a push notification
// So ignore values here.
nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>&, bool) { return true; });
cv.wait_for(lk, std::chrono::seconds(1));
// Reboot node (to avoid cache)
nodeClient.join();
clientConfig.push_token = "";
nodeClient.run(0, clientConfig);
// For the second subscribe, the proxy will return the value in the body
std::vector<std::shared_ptr<dht::Value>> values;
auto ftoken = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
if (not expired) {
std::lock_guard lk(cv_m);
values.insert(values.end(), v.begin(), v.end());
done = true;
cv.notify_all();
}
return true;
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
auto token = ftoken.get();
CPPUNIT_ASSERT(token);
nodeClient.cancelListen(key, token);
// Here values should still contains 1 values
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
CPPUNIT_ASSERT(firstVal_data == values.front()->data);
}
void
DhtProxyTester::testPutGet40KChars()
{
nodeClient.run(0, clientConfig);
constexpr size_t N = 40000;
// Arrange
auto key = dht::InfoHash::get("testPutGet40KChars");
std::vector<std::shared_ptr<dht::Value>> values;
std::vector<uint8_t> mtu;
mtu.reserve(N);
for (size_t i = 0; i < N; i++)
mtu.emplace_back((i % 2) ? 'T' : 'M');
std::condition_variable cv;
std::mutex cv_m;
std::unique_lock lk(cv_m);
bool done_put = false;
bool done_get = false;
// Act
dht::Value val {mtu};
nodePeer.put(key, std::move(val), [&](bool ok) {
std::lock_guard lk(cv_m);
done_put = ok;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done_put; }));
nodeClient.get(
key,
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
values.insert(values.end(), vals.begin(), vals.end());
return true;
},
[&](bool ok) {
std::lock_guard lk(cv_m);
done_get = ok;
cv.notify_all();
});
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done_get; }));
// Assert
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
for (const auto& value : values)
CPPUNIT_ASSERT(value->data == mtu);
}
void
DhtProxyTester::testFuzzy()
{
constexpr size_t N = 40000;
// Arrange
auto key = dht::InfoHash::get("testFuzzy");
std::vector<std::shared_ptr<dht::Value>> values;
std::vector<uint8_t> mtu;
mtu.reserve(N);
for (size_t i = 0; i < N; i++)
mtu.emplace_back((i % 2) ? 'T' : 'M');
// Act
for (size_t i = 0; i < 100; i++) {
auto nodeTest = std::make_shared<dht::DhtRunner>();
nodeTest->run(0, clientConfig);
nodeTest->put(key, dht::Value(mtu), [&](bool ok) { CPPUNIT_ASSERT(ok); });
nodeTest->get(
key,
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
values.insert(values.end(), vals.begin(), vals.end());
return true;
},
[&](bool ok) { CPPUNIT_ASSERT(ok); });
std::this_thread::sleep_for(5ms);
}
// Assert
for (const auto& value : values)
CPPUNIT_ASSERT(value->data == mtu);
}
void
DhtProxyTester::testShutdownStop()
{
constexpr size_t N = 40000;
constexpr unsigned C = 100;
// Arrange
auto key = dht::InfoHash::get("testShutdownStop");
std::vector<std::shared_ptr<dht::Value>> values;
std::vector<uint8_t> mtu;
mtu.reserve(N);
for (size_t i = 0; i < N; i++)
mtu.emplace_back((i % 2) ? 'T' : 'M');
std::atomic_uint callback_count {0};
// Act
for (size_t i = 0; i < C; i++) {
auto nodeTest = std::make_shared<dht::DhtRunner>();
nodeTest->run(0, clientConfig);
nodeTest->put(key, dht::Value(mtu), [&](bool /*ok*/) { callback_count++; });
nodeTest->get(
key,
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
values.insert(values.end(), vals.begin(), vals.end());
return true;
},
[&](bool /*ok*/) { callback_count++; });
bool done = false;
std::condition_variable cv;
std::mutex cv_m;
nodeTest->shutdown(
[&] {
std::lock_guard lk(cv_m);
done = true;
cv.notify_all();
},
true);
std::unique_lock lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
}
CPPUNIT_ASSERT_EQUAL(2 * C, callback_count.load());
}
} // namespace test
-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 {OpValueCache([](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(OpValueCache(
[](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
-936
View File
@@ -1,936 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_dhtrunner.h"
#include <opendht/thread_pool.h>
#include <chrono>
#include <future>
#include <mutex>
#include <condition_variable>
#include <fstream>
#include <cstdio>
using namespace std::chrono_literals;
using namespace std::literals;
namespace test {
CPPUNIT_TEST_SUITE_REGISTRATION(DhtRunnerTester);
template<typename T>
T
getFutureValue(std::future<T> future, std::chrono::steady_clock::duration timeout = 30s)
{
CPPUNIT_ASSERT(std::future_status::ready == future.wait_for(timeout));
return future.get();
}
void
DhtRunnerTester::setUp()
{
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
node1.run(0, config);
node2.run(0, config);
auto bound = node1.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node2.bootstrap(bound);
}
void
DhtRunnerTester::tearDown()
{
unsigned done {0};
std::condition_variable cv;
std::mutex cv_m;
auto shutdown = [&] {
std::lock_guard lk(cv_m);
done++;
cv.notify_all();
};
node1.shutdown(shutdown);
node2.shutdown(shutdown);
std::unique_lock lk(cv_m);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return done == 2u; }));
node1.join();
node2.join();
}
void
DhtRunnerTester::testConstructors()
{
CPPUNIT_ASSERT(node1.getBoundPort());
CPPUNIT_ASSERT_EQUAL(node1.getBoundPort(), node1.getBound().getPort());
CPPUNIT_ASSERT(node2.getBoundPort());
CPPUNIT_ASSERT_EQUAL(node2.getBoundPort(), node2.getBound().getPort());
dht::DhtRunner::Config config {};
dht::DhtRunner::Context context {};
dht::DhtRunner testNode;
testNode.run(config, std::move(context));
CPPUNIT_ASSERT(testNode.getBoundPort());
}
void
DhtRunnerTester::testGetPut()
{
auto key = dht::InfoHash::get("123");
dht::Value val {"hey"};
auto val_data = val.data;
std::promise<bool> p;
auto future = p.get_future();
node2.put(key, std::move(val), [&](bool ok) { p.set_value(ok); });
CPPUNIT_ASSERT(getFutureValue(std::move(future)));
auto vals = getFutureValue(node1.get(key));
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtRunnerTester::testPutDuplicate()
{
auto key = dht::InfoHash::get("123");
auto val = std::make_shared<dht::Value>("hey");
val->id = 42;
auto val_data = val->data;
std::promise<bool> p1;
std::promise<bool> p2;
auto p1future = p1.get_future();
auto p2future = p2.get_future();
node2.put(key, val, [&](bool ok) { p1.set_value(ok); });
node2.put(key, val, [&](bool ok) { p2.set_value(ok); });
auto p1ret = getFutureValue(std::move(p1future));
auto p2ret = getFutureValue(std::move(p2future));
CPPUNIT_ASSERT(p1ret);
CPPUNIT_ASSERT(p2ret);
auto vals = getFutureValue(node1.get(key));
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.size() == 1);
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtRunnerTester::testPutOverride()
{
auto key = dht::InfoHash::get("123");
auto val = std::make_shared<dht::Value>("meh");
val->id = 42;
auto val2 = std::make_shared<dht::Value>("hey");
val2->id = 42;
CPPUNIT_ASSERT_EQUAL(val->id, val2->id);
auto val_data = val2->data;
std::promise<bool> p1;
std::promise<bool> p2;
auto p1future = p1.get_future();
auto p2future = p2.get_future();
node2.put(key, val, [&](bool ok) { p1.set_value(ok); });
node2.put(key, val2, [&](bool ok) { p2.set_value(ok); });
auto p1ret = getFutureValue(std::move(p1future));
auto p2ret = getFutureValue(std::move(p2future));
CPPUNIT_ASSERT(!p1ret);
CPPUNIT_ASSERT(p2ret);
auto vals = getFutureValue(node1.get(key));
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.size() == 1);
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
void
DhtRunnerTester::testImportValuesPreservesRemoteQuota()
{
node1.shutdown();
node1.join();
node2.shutdown();
node2.join();
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = 1536;
config.dht_config.node_config.max_local_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
node1.run(0, config);
node2.run(0, config);
auto bound = node1.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node2.bootstrap(bound);
std::this_thread::sleep_for(1s);
auto key1 = dht::InfoHash::get("import-quota-1");
auto key2 = dht::InfoHash::get("import-quota-2");
auto value1 = std::make_shared<dht::Value>(std::string(1024, 'a'));
auto value2 = std::make_shared<dht::Value>(std::string(1024, 'b'));
std::promise<bool> p1;
auto p1future = p1.get_future();
node2.put(key1, value1, [&](bool ok) { p1.set_value(ok); });
CPPUNIT_ASSERT(getFutureValue(std::move(p1future)));
std::this_thread::sleep_for(2s);
auto exported = node1.exportValues();
CPPUNIT_ASSERT(!exported.empty());
node1.shutdown();
node1.join();
dht::DhtRunner restored;
restored.run(0, config);
auto restoredBound = restored.getBound();
if (restoredBound.isUnspecified())
restoredBound.setLoopback();
node2.shutdown();
node2.join();
node2.run(0, config);
node2.bootstrap(restoredBound);
std::this_thread::sleep_for(1s);
restored.importValues(exported);
std::this_thread::sleep_for(500ms);
std::promise<bool> p2;
auto p2future = p2.get_future();
node2.put(key2, value2, [&](bool ok) { p2.set_value(ok); });
CPPUNIT_ASSERT(getFutureValue(std::move(p2future)));
std::this_thread::sleep_for(2s);
auto vals1 = getFutureValue(restored.get(key1));
auto vals2 = getFutureValue(restored.get(key2));
CPPUNIT_ASSERT(vals1.size() + vals2.size() <= 1);
restored.shutdown();
restored.join();
}
void
DhtRunnerTester::testExportValuesUsesSystemTime()
{
auto key = dht::InfoHash::get("export-system-time");
const auto created = dht::clock::now();
msgpack::sbuffer importBuffer;
msgpack::packer<msgpack::sbuffer> pk(&importBuffer);
pk.pack_array(1);
pk.pack_array(2);
pk.pack(created.time_since_epoch().count());
dht::Value {std::string("persisted")}.msgpack_pack(pk);
node1.importValues({
dht::ValuesExport {key, {importBuffer.data(), importBuffer.data() + importBuffer.size()}}
});
auto exported = node1.exportValues();
CPPUNIT_ASSERT_EQUAL(size_t(1), exported.size());
msgpack::unpacked msg;
msgpack::unpack(msg, reinterpret_cast<const char*>(exported.front().second.data()), exported.front().second.size());
const auto values = msg.get();
CPPUNIT_ASSERT(values.type == msgpack::type::ARRAY);
CPPUNIT_ASSERT_EQUAL(uint32_t(1), values.via.array.size);
const auto& persistedValue = values.via.array.ptr[0];
CPPUNIT_ASSERT_EQUAL(uint32_t(4), persistedValue.via.array.size);
CPPUNIT_ASSERT(persistedValue.via.array.ptr[0].type == msgpack::type::EXT);
CPPUNIT_ASSERT(persistedValue.via.array.ptr[3].type == msgpack::type::EXT);
const auto persistedCreated = persistedValue.via.array.ptr[0].as<std::chrono::system_clock::time_point>();
const auto persistedExpiration = persistedValue.via.array.ptr[3].as<std::chrono::system_clock::time_point>();
const auto delta = std::chrono::system_clock::now() - persistedCreated;
CPPUNIT_ASSERT(delta >= std::chrono::system_clock::duration::zero());
CPPUNIT_ASSERT(delta < 5s);
CPPUNIT_ASSERT(persistedExpiration > persistedCreated);
}
void
DhtRunnerTester::testImportValuesPreservesStoredExpiration()
{
static const dht::ValueType SHORT_LIVED_TYPE {200, "Short lived", 2s};
node1.registerInsecureType(SHORT_LIVED_TYPE);
auto key = dht::InfoHash::get("import-expiration");
auto now = dht::clock::now();
auto created = now;
auto expiration = now + SHORT_LIVED_TYPE.expiration;
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_array(1);
pk.pack_array(4);
pk.pack(created.time_since_epoch().count());
dht::Value {SHORT_LIVED_TYPE.id, std::string("short-lived")}.msgpack_pack(pk);
pk.pack_bin(0);
pk.pack(expiration.time_since_epoch().count());
node1.importValues({
dht::ValuesExport {key, {buffer.data(), buffer.data() + buffer.size()}}
});
auto exported = node1.exportValues();
CPPUNIT_ASSERT(!exported.empty());
dht::DhtRunner restored;
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
restored.run(0, config);
restored.importValues(exported);
CPPUNIT_ASSERT(!restored.exportValues().empty());
std::this_thread::sleep_for(3s);
auto restoredValues = restored.exportValues();
CPPUNIT_ASSERT_EQUAL(size_t(1), restoredValues.size());
msgpack::unpacked msg;
msgpack::unpack(msg, (const char*) restoredValues.front().second.data(), restoredValues.front().second.size());
auto valarr = msg.get();
CPPUNIT_ASSERT(valarr.type == msgpack::type::ARRAY);
CPPUNIT_ASSERT_EQUAL(uint32_t(0), valarr.via.array.size);
restored.shutdown();
restored.join();
}
void
DhtRunnerTester::testImportPermanentValueExpires()
{
static const dht::ValueType SHORT_TYPE {201, "Short perm", 500ms};
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
dht::DhtRunner restored;
restored.run(0, config);
restored.registerInsecureType(SHORT_TYPE);
// Ensure the runner is waiting on a previously computed scheduler deadline.
std::promise<void> loopCompleted;
auto loopCompletedFuture = loopCompleted.get_future();
restored.getNodeInfo([&loopCompleted](auto) { loopCompleted.set_value(); });
getFutureValue(std::move(loopCompletedFuture));
auto key = dht::InfoHash::get("import-permanent-expiration");
auto now = dht::clock::now();
msgpack::sbuffer buffer;
msgpack::packer<msgpack::sbuffer> pk(&buffer);
pk.pack_array(1);
pk.pack_array(4);
pk.pack(now.time_since_epoch().count());
dht::Value {SHORT_TYPE.id, std::string("was-permanent")}.msgpack_pack(pk);
pk.pack_bin(0);
pk.pack(dht::clock::time_point::max().time_since_epoch().count());
restored.importValues({
dht::ValuesExport {key, {buffer.data(), buffer.data() + buffer.size()}}
});
auto containsValue = [&] {
for (const auto& [exportedKey, data] : restored.exportValues()) {
if (exportedKey != key)
continue;
msgpack::unpacked msg;
msgpack::unpack(msg, reinterpret_cast<const char*>(data.data()), data.size());
auto valarr = msg.get();
CPPUNIT_ASSERT(valarr.type == msgpack::type::ARRAY);
return valarr.via.array.size != 0;
}
return false;
};
CPPUNIT_ASSERT(containsValue());
auto timeout = dht::clock::now() + 2s;
while (containsValue() and dht::clock::now() < timeout)
std::this_thread::sleep_for(10ms);
CPPUNIT_ASSERT(!containsValue());
restored.shutdown();
restored.join();
}
void
DhtRunnerTester::testBootstrapSetsConnectingState()
{
node1.shutdown();
node1.join();
node2.shutdown();
node2.join();
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
std::mutex mutex;
std::condition_variable cv;
bool sawConnecting {false};
dht::DhtRunner::Context context;
context.statusChangedCallback = [&](dht::NodeStatus status4, dht::NodeStatus status6) {
std::lock_guard lock(mutex);
sawConnecting = sawConnecting || status4 == dht::NodeStatus::Connecting
|| status6 == dht::NodeStatus::Connecting;
cv.notify_all();
};
dht::DhtRunner bootstrapNode;
dht::DhtRunner clientNode;
bootstrapNode.run(0, config);
clientNode.run(config, std::move(context));
auto bound = bootstrapNode.getBound();
if (bound.isUnspecified())
bound.setLoopback();
clientNode.bootstrap("127.0.0.1", std::to_string(bound.getPort()));
{
std::unique_lock lock(mutex);
CPPUNIT_ASSERT(cv.wait_for(lock, 5s, [&] { return sawConnecting; }));
}
bootstrapNode.shutdown();
bootstrapNode.join();
clientNode.shutdown();
clientNode.join();
}
void
DhtRunnerTester::testBootstrapThenPutNoRace()
{
node1.shutdown();
node1.join();
node2.shutdown();
node2.join();
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
dht::DhtRunner bootstrapNode;
dht::DhtRunner clientNode;
bootstrapNode.run(0, config);
clientNode.run(0, config);
auto bound = bootstrapNode.getBound();
if (bound.isUnspecified())
bound.setLoopback();
clientNode.bootstrap(bound);
std::promise<bool> putDone;
auto putFuture = putDone.get_future();
clientNode.put(dht::InfoHash::get("bootstrap-then-put"), dht::Value {"value"}, [&](bool ok) {
putDone.set_value(ok);
});
CPPUNIT_ASSERT(getFutureValue(std::move(putFuture)));
auto vals = getFutureValue(bootstrapNode.get(dht::InfoHash::get("bootstrap-then-put")));
CPPUNIT_ASSERT(!vals.empty());
bootstrapNode.shutdown();
bootstrapNode.join();
clientNode.shutdown();
clientNode.join();
}
void
DhtRunnerTester::testBootstrapMissingNodeThenPutFails()
{
node1.shutdown();
node1.join();
node2.shutdown();
node2.join();
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
auto getUnusedPort = [&]() {
dht::DhtRunner tmpNode;
tmpNode.run(0, config);
auto port = tmpNode.getBoundPort();
tmpNode.shutdown();
tmpNode.join();
return port;
};
auto missingPort = getUnusedPort();
std::mutex mutex;
std::condition_variable cv;
bool sawConnecting {false};
bool sawDisconnectedAfterConnecting {false};
bool putReturnedBeforeDisconnect {false};
dht::DhtRunner::Context context;
context.statusChangedCallback = [&](dht::NodeStatus status4, dht::NodeStatus status6) {
std::lock_guard lock(mutex);
if (status4 == dht::NodeStatus::Connecting || status6 == dht::NodeStatus::Connecting)
sawConnecting = true;
if (sawConnecting && status4 == dht::NodeStatus::Disconnected && status6 == dht::NodeStatus::Disconnected)
sawDisconnectedAfterConnecting = true;
cv.notify_all();
};
dht::DhtRunner clientNode;
clientNode.run(0, config, std::move(context));
while (clientNode.getBoundPort() == missingPort)
missingPort = getUnusedPort();
std::promise<bool> putDone;
auto putFuture = putDone.get_future();
clientNode.bootstrap("127.0.0.1", std::to_string(missingPort));
clientNode.put(dht::InfoHash::get("bootstrap-missing-put"), dht::Value {"value"}, [&](bool ok) {
std::lock_guard lock(mutex);
putReturnedBeforeDisconnect = !sawDisconnectedAfterConnecting;
putDone.set_value(ok);
cv.notify_all();
});
{
std::unique_lock lock(mutex);
CPPUNIT_ASSERT(cv.wait_for(lock, 10s, [&] { return sawConnecting; }));
CPPUNIT_ASSERT(cv.wait_for(lock, 20s, [&] { return sawDisconnectedAfterConnecting; }));
}
CPPUNIT_ASSERT(std::future_status::ready == putFuture.wait_for(20s));
CPPUNIT_ASSERT(!putFuture.get());
CPPUNIT_ASSERT(!putReturnedBeforeDisconnect);
clientNode.shutdown();
clientNode.join();
}
void
DhtRunnerTester::testShutdownCompletesWithPendingPut()
{
node1.shutdown();
node1.join();
node2.shutdown();
node2.join();
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
config.dht_config.node_config.max_store_size = -1;
config.dht_config.node_config.max_store_keys = -1;
auto getUnusedPort = [&]() {
dht::DhtRunner tmpNode;
tmpNode.run(0, config);
auto port = tmpNode.getBoundPort();
tmpNode.shutdown();
tmpNode.join();
return port;
};
auto missingPort = getUnusedPort();
dht::DhtRunner clientNode;
clientNode.run(0, config);
while (clientNode.getBoundPort() == missingPort)
missingPort = getUnusedPort();
clientNode.bootstrap("127.0.0.1", std::to_string(missingPort));
clientNode.put(dht::InfoHash::get("shutdown-pending-put"), dht::Value {"value"}, [](bool) {});
std::promise<void> shutdownDone;
auto shutdownFuture = shutdownDone.get_future();
clientNode.shutdown([&]() { shutdownDone.set_value(); });
CPPUNIT_ASSERT(std::future_status::ready == shutdownFuture.wait_for(5s));
clientNode.join();
}
void
DhtRunnerTester::testSavedPortFallback()
{
// A saved port that is already taken by another process (e.g. fast
// application restart while the old instance still holds it) must not
// prevent the node from starting: it should fall back to a random port.
dht::DhtRunner::Config config;
config.dht_config.node_config.persist_path = "/tmp/opendht_test_port_fallback";
std::string portFile = config.dht_config.node_config.persist_path + "_port.txt";
// node1 (from setUp) holds a port; save it as our persisted port.
auto takenPort = node1.getBoundPort();
{
std::ofstream out(portFile);
out << takenPort << std::endl << takenPort << std::endl;
}
dht::DhtRunner testNode;
CPPUNIT_ASSERT_NO_THROW(testNode.run(0, config));
CPPUNIT_ASSERT(testNode.getBoundPort());
CPPUNIT_ASSERT(testNode.getBoundPort() != takenPort);
testNode.shutdown();
testNode.join();
std::remove(portFile.c_str());
std::remove((config.dht_config.node_config.persist_path + "_port.txt").c_str());
}
void
DhtRunnerTester::testListen()
{
std::mutex mutex;
std::condition_variable cv;
std::atomic_uint valueCounta(0);
std::atomic_uint valueCountb(0);
std::atomic_uint valueCountc(0);
std::atomic_uint valueCountd(0);
unsigned putCount(0);
unsigned putOkCount1(0);
unsigned putOkCount2(0);
unsigned putOkCount3(0);
auto a = dht::InfoHash::get("234");
auto b = dht::InfoHash::get("2345");
auto c = dht::InfoHash::get("23456");
auto d = dht::InfoHash::get("234567");
constexpr unsigned N = 256;
constexpr unsigned SZ = 56 * 1024;
auto ftokena = node1.listen(a, [&](const std::vector<std::shared_ptr<dht::Value>>& values, bool expired) {
if (expired)
valueCounta -= values.size();
else
valueCounta += values.size();
return true;
});
auto ftokenb = node1.listen(b, [&](const std::shared_ptr<dht::Value>&) {
/*if (expired)
valueCountb -= values.size();
else
valueCountb += values.size();*/
valueCountb++;
return false;
});
auto ftokenc = node1.listen(c, [&](const std::vector<std::shared_ptr<dht::Value>>& values, bool expired) {
if (expired)
valueCountc -= values.size();
else
valueCountc += values.size();
return true;
});
auto ftokend = node1.listen(d, [&](const std::vector<std::shared_ptr<dht::Value>>& values, bool expired) {
if (expired)
valueCountd -= values.size();
else
valueCountd += values.size();
return true;
});
std::vector<uint8_t> mtu;
mtu.reserve(SZ);
for (size_t i = 0; i < SZ; i++)
mtu.emplace_back((i % 2) ? 'T' : 'M');
for (unsigned i = 0; i < N; i++) {
node2.put(a, dht::Value("v1"), [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount1++;
cv.notify_all();
});
node2.put(b, dht::Value("v2"), [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount2++;
cv.notify_all();
});
auto bigVal = std::make_shared<dht::Value>();
bigVal->data = mtu;
node2.put(c, std::move(bigVal), [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount3++;
cv.notify_all();
});
}
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return putCount == N * 3u; }));
CPPUNIT_ASSERT_EQUAL(N, putOkCount1);
CPPUNIT_ASSERT_EQUAL(N, putOkCount2);
CPPUNIT_ASSERT_EQUAL(N, putOkCount3);
}
CPPUNIT_ASSERT(ftokena.valid());
CPPUNIT_ASSERT(ftokenb.valid());
CPPUNIT_ASSERT(ftokenc.valid());
CPPUNIT_ASSERT(ftokend.valid());
auto tokena = getFutureValue(std::move(ftokena));
auto tokenc = getFutureValue(std::move(ftokenc));
auto tokend = getFutureValue(std::move(ftokend));
// tokenb might be 0 since the callback returns false.
CPPUNIT_ASSERT(tokena);
CPPUNIT_ASSERT(tokenc);
CPPUNIT_ASSERT(tokend);
CPPUNIT_ASSERT_EQUAL(N, valueCounta.load());
CPPUNIT_ASSERT_EQUAL(1u, valueCountb.load());
CPPUNIT_ASSERT_EQUAL(N, valueCountc.load());
CPPUNIT_ASSERT_EQUAL(0u, valueCountd.load());
node1.cancelListen(a, tokena);
node1.cancelListen(b, std::move(ftokenb));
node1.cancelListen(c, tokenc);
node1.cancelListen(d, tokend);
}
void
DhtRunnerTester::testIdOps()
{
std::mutex mutex;
std::condition_variable cv;
unsigned identityCount(0);
unsigned valueCount(0);
unsigned valueCountEdit(0);
dht::DhtRunner::Config config2;
config2.dht_config.node_config.max_peer_req_per_sec = -1;
config2.dht_config.node_config.max_req_per_sec = -1;
config2.dht_config.id = dht::crypto::generateIdentity();
dht::DhtRunner::Context context2;
context2.identityAnnouncedCb = [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
identityCount++;
cv.notify_all();
};
node2.join();
node2.run(0, config2, std::move(context2));
auto bound = node1.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node2.bootstrap(bound);
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return identityCount == 1; }));
}
node1.findCertificate(node2.getPublicKey()->getLongId(), [&](const std::shared_ptr<dht::crypto::Certificate>& crt) {
CPPUNIT_ASSERT(crt);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
});
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCount == 1u; }));
CPPUNIT_ASSERT_EQUAL(1u, identityCount);
}
dht::DhtRunner::Context context1;
context1.identityAnnouncedCb = [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
identityCount++;
cv.notify_all();
};
config2.dht_config.id = dht::crypto::generateIdentity();
node1.join();
node1.run(0, config2, std::move(context1));
bound = node2.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node1.bootstrap(bound);
auto key = dht::InfoHash::get("key");
node1.putEncrypted(key, node2.getPublicKey()->getLongId(), std::make_shared<dht::Value>("yo"), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
});
node1.putEncrypted(key, node2.getPublicKey(), dht::Value("yo"), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
});
node2.listen<std::string>(key, [&](std::string&& value) {
CPPUNIT_ASSERT_EQUAL("yo"s, value);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
return true;
});
auto key2 = dht::InfoHash::get("key2");
auto editValue = std::make_shared<dht::Value>("v1");
node1.putSigned(key2, editValue, [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCountEdit++;
cv.notify_all();
});
node2.listen(key2, [&](const std::vector<std::shared_ptr<dht::Value>>& values, bool /*expired*/) {
for (const auto& v : values) {
if (v->seq == 0)
CPPUNIT_ASSERT_EQUAL("v1"s, dht::unpackMsg<std::string>(v->data));
else if (v->seq == 1)
CPPUNIT_ASSERT_EQUAL("v2"s, dht::unpackMsg<std::string>(v->data));
CPPUNIT_ASSERT_EQUAL(v->owner->getLongId(), node1.getPublicKey()->getLongId());
}
std::lock_guard lk(mutex);
valueCountEdit += values.size();
cv.notify_all();
return true;
});
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return identityCount == 2u; }));
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCount == 5u; }));
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCountEdit == 2u; }));
}
node2.findCertificate(node1.getPublicKey()->getLongId(), [&](const std::shared_ptr<dht::crypto::Certificate>& crt) {
CPPUNIT_ASSERT(crt);
std::lock_guard lk(mutex);
valueCount++;
cv.notify_all();
});
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCount == 6u; }));
}
// editValue->data = dht::packMsg("v2");
editValue = std::make_shared<dht::Value>(editValue->id);
editValue->data = dht::packMsg("v2");
node1.putSigned(key2, editValue, [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCountEdit++;
cv.notify_all();
});
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return valueCountEdit == 4u; }));
}
void
DhtRunnerTester::testListenLotOfBytes()
{
std::mutex mutex;
std::condition_variable cv;
std::atomic_uint valueCount(0);
unsigned putCount(0);
unsigned putOkCount(0);
std::string data(10000, 'a');
auto foo = dht::InfoHash::get("foo");
constexpr unsigned N = 1024;
for (unsigned i = 0; i < N; i++) {
node2.put(foo, data, [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount++;
cv.notify_all();
});
}
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return putCount == N; }));
}
dht::DhtRunner node3 {};
dht::DhtRunner::Config config;
config.dht_config.node_config.max_peer_req_per_sec = -1;
config.dht_config.node_config.max_req_per_sec = -1;
node3.run(42242, config);
auto bound = node1.getBound();
if (bound.isUnspecified())
bound.setLoopback();
node3.bootstrap(bound);
auto ftokenfoo = node3.listen(foo, [&](const std::shared_ptr<dht::Value>&) {
valueCount++;
cv.notify_all();
return true;
});
{
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return valueCount == N; }));
}
node3.cancelListen(foo, getFutureValue(std::move(ftokenfoo)));
}
void
DhtRunnerTester::testMultithread()
{
std::mutex mutex;
std::condition_variable cv;
unsigned putCount(0);
unsigned putOkCount(0);
constexpr unsigned N = 2048;
for (unsigned i = 0; i < N; i++) {
dht::ThreadPool::computation().run([&] {
node2.put(dht::InfoHash::get("123" + std::to_string(i)), "hehe", [&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount++;
cv.notify_all();
});
node2.get(
dht::InfoHash::get("123" + std::to_string(N - i - 1)),
[](const std::shared_ptr<dht::Value>&) { return true; },
[&](bool ok) {
std::lock_guard lock(mutex);
putCount++;
if (ok)
putOkCount++;
cv.notify_all();
});
});
}
std::unique_lock lk(mutex);
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return putCount == 2 * N; }));
CPPUNIT_ASSERT_EQUAL(2 * N, putOkCount);
}
} // namespace test
-280
View File
@@ -1,280 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_mdns.h"
#include "../src/peer_discovery/dns_sd.h"
#include "../src/peer_discovery/mdns.h"
#include "../src/peer_discovery/mdns_transport.h"
#include <array>
#include <string>
namespace test {
using namespace dht::mdns;
CPPUNIT_TEST_SUITE_REGISTRATION(MdnsTester);
void
MdnsTester::testName()
{
const auto name = Name::fromString("Node._opendht._udp.local.");
CPPUNIT_ASSERT_EQUAL(std::string("Node._opendht._udp.local."), name.toString());
CPPUNIT_ASSERT(name == Name::fromString("node._OpenDHT._UDP.LOCAL"));
CPPUNIT_ASSERT_THROW(Name::fromString("bad..local."), ParseError);
CPPUNIT_ASSERT_THROW(Name::fromString(std::string(64, 'a') + ".local."), ParseError);
}
void
MdnsTester::testRecordRoundTrip()
{
const auto service = Name::fromString("_opendht._udp.local.");
const auto instance = Name::fromString("0123456789abcdef0123456789abcdef01234567._opendht._udp.local.");
const auto host = Name::fromString("0123456789abcdef0123456789abcdef01234567.local.");
Message message;
message.response = true;
message.authoritative = true;
message.answers.push_back({service, Type::PTR, CLASS_IN, false, 4500, PtrData {instance}});
message.additionals.push_back({
instance, Type::SRV, CLASS_IN, true, 120, SrvData {0, 0, 4222, host}
});
message.additionals.push_back(
{instance,
Type::TXT,
CLASS_IN,
true,
4500,
TxtData {{{'t', 'x', 't', 'v', 'e', 'r', 's', '=', '1'}, {'n', 'e', 't', '=', '0'}}}});
message.additionals.push_back({host, Type::A, CLASS_IN, true, 120, AData {{192, 0, 2, 1}}});
message.additionals.push_back(
{host, Type::AAAA, CLASS_IN, true, 120, AaaaData {{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}});
message.additionals.push_back({
host, Type::NSEC, CLASS_IN, true, 120, NsecData {host, {Type::A, Type::AAAA}}
});
const auto packet = encode(message);
const auto decoded = decode(packet.data(), packet.size());
CPPUNIT_ASSERT(decoded.response);
CPPUNIT_ASSERT(decoded.authoritative);
CPPUNIT_ASSERT_EQUAL(size_t(1), decoded.answers.size());
CPPUNIT_ASSERT_EQUAL(size_t(5), decoded.additionals.size());
CPPUNIT_ASSERT(decoded == message);
}
void
MdnsTester::testCompression()
{
Message message;
message.questions.push_back({Name::fromString("_opendht._udp.local."), Type::PTR, CLASS_IN, false});
message.questions.push_back({Name::fromString("node._opendht._udp.local."), Type::SRV, CLASS_IN, false});
const auto packet = encode(message);
CPPUNIT_ASSERT(std::find(packet.begin(), packet.end(), uint8_t {0xc0}) != packet.end());
CPPUNIT_ASSERT(decode(packet.data(), packet.size()) == message);
}
void
MdnsTester::testMalformedPackets()
{
const std::array<uint8_t, 3> truncated {
{0, 0, 0}
};
CPPUNIT_ASSERT_THROW(decode(truncated.data(), truncated.size()), ParseError);
const std::array<uint8_t, 18> compressionLoop {
{
0, 0,
0, 0,
0, 1,
0, 0,
0, 0,
0, 0,
0xc0, 0x0c,
0, static_cast<uint8_t>(Type::PTR),
0, CLASS_IN,
}
};
CPPUNIT_ASSERT_THROW(decode(compressionLoop.data(), compressionLoop.size()), ParseError);
}
void
MdnsTester::testPacketSizeLimit()
{
std::array<uint8_t, MAX_PACKET_SIZE + 1> oversized {};
CPPUNIT_ASSERT_THROW(decode(oversized.data(), oversized.size()), ParseError);
}
void
MdnsTester::testDnsSdAdvertisement()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.network = 42;
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
service.addresses.emplace_back(AaaaData {
{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
});
const auto message = dns_sd::announcement(service);
CPPUNIT_ASSERT(message.response);
CPPUNIT_ASSERT(message.authoritative);
CPPUNIT_ASSERT_EQUAL(size_t(1), message.answers.size());
CPPUNIT_ASSERT_EQUAL(size_t(4), message.additionals.size());
CPPUNIT_ASSERT_EQUAL(std::string("_opendht._udp.local."), message.answers.front().name.toString());
const auto resolved = dns_sd::resolve(message);
CPPUNIT_ASSERT(resolved);
CPPUNIT_ASSERT_EQUAL(service.nodeId, resolved->nodeId);
CPPUNIT_ASSERT_EQUAL(service.network, resolved->network);
CPPUNIT_ASSERT_EQUAL(service.port, resolved->port);
CPPUNIT_ASSERT_EQUAL(service.addresses.size(), resolved->addresses.size());
}
void
MdnsTester::testDnsSdGoodbye()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
const auto message = dns_sd::goodbye(service);
CPPUNIT_ASSERT(std::all_of(message.answers.begin(), message.answers.end(), [](const auto& record) {
return record.ttl == 0;
}));
CPPUNIT_ASSERT(std::all_of(message.additionals.begin(), message.additionals.end(), [](const auto& record) {
return record.ttl == 0;
}));
}
void
MdnsTester::testDnsSdValidation()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.network = 42;
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
auto message = dns_sd::announcement(service);
auto& txt = std::get<TxtData>(message.additionals[1].data);
txt.strings.push_back({'n', 'e', 't', '=', '4', '3'});
CPPUNIT_ASSERT(not dns_sd::resolve(message));
message = dns_sd::announcement(service);
auto& srv = std::get<SrvData>(message.additionals[0].data);
srv.target = Name::fromString("other.local.");
CPPUNIT_ASSERT(not dns_sd::resolve(message));
CPPUNIT_ASSERT_THROW(dns_sd::announcement({}), ParseError);
}
void
MdnsTester::testDnsSdQueryResponse()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.network = 42;
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
const auto query = dns_sd::browseQuery();
CPPUNIT_ASSERT(not query.response);
CPPUNIT_ASSERT_EQUAL(size_t(1), query.questions.size());
CPPUNIT_ASSERT(query.questions.front().name == dns_sd::serviceName());
CPPUNIT_ASSERT(query.questions.front().type == Type::PTR);
const auto response = dns_sd::respond(query, service);
CPPUNIT_ASSERT(response);
CPPUNIT_ASSERT(dns_sd::resolve(*response));
auto knownAnswerQuery = query;
knownAnswerQuery.answers = response->answers;
CPPUNIT_ASSERT(not dns_sd::respond(knownAnswerQuery, service));
knownAnswerQuery.answers.front().ttl = dns_sd::SERVICE_RECORD_TTL / 2;
CPPUNIT_ASSERT(dns_sd::respond(knownAnswerQuery, service));
Message metaQuery;
const auto metaName = Name::fromString("_services._dns-sd._udp.local.");
metaQuery.questions.push_back({metaName, Type::PTR, CLASS_IN, false});
const auto metaResponse = dns_sd::respond(metaQuery, service);
CPPUNIT_ASSERT(metaResponse);
CPPUNIT_ASSERT_EQUAL(size_t(1), metaResponse->answers.size());
CPPUNIT_ASSERT(metaResponse->answers.front().name == metaName);
CPPUNIT_ASSERT(std::get<PtrData>(metaResponse->answers.front().data).name == dns_sd::serviceName());
CPPUNIT_ASSERT(metaResponse->additionals.empty());
metaQuery.answers = metaResponse->answers;
CPPUNIT_ASSERT(not dns_sd::respond(metaQuery, service));
}
void
MdnsTester::testDnsSdCache()
{
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.network = 42;
service.port = 4222;
service.addresses.emplace_back(AData {
{192, 0, 2, 1}
});
auto now = dns_sd::Cache::Clock::now();
dns_sd::Cache cache([&] { return now; });
auto ptr = dns_sd::announcement(service);
auto details = ptr;
ptr.additionals.clear();
details.answers.clear();
CPPUNIT_ASSERT(cache.update(ptr).empty());
const auto discovered = cache.update(details);
CPPUNIT_ASSERT_EQUAL(size_t(1), discovered.size());
CPPUNIT_ASSERT_EQUAL(service.nodeId, discovered.front().nodeId);
CPPUNIT_ASSERT_EQUAL(size_t(1), cache.services().size());
now += std::chrono::seconds(dns_sd::HOST_RECORD_TTL + 1);
CPPUNIT_ASSERT(cache.services().empty());
}
void
MdnsTester::testInterfaceScoping()
{
const std::vector<InterfaceAddress> addresses {
{1, 1, asio::ip::make_address("192.0.2.1"), true, false},
{1, 1, asio::ip::make_address("fe80::1"), true, false},
{2, 2, asio::ip::make_address("fe80::2"), true, false},
{3, 3, asio::ip::make_address("fe80::3"), false, false},
{4, 4, asio::ip::make_address("::1"), true, true },
};
const auto interfaces = groupInterfaces(addresses);
CPPUNIT_ASSERT_EQUAL(size_t(2), interfaces.size());
CPPUNIT_ASSERT_EQUAL(InterfaceId {1}, interfaces[0].id);
CPPUNIT_ASSERT_EQUAL(size_t(2), interfaces[0].addresses.size());
CPPUNIT_ASSERT_EQUAL(InterfaceId {2}, interfaces[1].id);
CPPUNIT_ASSERT_EQUAL(size_t(1), interfaces[1].addresses.size());
dns_sd::Service service;
service.nodeId = dht::InfoHash("0123456789abcdef0123456789abcdef01234567");
service.port = 4222;
service.addresses = interfaces[0].addresses;
const auto announcement = dns_sd::announcement(service);
const auto resolved = dns_sd::resolve(announcement);
CPPUNIT_ASSERT(resolved);
CPPUNIT_ASSERT(resolved->addresses == interfaces[0].addresses);
}
} // namespace test
-40
View File
@@ -1,40 +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 MdnsTester : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(MdnsTester);
CPPUNIT_TEST(testName);
CPPUNIT_TEST(testRecordRoundTrip);
CPPUNIT_TEST(testCompression);
CPPUNIT_TEST(testMalformedPackets);
CPPUNIT_TEST(testPacketSizeLimit);
CPPUNIT_TEST(testDnsSdAdvertisement);
CPPUNIT_TEST(testDnsSdGoodbye);
CPPUNIT_TEST(testDnsSdValidation);
CPPUNIT_TEST(testDnsSdQueryResponse);
CPPUNIT_TEST(testDnsSdCache);
CPPUNIT_TEST(testInterfaceScoping);
CPPUNIT_TEST_SUITE_END();
public:
void testName();
void testRecordRoundTrip();
void testCompression();
void testMalformedPackets();
void testPacketSizeLimit();
void testDnsSdAdvertisement();
void testDnsSdGoodbye();
void testDnsSdValidation();
void testDnsSdQueryResponse();
void testDnsSdCache();
void testInterfaceScoping();
};
} // namespace test
-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
-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
-66
View File
@@ -1,66 +0,0 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "test_peerdiscovery.h"
#include <mutex>
#include <condition_variable>
namespace test {
using namespace std::literals;
constexpr dht::NetId NETWORK_ID = 10;
constexpr in_port_t NODE_PORT = 50000;
CPPUNIT_TEST_SUITE_REGISTRATION(PeerDiscoveryTester);
void
PeerDiscoveryTester::setUp()
{}
void
PeerDiscoveryTester::testMulticastToTwoNodes()
{
const auto nodeId = dht::InfoHash::get("opendht01");
std::mutex lock;
std::condition_variable cv;
unsigned discovered {0};
unsigned wrongNetwork {0};
{
std::unique_lock l(lock);
dht::PeerDiscovery publisher;
dht::PeerDiscovery browser;
dht::PeerDiscovery otherNetworkBrowser;
browser.startDiscovery(NETWORK_ID, [&](const dht::InfoHash& peerId, dht::SockAddr&& address) {
CPPUNIT_ASSERT_EQUAL(nodeId, peerId);
CPPUNIT_ASSERT_EQUAL(NODE_PORT, address.getPort());
{
std::lock_guard l(lock);
discovered++;
}
cv.notify_all();
});
otherNetworkBrowser.startDiscovery(NETWORK_ID + 1, [&](const dht::InfoHash&, dht::SockAddr&&) {
std::lock_guard l(lock);
wrongNetwork++;
});
publisher.startPublish(nodeId, NETWORK_ID, NODE_PORT);
CPPUNIT_ASSERT(cv.wait_for(l, std::chrono::seconds(5), [&] { return discovered > 0; }));
CPPUNIT_ASSERT_EQUAL(0u, wrongNetwork);
l.unlock();
publisher.stopPublish();
browser.stopDiscovery();
otherNetworkBrowser.stopDiscovery();
}
}
void
PeerDiscoveryTester::tearDown()
{}
} // namespace test
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more