Compare commits

...
Author SHA1 Message Date
Adrien Beraud bb66dded17 build/cmake: set SIMDUTF_CXX_STANDARD to match CMAKE_CXX_STANDARD 2026-03-14 11:21:13 -04:00
Adrien Beraud 91d54d09d1 tests: unlock mutex before join 2026-03-14 11:20:44 -04:00
Adrien Béraud 4d0ecb8075 dht: expire requests on shutdown 2026-03-14 00:31:47 -04:00
Adrien Béraud b0f8526872 tests: add testShutdownCompletesWithPendingPut 2026-03-14 00:31:17 -04:00
Adrien Béraud 6dc004c338 net: forward length in write() on windows 2026-03-13 23:01:22 -04:00
Adrien Béraud 7183847e44 tests: use random ports 2026-03-13 22:59:41 -04:00
Adrien Béraud 884cb09220 tests: bound dhtrunner async waits 2026-03-13 22:00:09 -04:00
Adrien Béraud 04edaafa57 dht: simplify bootstrap logic 2026-03-13 20:59:24 -04:00
Adrien Béraud 6be58884aa tests: set timout for test_crypto to 60s 2026-03-13 20:58:53 -04:00
Adrien Béraud d50d3d4f32 tests: set timout for test_dhtrunner to 60s 2026-03-13 20:52:56 -04:00
Adrien Béraud 8acafe844d dht: release pending bootstrap after first attempt 2026-03-13 20:39:40 -04:00
Adrien Béraud 688b713c09 tests: cover bootstrap put failure after disconnect 2026-03-13 20:39:37 -04:00
Adrien Béraud 017df59dde update version to 3.7.6 2026-03-13 18:30:43 -04:00
Adrien Béraud 964800a506 tests: disable test_networkengine on MSVC 2026-03-13 16:30:36 -04:00
Adrien Béraud 4f423d066f tests: attempt to fix test_networkengine on MSVC 2026-03-13 16:09:49 -04:00
Adrien Béraud ae49b96c2d tests: attempt to fix test_networkengine on MSVC 2026-03-13 16:02:33 -04:00
Adrien Béraud 3701b21425 tests: attempt to fix test_networkengine on MSVC 2026-03-13 15:49:52 -04:00
Adrien Béraud 55af602026 tests: fix build with GCC 14 2026-03-13 15:32:45 -04:00
Adrien Béraud eda121297b proxy client: protect searches when updating push token 2026-03-13 15:10:12 -04:00
Adrien Béraud 603e777c9e dht: refresh token on listen confirmation 2026-03-13 15:10:12 -04:00
Adrien Béraud bef4e61fc6 tests: add test for update of listen token 2026-03-13 15:10:12 -04:00
Adrien Béraud 42971d9587 tests: add test for proxy client token change 2026-03-13 15:10:12 -04:00
Adrien Béraud 4a43326de1 tests: add more proxy tests 2026-03-13 13:06:36 -04:00
Adrien Béraud 8fcb19627d dht: mark listen as non-synced on auth failure 2026-03-13 13:06:08 -04:00
Adrien Béraud b6ea2ed8e2 proxy client: resubscribe on new endpoint 2026-03-13 13:05:34 -04:00
Adrien Béraud ac520bd587 tests: make sure bootstrap then put always succeed 2026-03-13 12:03:09 -04:00
Adrien Béraud 4bd796cddd dht: state is connecting if bootstrap is pending 2026-03-13 11:53:32 -04:00
Adrien Béraud ad372bf232 dht: restart searches on connect 2026-03-13 11:52:57 -04:00
Adrien Béraud 299b4510f5 AGENTS.md: add commit instructions 2026-03-13 11:07:01 -04:00
Adrien Béraud ae44ac98fb dht: open new socket if previous was closed 2026-03-13 11:06:50 -04:00
Adrien Béraud c37ff1b167 tests: add test for reopens socket after node expiration 2026-03-13 11:06:30 -04:00
Adrien Béraud f94f072ad6 dht: send token in listen confirmation 2026-03-13 00:53:05 -04:00
Adrien Béraud c51beca374 dht: properly persist expiration time 2026-03-13 00:38:31 -04:00
Adrien Béraud 4b9a6489c3 dht: persist storage usage 2026-03-13 00:28:38 -04:00
Adrien Béraud 34722cab7a dht: clarify storage log 2026-03-13 00:04:49 -04:00
Adrien Béraud 9e1dc3f793 dht: log stats about imported values 2026-03-13 00:04:49 -04:00
Adrien Béraud 0eacd7fe6c tools: move to dht::tools namespace 2026-03-13 00:04:49 -04:00
Adrien Béraud 37f4469961 update version to 3.7.3 2026-03-09 16:08:26 -04:00
Adrien Béraud e9c4a31949 Support listing tests and running a single test by name 2026-03-09 13:57:56 -04:00
dependabot[bot] 1b5d9569ec build(deps): bump pypa/cibuildwheel from 3.3.1 to 3.4.0
Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/pypa/cibuildwheel/releases)
- [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md)
- [Commits](https://github.com/pypa/cibuildwheel/compare/v3.3.1...v3.4.0)

---
updated-dependencies:
- dependency-name: pypa/cibuildwheel
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 10:35:40 -04:00
dependabot[bot] e70f76f7f9 build(deps): bump docker/setup-buildx-action from 3 to 4
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 10:34:43 -04:00
dependabot[bot] 0f25da371e build(deps): bump docker/setup-qemu-action from 3 to 4
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 10:33:43 -04:00
dependabot[bot] f33f66c038 build(deps): bump docker/login-action from 3 to 4
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 10:33:06 -04:00
dependabot[bot] 1d08ff1489 build(deps): bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 10:32:35 -04:00
Adrien Béraud b892be092a infohash: make core APIs constexpr
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-03-08 23:34:50 -04:00
Adrien Béraud 9a902fa80c dht: fix incorrect id in log 2026-03-08 12:27:00 -04:00
Adrien Béraud 984d720fac meson: export opendht_EXPORTS 2026-03-08 12:26:23 -04:00
Adrien Béraud 931d79b701 dht: handle out-of-order partial messages 2026-03-08 12:26:23 -04:00
Adrien Béraud 58a5791063 ci: fix ccpp.yml syntax 2026-03-07 14:52:14 -05:00
Adrien Béraud 33b2bf12fa dht: fix recycling of existing search 2026-03-06 23:19:51 -05:00
Adrien Béraud 08e9f0b763 network_engine: always erase partial message after processing 2026-03-06 23:19:51 -05:00
Adrien Béraud 12cb93f9cf AGENTS.md: update 2026-03-06 23:19:51 -05:00
Adrien Béraud 71d14bb0fe proxy stats: use localhost by default 2026-03-02 16:32:38 -05:00
Adrien Béraud 635a73141a build/cmake: use three dots as policy_max separator 2026-03-01 21:39:53 -05:00
Adrien Béraud de4327c056 proxy stats: add storage stats 2026-02-27 16:52:07 -05:00
Adrien Béraud 348d6be920 proxy stats: add storage stats 2026-02-27 16:00:16 -05:00
Adrien Béraud dbdf2ba94d update version to 3.7.2 2026-02-27 15:27:44 -05:00
Adrien Béraud ef7505baaa tests/dhtrunner: adapt to use longId 2026-02-27 15:12:31 -05:00
Adrien Béraud 9ad6b3a608 dhtrunner, securedht: mark findCertificate(InfoHash hash) as deprecated 2026-02-27 14:26:39 -05:00
Adrien Béraud a32d76d312 securedht: cleanup 2026-02-27 00:18:20 -05:00
Adrien Béraud 8ebecfa25a securedht: stop storing certificate at sha1(publicKey) 2026-02-27 00:12:40 -05:00
Adrien Béraud ae3c2ae0d3 build/meson: use c++20 by default 2026-02-26 22:53:06 -05:00
Adrien Béraud 808ef0c51e rename tests to test_x format 2026-02-26 22:52:48 -05:00
Adrien Béraud 6cf6892767 add AGENTS.md 2026-02-26 22:51:41 -05:00
Adrien Béraud 9ebe6e8bb6 tools: pretty print byte count 2026-02-24 00:15:47 -05:00
Adrien Béraud a5455c19a6 update version to 3.7.1 2026-02-22 23:34:54 -05:00
Adrien Béraud 166566dbf4 remove automake files 2026-02-22 23:34:16 -05:00
Adrien Béraud 9b1534e6fe update README.md 2026-02-22 21:42:24 -05:00
Adrien Béraud 0263104373 proxy client: implement getLocalStoreSize 2026-02-22 18:55:02 -05:00
72 changed files with 2533 additions and 585 deletions

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ env:
on: [push, pull_request]
jobs:
build-ubuntu-meson:
build-ubuntu-meson:
name: Ubuntu/GCC Meson build
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
# Run locally with: pipx run cibuildwheel --platform linux python
- name: Build wheels
uses: pypa/cibuildwheel@v3.3.1
uses: pypa/cibuildwheel@v3.4.0
with:
package-dir: python
+28 -28
View File
@@ -28,13 +28,13 @@ jobs:
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -42,7 +42,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DEPS }}
@@ -69,13 +69,13 @@ jobs:
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -83,7 +83,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
@@ -110,13 +110,13 @@ jobs:
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -124,7 +124,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DEPS_LLVM }}
@@ -151,13 +151,13 @@ jobs:
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -165,7 +165,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LLVM }}
@@ -193,13 +193,13 @@ jobs:
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -207,7 +207,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DHTNODE }}
@@ -234,13 +234,13 @@ jobs:
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -248,7 +248,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE_DEPS }}
@@ -276,13 +276,13 @@ jobs:
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -290,7 +290,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE }}
+74
View File
@@ -0,0 +1,74 @@
# 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.
---
+20 -8
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.3/restinio-0.7.3.tar.bz2
tar -xjf restinio-0.7.3.tar.bz2
cd restinio-0.7.3/dev
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
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 -j2 && make install
make && 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.3/restinio-0.7.3.tar.bz2
tar -xjf restinio-0.7.3.tar.bz2
cd restinio-0.7.3/dev
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
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 -j2 && make install
make && make install
```
### Fedora
@@ -104,6 +104,18 @@ cmake -DOPENDHT_PYTHON=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.
+32 -21
View File
@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.21..3.31)
cmake_minimum_required (VERSION 3.21...3.31)
if(POLICY CMP0091)
cmake_policy(SET CMP0091 NEW)
endif()
@@ -10,7 +10,7 @@ if(POLICY CMP0167)
endif()
set (opendht_VERSION_MAJOR 3)
set (opendht_VERSION_MINOR 7.0)
set (opendht_VERSION_MINOR 7.6)
set (opendht_VERSION ${opendht_VERSION_MAJOR}.${opendht_VERSION_MINOR})
set (PACKAGE_VERSION ${opendht_VERSION})
set (VERSION "${opendht_VERSION}")
@@ -130,6 +130,7 @@ 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
@@ -853,32 +854,42 @@ if (BUILD_TESTING)
endif()
# unit testing
list (APPEND test_FILES
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/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
)
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/httptester.h
tests/httptester.cpp
tests/dhtproxytester.h
tests/dhtproxytester.cpp
tests/test_http.h
tests/test_http.cpp
tests/test_dhtproxy.h
tests/test_dhtproxy.cpp
)
endif()
if (OPENDHT_PEER_DISCOVERY)
list (APPEND test_FILES
tests/peerdiscoverytester.h
tests/peerdiscoverytester.cpp
tests/test_peerdiscovery.h
tests/test_peerdiscovery.cpp
)
endif()
endif()
@@ -886,7 +897,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. infohashtester
get_filename_component(test_name ${test_src} NAME_WE) # e.g. test_infohash
set(target_name ${test_name})
add_executable(${target_name}
tests/tests_runner.cpp
+9 -7
View File
@@ -140,20 +140,22 @@ 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) restinio used for the REST API.
- (optional) llhttp used for the REST API.
- (optional) jsoncpp 1.7.4-3+, used for the REST API.
Optional dependencies for the REST API (DHT proxy client/server):
- (optional) restinio
- (optional) llhttp
- (optional) jsoncpp 1.7.4-3+
- (optional) simdutf
Build environment:
- 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
@@ -1,15 +0,0 @@
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
-16
View File
@@ -1,16 +0,0 @@
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
+4 -4
View File
@@ -23,15 +23,15 @@ struct Node;
*/
enum class NodeStatus {
Disconnected, // 0 nodes
Connecting, // 1+ nodes
Connecting, // bootstrap pending or 1+ nodes
Connected // 1+ good nodes
};
inline constexpr const char*
inline constexpr std::string_view
statusToStr(NodeStatus status)
{
return status == NodeStatus::Connected ? "connected"
: (status == NodeStatus::Connecting ? "connecting" : "disconnected");
return status == NodeStatus::Connected ? "connected"sv
: (status == NodeStatus::Connecting ? "connecting"sv : "disconnected"sv);
}
struct OPENDHT_PUBLIC NodeStats
+2 -2
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_ {};
};
@@ -685,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_ {};
};
+4 -2
View File
@@ -411,6 +411,7 @@ 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;
@@ -464,7 +465,8 @@ private:
const Sp<Value>& value,
time_point created,
const SockAddr& sa = {},
bool permanent = false);
bool permanent = false,
time_point expiration = time_point::min());
bool storageRefresh(const InfoHash& id, Value::Id vid);
void expireStore();
void expireStorage(InfoHash h);
@@ -626,7 +628,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>& status, net::RequestAnswer& a, Sp<Search>& sr);
void onListenDone(const Sp<Node>& node, 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,6 +276,7 @@ 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 {}
+5 -4
View File
@@ -451,7 +451,8 @@ public:
// securedht methods
void findCertificate(InfoHash hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)>);
[[deprecated("Use PkId version")]] void findCertificate(
InfoHash hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)>);
void findCertificate(PkId hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)>);
void registerCertificate(const std::shared_ptr<crypto::Certificate>& cert);
@@ -578,16 +579,16 @@ private:
*/
void resetDht();
mutable std::mutex dht_mtx {};
mutable std::mutex dht_mtx;
std::thread dht_thread {};
std::condition_variable cv {};
std::mutex sock_mtx {};
std::mutex sock_mtx;
net::PacketList rcv {};
decltype(rcv) rcv_free {};
std::queue<std::function<void(SecureDht&)>> pending_ops_prio {};
std::queue<std::function<void(SecureDht&)>> pending_ops {};
std::mutex storage_mtx {};
std::mutex storage_mtx;
std::atomic<State> running {State::Idle};
std::atomic_size_t ongoing_ops {0};
+97 -50
View File
@@ -42,8 +42,7 @@ 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>
@@ -54,31 +53,25 @@ public:
typedef typename T::iterator iterator;
typedef typename T::const_iterator const_iterator;
Hash() noexcept { data_.fill(0); }
Hash(const uint8_t* h, size_t data_len)
{
if (data_len < N)
data_.fill(0);
else
std::copy_n(h, N, data_.begin());
}
constexpr Hash() noexcept
: data_ {}
{}
constexpr Hash(const uint8_t* h, size_t data_len)
: data_(fromBytes(h, data_len))
{}
/**
* Constructor from an hexadecimal string (without "0x").
* hex must be at least 2.HASH_LEN characters long.
* If too long, only the first 2.HASH_LEN characters are read.
* hex must be at least 2.N characters long.
* If too long, only the first 2.N characters are read.
*/
explicit Hash(std::string_view hex)
{
if (hex.size() < 2 * N)
data_.fill(0);
else
fromString(hex.data());
}
explicit constexpr Hash(std::string_view hex)
: data_(fromString(hex))
{}
Hash(const msgpack::object& o) { msgpack_unpack(o); }
static constexpr size_t size() noexcept { return N; }
const uint8_t* data() const { return data_.data(); }
constexpr const uint8_t* data() const { return data_.data(); }
uint8_t* data() { return data_.data(); }
iterator begin() { return data_.begin(); }
const_iterator cbegin() const { return data_.cbegin(); }
@@ -87,10 +80,21 @@ public:
static constexpr inline Hash zero() noexcept { return Hash {}; }
bool operator==(const Hash& h) const { return data_ == h.data_; }
bool operator!=(const Hash& h) const { return !(*this == h); }
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& o) const
constexpr bool operator<(const Hash& o) const
{
for (unsigned i = 0; i < N; i++) {
if (data_[i] != o.data_[i])
@@ -99,7 +103,7 @@ public:
return false;
}
Hash operator^(const Hash& o) const
constexpr Hash operator^(const Hash& o) const
{
Hash result;
for (auto i = 0u; i < N; i++) {
@@ -108,8 +112,9 @@ public:
return result;
}
explicit operator bool() const
explicit constexpr 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++) {
@@ -117,24 +122,32 @@ 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]; }
const uint8_t& operator[](size_t index) const { return data_[index]; }
constexpr const uint8_t& operator[](size_t index) const { return data_[index]; }
/**
* Find the lowest 1 bit in an id.
* Result will allways be lower than 8*N
*/
inline int lowbit() const
constexpr int lowbit() const
{
int i, j;
for (i = N - 1; i >= 0; i--)
int i = N - 1;
for (; i >= 0; i--)
if (data_[i] != 0)
break;
if (i < 0)
return -1;
for (j = 7; j >= 0; j--)
int j = 7;
for (; j >= 0; j--)
if ((data_[i] & (0x80 >> j)) != 0)
break;
return 8 * i + j;
@@ -146,11 +159,10 @@ public:
}
/** Find how many bits two ids have in common. */
static inline unsigned commonBits(const Hash& id1, const Hash& id2)
static constexpr inline unsigned commonBits(const Hash& id1, const Hash& id2)
{
unsigned i, j;
uint8_t x;
for (i = 0; i < N; i++) {
unsigned i = 0;
for (; i < N; i++) {
if (id1.data_[i] != id2.data_[i])
break;
}
@@ -158,9 +170,9 @@ public:
if (i == N)
return 8 * N;
x = id1.data_[i] ^ id2.data_[i];
uint8_t x = id1.data_[i] ^ id2.data_[i];
j = 0;
unsigned j = 0;
while ((x & 0x80) == 0) {
x <<= 1;
j++;
@@ -170,7 +182,7 @@ public:
}
/** Determine whether id1 or id2 is closer to this */
int xorCmp(const Hash& id1, const Hash& id2) const
constexpr int xorCmp(const Hash& id1, const Hash& id2) const
{
for (unsigned i = 0; i < N; i++) {
if (id1.data_[i] == id2.data_[i])
@@ -182,7 +194,7 @@ public:
return 0;
}
bool getBit(unsigned nbit) const
constexpr bool getBit(unsigned nbit) const
{
auto& num = *(data_.cbegin() + (nbit / 8));
unsigned bit = 7 - (nbit % 8);
@@ -258,7 +270,8 @@ public:
private:
T data_;
void fromString(const char*);
static constexpr T fromBytes(const uint8_t*, size_t);
static constexpr T fromString(std::string_view);
};
#define HASH_LEN 20u
@@ -281,30 +294,51 @@ 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());
fromString(dat.data());
h = Hash<N>(std::string_view(dat.data(), dat.size()));
return s;
}
template<size_t N>
void
Hash<N>::fromString(const char* in)
constexpr typename Hash<N>::T
Hash<N>::fromBytes(const uint8_t* in, size_t size)
{
auto hex2bin = [](char c) -> uint8_t {
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';
else if (c >= 'A' and c <= 'F')
return 10 + c - 'A';
else if (c >= '0' and c <= '9')
return c - '0';
else
throw std::domain_error("not an hex character");
else {
valid = false;
return 0;
}
};
try {
for (size_t i = 0; i < N; i++)
data_[i] = (hex2bin(in[2 * i]) << 4) | hex2bin(in[2 * i + 1]);
} catch (const std::domain_error&) {
data_.fill(0);
bool valid = true;
for (size_t i = 0; i < N && valid; i++) {
uint8_t high = hex2bin(in[2 * i], valid);
uint8_t low = hex2bin(in[2 * i + 1], valid);
data[i] = (high << 4) | low;
}
if (!valid)
return {};
return data;
}
template<size_t N>
@@ -369,6 +403,19 @@ toHex(const std::vector<uint8_t>& data)
return toHex(data.data(), data.size());
}
template<size_t N>
constexpr std::array<char, N * 2>
toHexArray(const Hash<N>& h)
{
std::array<char, N * 2> ret {};
for (size_t i = 0; i < N; i++) {
const auto& m = hex_map[h[i]];
ret[i * 2] = m[0];
ret[i * 2 + 1] = m[1];
}
return ret;
}
template<size_t N>
const char*
Hash<N>::to_c_str() const
+1 -1
View File
@@ -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};
};
+1 -1
View File
@@ -547,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);
void sendListenConfirmation(const SockAddr& addr, Tid tid, const Blob& token = {});
/* answer to put request */
void sendValueAnnounced(const SockAddr& addr, Tid, Value::Id);
/* answer in case of error */
+6 -4
View File
@@ -145,16 +145,18 @@ 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);
[[deprecated("Use PkId version")]] void findCertificate(
const InfoHash& node, const std::function<void(const Sp<crypto::Certificate>)>& cb);
[[deprecated("Use PkId version")]] void findPublicKey(const InfoHash& node,
const std::function<void(const Sp<crypto::PublicKey>)>& cb);
void findCertificate(const PkId& id, const std::function<void(const Sp<crypto::Certificate>)>& cb);
void findPublicKey(const PkId& id, const std::function<void(const Sp<crypto::PublicKey>)>& cb);
void registerCertificate(const Sp<crypto::Certificate>& cert);
Sp<crypto::Certificate> getCertificate(const InfoHash& node) const;
Sp<crypto::PublicKey> getPublicKey(const InfoHash& node) const;
[[deprecated("Use PkId version")]] Sp<crypto::Certificate> getCertificate(const InfoHash& node) const;
[[deprecated("Use PkId version")]] Sp<crypto::PublicKey> getPublicKey(const InfoHash& node) const;
Sp<crypto::Certificate> getCertificate(const PkId& node) const;
Sp<crypto::PublicKey> getPublicKey(const PkId& node) const;
+3 -3
View File
@@ -53,7 +53,7 @@ public:
void detach();
private:
std::mutex lock_ {};
std::mutex lock_;
std::condition_variable cv_ {};
std::queue<std::function<void()>> tasks_ {};
std::vector<std::unique_ptr<std::thread>> threads_;
@@ -81,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_ {};
@@ -114,7 +114,7 @@ public:
private:
struct SharedState
{
std::mutex mtx {};
std::mutex mtx;
std::condition_variable cv {};
unsigned pendingTasks {0};
unsigned ongoingTasks {0};
+5 -3
View File
@@ -85,8 +85,8 @@ using system_clock = std::chrono::system_clock;
using time_point = clock::time_point;
using duration = clock::duration;
time_point from_time_t(std::time_t t);
std::time_t to_time_t(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);
template<class DT>
static std::string
@@ -126,6 +126,8 @@ 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>
{
@@ -184,7 +186,7 @@ unpackMsg(const Blob& b)
return msgpack::unpack((const char*) b.data(), b.size());
}
msgpack::object* findMapValue(const msgpack::object& map, const char* key, size_t length);
OPENDHT_PUBLIC msgpack::object* findMapValue(const msgpack::object& map, const char* key, size_t length);
inline msgpack::object*
findMapValue(const msgpack::object& map, const char* key)
+63 -57
View File
@@ -5,9 +5,9 @@ project(
'cython',
license: 'MIT',
license_files: 'LICENSE',
version: '3.7.0',
version: '3.7.6',
meson_version: '>=1.1.0',
default_options: ['cpp_std=c++17', 'warning_level=3'],
default_options: ['cpp_std=c++20', 'warning_level=3'],
)
gnutls = dependency('gnutls')
@@ -118,6 +118,17 @@ opendht = library(
install: true,
)
opendht_dep_args = []
if get_option('default_library') != 'static'
opendht_dep_args += '-Dopendht_EXPORTS'
endif
opendht_dep = declare_dependency(
link_with: opendht,
include_directories: opendht_interface_inc,
compile_args: opendht_dep_args,
)
# Install library headers.
install_subdir('include', install_dir: get_option('includedir'), strip_directory: true)
@@ -128,6 +139,13 @@ 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',
@@ -143,9 +161,7 @@ if get_option('c').enabled()
opendht_c = library(
'opendht-c',
['c/opendht.cpp', 'c/opendht_c.h'],
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [msgpack, jsoncpp, fmt],
dependencies: [opendht_dep, msgpack, jsoncpp, fmt],
cpp_args: ['-DOPENDHT_C_BUILD', '-Dopendht_c_EXPORTS'],
install: true,
)
@@ -181,10 +197,8 @@ if get_option('python').require(
py.extension_module(
'opendht',
'python/opendht.pyx',
include_directories: opendht_interface_inc,
override_options: ['cython_language=cpp'],
link_with: [opendht],
dependencies: [msgpack, jsoncpp, fmt, dep_py],
dependencies: [opendht_dep, msgpack, jsoncpp, fmt, dep_py],
install: true,
)
endif
@@ -193,25 +207,19 @@ if get_option('tools').enabled()
dhtnode = executable(
'dhtnode',
'tools/dhtnode.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
install: true,
)
dhtchat = executable(
'dhtchat',
'tools/dhtchat.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
install: true,
)
dhtscanner = executable(
'dhtscanner',
'tools/dhtscanner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
install: true,
)
if llhttp.found()
@@ -224,9 +232,7 @@ if get_option('tools').enabled()
durl = executable(
'durl',
'tools/durl.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: durl_deps,
dependencies: [opendht_dep] + durl_deps,
)
endif
endif
@@ -237,62 +243,68 @@ if get_option('tests').enabled()
test_infohash = executable(
'test_infohash',
'tests/infohashtester.cpp',
'tests/test_infohash.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('Infohash', test_infohash)
test_value = executable(
'test_value',
'tests/valuetester.cpp',
'tests/test_value.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
dependencies: [opendht_dep, 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/cryptotester.cpp',
'tests/test_crypto.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack, gnutls],
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack, gnutls],
)
test('Crypto', test_crypto)
test('Crypto', test_crypto, timeout: 60)
test_dhtrunner = executable(
'test_dhtrunner',
'tests/dhtrunnertester.cpp',
'tests/test_dhtrunner.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('DhtRunner', test_dhtrunner)
test('DhtRunner', test_dhtrunner, timeout: 60)
test_threadpool = executable(
'test_threadpool',
'tests/threadpooltester.cpp',
'tests/test_threadpool.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
dependencies: [opendht_dep, 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/httptester.cpp',
'tests/test_http.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('Http', test_http)
endif
@@ -300,11 +312,9 @@ if get_option('tests').enabled()
if get_option('peer_discovery').enabled()
test_peerdiscovery = executable(
'test_peerdiscovery',
'tests/peerdiscoverytester.cpp',
'tests/test_peerdiscovery.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('PeerDiscovery', test_peerdiscovery)
endif
@@ -312,11 +322,9 @@ if get_option('tests').enabled()
if get_option('proxy_server').enabled()
test_proxy = executable(
'test_dhtproxy',
'tests/dhtproxytester.cpp',
'tests/test_dhtproxy.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
)
test('DhtProxy', test_proxy)
@@ -325,9 +333,7 @@ if get_option('tests').enabled()
'test_dhtproxystress',
'tests/dhtproxy_stress_tester.cpp',
'tests/tests_runner.cpp',
include_directories: opendht_interface_inc,
link_with: opendht,
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
dependencies: [opendht_dep, 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}
Cflags: -I${includedir}@opendht_c_public_cflags@
+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}
Cflags: -I${includedir}@opendht_public_cflags@
-30
View File
@@ -1,30 +0,0 @@
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
-82
View File
@@ -1,82 +0,0 @@
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
+126 -35
View File
@@ -35,6 +35,8 @@ 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) {
@@ -385,7 +387,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.to_view(), n->node->toString(), select_q->toString());
logger_->debug("[search {}] [node {}] Sending {}", sr->id.toString(), 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));
}
@@ -654,6 +656,19 @@ 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());
sr->insertNode(node, scheduler.time(), answer.ntoken);
if (auto sn = sr->getNode(node)) {
sn->onValues(query, std::move(answer), types, scheduler);
}
}
});
};
auto r = n.listenStatus.find(query);
if (n.getListenTime(r, listenExp) > scheduler.time())
continue;
@@ -665,21 +680,7 @@ 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,
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);
}
}
})))
std::forward_as_tuple(l.second.get_cb, l.second.sync_cb, openListenSocket()))
.first;
r->second.cacheExpirationJob = scheduler.add(time_point::max(), [this, ws, query, node = n.node] {
if (auto sr = ws.lock()) {
@@ -688,6 +689,8 @@ 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,
@@ -889,10 +892,16 @@ Dht::search(const InfoHash& id,
sr = std::make_shared<Search>();
srs.emplace(id, sr);
} else {
for (auto it = srs.begin(); it != srs.end();) {
for (auto it = srs.begin(); it != srs.end(); ++it) {
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;
}
}
@@ -901,8 +910,11 @@ 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++;
@@ -1408,11 +1420,17 @@ 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)
Dht::storageStore(const InfoHash& id,
const Sp<Value>& value,
time_point created,
const SockAddr& sa,
bool permanent,
time_point expiration)
{
const auto& now = scheduler.time();
created = std::min(created, now);
auto expiration = permanent ? time_point::max() : created + getType(value->type).expiration;
if (expiration == time_point::min())
expiration = permanent ? time_point::max() : created + getType(value->type).expiration;
if (expiration < now)
return false;
@@ -1429,7 +1447,7 @@ Dht::storageStore(const InfoHash& id, const Sp<Value>& value, time_point created
StorageBucket* store_bucket {nullptr};
bool is_local = !sa;
if (sa)
store_bucket = &store_quota[sa];
store_bucket = &store_quota.try_emplace(sa, sa).first->second;
else
store_bucket = local_store_quota.get();
@@ -1835,15 +1853,18 @@ std::string
Dht::getStorageLog() const
{
std::ostringstream out;
for (const auto& s : store)
out << printStorageLog(s);
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";
}
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 " << ip->first << " bytes" << std::endl;
out << "IP " << ip->second->toString() << " uses " << printByteCount(ip->first) << std::endl;
out << std::endl;
out << "Total " << store.size() << " storages, " << total_values << " values (";
if (total_store_size < 1024)
@@ -1866,11 +1887,8 @@ std::string
Dht::getStorageLog(const InfoHash& h) const
{
auto s = store.find(h);
if (s == store.end()) {
std::ostringstream out;
out << "Storage " << h << " empty" << std::endl;
return out.str();
}
if (s == store.end())
return fmt::format("Storage {} empty\n", h);
return printStorageLog(*s);
}
@@ -1880,8 +1898,14 @@ Dht::printStorageLog(const decltype(store)::value_type& s) const
std::ostringstream out;
using namespace std::chrono;
const auto& st = s.second;
out << "Storage " << s.first << " " << st.listeners.size() << " list., " << st.valueCount() << " values ("
<< st.totalSize() << " bytes)" << std::endl;
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;
}
if (not st.local_listeners.empty())
out << " " << st.local_listeners.size() << " local listeners" << std::endl;
for (const auto& node_listeners : st.listeners) {
@@ -2214,6 +2238,18 @@ 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()();
@@ -2235,6 +2271,7 @@ Dht::bootstrap()
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);
@@ -2261,6 +2298,7 @@ void
Dht::startBootstrap()
{
stopBootstrap();
bootstrap_pending = !bootstrap_nodes.empty();
bootstrapJob = scheduler.add(scheduler.time(), std::bind(&Dht::bootstrap, this));
}
@@ -2268,6 +2306,7 @@ void
Dht::stopBootstrap()
{
scheduler.cancel(bootstrapJob);
bootstrap_pending = false;
bootstrap_period = BOOTSTRAP_PERIOD;
}
@@ -2324,9 +2363,16 @@ Dht::exportValues() const
const auto& vals = h.second.getValues();
pk.pack_array(vals.size());
for (const auto& v : vals) {
pk.pack_array(2);
pk.pack_array(4);
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(v.expiration.time_since_epoch().count());
}
ve.second = {buffer.data(), buffer.data() + buffer.size()};
e.push_back(std::move(ve));
@@ -2339,9 +2385,13 @@ Dht::importValues(const std::vector<ValuesExport>& import)
{
const auto& now = scheduler.time();
unsigned imported = 0, ignored = 0;
size_t imported_size = 0;
for (const auto& value : import) {
if (value.second.empty())
if (value.second.empty()) {
ignored++;
continue;
}
try {
msgpack::unpacked msg;
@@ -2354,25 +2404,53 @@ 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 {
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) {
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);
storageStore(value.first, std::make_shared<Value>(std::move(tmp_val)), val_time);
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++;
}
}
} 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>
@@ -2457,6 +2535,16 @@ 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 = {};
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;
@@ -2613,11 +2701,13 @@ 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);
return {};
net::RequestAnswer answer {};
answer.ntoken = makeToken(node->getAddr(), false);
return answer;
}
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",
@@ -2625,6 +2715,7 @@ Dht::onListenDone(const Sp<Node>& /* node */, net::RequestAnswer& /* answer */,
if (not sr->done) {
const auto& now = scheduler.time();
sr->insertNode(node, now, answer.ntoken);
searchSendGetValues(sr);
scheduler.edit(sr->nextSearchStep, now);
}
+14 -11
View File
@@ -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.toString());
header.request_target("/key/" + key.toString());
if (!listener.refreshSubscriberTimer) {
listener.refreshSubscriberTimer = std::make_unique<asio::steady_timer>(httpContext_);
}
@@ -1422,17 +1422,20 @@ void
DhtProxyClient::setPushNotificationToken([[maybe_unused]] const std::string& token)
{
#ifdef OPENDHT_PUSH_NOTIFICATIONS
std::unique_lock<std::mutex> l(lockCurrentProxyInfos_);
if (deviceKey_ != token) {
{
std::unique_lock<std::mutex> l(lockCurrentProxyInfos_);
if (deviceKey_ == token)
return;
deviceKey_ = token;
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);
}
}
if (!(statusIpv4_ == NodeStatus::Connected || statusIpv6_ == NodeStatus::Connected))
return;
}
if (logger_)
logger_->debug("[proxy:client] [push] token changed, resubscribing");
std::lock_guard<std::mutex> lock(searchLock_);
for (auto& search : searches_) {
for (auto& listener : search.second.listeners) {
resubscribe(search.first, listener.first, listener.second);
}
}
#endif
+13 -6
View File
@@ -294,10 +294,13 @@ void
NetworkEngine::clear()
{
for (auto& request : requests) {
request.second->cancel();
request.second->node->setExpired();
if (request.second->node)
request.second->node->setExpired();
else
request.second->setExpired();
}
requests.clear();
partial_messages.clear();
}
void
@@ -487,10 +490,10 @@ NetworkEngine::processMessage(const uint8_t* buf, size_t buflen, SockAddr f)
try {
// process the full message
process(std::move(pmsg_it->second.msg), from);
partial_messages.erase(pmsg_it);
} catch (...) {
return;
}
partial_messages.erase(pmsg_it);
} else
scheduler.add(now + RX_TIMEOUT, std::bind(&NetworkEngine::maintainRxBuffer, this, msg->tid));
}
@@ -699,7 +702,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);
sendListenConfirmation(from, msg->tid, answer.ntoken);
break;
}
case MessageType::UpdateValue: {
@@ -1278,17 +1281,21 @@ NetworkEngine::sendListen(const Sp<Node>& n,
}
void
NetworkEngine::sendListenConfirmation(const SockAddr& addr, Tid tid)
NetworkEngine::sendListenConfirmation(const SockAddr& addr, Tid tid, const Blob& token)
{
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);
pk.pack_map(2 + (not token.empty() ? 1 : 0));
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);
+1 -1
View File
@@ -9,7 +9,7 @@
#include <string>
#include <cstring>
#define close(x) closesocket(x)
#define write(s, b, f) send(s, b, (int) strlen(b), 0)
#define write(s, b, f) send(s, b, (int) (f), 0)
#define _poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
#else
#include <fcntl.h>
+7
View File
@@ -210,6 +210,13 @@ 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)
{
+1
View File
@@ -164,6 +164,7 @@ 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_; }
+49 -19
View File
@@ -6,6 +6,7 @@
#include "sockaddr.h"
#include "net.h"
#include <algorithm>
#include <map>
#include <string_view>
@@ -95,9 +96,25 @@ struct ParsedMessage
std::vector<Value::Id> expired_values {};
/* index for fields values */
std::vector<Sp<FieldValueIndex>> fields;
/** 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;
/** 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;
/* query describing a filter to apply on values. */
Query query;
/* states if ipv4 or ipv6 request */
@@ -118,17 +135,32 @@ bool
ParsedMessage::append(const ParsedMessage& block)
{
bool ret(false);
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)
for (const auto& [index, fragment] : block.fragment_parts) {
auto part_it = value_parts.find(index);
if (part_it == value_parts.end())
continue;
// TODO: handle out-of-order packets
if (ve.second.first != part_val->second.second.size()) {
// std::cout << "skipping out-of-order packet" << std::endl;
auto& pv = part_it->second;
if (pv.isComplete())
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;
}
@@ -136,16 +168,14 @@ ParsedMessage::append(const ParsedMessage& block)
bool
ParsedMessage::complete()
{
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;
for (const auto& [index, pv] : value_parts) {
if (!pv.isComplete())
return false;
}
}
for (auto& e : value_parts) {
for (auto& [index, pv] : value_parts) {
auto assembled = pv.reassemble();
msgpack::unpacked msg;
msgpack::unpack(msg, (const char*) e.second.second.data(), e.second.second.size());
msgpack::unpack(msg, (const char*) assembled.data(), assembled.size());
values.emplace_back(std::make_shared<Value>(msg.get()));
}
return true;
@@ -233,7 +263,7 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
auto d = findMapValue(vdat.val, "d"sv);
if (not o or not d)
continue;
value_parts.emplace(vdat.key.as<unsigned>(), std::pair<size_t, Blob>(o->as<size_t>(), unpackBlob(*d)));
fragment_parts.emplace(vdat.key.as<unsigned>(), std::pair<size_t, Blob>(o->as<size_t>(), unpackBlob(*d)));
}
return;
}
@@ -324,7 +354,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.emplace(i, std::make_pair(packed_v.via.u64, Blob {}));
value_parts[i].total_size = packed_v.via.u64;
} else {
try {
values.emplace_back(std::make_shared<Value>(parsedReq.values->via.array.ptr[i]));
+1
View File
@@ -836,6 +836,7 @@ struct Dht::Search
listeners.clear();
nodes.clear();
nextSearchStep.reset();
cache.clear();
}
};
+3 -17
View File
@@ -40,26 +40,12 @@ SecureDht::SecureDht(std::unique_ptr<DhtInterface> dht,
registerInsecureType(CERTIFICATE_TYPE);
if (certificate_) {
auto certId = certificate_->getId();
auto certLongId = certificate_->getLongId();
if (key_ and (certId != key_->getPublicKey().getId() or certLongId != key_->getPublicKey().getLongId()))
if (key_ and (certLongId != key_->getPublicKey().getLongId()))
throw DhtException("SecureDht: provided certificate doesn't match private key.");
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);
dht_->addOnConnectedCallback([this, certLongId, cb = std::move(iacb)]() mutable {
dht_->put(InfoHash::get(certLongId), Value {CERTIFICATE_TYPE, *certificate_, 1}, std::move(cb), {}, true);
});
}
}
+9 -1
View File
@@ -3,8 +3,9 @@
#pragma once
#include "infohash.h"
#include "value.h"
#include "listener.h"
#include "sockaddr.h"
#include "value.h"
#include <map>
#include <utility>
@@ -17,6 +18,11 @@ 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();
@@ -56,8 +62,10 @@ 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};
};
+15
View File
@@ -296,4 +296,19 @@ 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
-9
View File
@@ -1,9 +0,0 @@
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 "cryptotester.h"
#include "test_crypto.h"
#include <opendht/crypto.h>
File renamed without changes.
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "dhtproxytester.h"
#include "test_dhtproxy.h"
// std
#include <iostream>
File renamed without changes.
+93
View File
@@ -0,0 +1,93 @@
// 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
@@ -0,0 +1,22 @@
// 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
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "dhtproxy_stress_tester.h"
#include "test_dhtproxy_stress.h"
#include <iostream>
#include <string>
File renamed without changes.
@@ -1,11 +1,12 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "dhtrunnertester.h"
#include "test_dhtrunner.h"
#include <opendht/thread_pool.h>
#include <chrono>
#include <future>
#include <mutex>
#include <condition_variable>
using namespace std::chrono_literals;
@@ -14,6 +15,14 @@ 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()
{
@@ -72,9 +81,10 @@ DhtRunnerTester::testGetPut()
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(p.get_future().get());
auto vals = node1.get(key).get();
CPPUNIT_ASSERT(getFutureValue(std::move(future)));
auto vals = getFutureValue(node1.get(key));
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.front()->data == val_data);
}
@@ -88,13 +98,15 @@ DhtRunnerTester::testPutDuplicate()
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 = p1.get_future().get();
auto p2ret = p2.get_future().get();
auto p1ret = getFutureValue(std::move(p1future));
auto p2ret = getFutureValue(std::move(p2future));
CPPUNIT_ASSERT(p1ret);
CPPUNIT_ASSERT(p2ret);
auto vals = node1.get(key).get();
auto vals = getFutureValue(node1.get(key));
CPPUNIT_ASSERT(not vals.empty());
CPPUNIT_ASSERT(vals.size() == 1);
CPPUNIT_ASSERT(vals.front()->data == val_data);
@@ -112,18 +124,345 @@ DhtRunnerTester::testPutOverride()
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 = p1.get_future().get();
auto p2ret = p2.get_future().get();
auto p1ret = getFutureValue(std::move(p1future));
auto p2ret = getFutureValue(std::move(p2future));
CPPUNIT_ASSERT(!p1ret);
CPPUNIT_ASSERT(p2ret);
auto vals = node1.get(key).get();
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::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::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::testListen()
{
@@ -223,9 +562,9 @@ DhtRunnerTester::testListen()
CPPUNIT_ASSERT(ftokenc.valid());
CPPUNIT_ASSERT(ftokend.valid());
auto tokena = ftokena.get();
auto tokenc = ftokenc.get();
auto tokend = ftokend.get();
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);
@@ -265,7 +604,7 @@ DhtRunnerTester::testIdOps()
};
node2.join();
node2.run(42232, config2, std::move(context2));
node2.run(0, config2, std::move(context2));
auto bound = node1.getBound();
if (bound.isUnspecified())
bound.setLoopback();
@@ -276,7 +615,7 @@ DhtRunnerTester::testIdOps()
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return identityCount == 1; }));
}
node1.findCertificate(node2.getId(), [&](const std::shared_ptr<dht::crypto::Certificate>& crt) {
node1.findCertificate(node2.getPublicKey()->getLongId(), [&](const std::shared_ptr<dht::crypto::Certificate>& crt) {
CPPUNIT_ASSERT(crt);
std::lock_guard lk(mutex);
valueCount++;
@@ -299,21 +638,21 @@ DhtRunnerTester::testIdOps()
config2.dht_config.id = dht::crypto::generateIdentity();
node1.join();
node1.run(42222, config2, std::move(context1));
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.getId(), dht::Value("yo"), [&](bool ok) {
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(), std::make_shared<dht::Value>("yo"), [&](bool ok) {
node1.putEncrypted(key, node2.getPublicKey(), dht::Value("yo"), [&](bool ok) {
CPPUNIT_ASSERT(ok);
std::lock_guard lk(mutex);
valueCount++;
@@ -431,7 +770,7 @@ DhtRunnerTester::testListenLotOfBytes()
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return valueCount == N; }));
}
node3.cancelListen(foo, ftokenfoo.get());
node3.cancelListen(foo, getFutureValue(std::move(ftokenfoo)));
}
void
@@ -20,6 +20,12 @@ class DhtRunnerTester : public CppUnit::TestFixture
CPPUNIT_TEST(testListen);
CPPUNIT_TEST(testListenLotOfBytes);
CPPUNIT_TEST(testIdOps);
CPPUNIT_TEST(testImportValuesPreservesRemoteQuota);
CPPUNIT_TEST(testImportValuesPreservesStoredExpiration);
CPPUNIT_TEST(testBootstrapSetsConnectingState);
CPPUNIT_TEST(testBootstrapThenPutNoRace);
CPPUNIT_TEST(testBootstrapMissingNodeThenPutFails);
CPPUNIT_TEST(testShutdownCompletesWithPendingPut);
CPPUNIT_TEST_SUITE_END();
dht::DhtRunner node1 {};
@@ -58,6 +64,12 @@ public:
* Test methods requiring a node identity
*/
void testIdOps();
void testImportValuesPreservesRemoteQuota();
void testImportValuesPreservesStoredExpiration();
void testBootstrapSetsConnectingState();
void testBootstrapThenPutNoRace();
void testBootstrapMissingNodeThenPutFails();
void testShutdownCompletesWithPendingPut();
/**
* Test listen method with lot of datas
*/
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "httptester.h"
#include "test_http.h"
#include <opendht/value.h>
#include <opendht/dhtrunner.h>
File renamed without changes.
@@ -1,11 +1,12 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "infohashtester.h"
#include "test_infohash.h"
// std
#include <iostream>
#include <string>
#include <string_view>
// opendht
#include "opendht/infohash.h"
@@ -21,47 +22,60 @@ void
InfoHashTester::testConstructors()
{
// Default constructor creates a null infohash
auto nullHash = dht::InfoHash();
constexpr 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
uint8_t to_short[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
auto infohash = dht::InfoHash(to_short, 8);
constexpr uint8_t too_short[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
auto infohash = dht::InfoHash(too_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
uint8_t enough[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa};
constexpr 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);
CPPUNIT_ASSERT(infohash.size() == 20);
static_assert(dht::InfoHash(enough, 20) != nullHash);
CPPUNIT_ASSERT_EQUAL((size_t) 20u, infohash.size());
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
uint8_t tooLong[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x1,
0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb0};
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};
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
auto infohashFromStr = dht::InfoHash("0102030405060708090A0102030405060708090A");
constexpr 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()
{
auto nullHash = dht::InfoHash();
auto minHash = dht::InfoHash("0000000000000000000000000000000000111110");
auto maxHash = dht::InfoHash("0111110000000000000000000000000000000000");
constexpr auto nullHash = dht::InfoHash();
constexpr auto minHash = dht::InfoHash("0000000000000000000000000000000000111110");
constexpr auto maxHash = dht::InfoHash("0111110000000000000000000000000000000000");
// operator ==
CPPUNIT_ASSERT_EQUAL(minHash, minHash);
CPPUNIT_ASSERT_EQUAL(minHash, dht::InfoHash("0000000000000000000000000000000000111110"));
@@ -126,10 +140,21 @@ InfoHashTester::testXorCmp()
void
InfoHashTester::testHex()
{
const std::string TEST_HASH_STR("01b20304d5060708090a010203e05060708090ae");
using namespace std::literals;
static constexpr auto H = "0123456789abcdef0123456789abcdef01234567"sv;
const std::string TEST_HASH_STR(H);
dht::InfoHash TEST_HASH(TEST_HASH_STR);
static constexpr dht::InfoHash TEST_HASH_CONST(H);
CPPUNIT_ASSERT_EQUAL(TEST_HASH_STR, TEST_HASH.toString());
CPPUNIT_ASSERT_EQUAL(H, TEST_HASH.to_view());
CPPUNIT_ASSERT_EQUAL(TEST_HASH_STR, dht::toHex(TEST_HASH.data(), TEST_HASH.size()));
CPPUNIT_ASSERT_EQUAL(TEST_HASH_CONST.to_view(), H);
static constexpr auto hexArray = dht::toHexArray(TEST_HASH_CONST);
static constexpr auto hexArrayView = std::string_view(hexArray.data(), hexArray.size());
static_assert(hexArrayView == H, "Hex array does not match");
CPPUNIT_ASSERT_EQUAL(std::string_view(TEST_HASH_STR), hexArrayView);
}
void
File renamed without changes.
+549
View File
@@ -0,0 +1,549 @@
// 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)
{
return net::NetworkEngine(
myid,
{},
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 {}; });
}
void
NetworkEngineTester::setUp()
{}
void
NetworkEngineTester::tearDown()
{}
void
NetworkEngineTester::testIgnoresUnknownPartialData()
{
Scheduler scheduler;
std::mt19937_64 rd(0);
InfoHash myid = InfoHash::getRandom(rd);
int onNewNodeCalls = 0;
auto engine = makeEngine(std::make_unique<TestDatagramSocket>(), scheduler, myid, rd, onNewNodeCalls);
Blob fragment = {1, 2, 3};
auto packet = makeValueDataPacketBlob(99, 0, 0, fragment);
auto from = makeIPv4("127.0.0.2", 5000);
engine.processMessage(packet.data(), packet.size(), from);
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL(0, onNewNodeCalls);
}
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);
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 > 0);
}
void
NetworkEngineTester::testKeepsSessionForWrongSourceFragment()
{
Scheduler scheduler;
std::mt19937_64 rd(2);
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 + 32, 'w');
auto serialized = serializeValue(data);
auto goodFrom = makeIPv4("127.0.0.2", 5002);
auto badFrom = makeIPv4("127.0.0.3", 5002);
auto header = makeReplyHeaderPacketBlob(remoteId, 11, {serialized.size()});
engine.processMessage(header.data(), header.size(), goodFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
auto firstEnd = std::min(TEST_MTU, serialized.size());
Blob firstFragment(serialized.begin(), serialized.begin() + firstEnd);
auto firstPacket = makeValueDataPacketBlob(11, 0, 0, firstFragment);
engine.processMessage(firstPacket.data(), firstPacket.size(), badFrom);
CPPUNIT_ASSERT_EQUAL((size_t) 1, engine.getPartialCount());
CPPUNIT_ASSERT_EQUAL(0, onNewNodeCalls);
engine.processMessage(firstPacket.data(), firstPacket.size(), goodFrom);
if (firstEnd < serialized.size()) {
Blob tail(serialized.begin() + firstEnd, serialized.end());
auto tailPacket = makeValueDataPacketBlob(11, 0, firstEnd, tail);
engine.processMessage(tailPacket.data(), tailPacket.size(), goodFrom);
}
CPPUNIT_ASSERT_EQUAL((size_t) 0, engine.getPartialCount());
CPPUNIT_ASSERT(onNewNodeCalls > 0);
}
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);
CPPUNIT_ASSERT(searchNodePtr->last_get_reply == localDht.scheduler.time());
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
+43
View File
@@ -0,0 +1,43 @@
// 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(testIgnoresUnknownPartialData);
CPPUNIT_TEST(testCompletesPartialSessionFromSameSource);
CPPUNIT_TEST(testKeepsSessionForWrongSourceFragment);
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 testIgnoresUnknownPartialData();
void testCompletesPartialSessionFromSameSource();
void testKeepsSessionForWrongSourceFragment();
void testListenConfirmationCarriesToken();
void testListenConfirmationUpdatesSearchNodeToken();
void testListenReopensSocketAfterNodeExpiration();
void testUnauthorizedListenFlushClearsListenState();
#endif
};
} // namespace test
+540
View File
@@ -0,0 +1,540 @@
// 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::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
+68
View File
@@ -0,0 +1,68 @@
// 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(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 testParseIgnoresIncompleteValueDataEntry();
void testCompleteEmpty();
void testCompleteIncomplete();
void testCompleteZeroSizedValue();
void testCompleteSingleValue();
void testCompleteMultipleValues();
void testCompleteReassemblyOrder();
void testAppendMultipleValuesOutOfOrder();
void testReceiveLargeFragmentedValue();
void testReceiveLargeFragmentedValueOutOfOrder();
};
} // namespace test
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "peerdiscoverytester.h"
#include "test_peerdiscovery.h"
#include "opendht/value.h"
#include <mutex>
File renamed without changes.
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "storagetester.h"
#include "test_storage.h"
#include <opendht/thread_pool.h>
File renamed without changes.
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "threadpooltester.h"
#include "test_threadpool.h"
#include "opendht/thread_pool.h"
#include <atomic>
File renamed without changes.
@@ -1,7 +1,7 @@
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#include "valuetester.h"
#include "test_value.h"
#include <iostream>
#include <string>
File renamed without changes.
+26 -2
View File
@@ -5,12 +5,29 @@
#include <cppunit/CompilerOutputter.h>
#include <iostream>
namespace {
void
listTests(CppUnit::Test* test, const std::string& prefix = {})
{
const auto name = test->getName();
const auto fullName = prefix.empty() ? name : prefix + "/" + name;
if (test->getChildTestCount() == 0) {
std::cout << fullName << std::endl;
return;
}
for (int index = 0; index < test->getChildTestCount(); ++index)
listTests(test->getChildTestAt(index), fullName);
}
} // namespace
extern "C" {
#include <gnutls/gnutls.h>
}
int
main(int /*argc*/, char** /*argv*/)
main(int argc, char** argv)
{
#ifdef _MSC_VER
if (auto err = gnutls_global_init()) {
@@ -25,8 +42,15 @@ main(int /*argc*/, char** /*argv*/)
std::cout << "No test cases specified for suite" << std::endl;
return 1;
}
if (argc > 1 && std::string(argv[1]) == "--list") {
listTests(suite);
return 0;
}
CppUnit::TextUi::TestRunner runner;
runner.addTest(suite);
if (argc > 1)
runner.addTest(suite->findTest(argv[1]));
else
runner.addTest(suite);
auto result = runner.run() ? 0 : 1;
return result;
}
-20
View File
@@ -1,20 +0,0 @@
bin_PROGRAMS = dhtnode dhtchat dhtscanner
noinst_HEADERS = tools_common.h
AM_CPPFLAGS = -isystem @top_srcdir@/include @JsonCpp_CFLAGS@ @MsgPack_CFLAGS@
dhtnode_SOURCES = dhtnode.cpp
dhtnode_LDFLAGS = -lopendht -lreadline -lfmt -L@top_builddir@/src/.libs @GnuTLS_LIBS@
dhtchat_SOURCES = dhtchat.cpp
dhtchat_LDFLAGS = -lopendht -lreadline -lfmt -L@top_builddir@/src/.libs @GnuTLS_LIBS@
dhtscanner_SOURCES = dhtscanner.cpp
dhtscanner_LDFLAGS = -lopendht -lreadline -lfmt -L@top_builddir@/src/.libs @GnuTLS_LIBS@
if ENABLE_C
bin_PROGRAMS += dhtcnode
dhtcnode_CFLAGS = -std=c11 -isystem @top_srcdir@/c -isystem @top_srcdir@/include
dhtcnode_SOURCES = dhtcnode.c
dhtcnode_LDFLAGS = -lopendht-c -lreadline -lfmt -L@top_builddir@/c/.libs
endif
+19 -13
View File
@@ -12,10 +12,10 @@ extern "C" {
}
#include <ctime>
using namespace dht;
namespace dht {
namespace tools {
static std::mt19937_64 rd {dht::crypto::getSeededRandomEngine<std::mt19937_64>()};
static std::uniform_int_distribution<dht::Value::Id> rand_id;
const std::string
printTime(const std::time_t& now)
@@ -34,12 +34,18 @@ print_usage()
std::cout << "Report bugs to: https://opendht.net" << std::endl;
}
} // namespace tools
} // namespace dht
int
main(int argc, char** argv)
{
auto params = parseArgs(argc, argv);
using namespace dht;
using IdDist = std::uniform_int_distribution<dht::Value::Id>;
auto params = tools::parseArgs(argc, argv);
if (params.help) {
print_usage();
tools::print_usage();
return 0;
}
#ifdef _MSC_VER
@@ -58,14 +64,14 @@ main(int argc, char** argv)
if (not params.bootstrap.empty())
dht.bootstrap(params.bootstrap);
print_node_info(dht.getNodeInfo());
tools::print_node_info(dht.getNodeInfo());
std::cout << " type 'c {hash}' to join a channel" << std::endl << std::endl;
bool connected {false};
InfoHash room;
std::future<size_t> token;
const InfoHash myid = dht.getId();
const auto mypk = dht.getPublicKey();
#ifndef _MSC_VER
// using the GNU History API
@@ -74,7 +80,7 @@ main(int argc, char** argv)
while (true) {
// using the GNU Readline API
std::string line = readLine(connected ? PROMPT : "> ");
std::string line = tools::readLine(connected ? tools::PROMPT : "> ");
if (!line.empty() && line[0] == '\0')
break;
if (line.empty())
@@ -95,12 +101,12 @@ main(int argc, char** argv)
}
token = dht.listen<dht::ImMessage>(room, [&](dht::ImMessage&& msg) {
if (msg.from != myid)
std::cout << msg.from.toString() << " at " << printTime(msg.date) << " (took "
if (msg.owner && msg.owner->getLongId() != mypk->getLongId())
std::cout << msg.from.toString() << " at " << tools::printTime(msg.date) << " (took "
<< print_duration(std::chrono::system_clock::now()
- std::chrono::system_clock::from_time_t(msg.date))
<< ") " << (msg.to == myid ? "ENCRYPTED " : "") << ": " << msg.id << " - "
<< msg.msg << std::endl;
<< ") " << (msg.to == mypk->getId() ? "ENCRYPTED " : "") << ": " << msg.id
<< " - " << msg.msg << std::endl;
return true;
});
connected = true;
@@ -118,14 +124,14 @@ main(int argc, char** argv)
std::getline(iss, line);
dht.putEncrypted(room,
InfoHash(idstr),
dht::ImMessage(rand_id(rd), std::move(line), now),
dht::ImMessage(IdDist()(tools::rd), std::move(line), now),
[](bool ok) {
// dht.cancelPut(room, id);
if (not ok)
std::cout << "Message publishing failed !" << std::endl;
});
} else {
dht.putSigned(room, dht::ImMessage(rand_id(rd), std::move(line), now), [](bool ok) {
dht.putSigned(room, dht::ImMessage(IdDist()(tools::rd), std::move(line), now), [](bool ok) {
// dht.cancelPut(room, id);
if (not ok)
std::cout << "Message publishing failed !" << std::endl;
+7 -1
View File
@@ -16,7 +16,8 @@ extern "C" {
#include <set>
#include <thread> // std::this_thread::sleep_for
using namespace dht;
namespace dht {
namespace tools {
void
print_info()
@@ -548,9 +549,14 @@ cmd_loop(std::shared_ptr<DhtRunner>& node,
std::cout << std::endl << "Stopping node..." << std::endl;
}
} // namespace tools
} // namespace dht
int
main(int argc, char** argv)
{
using namespace dht;
using namespace dht::tools;
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
+113 -45
View File
@@ -1,54 +1,122 @@
import requests
#!/usr/bin/env python3
"""Collect and aggregate statistics from DHT proxy servers."""
import asyncio
import json
import sys
import time
from typing import Dict, Any
ts = time.time()
import aiohttp
stats_total = {"users":0, "pushListenersCount":0, "listenCount":0, "totalListeners":0, "totalPermanentPuts":0, "timestamp": str(ts)}
for i in range(80,101):
print("Collecting stats for proxy " + str(i))
response = requests.request('GET', 'http://127.0.0.1:' + str(i) + '/node/stats')
# Configuration
START_PORT = 80
END_PORT = 101
HOST = "localhost"
OUTPUT_FILE = "stats_proxy_total"
REQUEST_TIMEOUT = 20 # seconds
if response.status_code == 200:
result = response.json()
stats = {}
# Get Total users
try:
stats['users'] = int(int(result["putCount"])/2)
stats_total['users'] += int(int(result["putCount"])/2)
except:
pass
# Get android push
try:
stats['pushListenersCount'] = int(result["pushListenersCount"])
stats_total['pushListenersCount'] += int(result["pushListenersCount"])
except:
pass
# Get Listeners
try:
stats['listenCount'] = int(result["listenCount"])
stats_total['listenCount'] += int(result["listenCount"])
except:
pass
# Get permanents put nb
try:
stats['totalListeners'] = int(result["pushListenersCount"]) + int(result["listenCount"])
stats_total['totalListeners'] += int(result["pushListenersCount"]) + int(result["listenCount"])
except:
pass
try:
stats['totalPermanentPuts'] = int(result["totalPermanentPuts"])
stats_total['totalPermanentPuts'] += int(result["totalPermanentPuts"])
except:
pass
def initialize_stats(timestamp: float) -> Dict[str, Any]:
"""Initialize the stats dictionary with zero values."""
return {
"users": 0,
"pushListenersCount": 0,
"listenCount": 0,
"totalListeners": 0,
"totalPermanentPuts": 0,
"timestamp": timestamp,
"local_storage_size": 0,
"local_storage_values": 0,
"storage_size": 0,
"storage_values": 0
}
stats['timestamp'] = str(ts)
#with open("stats_proxy_" + str(i), "a") as stat_file:
# stat_file.write(str(stats))
# stat_file.write('\n')
async def get_proxy_stats(session: aiohttp.ClientSession, port: int) -> Dict[str, Any]:
url = f"http://{HOST}:{port}/node/stats"
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)) as response:
response.raise_for_status()
return await response.json()
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
print(f"Error fetching stats from proxy {port}: {e}", file=sys.stderr)
return None
with open("stats_proxy_total", "w") as stat_file:
stat_file.write(str(stats_total))
stat_file.write('\n')
def extract_stat(data: Dict[str, Any], key: str, default: int = 0) -> int:
try:
return int(data.get(key, default))
except (ValueError, TypeError):
return default
def aggregate_proxy_stats(result: Dict[str, Any], stats_total: Dict[str, Any]):
"""Aggregate statistics from a single proxy into the total."""
# Calculate users from putCount (divide by 2)
put_count = extract_stat(result, "putCount")
stats_total["users"] += put_count // 2
# Aggregate listener counts
push_listeners = extract_stat(result, "pushListenersCount")
listen_count = extract_stat(result, "listenCount")
stats_total["pushListenersCount"] += push_listeners
stats_total["listenCount"] += listen_count
stats_total["totalListeners"] += push_listeners + listen_count
stats_total["totalPermanentPuts"] += extract_stat(
result, "totalPermanentPuts")
# Aggregate storage statistics from nodeInfo
node_info = result.get("nodeInfo", {})
stats_total["local_storage_size"] += extract_stat(
node_info, "local_storage_size")
stats_total["local_storage_values"] += extract_stat(
node_info, "local_storage_values")
stats_total["storage_size"] += extract_stat(node_info, "storage_size")
stats_total["storage_values"] += extract_stat(node_info, "storage_values")
async def collect_all_stats() -> Dict[str, Any]:
timestamp = time.time()
stats_total = initialize_stats(timestamp)
async with aiohttp.ClientSession() as session:
# Create tasks for all proxy requests in parallel
tasks = []
for port in range(START_PORT, END_PORT):
tasks.append(get_proxy_stats(session, port))
# Wait for all requests to complete
print(f"Collecting stats from {len(tasks)} proxies in parallel...")
results = await asyncio.gather(*tasks)
# Aggregate results
for port, result in zip(range(START_PORT, END_PORT), results):
if result:
print(f"Processing stats from proxy {port}")
aggregate_proxy_stats(result, stats_total)
return stats_total
def save_stats(stats: Dict[str, Any], filepath: str) -> None:
try:
with open(filepath, "w") as stat_file:
json.dump(stats, stat_file, indent=2)
stat_file.write("\n")
print(f"Statistics saved to {filepath}")
except IOError as e:
print(f"Error writing to file {filepath}: {e}", file=sys.stderr)
sys.exit(1)
async def main() -> None:
"""Main entry point for the statistics collection script."""
stats_total = await collect_all_stats()
save_stats(stats_total, OUTPUT_FILE)
if __name__ == "__main__":
asyncio.run(main())
+7 -2
View File
@@ -15,7 +15,8 @@ extern "C" {
#include <condition_variable>
#include <mutex>
using namespace dht;
namespace dht {
namespace tools {
void
print_usage()
@@ -82,9 +83,13 @@ step(DhtRunner& dht, std::atomic_uint& done, std::shared_ptr<NodeSet> all_nodes,
});
}
} // namespace tools
} // namespace dht
int
main(int argc, char** argv)
{
using namespace dht::tools;
#ifdef _MSC_VER
if (auto err = gnutls_global_init()) {
fmt::print(stderr, "Failed to initialize GnuTLS: {}\n", gnutls_strerror(err));
@@ -97,7 +102,7 @@ main(int argc, char** argv)
return EXIT_SUCCESS;
}
DhtRunner dht;
dht::DhtRunner dht;
try {
auto [config, context] = getDhtConfig(params);
config.dht_config.node_config.client_mode = true;
+18 -15
View File
@@ -14,6 +14,14 @@ extern "C" {
#include <mutex>
#include <atomic>
namespace dht {
namespace tests {
constexpr unsigned PINGPONG_MAX = 2048;
using clock = std::chrono::high_resolution_clock;
using duration = clock::duration;
void
print_usage()
{
@@ -21,14 +29,6 @@ print_usage()
std::cout << "perftest, a simple OpenDHT basic performance tester." << std::endl;
std::cout << "Report bugs to: https://opendht.net" << std::endl;
}
constexpr unsigned PINGPONG_MAX = 2048;
using namespace dht;
namespace tests {
using clock = std::chrono::high_resolution_clock;
using duration = clock::duration;
duration
benchPingPong(unsigned netSize, unsigned n_parallel)
{
@@ -115,10 +115,13 @@ benchPingPong(unsigned netSize, unsigned n_parallel)
}
} // namespace tests
} // namespace dht
int
main(int argc, char** argv)
{
using namespace dht::tools;
using namespace dht::tests;
#ifdef _MSC_VER
if (auto err = gnutls_global_init()) {
std::cerr << "Failed to initialize GnuTLS: " << gnutls_strerror(err) << std::endl;
@@ -140,10 +143,10 @@ main(int argc, char** argv)
results.reserve(8);
duration total {0};
for (unsigned i = 2; i < 32; i *= 2) {
auto dt = tests::benchPingPong(i - 2, nparallel);
auto dt = benchPingPong(i - 2, nparallel);
std::cout << "Network size: " << i << std::endl;
std::cout << max << " ping-pong done, took " << print_duration(dt) << std::endl;
std::cout << print_duration(dt / max) << " per rt, " << max / std::chrono::duration<double>(dt).count()
std::cout << max << " ping-pong done, took " << dht::print_duration(dt) << std::endl;
std::cout << dht::print_duration(dt / max) << " per rt, " << max / std::chrono::duration<double>(dt).count()
<< " ping per s" << std::endl
<< std::endl;
total += dt;
@@ -155,14 +158,14 @@ main(int argc, char** argv)
std::cout << "Total for " << nparallel << std::endl;
auto totNum = max * results.size();
std::cout << totNum << " ping-pong done, took " << print_duration(total) << std::endl;
std::cout << print_duration(total / totNum) << " per rt, "
std::cout << totNum << " ping-pong done, took " << dht::print_duration(total) << std::endl;
std::cout << dht::print_duration(total / totNum) << " per rt, "
<< totNum / std::chrono::duration<double>(total).count() << " ping per s" << std::endl
<< std::endl;
}
std::cout << std::endl << "Grand total: " << print_duration(totalTime) << " for " << totalOps << std::endl;
std::cout << print_duration(totalTime / totalOps) << " per rt, "
std::cout << std::endl << "Grand total: " << dht::print_duration(totalTime) << " for " << totalOps << std::endl;
std::cout << dht::print_duration(totalTime / totalOps) << " per rt, "
<< totalOps / std::chrono::duration<double>(totalTime).count() << " ping per s" << std::endl
<< std::endl;
+12 -4
View File
@@ -54,6 +54,9 @@
#include <sstream>
#include <fstream>
namespace dht {
namespace tools {
/*
* The mapString shall have the following format:
*
@@ -193,7 +196,9 @@ getDhtConfig(dht_params& params)
}
if (context.logger) {
context.statusChangedCallback = [logger = context.logger](dht::NodeStatus status4, dht::NodeStatus status6) {
logger->w("Connectivity changed: IPv4: %s, IPv6: %s", dht::statusToStr(status4), dht::statusToStr(status6));
logger->warn("Connectivity changed: IPv4: {}, IPv6: {}",
dht::statusToStr(status4),
dht::statusToStr(status6));
};
context.publicAddressChangedCb = [logger = context.logger](std::vector<dht::SockAddr> addrs) {
logger->warn("Public address changed: {}", addrs);
@@ -212,9 +217,9 @@ print_node_info(const dht::NodeInfo& info)
std::cout << "IPv4 port " << info.bound4 << ", IPv6 port " << info.bound6 << std::endl;
if (info.id)
std::cout << "Public key ID " << info.id << std::endl;
std::cout << "Storage: " << info.storage_values << " values, " << info.storage_size << " bytes" << std::endl;
std::cout << "Local storage: " << info.local_storage_values << " values, " << info.local_storage_size << " bytes"
<< std::endl;
std::cout << "Storage: " << info.storage_values << " values, " << printByteCount(info.storage_size) << std::endl;
std::cout << "Local storage: " << info.local_storage_values << " values, "
<< printByteCount(info.local_storage_size) << std::endl;
std::cout << "Ongoing operations: " << info.ongoing_ops << std::endl;
}
@@ -487,3 +492,6 @@ daemonize()
close(STDERR_FILENO);
#endif
}
} // namespace tools
} // namespace dht
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "opendht",
"version-string": "3.7.0",
"version-string": "3.7.6",
"port-version": 1,
"description": "OpenDHT: A lightweight C++17 Distributed Hash Table library.",
"homepage": "https://opendht.net",