mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-11 05:47:17 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eda36c5044 | ||
|
|
659b64bfb2 | ||
|
|
4de6d6cfd9 | ||
|
|
c2a74045c1 | ||
|
|
a9c9b75bee | ||
|
|
b2ff05407b | ||
|
|
6a45891ee7 | ||
|
|
d1f432b6d6 | ||
|
|
432b6792ca | ||
|
|
d32f51455a |
No files matched your search
@@ -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:
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
# Run locally with: pipx run cibuildwheel --platform linux python
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v3.4.0
|
||||
uses: pypa/cibuildwheel@v3.3.1
|
||||
with:
|
||||
package-dir: python
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DEPS }}
|
||||
|
||||
@@ -69,13 +69,13 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
@@ -110,13 +110,13 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DEPS_LLVM }}
|
||||
|
||||
@@ -151,13 +151,13 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LLVM }}
|
||||
@@ -193,13 +193,13 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_DHTNODE }}
|
||||
@@ -234,13 +234,13 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -248,7 +248,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE_DEPS }}
|
||||
@@ -276,13 +276,13 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -290,7 +290,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALPINE }}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# OpenDHT – Agent Instructions
|
||||
|
||||
## Build
|
||||
|
||||
### Primary: CMake (C++17)
|
||||
```sh
|
||||
mkdir build && cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON -GNinja
|
||||
ninja
|
||||
```
|
||||
|
||||
### Common CMake options
|
||||
| Option | Default | Purpose |
|
||||
|--------|---------|---------|
|
||||
| `OPENDHT_TOOLS` | ON | CLI tools (dhtnode, dhtchat, …) |
|
||||
| `OPENDHT_C` | OFF | C language bindings |
|
||||
| `OPENDHT_PYTHON` | OFF | Python 3 Cython bindings |
|
||||
| `OPENDHT_PROXY_SERVER` | OFF | REST API proxy server (requires RESTinio + llhttp) |
|
||||
| `OPENDHT_PROXY_CLIENT` | OFF | REST API proxy client |
|
||||
| `OPENDHT_PEER_DISCOVERY` | OFF | Multicast local peer discovery |
|
||||
| `OPENDHT_PUSH_NOTIFICATIONS` | OFF | Push notification support |
|
||||
| `OPENDHT_SANITIZE` | OFF | AddressSanitizer + stack protector |
|
||||
|
||||
### Alternative: Meson (C++20)
|
||||
```sh
|
||||
meson setup build . && cd build && ninja
|
||||
```
|
||||
|
||||
### Run tests
|
||||
```sh
|
||||
# Full suite
|
||||
ctest --output-on-failure --test-dir build
|
||||
|
||||
# Single test executable
|
||||
./build/test_testName
|
||||
```
|
||||
|
||||
## dhtnode
|
||||
|
||||
The `dhtnode` CLI tool is a simple DHT node implementation using `DhtRunner`.
|
||||
|
||||
---
|
||||
|
||||
## Commit
|
||||
|
||||
After implementing a feature or fixing a bug, commit your changes in clean, atomic commits with descriptive messages with format:
|
||||
`topic: short description`
|
||||
|
||||
If any test are added or modified, commit them separately before the fix (if any).
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
OpenDHT is a Kademlia-based DHT library. The implementation is layered:
|
||||
|
||||
```
|
||||
DhtRunner (public API — thread-safe, manages lifecycle & I/O threads)
|
||||
└── SecureDht (GnuTLS crypto: signing, encryption, identity)
|
||||
└── Dht (core DHT logic: routing, storage, find/get/put operations)
|
||||
└── NetworkEngine (raw UDP sockets, message parsing, IPv4/IPv6)
|
||||
```
|
||||
|
||||
- **`DhtRunner`** (`include/opendht/dhtrunner.h`, `src/dhtrunner.cpp`) — entry point for all users of the library. Wraps everything in a thread-pool and exposes async callbacks as well as blocking calls.
|
||||
- **`Dht`** (`include/opendht/dht.h`, `src/dht.cpp`) — Kademlia protocol: routing table, k-buckets, value replication, expiry.
|
||||
- **`SecureDht`** (`include/opendht/securedht.h`, `src/securedht.cpp`) — transparent encryption/signing layer on top of `Dht`. Identity is a GnuTLS certificate.
|
||||
- **`NetworkEngine`** (`include/opendht/network_engine.h`, `src/network_engine.cpp`) — UDP socket layer, serializes/deserializes msgpack messages.
|
||||
- **`RoutingTable`** (`include/opendht/routing_table.h`, `src/routing_table.cpp`) — Kademlia routing table with bucket splitting.
|
||||
- **`Value`** (`include/opendht/value.h`) — the storable unit: binary blob, optional signature, optional encryption; typed via `ValueType`.
|
||||
- **`InfoHash`** (`include/opendht/infohash.h`) — 20-byte SHA-1 identifier; alias `Hash<HASH_LEN>`.
|
||||
|
||||
Optional components (`DhtProxyServer`, `DhtProxyClient`, `PeerDiscovery`) are compiled in only when the corresponding CMake options are enabled.
|
||||
|
||||
---
|
||||
@@ -21,11 +21,11 @@ Optionally, install RESTinio to enable the OpenDHT proxy client and server:
|
||||
mkdir /usr/include/nonstd
|
||||
wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp -O /usr/include/nonstd/expected.hpp
|
||||
|
||||
wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.9/restinio-0.7.9.tar.bz2
|
||||
tar -xjf restinio-0.7.9.tar.bz2
|
||||
cd restinio-0.7.9/dev
|
||||
wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.3/restinio-0.7.3.tar.bz2
|
||||
tar -xjf restinio-0.7.3.tar.bz2
|
||||
cd restinio-0.7.3/dev
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system .
|
||||
make && make install
|
||||
make -j2 && make install
|
||||
```
|
||||
|
||||
To run openDHT on a different computer from the build computer, the following runtime dependencies are required:
|
||||
@@ -48,11 +48,11 @@ Optionally, install RESTinio to enable the OpenDHT proxy client and server:
|
||||
mkdir /usr/include/nonstd
|
||||
wget https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp -O /usr/include/nonstd/expected.hpp
|
||||
|
||||
wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.9/restinio-0.7.9.tar.bz2
|
||||
tar -xjf restinio-0.7.9.tar.bz2
|
||||
cd restinio-0.7.9/dev
|
||||
wget https://github.com/Stiffstream/restinio/releases/download/v.0.7.3/restinio-0.7.3.tar.bz2
|
||||
tar -xjf restinio-0.7.3.tar.bz2
|
||||
cd restinio-0.7.3/dev
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr -DRESTINIO_TEST=Off -DRESTINIO_SAMPLE=Off -DRESTINIO_BENCHMARK=Off -DRESTINIO_WITH_SOBJECTIZER=Off -DRESTINIO_DEP_STANDALONE_ASIO=system -DRESTINIO_DEP_LLHTTP=system -DRESTINIO_DEP_FMT=system -DRESTINIO_DEP_EXPECTED_LITE=system .
|
||||
make && make install
|
||||
make -j2 && make install
|
||||
```
|
||||
|
||||
### Fedora
|
||||
@@ -104,18 +104,6 @@ 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.
|
||||
|
||||
+25
-32
@@ -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.6)
|
||||
set (opendht_VERSION_MINOR 7.0)
|
||||
set (opendht_VERSION ${opendht_VERSION_MAJOR}.${opendht_VERSION_MINOR})
|
||||
set (PACKAGE_VERSION ${opendht_VERSION})
|
||||
set (VERSION "${opendht_VERSION}")
|
||||
@@ -130,7 +130,6 @@ if (NOT MSVC)
|
||||
message(STATUS "simdutf not found with pkgconfig")
|
||||
if (OPENDHT_DOWNLOAD_DEPS)
|
||||
message(STATUS "Fallback mode specified, downloading simdutf")
|
||||
set(SIMDUTF_CXX_STANDARD ${CMAKE_CXX_STANDARD} CACHE INTERNAL "C++ standard for simdutf downloader")
|
||||
set(SIMDUTF_TESTS OFF CACHE INTERNAL "Disable tests for simdutf downloader")
|
||||
set(SIMDUTF_TOOLS OFF CACHE INTERNAL "Disable tools for simdutf downloader")
|
||||
FetchContent_Declare(simdutf
|
||||
@@ -854,42 +853,36 @@ if (BUILD_TESTING)
|
||||
endif()
|
||||
# unit testing
|
||||
list (APPEND test_FILES
|
||||
tests/test_infohash.h
|
||||
tests/test_infohash.cpp
|
||||
tests/test_value.h
|
||||
tests/test_value.cpp
|
||||
tests/test_crypto.h
|
||||
tests/test_crypto.cpp
|
||||
tests/test_dhtrunner.h
|
||||
tests/test_dhtrunner.cpp
|
||||
tests/test_storage.h
|
||||
tests/test_storage.cpp
|
||||
tests/test_threadpool.h
|
||||
tests/test_threadpool.cpp
|
||||
tests/test_parsedmessage.h
|
||||
tests/test_parsedmessage.cpp
|
||||
tests/test_networkengine.h
|
||||
tests/test_networkengine.cpp
|
||||
tests/infohashtester.h
|
||||
tests/infohashtester.cpp
|
||||
tests/valuetester.h
|
||||
tests/valuetester.cpp
|
||||
tests/cryptotester.h
|
||||
tests/cryptotester.cpp
|
||||
tests/dhtrunnertester.h
|
||||
tests/dhtrunnertester.cpp
|
||||
tests/storagetester.h
|
||||
tests/storagetester.cpp
|
||||
tests/threadpooltester.h
|
||||
tests/threadpooltester.cpp
|
||||
tests/opcachetester.h
|
||||
tests/opcachetester.cpp
|
||||
tests/valuecachetester.h
|
||||
tests/valuecachetester.cpp
|
||||
)
|
||||
if (OPENDHT_PROXY_CLIENT)
|
||||
list (APPEND test_FILES
|
||||
tests/test_dhtproxy_client.h
|
||||
tests/test_dhtproxy_client.cpp
|
||||
)
|
||||
endif()
|
||||
if (OPENDHT_TESTS_NETWORK)
|
||||
if (OPENDHT_PROXY_SERVER AND OPENDHT_PROXY_CLIENT)
|
||||
list (APPEND test_FILES
|
||||
tests/test_http.h
|
||||
tests/test_http.cpp
|
||||
tests/test_dhtproxy.h
|
||||
tests/test_dhtproxy.cpp
|
||||
tests/httptester.h
|
||||
tests/httptester.cpp
|
||||
tests/dhtproxytester.h
|
||||
tests/dhtproxytester.cpp
|
||||
)
|
||||
endif()
|
||||
if (OPENDHT_PEER_DISCOVERY)
|
||||
list (APPEND test_FILES
|
||||
tests/test_peerdiscovery.h
|
||||
tests/test_peerdiscovery.cpp
|
||||
tests/peerdiscoverytester.h
|
||||
tests/peerdiscoverytester.cpp
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
@@ -897,7 +890,7 @@ if (BUILD_TESTING)
|
||||
# We keep using the common tests_runner.cpp (CppUnit registry based) and compile it with each test file.
|
||||
foreach(test_src ${test_FILES})
|
||||
if (test_src MATCHES ".*\\.cpp$")
|
||||
get_filename_component(test_name ${test_src} NAME_WE) # e.g. test_infohash
|
||||
get_filename_component(test_name ${test_src} NAME_WE) # e.g. infohashtester
|
||||
set(target_name ${test_name})
|
||||
add_executable(${target_name}
|
||||
tests/tests_runner.cpp
|
||||
|
||||
@@ -140,22 +140,20 @@ asyncio.run(dht_async_demo("unique_key"))
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
Core dependencies:
|
||||
- msgpack-c 1.2+, used for data serialization.
|
||||
- GnuTLS 3.3+, used for cryptographic operations.
|
||||
- Nettle 2.4+, a GnuTLS dependency for crypto.
|
||||
- {fmt} 9.0+, for log formatting.
|
||||
|
||||
Optional dependencies for the REST API (DHT proxy client/server):
|
||||
- (optional) restinio
|
||||
- (optional) llhttp
|
||||
- (optional) jsoncpp 1.7.4-3+
|
||||
- (optional) simdutf
|
||||
|
||||
Build environment:
|
||||
- (optional) restinio used for the REST API.
|
||||
- (optional) llhttp used for the REST API.
|
||||
- (optional) jsoncpp 1.7.4-3+, used for the REST API.
|
||||
- Build tested with GCC 9+ (GNU/Linux, Windows with MinGW), Clang/LLVM (GNU/Linux, Android, macOS, iOS).
|
||||
- Build tested with Microsoft Visual Studio 2019, 2022
|
||||
|
||||
## Contact
|
||||
|
||||
IRC: join us on Libera.chat at [`#opendht`](https://web.libera.chat/#opendht).
|
||||
|
||||
## License
|
||||
Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
lib_LTLIBRARIES = libopendht-c.la
|
||||
noinst_HEADERS = opendht_c.h
|
||||
|
||||
AM_CPPFLAGS = -DOPENDHT_C_BUILD -isystem @top_srcdir@/include @JsonCpp_CFLAGS@ @MsgPack_CFLAGS@
|
||||
if OPENDHT_SHARED
|
||||
AM_CPPFLAGS += -Dopendht_c_EXPORTS
|
||||
endif
|
||||
|
||||
libopendht_c_la_LIBADD = $(LIBOBJS) ../src/.libs/libopendht.la
|
||||
|
||||
libopendht_c_la_SOURCES = \
|
||||
opendht.cpp
|
||||
|
||||
nobase_include_HEADERS = \
|
||||
opendht_c.h
|
||||
@@ -0,0 +1,16 @@
|
||||
dist_man1_MANS = dhtnode.1
|
||||
|
||||
noinst_HEADERS = \
|
||||
Doxyfile
|
||||
|
||||
if HAVE_DOXYGEN
|
||||
doxyfile.stamp: Doxyfile
|
||||
$(DOXYGEN) Doxyfile
|
||||
echo stamp > doxyfile.stamp
|
||||
|
||||
CLEANFILES = doxyfile.stamp
|
||||
|
||||
all-local: doxyfile.stamp
|
||||
clean-local:
|
||||
rm -rf $(top_srcdir)/doc/man $(top_srcdir)/doc/html
|
||||
endif
|
||||
@@ -23,15 +23,15 @@ struct Node;
|
||||
*/
|
||||
enum class NodeStatus {
|
||||
Disconnected, // 0 nodes
|
||||
Connecting, // bootstrap pending or 1+ nodes
|
||||
Connecting, // 1+ nodes
|
||||
Connected // 1+ good nodes
|
||||
};
|
||||
|
||||
inline constexpr std::string_view
|
||||
inline constexpr const char*
|
||||
statusToStr(NodeStatus status)
|
||||
{
|
||||
return status == NodeStatus::Connected ? "connected"sv
|
||||
: (status == NodeStatus::Connecting ? "connecting"sv : "disconnected"sv);
|
||||
return status == NodeStatus::Connected ? "connected"
|
||||
: (status == NodeStatus::Connecting ? "connecting" : "disconnected");
|
||||
}
|
||||
|
||||
struct OPENDHT_PUBLIC NodeStats
|
||||
|
||||
@@ -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_ {};
|
||||
};
|
||||
|
||||
|
||||
@@ -411,7 +411,6 @@ private:
|
||||
std::vector<std::pair<std::string, std::string>> bootstrap_nodes {};
|
||||
std::chrono::steady_clock::duration bootstrap_period {BOOTSTRAP_PERIOD};
|
||||
Sp<Scheduler::Job> bootstrapJob {};
|
||||
bool bootstrap_pending {false};
|
||||
|
||||
std::map<InfoHash, Storage> store;
|
||||
std::map<SockAddr, StorageBucket, SockAddr::ipCmp> store_quota;
|
||||
@@ -465,8 +464,7 @@ private:
|
||||
const Sp<Value>& value,
|
||||
time_point created,
|
||||
const SockAddr& sa = {},
|
||||
bool permanent = false,
|
||||
time_point expiration = time_point::min());
|
||||
bool permanent = false);
|
||||
bool storageRefresh(const InfoHash& id, Value::Id vid);
|
||||
void expireStore();
|
||||
void expireStorage(InfoHash h);
|
||||
@@ -628,7 +626,7 @@ private:
|
||||
/* when we receive a listen request */
|
||||
net::RequestAnswer onListen(
|
||||
Sp<Node> node, const InfoHash& hash, const Blob& token, size_t socket_id, const Query& query, int version = 0);
|
||||
void onListenDone(const Sp<Node>& node, net::RequestAnswer& a, Sp<Search>& sr);
|
||||
void onListenDone(const Sp<Node>& status, net::RequestAnswer& a, Sp<Search>& sr);
|
||||
/* when we receive an announce request */
|
||||
net::RequestAnswer onAnnounce(Sp<Node> node,
|
||||
const InfoHash& hash,
|
||||
|
||||
@@ -276,7 +276,6 @@ public:
|
||||
void insertNode(const InfoHash&, const SockAddr&) override {}
|
||||
void insertNode(const NodeExport&) override {}
|
||||
std::pair<size_t, size_t> getStoreSize() const override { return {}; }
|
||||
std::pair<size_t, size_t> getLocalStoreSize() const override { return {}; }
|
||||
std::vector<NodeExport> exportNodes() const override { return {}; }
|
||||
std::vector<ValuesExport> exportValues() const override { return {}; }
|
||||
void importValues(const std::vector<ValuesExport>&) override {}
|
||||
|
||||
@@ -451,8 +451,7 @@ public:
|
||||
|
||||
// securedht methods
|
||||
|
||||
[[deprecated("Use PkId version")]] void findCertificate(
|
||||
InfoHash hash, std::function<void(const std::shared_ptr<crypto::Certificate>&)>);
|
||||
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);
|
||||
@@ -579,16 +578,16 @@ private:
|
||||
*/
|
||||
void resetDht();
|
||||
|
||||
mutable std::mutex dht_mtx;
|
||||
mutable std::mutex dht_mtx {};
|
||||
std::thread dht_thread {};
|
||||
std::condition_variable cv {};
|
||||
std::mutex sock_mtx;
|
||||
std::mutex sock_mtx {};
|
||||
net::PacketList rcv {};
|
||||
decltype(rcv) rcv_free {};
|
||||
|
||||
std::queue<std::function<void(SecureDht&)>> pending_ops_prio {};
|
||||
std::queue<std::function<void(SecureDht&)>> pending_ops {};
|
||||
std::mutex storage_mtx;
|
||||
std::mutex storage_mtx {};
|
||||
|
||||
std::atomic<State> running {State::Idle};
|
||||
std::atomic_size_t ongoing_ops {0};
|
||||
|
||||
+41
-70
@@ -42,7 +42,8 @@ OPENDHT_PUBLIC void hash(const uint8_t* data, size_t data_length, uint8_t* hash,
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an Hash, a byte array of N bytes.
|
||||
* Represents an Hash,
|
||||
* a byte array of N bytes.
|
||||
* Hashes identify nodes and values in the Dht.
|
||||
*/
|
||||
template<size_t N>
|
||||
@@ -53,20 +54,27 @@ public:
|
||||
typedef typename T::iterator iterator;
|
||||
typedef typename T::const_iterator const_iterator;
|
||||
|
||||
constexpr Hash() noexcept
|
||||
: data_ {}
|
||||
{}
|
||||
constexpr Hash() noexcept { data_.fill(0); }
|
||||
constexpr Hash(const uint8_t* h, size_t data_len)
|
||||
: data_(fromBytes(h, data_len))
|
||||
{}
|
||||
{
|
||||
if (data_len < N)
|
||||
data_.fill(0);
|
||||
else
|
||||
std::copy_n(h, N, data_.begin());
|
||||
}
|
||||
/**
|
||||
* Constructor from an hexadecimal string (without "0x").
|
||||
* hex must be at least 2.N characters long.
|
||||
* If too long, only the first 2.N characters are read.
|
||||
* hex must be at least 2.HASH_LEN characters long.
|
||||
* If too long, only the first 2.HASH_LEN characters are read.
|
||||
*/
|
||||
explicit constexpr Hash(std::string_view hex)
|
||||
: data_(fromString(hex))
|
||||
{}
|
||||
: data_()
|
||||
{
|
||||
if (hex.size() < 2 * N)
|
||||
data_.fill(0);
|
||||
else
|
||||
fromString(hex.data());
|
||||
}
|
||||
|
||||
Hash(const msgpack::object& o) { msgpack_unpack(o); }
|
||||
|
||||
@@ -80,21 +88,10 @@ public:
|
||||
|
||||
static constexpr inline Hash zero() noexcept { return Hash {}; }
|
||||
|
||||
constexpr bool operator==(const Hash& h) const
|
||||
{
|
||||
#if __cplusplus >= 202002L
|
||||
return data_ == h.data_;
|
||||
#else
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (data_[i] != h.data_[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
constexpr bool operator!=(const Hash& h) const { return !(*this == h); }
|
||||
bool operator==(const Hash& h) const { return data_ == h.data_; }
|
||||
bool operator!=(const Hash& h) const { return !(*this == h); }
|
||||
|
||||
constexpr bool operator<(const Hash& o) const
|
||||
bool operator<(const Hash& o) const
|
||||
{
|
||||
for (unsigned i = 0; i < N; i++) {
|
||||
if (data_[i] != o.data_[i])
|
||||
@@ -103,7 +100,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr Hash operator^(const Hash& o) const
|
||||
Hash operator^(const Hash& o) const
|
||||
{
|
||||
Hash result;
|
||||
for (auto i = 0u; i < N; i++) {
|
||||
@@ -112,9 +109,8 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
explicit constexpr operator bool() const
|
||||
explicit operator bool() const
|
||||
{
|
||||
#if __cplusplus >= 202002L
|
||||
auto a = reinterpret_cast<const uint32_t*>(data_.data());
|
||||
auto b = reinterpret_cast<const uint32_t*>(data_.data() + N);
|
||||
for (; a != b; a++) {
|
||||
@@ -122,13 +118,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (data_[i] != 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t& operator[](size_t index) { return data_[index]; }
|
||||
@@ -138,16 +127,15 @@ public:
|
||||
* Find the lowest 1 bit in an id.
|
||||
* Result will allways be lower than 8*N
|
||||
*/
|
||||
constexpr int lowbit() const
|
||||
inline int lowbit() const
|
||||
{
|
||||
int i = N - 1;
|
||||
for (; i >= 0; i--)
|
||||
int i, j;
|
||||
for (i = N - 1; i >= 0; i--)
|
||||
if (data_[i] != 0)
|
||||
break;
|
||||
if (i < 0)
|
||||
return -1;
|
||||
int j = 7;
|
||||
for (; j >= 0; j--)
|
||||
for (j = 7; j >= 0; j--)
|
||||
if ((data_[i] & (0x80 >> j)) != 0)
|
||||
break;
|
||||
return 8 * i + j;
|
||||
@@ -159,10 +147,11 @@ public:
|
||||
}
|
||||
|
||||
/** Find how many bits two ids have in common. */
|
||||
static constexpr inline unsigned commonBits(const Hash& id1, const Hash& id2)
|
||||
static inline unsigned commonBits(const Hash& id1, const Hash& id2)
|
||||
{
|
||||
unsigned i = 0;
|
||||
for (; i < N; i++) {
|
||||
unsigned i, j;
|
||||
uint8_t x;
|
||||
for (i = 0; i < N; i++) {
|
||||
if (id1.data_[i] != id2.data_[i])
|
||||
break;
|
||||
}
|
||||
@@ -170,9 +159,9 @@ public:
|
||||
if (i == N)
|
||||
return 8 * N;
|
||||
|
||||
uint8_t x = id1.data_[i] ^ id2.data_[i];
|
||||
x = id1.data_[i] ^ id2.data_[i];
|
||||
|
||||
unsigned j = 0;
|
||||
j = 0;
|
||||
while ((x & 0x80) == 0) {
|
||||
x <<= 1;
|
||||
j++;
|
||||
@@ -182,7 +171,7 @@ public:
|
||||
}
|
||||
|
||||
/** Determine whether id1 or id2 is closer to this */
|
||||
constexpr int xorCmp(const Hash& id1, const Hash& id2) const
|
||||
int xorCmp(const Hash& id1, const Hash& id2) const
|
||||
{
|
||||
for (unsigned i = 0; i < N; i++) {
|
||||
if (id1.data_[i] == id2.data_[i])
|
||||
@@ -194,7 +183,7 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr bool getBit(unsigned nbit) const
|
||||
bool getBit(unsigned nbit) const
|
||||
{
|
||||
auto& num = *(data_.cbegin() + (nbit / 8));
|
||||
unsigned bit = 7 - (nbit % 8);
|
||||
@@ -270,8 +259,7 @@ public:
|
||||
|
||||
private:
|
||||
T data_;
|
||||
static constexpr T fromBytes(const uint8_t*, size_t);
|
||||
static constexpr T fromString(std::string_view);
|
||||
constexpr void fromString(const char*);
|
||||
};
|
||||
|
||||
#define HASH_LEN 20u
|
||||
@@ -294,30 +282,14 @@ operator>>(std::istream& s, Hash<N>& h)
|
||||
std::array<char, h.size() * 2> dat;
|
||||
s.exceptions(std::istream::eofbit | std::istream::failbit);
|
||||
s.read(&(*dat.begin()), dat.size());
|
||||
h = Hash<N>(std::string_view(dat.data(), dat.size()));
|
||||
fromString(dat.data());
|
||||
return s;
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
constexpr typename Hash<N>::T
|
||||
Hash<N>::fromBytes(const uint8_t* in, size_t size)
|
||||
constexpr void
|
||||
Hash<N>::fromString(const char* in)
|
||||
{
|
||||
T data {};
|
||||
if (size < N)
|
||||
return data;
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
data[i] = in[i];
|
||||
return data;
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
constexpr typename Hash<N>::T
|
||||
Hash<N>::fromString(std::string_view in)
|
||||
{
|
||||
T data {};
|
||||
if (in.size() < 2 * N)
|
||||
return data;
|
||||
|
||||
constexpr auto hex2bin = [](char c, bool& valid) -> uint8_t {
|
||||
if (c >= 'a' and c <= 'f')
|
||||
return 10 + c - 'a';
|
||||
@@ -334,11 +306,10 @@ Hash<N>::fromString(std::string_view in)
|
||||
for (size_t i = 0; i < N && valid; i++) {
|
||||
uint8_t high = hex2bin(in[2 * i], valid);
|
||||
uint8_t low = hex2bin(in[2 * i + 1], valid);
|
||||
data[i] = (high << 4) | low;
|
||||
data_[i] = (high << 4) | low;
|
||||
}
|
||||
if (!valid)
|
||||
return {};
|
||||
return data;
|
||||
data_.fill(0);
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
@@ -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, const Blob& token = {});
|
||||
void sendListenConfirmation(const SockAddr& addr, Tid tid);
|
||||
/* answer to put request */
|
||||
void sendValueAnnounced(const SockAddr& addr, Tid, Value::Id);
|
||||
/* answer in case of error */
|
||||
|
||||
@@ -145,18 +145,16 @@ public:
|
||||
|
||||
Value decrypt(const Value& v);
|
||||
|
||||
[[deprecated("Use PkId version")]] void findCertificate(
|
||||
const InfoHash& node, const std::function<void(const Sp<crypto::Certificate>)>& cb);
|
||||
[[deprecated("Use PkId version")]] void findPublicKey(const InfoHash& node,
|
||||
const std::function<void(const Sp<crypto::PublicKey>)>& cb);
|
||||
void findCertificate(const InfoHash& node, const std::function<void(const Sp<crypto::Certificate>)>& cb);
|
||||
void findPublicKey(const InfoHash& node, const std::function<void(const Sp<crypto::PublicKey>)>& cb);
|
||||
|
||||
void findCertificate(const PkId& id, const std::function<void(const Sp<crypto::Certificate>)>& cb);
|
||||
void findPublicKey(const PkId& id, const std::function<void(const Sp<crypto::PublicKey>)>& cb);
|
||||
|
||||
void registerCertificate(const Sp<crypto::Certificate>& cert);
|
||||
|
||||
[[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 InfoHash& node) const;
|
||||
Sp<crypto::PublicKey> getPublicKey(const InfoHash& node) const;
|
||||
|
||||
Sp<crypto::Certificate> getCertificate(const PkId& node) const;
|
||||
Sp<crypto::PublicKey> getPublicKey(const PkId& node) const;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -85,8 +85,8 @@ using system_clock = std::chrono::system_clock;
|
||||
using time_point = clock::time_point;
|
||||
using duration = clock::duration;
|
||||
|
||||
OPENDHT_PUBLIC time_point from_time_t(std::time_t t);
|
||||
OPENDHT_PUBLIC std::time_t to_time_t(time_point t);
|
||||
time_point from_time_t(std::time_t t);
|
||||
std::time_t to_time_t(time_point t);
|
||||
|
||||
template<class DT>
|
||||
static std::string
|
||||
@@ -126,8 +126,6 @@ print_time_relative(TimePoint now, TimePoint d)
|
||||
return (d > now) ? concat("in "sv, print_duration(d - now)) : concat(print_duration(now - d), " ago"sv);
|
||||
}
|
||||
|
||||
OPENDHT_PUBLIC std::string printByteCount(size_t bytes);
|
||||
|
||||
template<typename Duration = duration>
|
||||
class uniform_duration_distribution : public std::uniform_int_distribution<typename Duration::rep>
|
||||
{
|
||||
@@ -186,7 +184,7 @@ unpackMsg(const Blob& b)
|
||||
return msgpack::unpack((const char*) b.data(), b.size());
|
||||
}
|
||||
|
||||
OPENDHT_PUBLIC msgpack::object* findMapValue(const msgpack::object& map, const char* key, size_t length);
|
||||
msgpack::object* findMapValue(const msgpack::object& map, const char* key, size_t length);
|
||||
|
||||
inline msgpack::object*
|
||||
findMapValue(const msgpack::object& map, const char* key)
|
||||
|
||||
+57
-63
@@ -5,9 +5,9 @@ project(
|
||||
'cython',
|
||||
license: 'MIT',
|
||||
license_files: 'LICENSE',
|
||||
version: '3.7.6',
|
||||
version: '3.7.0',
|
||||
meson_version: '>=1.1.0',
|
||||
default_options: ['cpp_std=c++20', 'warning_level=3'],
|
||||
default_options: ['cpp_std=c++17', 'warning_level=3'],
|
||||
)
|
||||
|
||||
gnutls = dependency('gnutls')
|
||||
@@ -118,17 +118,6 @@ opendht = library(
|
||||
install: true,
|
||||
)
|
||||
|
||||
opendht_dep_args = []
|
||||
if get_option('default_library') != 'static'
|
||||
opendht_dep_args += '-Dopendht_EXPORTS'
|
||||
endif
|
||||
|
||||
opendht_dep = declare_dependency(
|
||||
link_with: opendht,
|
||||
include_directories: opendht_interface_inc,
|
||||
compile_args: opendht_dep_args,
|
||||
)
|
||||
|
||||
# Install library headers.
|
||||
install_subdir('include', install_dir: get_option('includedir'), strip_directory: true)
|
||||
|
||||
@@ -139,13 +128,6 @@ conf_data.set('libdir', join_paths(get_option('prefix'), get_option('libdir')))
|
||||
conf_data.set('includedir', join_paths(get_option('prefix'), get_option('includedir')))
|
||||
conf_data.set('argon2_lib', ', libargon2')
|
||||
conf_data.set('simdutf_lib', ', simdutf')
|
||||
conf_data.set('opendht_public_cflags', '')
|
||||
conf_data.set('opendht_c_public_cflags', '')
|
||||
|
||||
if get_option('default_library') != 'static'
|
||||
conf_data.set('opendht_public_cflags', ' -Dopendht_EXPORTS')
|
||||
conf_data.set('opendht_c_public_cflags', ' -Dopendht_c_EXPORTS')
|
||||
endif
|
||||
|
||||
configure_file(
|
||||
input: 'opendht.pc.in',
|
||||
@@ -161,7 +143,9 @@ if get_option('c').enabled()
|
||||
opendht_c = library(
|
||||
'opendht-c',
|
||||
['c/opendht.cpp', 'c/opendht_c.h'],
|
||||
dependencies: [opendht_dep, msgpack, jsoncpp, fmt],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [msgpack, jsoncpp, fmt],
|
||||
cpp_args: ['-DOPENDHT_C_BUILD', '-Dopendht_c_EXPORTS'],
|
||||
install: true,
|
||||
)
|
||||
@@ -197,8 +181,10 @@ if get_option('python').require(
|
||||
py.extension_module(
|
||||
'opendht',
|
||||
'python/opendht.pyx',
|
||||
include_directories: opendht_interface_inc,
|
||||
override_options: ['cython_language=cpp'],
|
||||
dependencies: [opendht_dep, msgpack, jsoncpp, fmt, dep_py],
|
||||
link_with: [opendht],
|
||||
dependencies: [msgpack, jsoncpp, fmt, dep_py],
|
||||
install: true,
|
||||
)
|
||||
endif
|
||||
@@ -207,19 +193,25 @@ if get_option('tools').enabled()
|
||||
dhtnode = executable(
|
||||
'dhtnode',
|
||||
'tools/dhtnode.cpp',
|
||||
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
|
||||
install: true,
|
||||
)
|
||||
dhtchat = executable(
|
||||
'dhtchat',
|
||||
'tools/dhtchat.cpp',
|
||||
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
|
||||
install: true,
|
||||
)
|
||||
dhtscanner = executable(
|
||||
'dhtscanner',
|
||||
'tools/dhtscanner.cpp',
|
||||
dependencies: [opendht_dep, readline, jsoncpp, msgpack, fmt, openssl],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [readline, jsoncpp, msgpack, fmt, openssl],
|
||||
install: true,
|
||||
)
|
||||
if llhttp.found()
|
||||
@@ -232,7 +224,9 @@ if get_option('tools').enabled()
|
||||
durl = executable(
|
||||
'durl',
|
||||
'tools/durl.cpp',
|
||||
dependencies: [opendht_dep] + durl_deps,
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: durl_deps,
|
||||
)
|
||||
endif
|
||||
endif
|
||||
@@ -243,68 +237,62 @@ if get_option('tests').enabled()
|
||||
|
||||
test_infohash = executable(
|
||||
'test_infohash',
|
||||
'tests/test_infohash.cpp',
|
||||
'tests/infohashtester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('Infohash', test_infohash)
|
||||
|
||||
test_value = executable(
|
||||
'test_value',
|
||||
'tests/test_value.cpp',
|
||||
'tests/valuetester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('Value', test_value)
|
||||
|
||||
test_parsedmessage = executable(
|
||||
'test_parsedmessage',
|
||||
'tests/test_parsedmessage.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
include_directories: opendht_inc,
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('ParsedMessage', test_parsedmessage)
|
||||
|
||||
test_networkengine = executable(
|
||||
'test_networkengine',
|
||||
'tests/test_networkengine.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
include_directories: opendht_inc,
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('NetworkEngine', test_networkengine)
|
||||
|
||||
test_crypto = executable(
|
||||
'test_crypto',
|
||||
'tests/test_crypto.cpp',
|
||||
'tests/cryptotester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack, gnutls],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack, gnutls],
|
||||
)
|
||||
test('Crypto', test_crypto, timeout: 60)
|
||||
test('Crypto', test_crypto)
|
||||
|
||||
test_dhtrunner = executable(
|
||||
'test_dhtrunner',
|
||||
'tests/test_dhtrunner.cpp',
|
||||
'tests/dhtrunnertester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('DhtRunner', test_dhtrunner, timeout: 60)
|
||||
test('DhtRunner', test_dhtrunner)
|
||||
|
||||
test_threadpool = executable(
|
||||
'test_threadpool',
|
||||
'tests/test_threadpool.cpp',
|
||||
'tests/threadpooltester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('ThreadPool', test_threadpool)
|
||||
|
||||
if get_option('proxy_client').enabled() or get_option('proxy_server').enabled()
|
||||
test_http = executable(
|
||||
'test_http',
|
||||
'tests/test_http.cpp',
|
||||
'tests/httptester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('Http', test_http)
|
||||
endif
|
||||
@@ -312,9 +300,11 @@ if get_option('tests').enabled()
|
||||
if get_option('peer_discovery').enabled()
|
||||
test_peerdiscovery = executable(
|
||||
'test_peerdiscovery',
|
||||
'tests/test_peerdiscovery.cpp',
|
||||
'tests/peerdiscoverytester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('PeerDiscovery', test_peerdiscovery)
|
||||
endif
|
||||
@@ -322,9 +312,11 @@ if get_option('tests').enabled()
|
||||
if get_option('proxy_server').enabled()
|
||||
test_proxy = executable(
|
||||
'test_dhtproxy',
|
||||
'tests/test_dhtproxy.cpp',
|
||||
'tests/dhtproxytester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('DhtProxy', test_proxy)
|
||||
|
||||
@@ -333,7 +325,9 @@ if get_option('tests').enabled()
|
||||
'test_dhtproxystress',
|
||||
'tests/dhtproxy_stress_tester.cpp',
|
||||
'tests/tests_runner.cpp',
|
||||
dependencies: [opendht_dep, cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
include_directories: opendht_interface_inc,
|
||||
link_with: opendht,
|
||||
dependencies: [cppunit, jsoncpp, fmt, openssl, msgpack],
|
||||
)
|
||||
test('DhtProxy Stress', test_proxystress)
|
||||
endif
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@ Description: Distributed Hash Table library (C Wrapper)
|
||||
Version: @VERSION@
|
||||
Libs: -L${libdir} -lopendht-c
|
||||
Requires.private: opendht gnutls >= 3.1 @argon2_lib@
|
||||
Cflags: -I${includedir}@opendht_c_public_cflags@
|
||||
Cflags: -I${includedir}
|
||||
+1
-1
@@ -9,4 +9,4 @@ Libs: -L${libdir} -lopendht -lfmt
|
||||
Libs.private: @http_lib@ -pthread
|
||||
Requires: gnutls >= 3.3@jsoncpp_lib@@openssl_lib@
|
||||
Requires.private: nettle >= 2.4@argon2_lib@@llhttp_lib@@iouring_lib@@simdutf_lib@
|
||||
Cflags: -I${includedir}@opendht_public_cflags@
|
||||
Cflags: -I${includedir}
|
||||
@@ -0,0 +1,30 @@
|
||||
if USE_CYTHON
|
||||
|
||||
noinst_HEADERS = \
|
||||
opendht.pyx \
|
||||
opendht_cpp.pxd \
|
||||
pyproject.toml
|
||||
|
||||
PYTHON_INSTALL_RECORD = $(builddir)/install_record.txt
|
||||
|
||||
pybuild.stamp:
|
||||
LDFLAGS="-L$(top_srcdir)/src/.libs" $(PYTHON) -m build .
|
||||
echo stamp > pybuild.stamp
|
||||
|
||||
CLEANFILES = pybuild.stamp
|
||||
|
||||
all-local: pybuild.stamp
|
||||
clean-local:
|
||||
rm -rf $(builddir)/build $(builddir)/*.so $(PYTHON_INSTALL_RECORD)
|
||||
|
||||
install-exec-local:
|
||||
$(PYTHON) -m pip install --no-deps --no-build-isolation . \
|
||||
--root=$(DESTDIR)/ --prefix=$(prefix)
|
||||
rm -rf $(builddir)/build
|
||||
|
||||
if HAVE_PIP
|
||||
uninstall-local:
|
||||
/usr/bin/yes | $(PIP) uninstall $(PACKAGE)
|
||||
endif
|
||||
|
||||
endif
|
||||
@@ -0,0 +1,82 @@
|
||||
lib_LTLIBRARIES = libopendht.la
|
||||
|
||||
libopendht_la_CPPFLAGS = @CPPFLAGS@ -I$(top_srcdir)/include/opendht @Argon2_CFLAGS@ @JsonCpp_CFLAGS@ @MsgPack_CFLAGS@ @OpenSSL_CFLAGS@ @Fmt_CFLAGS@ @Llhttp_CFLAGS@ @Liburing_CFLAGS@ @simdutf_CFLAGS@
|
||||
libopendht_la_LIBADD = @Argon2_LIBS@ @JsonCpp_LIBS@ @GnuTLS_LIBS@ @Nettle_LIBS@ @OpenSSL_LIBS@ @Fmt_LIBS@ @Llhttp_LIBS@ @Liburing_LIBS@ @simdutf_LIBS@
|
||||
libopendht_la_LDFLAGS = @LDFLAGS@ -version-number @OPENDHT_MAJOR_VERSION@:@OPENDHT_MINOR_VERSION@:@OPENDHT_PATCH_VERSION@
|
||||
libopendht_la_SOURCES = \
|
||||
dht.cpp \
|
||||
storage.h \
|
||||
listener.h \
|
||||
request.h \
|
||||
search.h \
|
||||
value_cache.h \
|
||||
op_cache.h \
|
||||
op_cache.cpp \
|
||||
net.h \
|
||||
parsed_message.h \
|
||||
node_cache.cpp \
|
||||
callbacks.cpp \
|
||||
routing_table.cpp \
|
||||
network_engine.cpp \
|
||||
utils.cpp \
|
||||
node.cpp \
|
||||
value.cpp \
|
||||
crypto.cpp \
|
||||
securedht.cpp \
|
||||
dhtrunner.cpp \
|
||||
default_types.cpp \
|
||||
log.cpp \
|
||||
network_utils.cpp \
|
||||
thread_pool.cpp
|
||||
|
||||
nobase_include_HEADERS = \
|
||||
../include/opendht.h \
|
||||
../include/opendht/def.h \
|
||||
../include/opendht/dht.h \
|
||||
../include/opendht/callbacks.h \
|
||||
../include/opendht/node_cache.h \
|
||||
../include/opendht/node_export.h \
|
||||
../include/opendht/routing_table.h \
|
||||
../include/opendht/network_engine.h \
|
||||
../include/opendht/scheduler.h \
|
||||
../include/opendht/rate_limiter.h \
|
||||
../include/opendht/utils.h \
|
||||
../include/opendht/sockaddr.h \
|
||||
../include/opendht/infohash.h \
|
||||
../include/opendht/node.h \
|
||||
../include/opendht/value.h \
|
||||
../include/opendht/crypto.h \
|
||||
../include/opendht/securedht.h \
|
||||
../include/opendht/dhtrunner.h \
|
||||
../include/opendht/default_types.h \
|
||||
../include/opendht/log.h \
|
||||
../include/opendht/logger.h \
|
||||
../include/opendht/network_utils.h \
|
||||
../include/opendht/rng.h \
|
||||
../include/opendht/thread_pool.h
|
||||
|
||||
if ENABLE_PROXY_SERVER
|
||||
libopendht_la_SOURCES += dht_proxy_server.cpp
|
||||
nobase_include_HEADERS += ../include/opendht/dht_proxy_server.h
|
||||
endif
|
||||
|
||||
if ENABLE_PROXY_CLIENT
|
||||
libopendht_la_SOURCES += dht_proxy_client.cpp
|
||||
nobase_include_HEADERS += ../include/opendht/dht_proxy_client.h ../include/opendht/dht_interface.h
|
||||
endif
|
||||
|
||||
libopendht_la_SOURCES += base64.h base64.cpp
|
||||
if PROXY_CLIENT_OR_SERVER
|
||||
libopendht_la_SOURCES += http.cpp compat/os_cert.cpp
|
||||
nobase_include_HEADERS += ../include/opendht/proxy.h ../include/opendht/http.h compat/os_cert.h
|
||||
endif
|
||||
|
||||
if ENABLE_PEER_DISCOVERY
|
||||
libopendht_la_SOURCES += peer_discovery.cpp
|
||||
nobase_include_HEADERS += ../include/opendht/peer_discovery.h
|
||||
endif
|
||||
|
||||
if ENABLE_INDEXATION
|
||||
libopendht_la_SOURCES += indexation/pht.cpp
|
||||
nobase_include_HEADERS += ../include/opendht/indexation/pht.h
|
||||
endif
|
||||
+40
-135
@@ -35,8 +35,6 @@ Dht::updateStatus(sa_family_t af)
|
||||
auto& d = dht(af);
|
||||
auto old = d.status;
|
||||
d.status = d.getStatus(scheduler.time());
|
||||
if (d.status == NodeStatus::Disconnected && bootstrap_pending && bootstrapJob && !bootstrap_nodes.empty())
|
||||
d.status = NodeStatus::Connecting;
|
||||
if (d.status != old) {
|
||||
auto& other = dht(af == AF_INET ? AF_INET6 : AF_INET);
|
||||
if (other.status == NodeStatus::Disconnected && d.status == NodeStatus::Disconnected) {
|
||||
@@ -387,7 +385,7 @@ Dht::paginate(std::weak_ptr<Search> ws, Sp<Query> query, SearchNode* n)
|
||||
n->pagination_queries[query].push_back(select_q);
|
||||
|
||||
if (logger_)
|
||||
logger_->debug("[search {}] [node {}] Sending {}", sr->id.toString(), n->node->toString(), select_q->toString());
|
||||
logger_->debug("[search {}] [node {}] Sending {}", sr->id.to_view(), n->node->toString(), select_q->toString());
|
||||
n->getStatus[select_q] = network_engine.sendGetValues(
|
||||
n->node, sr->id, *select_q, -1, onSelectDone, std::bind(&Dht::searchNodeGetExpired, this, _1, _2, ws, select_q));
|
||||
}
|
||||
@@ -656,19 +654,6 @@ Dht::searchSynchedNodeListen(const Sp<Search>& sr, SearchNode& n)
|
||||
for (const auto& l : sr->listeners) {
|
||||
const auto& query = l.second.query;
|
||||
|
||||
auto openListenSocket = [this, ws, query, node = n.node] {
|
||||
return node->openSocket([this, ws, query](const Sp<Node>& node, net::RequestAnswer&& answer) mutable {
|
||||
/* on new values */
|
||||
if (auto sr = ws.lock()) {
|
||||
scheduler.edit(sr->nextSearchStep, scheduler.time());
|
||||
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;
|
||||
@@ -680,7 +665,21 @@ Dht::searchSynchedNodeListen(const Sp<Search>& sr, SearchNode& n)
|
||||
r = n.listenStatus
|
||||
.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(query),
|
||||
std::forward_as_tuple(l.second.get_cb, l.second.sync_cb, openListenSocket()))
|
||||
std::forward_as_tuple(l.second.get_cb,
|
||||
l.second.sync_cb,
|
||||
n.node->openSocket([this,
|
||||
ws,
|
||||
query](const Sp<Node>& node,
|
||||
net::RequestAnswer&& answer) mutable {
|
||||
/* on new values */
|
||||
if (auto sr = ws.lock()) {
|
||||
scheduler.edit(sr->nextSearchStep, scheduler.time());
|
||||
sr->insertNode(node, scheduler.time(), answer.ntoken);
|
||||
if (auto sn = sr->getNode(node)) {
|
||||
sn->onValues(query, std::move(answer), types, scheduler);
|
||||
}
|
||||
}
|
||||
})))
|
||||
.first;
|
||||
r->second.cacheExpirationJob = scheduler.add(time_point::max(), [this, ws, query, node = n.node] {
|
||||
if (auto sr = ws.lock()) {
|
||||
@@ -689,8 +688,6 @@ Dht::searchSynchedNodeListen(const Sp<Search>& sr, SearchNode& n)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (not n.node->getSocket(r->second.socketId)) {
|
||||
r->second.socketId = openListenSocket();
|
||||
}
|
||||
auto new_req = network_engine.sendListen(
|
||||
n.node,
|
||||
@@ -892,16 +889,10 @@ Dht::search(const InfoHash& id,
|
||||
sr = std::make_shared<Search>();
|
||||
srs.emplace(id, sr);
|
||||
} else {
|
||||
for (auto it = srs.begin(); it != srs.end(); ++it) {
|
||||
for (auto it = srs.begin(); it != srs.end();) {
|
||||
auto& s = *it->second;
|
||||
if ((s.done or s.expired) and s.announce.empty() and s.listeners.empty()) {
|
||||
sr = it->second;
|
||||
if (sr->nextSearchStep)
|
||||
scheduler.cancel(sr->nextSearchStep);
|
||||
if (sr->opExpirationJob)
|
||||
scheduler.cancel(sr->opExpirationJob);
|
||||
sr->clear();
|
||||
srs.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -910,11 +901,8 @@ Dht::search(const InfoHash& id,
|
||||
logger_->error("[search {} IPv{}] Maximum number of searches reached!",
|
||||
id.toString(),
|
||||
(af == AF_INET) ? '4' : '6');
|
||||
if (dcb)
|
||||
dcb(false, {});
|
||||
return {};
|
||||
}
|
||||
srs.emplace(id, sr);
|
||||
}
|
||||
sr->af = af;
|
||||
sr->tid = search_id++;
|
||||
@@ -1420,17 +1408,11 @@ Dht::storageChanged(const InfoHash& id, Storage& st, const Sp<Value>& v, bool ne
|
||||
}
|
||||
|
||||
bool
|
||||
Dht::storageStore(const InfoHash& id,
|
||||
const Sp<Value>& value,
|
||||
time_point created,
|
||||
const SockAddr& sa,
|
||||
bool permanent,
|
||||
time_point expiration)
|
||||
Dht::storageStore(const InfoHash& id, const Sp<Value>& value, time_point created, const SockAddr& sa, bool permanent)
|
||||
{
|
||||
const auto& now = scheduler.time();
|
||||
created = std::min(created, now);
|
||||
if (expiration == time_point::min())
|
||||
expiration = permanent ? time_point::max() : created + getType(value->type).expiration;
|
||||
auto expiration = permanent ? time_point::max() : created + getType(value->type).expiration;
|
||||
if (expiration < now)
|
||||
return false;
|
||||
|
||||
@@ -1447,7 +1429,7 @@ Dht::storageStore(const InfoHash& id,
|
||||
StorageBucket* store_bucket {nullptr};
|
||||
bool is_local = !sa;
|
||||
if (sa)
|
||||
store_bucket = &store_quota.try_emplace(sa, sa).first->second;
|
||||
store_bucket = &store_quota[sa];
|
||||
else
|
||||
store_bucket = local_store_quota.get();
|
||||
|
||||
@@ -1853,18 +1835,15 @@ std::string
|
||||
Dht::getStorageLog() const
|
||||
{
|
||||
std::ostringstream out;
|
||||
for (const auto& s : store) {
|
||||
const auto& st = s.second;
|
||||
out << "Storage " << s.first << " " << st.listeners.size() << " list., " << st.valueCount() << " values, "
|
||||
<< printByteCount(st.totalSize()) << "\n";
|
||||
}
|
||||
for (const auto& s : store)
|
||||
out << printStorageLog(s);
|
||||
out << std::endl << std::endl;
|
||||
std::multimap<size_t, const SockAddr*> q_map;
|
||||
for (const auto& ip : store_quota)
|
||||
if (ip.second.size())
|
||||
q_map.emplace(ip.second.size(), &ip.first);
|
||||
for (auto ip = q_map.rbegin(); ip != q_map.rend(); ++ip)
|
||||
out << "IP " << ip->second->toString() << " uses " << printByteCount(ip->first) << std::endl;
|
||||
out << "IP " << ip->second->toString() << " uses " << ip->first << " bytes" << std::endl;
|
||||
out << std::endl;
|
||||
out << "Total " << store.size() << " storages, " << total_values << " values (";
|
||||
if (total_store_size < 1024)
|
||||
@@ -1887,8 +1866,11 @@ std::string
|
||||
Dht::getStorageLog(const InfoHash& h) const
|
||||
{
|
||||
auto s = store.find(h);
|
||||
if (s == store.end())
|
||||
return fmt::format("Storage {} empty\n", h);
|
||||
if (s == store.end()) {
|
||||
std::ostringstream out;
|
||||
out << "Storage " << h << " empty" << std::endl;
|
||||
return out.str();
|
||||
}
|
||||
return printStorageLog(*s);
|
||||
}
|
||||
|
||||
@@ -1898,14 +1880,8 @@ Dht::printStorageLog(const decltype(store)::value_type& s) const
|
||||
std::ostringstream out;
|
||||
using namespace std::chrono;
|
||||
const auto& st = s.second;
|
||||
const auto& now = scheduler.time();
|
||||
out << "Storage " << s.first << " " << st.listeners.size() << " list., " << st.valueCount() << " values, "
|
||||
<< printByteCount(st.totalSize()) << std::endl;
|
||||
for (const auto& v : st.getValues()) {
|
||||
out << " Value " << v.data->id << " size: " << printByteCount(v.data->size())
|
||||
<< " created: " << print_time_relative(now, v.created)
|
||||
<< " expires: " << print_time_relative(now, v.expiration) << std::endl;
|
||||
}
|
||||
out << "Storage " << s.first << " " << st.listeners.size() << " list., " << st.valueCount() << " values ("
|
||||
<< st.totalSize() << " bytes)" << std::endl;
|
||||
if (not st.local_listeners.empty())
|
||||
out << " " << st.local_listeners.size() << " local listeners" << std::endl;
|
||||
for (const auto& node_listeners : st.listeners) {
|
||||
@@ -2238,18 +2214,6 @@ void
|
||||
Dht::onConnected()
|
||||
{
|
||||
stopBootstrap();
|
||||
|
||||
auto restartSearches = [this](std::map<InfoHash, Sp<Search>>& searches) {
|
||||
const auto& now = scheduler.time();
|
||||
for (auto& srp : searches) {
|
||||
auto& sr = *srp.second;
|
||||
if (sr.done)
|
||||
continue;
|
||||
scheduler.edit(sr.nextSearchStep, now);
|
||||
}
|
||||
};
|
||||
restartSearches(dht4.searches);
|
||||
restartSearches(dht6.searches);
|
||||
auto callbacks = std::move(onConnectCallbacks_);
|
||||
while (not callbacks.empty()) {
|
||||
callbacks.front()();
|
||||
@@ -2271,7 +2235,6 @@ 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);
|
||||
@@ -2298,7 +2261,6 @@ void
|
||||
Dht::startBootstrap()
|
||||
{
|
||||
stopBootstrap();
|
||||
bootstrap_pending = !bootstrap_nodes.empty();
|
||||
bootstrapJob = scheduler.add(scheduler.time(), std::bind(&Dht::bootstrap, this));
|
||||
}
|
||||
|
||||
@@ -2306,7 +2268,6 @@ void
|
||||
Dht::stopBootstrap()
|
||||
{
|
||||
scheduler.cancel(bootstrapJob);
|
||||
bootstrap_pending = false;
|
||||
bootstrap_period = BOOTSTRAP_PERIOD;
|
||||
}
|
||||
|
||||
@@ -2363,16 +2324,9 @@ Dht::exportValues() const
|
||||
const auto& vals = h.second.getValues();
|
||||
pk.pack_array(vals.size());
|
||||
for (const auto& v : vals) {
|
||||
pk.pack_array(4);
|
||||
pk.pack_array(2);
|
||||
pk.pack(v.created.time_since_epoch().count());
|
||||
v.data->msgpack_pack(pk);
|
||||
if (const auto& store_addr = v.store_bucket ? v.store_bucket->getAddr() : SockAddr {}) {
|
||||
pk.pack_bin(store_addr.getLength());
|
||||
pk.pack_bin_body(reinterpret_cast<const char*>(store_addr.get()), store_addr.getLength());
|
||||
} else {
|
||||
pk.pack_bin(0);
|
||||
}
|
||||
pk.pack(v.expiration.time_since_epoch().count());
|
||||
}
|
||||
ve.second = {buffer.data(), buffer.data() + buffer.size()};
|
||||
e.push_back(std::move(ve));
|
||||
@@ -2385,13 +2339,9 @@ 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()) {
|
||||
ignored++;
|
||||
if (value.second.empty())
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
msgpack::unpacked msg;
|
||||
@@ -2404,53 +2354,25 @@ Dht::importValues(const std::vector<ValuesExport>& import)
|
||||
if (valel.type != msgpack::type::ARRAY or valel.via.array.size < 2)
|
||||
throw msgpack::type_error();
|
||||
time_point val_time;
|
||||
time_point expiration = time_point::min();
|
||||
Value tmp_val;
|
||||
SockAddr store_addr;
|
||||
try {
|
||||
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);
|
||||
auto val_size = tmp_val.size();
|
||||
if (storageStore(value.first,
|
||||
std::make_shared<Value>(std::move(tmp_val)),
|
||||
val_time,
|
||||
store_addr,
|
||||
false,
|
||||
expiration)) {
|
||||
imported++;
|
||||
imported_size += val_size;
|
||||
} else {
|
||||
ignored++;
|
||||
}
|
||||
storageStore(value.first, std::make_shared<Value>(std::move(tmp_val)), val_time);
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
ignored++;
|
||||
if (logger_)
|
||||
logger_->error("Error reading values at {}", value.first.to_view());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (logger_)
|
||||
logger_->debug("Imported {} values, {}, ignored {}", imported, dht::printByteCount(imported_size), ignored);
|
||||
}
|
||||
|
||||
std::vector<NodeExport>
|
||||
@@ -2535,16 +2457,6 @@ Dht::onError(Sp<net::Request> req, net::DhtProtocolException e)
|
||||
continue;
|
||||
n->token.clear();
|
||||
n->last_get_reply = time_point::min();
|
||||
for (auto& listen : n->listenStatus) {
|
||||
if (listen.second.req and listen.second.req->pending())
|
||||
n->node->cancelRequest(listen.second.req);
|
||||
listen.second.req = {};
|
||||
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;
|
||||
@@ -2701,13 +2613,11 @@ Dht::onListen(Sp<Node> node, const InfoHash& hash, const Blob& token, size_t soc
|
||||
}
|
||||
Query q = query;
|
||||
storageAddListener(hash, node, socket_id, std::move(q), version);
|
||||
net::RequestAnswer answer {};
|
||||
answer.ntoken = makeToken(node->getAddr(), false);
|
||||
return answer;
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
Dht::onListenDone(const Sp<Node>& node, net::RequestAnswer& answer, Sp<Search>& sr)
|
||||
Dht::onListenDone(const Sp<Node>& /* node */, net::RequestAnswer& /* answer */, Sp<Search>& sr)
|
||||
{
|
||||
// if (logger_)
|
||||
// logger_->d(sr->id, node->id, "[search %s] [node %s] Got listen confirmation",
|
||||
@@ -2715,7 +2625,6 @@ Dht::onListenDone(const Sp<Node>& node, net::RequestAnswer& answer, Sp<Search>&
|
||||
|
||||
if (not sr->done) {
|
||||
const auto& now = scheduler.time();
|
||||
sr->insertNode(node, now, answer.ntoken);
|
||||
searchSendGetValues(sr);
|
||||
scheduler.edit(sr->nextSearchStep, now);
|
||||
}
|
||||
@@ -2742,10 +2651,9 @@ Dht::onAnnounce(Sp<Node> n,
|
||||
net::DhtProtocolException::PUT_WRONG_TOKEN};
|
||||
}
|
||||
{
|
||||
// We store a value only if we think we're part of the
|
||||
// SEARCH_NODES nodes around the target id.
|
||||
// We store a value only if we think we're part of the SEARCH_NODES nodes around the target id.
|
||||
auto closest_nodes = buckets(node.getFamily()).findClosestNodes(hash, scheduler.time(), SEARCH_NODES);
|
||||
if (closest_nodes.size() >= TARGET_NODES and hash.xorCmp(closest_nodes.back()->id, myid) < 0) {
|
||||
if (closest_nodes.size() > TARGET_NODES and hash.xorCmp(closest_nodes.back()->id, myid) < 0) {
|
||||
if (logger_)
|
||||
logger_->warn("[node {}] Announce too far from the target. Dropping value.", node.toString());
|
||||
return {};
|
||||
@@ -2771,8 +2679,7 @@ Dht::onAnnounce(Sp<Node> n,
|
||||
node.toString(),
|
||||
v->id);
|
||||
} else {
|
||||
const auto& type = getType(lv->type);
|
||||
if (type.editPolicy(hash, lv, vc, node.id, node.getAddr())) {
|
||||
if (getType(lv->type).editPolicy(hash, lv, vc, node.id, node.getAddr())) {
|
||||
if (logger_)
|
||||
logger_->debug("[store {}] Editing {}", hash.toString(), vc->toString());
|
||||
storageStore(hash, vc, created, node.getAddr());
|
||||
@@ -2785,11 +2692,9 @@ Dht::onAnnounce(Sp<Node> n,
|
||||
}
|
||||
} else {
|
||||
// Allow the value to be edited by the storage policy
|
||||
const auto& type = getType(vc->type);
|
||||
if (type.storePolicy(hash, vc, node.id, node.getAddr())) {
|
||||
if (getType(vc->type).storePolicy(hash, vc, node.id, node.getAddr())) {
|
||||
// if (logger_)
|
||||
// logger_->d(hash, node.id, "[store %s] Storing %s", hash.toString().c_str(),
|
||||
// std::to_string(vc->id).c_str());
|
||||
// logger_->d(hash, node.id, "[store %s] Storing %s", hash.toString().c_str(), vc->id);
|
||||
storageStore(hash, vc, created, node.getAddr());
|
||||
} else {
|
||||
if (logger_)
|
||||
|
||||
+11
-14
@@ -1376,7 +1376,7 @@ DhtProxyClient::resubscribe([[maybe_unused]] const InfoHash& key,
|
||||
|
||||
restinio::http_request_header_t header;
|
||||
header.method(restinio::http_method_subscribe());
|
||||
header.request_target("/key/" + key.toString());
|
||||
header.request_target("/" + key.toString());
|
||||
if (!listener.refreshSubscriberTimer) {
|
||||
listener.refreshSubscriberTimer = std::make_unique<asio::steady_timer>(httpContext_);
|
||||
}
|
||||
@@ -1422,20 +1422,17 @@ void
|
||||
DhtProxyClient::setPushNotificationToken([[maybe_unused]] const std::string& token)
|
||||
{
|
||||
#ifdef OPENDHT_PUSH_NOTIFICATIONS
|
||||
{
|
||||
std::unique_lock<std::mutex> l(lockCurrentProxyInfos_);
|
||||
if (deviceKey_ == token)
|
||||
return;
|
||||
std::unique_lock<std::mutex> l(lockCurrentProxyInfos_);
|
||||
if (deviceKey_ != token) {
|
||||
deviceKey_ = token;
|
||||
if (!(statusIpv4_ == NodeStatus::Connected || statusIpv6_ == NodeStatus::Connected))
|
||||
return;
|
||||
}
|
||||
if (logger_)
|
||||
logger_->debug("[proxy:client] [push] token changed, resubscribing");
|
||||
std::lock_guard<std::mutex> lock(searchLock_);
|
||||
for (auto& search : searches_) {
|
||||
for (auto& listener : search.second.listeners) {
|
||||
resubscribe(search.first, listener.first, listener.second);
|
||||
if (statusIpv4_ == NodeStatus::Connected || statusIpv6_ == NodeStatus::Connected) {
|
||||
if (logger_)
|
||||
logger_->debug("[proxy:client] [push] token changed, resubscribing");
|
||||
for (auto& search : searches_) {
|
||||
for (auto& listener : search.second.listeners) {
|
||||
resubscribe(search.first, listener.first, listener.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
+6
-13
@@ -294,13 +294,10 @@ void
|
||||
NetworkEngine::clear()
|
||||
{
|
||||
for (auto& request : requests) {
|
||||
if (request.second->node)
|
||||
request.second->node->setExpired();
|
||||
else
|
||||
request.second->setExpired();
|
||||
request.second->cancel();
|
||||
request.second->node->setExpired();
|
||||
}
|
||||
requests.clear();
|
||||
partial_messages.clear();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -490,10 +487,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));
|
||||
}
|
||||
@@ -702,7 +699,7 @@ NetworkEngine::process(std::unique_ptr<ParsedMessage>&& msg, const SockAddr& fro
|
||||
RequestAnswer answer
|
||||
= onListen(node, msg->info_hash, msg->token, msg->socket_id, std::move(msg->query), msg->version);
|
||||
auto nnodes = bufferNodes(from.getFamily(), msg->info_hash, msg->want, answer.nodes4, answer.nodes6);
|
||||
sendListenConfirmation(from, msg->tid, answer.ntoken);
|
||||
sendListenConfirmation(from, msg->tid);
|
||||
break;
|
||||
}
|
||||
case MessageType::UpdateValue: {
|
||||
@@ -1281,21 +1278,17 @@ NetworkEngine::sendListen(const Sp<Node>& n,
|
||||
}
|
||||
|
||||
void
|
||||
NetworkEngine::sendListenConfirmation(const SockAddr& addr, Tid tid, const Blob& token)
|
||||
NetworkEngine::sendListenConfirmation(const SockAddr& addr, Tid tid)
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
pk.pack_map(4 + (config.network ? 1 : 0) + (config.is_client ? 1 : 0));
|
||||
|
||||
pk.pack(KEY_R);
|
||||
pk.pack_map(2 + (not token.empty() ? 1 : 0));
|
||||
pk.pack_map(2);
|
||||
pk.pack(KEY_REQ_ID);
|
||||
pk.pack(myid);
|
||||
insertAddr(pk, addr);
|
||||
if (not token.empty()) {
|
||||
pk.pack(KEY_REQ_TOKEN);
|
||||
packToken(pk, token);
|
||||
}
|
||||
|
||||
pk.pack(KEY_TID);
|
||||
pk.pack(tid);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#define close(x) closesocket(x)
|
||||
#define write(s, b, f) send(s, b, (int) (f), 0)
|
||||
#define write(s, b, f) send(s, b, (int) strlen(b), 0)
|
||||
#define _poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
|
||||
+7
-13
@@ -14,14 +14,15 @@ OpValueCache::onValuesAdded(const std::vector<Sp<Value>>& vals, const system_clo
|
||||
auto viop = values.emplace(v->id, v);
|
||||
if (viop.second) {
|
||||
newValues.emplace_back(v);
|
||||
} else if (*viop.first->second.data != *v) {
|
||||
// Special case for edition
|
||||
if (v->seq > viop.first->second.data->seq) {
|
||||
viop.first->second.data = v;
|
||||
newValues.emplace_back(v);
|
||||
}
|
||||
} else {
|
||||
viop.first->second.refCount++;
|
||||
if (*viop.first->second.data != *v) {
|
||||
// Special case for edition
|
||||
if (v->seq > viop.first->second.data->seq) {
|
||||
viop.first->second.data = v;
|
||||
newValues.emplace_back(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
viop.first->second.updated = t;
|
||||
}
|
||||
@@ -210,13 +211,6 @@ SearchCache::cancelAll(const std::function<void(size_t)>& onCancel)
|
||||
ops.clear();
|
||||
}
|
||||
|
||||
void
|
||||
SearchCache::clear()
|
||||
{
|
||||
ops.clear();
|
||||
nextExpiration_ = time_point::max();
|
||||
}
|
||||
|
||||
time_point
|
||||
SearchCache::expire(const time_point& now, const std::function<void(size_t)>& onCancel)
|
||||
{
|
||||
|
||||
@@ -164,7 +164,6 @@ public:
|
||||
|
||||
bool cancelListen(size_t gtoken, const time_point& now);
|
||||
void cancelAll(const std::function<void(size_t)>& onCancel);
|
||||
void clear();
|
||||
|
||||
time_point expire(const time_point& now, const std::function<void(size_t)>& onCancel);
|
||||
time_point getExpiration() const { return nextExpiration_; }
|
||||
|
||||
+19
-49
@@ -6,7 +6,6 @@
|
||||
#include "sockaddr.h"
|
||||
#include "net.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
|
||||
@@ -96,25 +95,9 @@ struct ParsedMessage
|
||||
std::vector<Value::Id> expired_values {};
|
||||
/* index for fields values */
|
||||
std::vector<Sp<FieldValueIndex>> fields;
|
||||
/** Tracks partial value reassembly for fragmented messages.
|
||||
* Populated from message header with total expected sizes. */
|
||||
struct PartialValue
|
||||
{
|
||||
size_t total_size {0};
|
||||
size_t received_bytes {0};
|
||||
std::map<size_t, Blob> fragments {}; // offset -> fragment data
|
||||
bool isComplete() const { return total_size > 0 && received_bytes == total_size; }
|
||||
Blob reassemble() const
|
||||
{
|
||||
Blob result(total_size);
|
||||
for (const auto& [offset, data] : fragments)
|
||||
std::copy(data.begin(), data.end(), result.begin() + offset);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
std::map<unsigned, PartialValue> value_parts;
|
||||
/** When part of partial value data: {index -> (offset, part_data)} */
|
||||
std::map<unsigned, std::pair<size_t, Blob>> fragment_parts;
|
||||
/** When part of the message header: {index -> (total size, {})}
|
||||
* When part of partial value data: {index -> (offset, part_data)} */
|
||||
std::map<unsigned, std::pair<unsigned, Blob>> value_parts;
|
||||
/* query describing a filter to apply on values. */
|
||||
Query query;
|
||||
/* states if ipv4 or ipv6 request */
|
||||
@@ -135,32 +118,17 @@ bool
|
||||
ParsedMessage::append(const ParsedMessage& block)
|
||||
{
|
||||
bool ret(false);
|
||||
for (const auto& [index, fragment] : block.fragment_parts) {
|
||||
auto part_it = value_parts.find(index);
|
||||
if (part_it == value_parts.end())
|
||||
for (const auto& ve : block.value_parts) {
|
||||
auto part_val = value_parts.find(ve.first);
|
||||
if (part_val == value_parts.end() || part_val->second.second.size() >= part_val->second.first)
|
||||
continue;
|
||||
auto& pv = part_it->second;
|
||||
if (pv.isComplete())
|
||||
// TODO: handle out-of-order packets
|
||||
if (ve.second.first != part_val->second.second.size()) {
|
||||
// std::cout << "skipping out-of-order packet" << std::endl;
|
||||
continue;
|
||||
auto offset = fragment.first;
|
||||
const auto& data = fragment.second;
|
||||
if (data.empty() || offset > pv.total_size || data.size() > pv.total_size - offset)
|
||||
continue;
|
||||
// Check for overlap with existing fragments
|
||||
auto it = pv.fragments.lower_bound(offset);
|
||||
if (it != pv.fragments.end()) {
|
||||
auto distance = it->first - offset;
|
||||
if (distance < data.size())
|
||||
continue;
|
||||
}
|
||||
if (it != pv.fragments.begin()) {
|
||||
auto prev = std::prev(it);
|
||||
if (prev->second.size() > offset - prev->first)
|
||||
continue;
|
||||
}
|
||||
pv.fragments.emplace(offset, data);
|
||||
pv.received_bytes += data.size();
|
||||
ret = true;
|
||||
part_val->second.second.insert(part_val->second.second.end(), ve.second.second.begin(), ve.second.second.end());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -168,14 +136,16 @@ ParsedMessage::append(const ParsedMessage& block)
|
||||
bool
|
||||
ParsedMessage::complete()
|
||||
{
|
||||
for (const auto& [index, pv] : value_parts) {
|
||||
if (!pv.isComplete())
|
||||
for (auto& e : value_parts) {
|
||||
if (e.second.first > e.second.second.size()) {
|
||||
// std::cout << "uncomplete part " << e.first << ": " << e.second.second.size() << "/" << e.second.first <<
|
||||
// std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto& [index, pv] : value_parts) {
|
||||
auto assembled = pv.reassemble();
|
||||
for (auto& e : value_parts) {
|
||||
msgpack::unpacked msg;
|
||||
msgpack::unpack(msg, (const char*) assembled.data(), assembled.size());
|
||||
msgpack::unpack(msg, (const char*) e.second.second.data(), e.second.second.size());
|
||||
values.emplace_back(std::make_shared<Value>(msg.get()));
|
||||
}
|
||||
return true;
|
||||
@@ -263,7 +233,7 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
|
||||
auto d = findMapValue(vdat.val, "d"sv);
|
||||
if (not o or not d)
|
||||
continue;
|
||||
fragment_parts.emplace(vdat.key.as<unsigned>(), std::pair<size_t, Blob>(o->as<size_t>(), unpackBlob(*d)));
|
||||
value_parts.emplace(vdat.key.as<unsigned>(), std::pair<size_t, Blob>(o->as<size_t>(), unpackBlob(*d)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -354,7 +324,7 @@ ParsedMessage::msgpack_unpack(const msgpack::object& msg)
|
||||
// Skip oversize values with a small margin for header overhead
|
||||
if (packed_v.via.u64 > MAX_VALUE_SIZE + 32)
|
||||
continue;
|
||||
value_parts[i].total_size = packed_v.via.u64;
|
||||
value_parts.emplace(i, std::make_pair(packed_v.via.u64, Blob {}));
|
||||
} else {
|
||||
try {
|
||||
values.emplace_back(std::make_shared<Value>(parsedReq.values->via.array.ptr[i]));
|
||||
|
||||
@@ -836,7 +836,6 @@ struct Dht::Search
|
||||
listeners.clear();
|
||||
nodes.clear();
|
||||
nextSearchStep.reset();
|
||||
cache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+17
-3
@@ -40,12 +40,26 @@ SecureDht::SecureDht(std::unique_ptr<DhtInterface> dht,
|
||||
registerInsecureType(CERTIFICATE_TYPE);
|
||||
|
||||
if (certificate_) {
|
||||
auto certId = certificate_->getId();
|
||||
auto certLongId = certificate_->getLongId();
|
||||
if (key_ and (certLongId != key_->getPublicKey().getLongId()))
|
||||
if (key_ and (certId != key_->getPublicKey().getId() or certLongId != key_->getPublicKey().getLongId()))
|
||||
throw DhtException("SecureDht: provided certificate doesn't match private key.");
|
||||
|
||||
dht_->addOnConnectedCallback([this, certLongId, cb = std::move(iacb)]() mutable {
|
||||
dht_->put(InfoHash::get(certLongId), Value {CERTIFICATE_TYPE, *certificate_, 1}, std::move(cb), {}, true);
|
||||
auto cb = iacb ? [
|
||||
this,
|
||||
completedState = std::make_shared<std::pair<unsigned,bool>>(2, true),
|
||||
iacb = std::move(iacb)
|
||||
](bool ok) {
|
||||
if (logger_)
|
||||
logger_->debug("SecureDht: certificate announcement {}", ok ? "succeeded" : "failed");
|
||||
completedState->second = completedState->second and ok;
|
||||
if (--completedState->first == 0)
|
||||
iacb(completedState->second);
|
||||
} : iacb;
|
||||
|
||||
dht_->addOnConnectedCallback([this, certId, certLongId, cb = std::move(cb)] {
|
||||
dht_->put(certId, Value {CERTIFICATE_TYPE, *certificate_, 1}, cb, {}, true);
|
||||
dht_->put(InfoHash::get(certLongId), Value {CERTIFICATE_TYPE, *certificate_, 1}, cb, {}, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+1
-9
@@ -3,9 +3,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "infohash.h"
|
||||
#include "listener.h"
|
||||
#include "sockaddr.h"
|
||||
#include "value.h"
|
||||
#include "listener.h"
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
@@ -18,11 +17,6 @@ namespace dht {
|
||||
class StorageBucket
|
||||
{
|
||||
public:
|
||||
StorageBucket() = default;
|
||||
explicit StorageBucket(SockAddr addr)
|
||||
: addr_(std::move(addr))
|
||||
{}
|
||||
|
||||
void insert(const InfoHash& id, const Value& value, time_point expiration)
|
||||
{
|
||||
totalSize_ += value.size();
|
||||
@@ -62,10 +56,8 @@ public:
|
||||
{
|
||||
return storedValues_.empty() ? std::pair<InfoHash, Value::Id> {} : storedValues_.begin()->second;
|
||||
}
|
||||
const SockAddr& getAddr() const { return addr_; }
|
||||
|
||||
private:
|
||||
SockAddr addr_ {};
|
||||
std::multimap<time_point, std::pair<InfoHash, Value::Id>> storedValues_;
|
||||
size_t totalSize_ {0};
|
||||
};
|
||||
|
||||
@@ -296,19 +296,4 @@ findMapValue(const msgpack::object& map, const char* key, size_t key_length)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string
|
||||
printByteCount(size_t bytes)
|
||||
{
|
||||
static constexpr std::string_view suffixes[] = {"B", "KB", "MB", "GB", "TB"};
|
||||
size_t suffixIndex = 0;
|
||||
double count = static_cast<double>(bytes);
|
||||
while (count >= 1024 && suffixIndex < std::size(suffixes) - 1) {
|
||||
count /= 1024;
|
||||
suffixIndex++;
|
||||
}
|
||||
if (suffixIndex == 0)
|
||||
return fmt::format("{} {}", bytes, suffixes[suffixIndex]);
|
||||
return fmt::format("{:.2f} {} ({} bytes)", count, suffixes[suffixIndex], bytes);
|
||||
}
|
||||
|
||||
} // namespace dht
|
||||
@@ -165,6 +165,7 @@ private:
|
||||
CallbackQueue addValues(const std::vector<Sp<Value>>& new_values, const TypeStore& types, const time_point& now)
|
||||
{
|
||||
std::vector<Sp<Value>> nvals;
|
||||
std::vector<Sp<Value>> expired_vals;
|
||||
for (const auto& value : new_values) {
|
||||
auto v = values.find(value->id);
|
||||
if (v == values.end()) {
|
||||
@@ -175,9 +176,18 @@ private:
|
||||
// refreshed value
|
||||
v->second.created = now;
|
||||
v->second.expiration = now + types.getType(v->second.data->type).expiration;
|
||||
if (*v->second.data != *value) {
|
||||
expired_vals.emplace_back(v->second.data);
|
||||
v->second.data = value;
|
||||
nvals.emplace_back(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
CallbackQueue ret;
|
||||
if (callback and not expired_vals.empty())
|
||||
ret.emplace_back([cb = callback, expired_vals = std::move(expired_vals)]{
|
||||
cb(expired_vals, true);
|
||||
});
|
||||
if (callback and not nvals.empty())
|
||||
ret.emplace_back([cb = callback, nvals = std::move(nvals)] { cb(nvals, false); });
|
||||
return ret;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
if ENABLE_TESTS
|
||||
bin_PROGRAMS = opendht_unit_tests
|
||||
|
||||
AM_CPPFLAGS = -I../include -DOPENDHT_JSONCPP
|
||||
|
||||
nobase_include_HEADERS = infohashtester.h valuetester.h cryptotester.h dhtrunnertester.h httptester.h dhtproxytester.h
|
||||
opendht_unit_tests_SOURCES = tests_runner.cpp cryptotester.cpp infohashtester.cpp valuetester.cpp dhtrunnertester.cpp httptester.cpp dhtproxytester.cpp
|
||||
opendht_unit_tests_LDFLAGS = -lopendht -lcppunit -ljsoncpp -L@top_builddir@/src/.libs @GnuTLS_LIBS@
|
||||
endif
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_crypto.h"
|
||||
#include "cryptotester.h"
|
||||
|
||||
#include <opendht/crypto.h>
|
||||
|
||||
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_dhtproxy_stress.h"
|
||||
#include "dhtproxy_stress_tester.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
File renamed without changes.
@@ -0,0 +1,705 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "dhtproxytester.h"
|
||||
|
||||
// std
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <asio.hpp>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace test {
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(DhtProxyTester);
|
||||
|
||||
void
|
||||
DhtProxyTester::setUp()
|
||||
{
|
||||
clientConfig.dht_config.node_config.max_peer_req_per_sec = -1;
|
||||
clientConfig.dht_config.node_config.max_req_per_sec = -1;
|
||||
|
||||
nodePeer.run(0, clientConfig);
|
||||
|
||||
nodeProxy = std::make_shared<dht::DhtRunner>();
|
||||
nodeProxy->run(0, clientConfig);
|
||||
|
||||
auto bound = nodePeer.getBound();
|
||||
if (bound.isUnspecified())
|
||||
bound.setLoopback();
|
||||
nodeProxy->bootstrap(bound);
|
||||
|
||||
// auto serverCAIdentity = dht::crypto::generateEcIdentity("DHT Node CA");
|
||||
|
||||
uint16_t port = 1024 + (std::rand() % (65535 - 1024));
|
||||
|
||||
dht::ProxyServerConfig serverConfig;
|
||||
// serverConfig.identity = dht::crypto::generateIdentity("DHT Node", serverCAIdentity);
|
||||
serverConfig.port = port;
|
||||
// serverConfig.pushServer = "127.0.0.1:8090";
|
||||
serverProxy = std::make_unique<dht::DhtProxyServer>(nodeProxy, serverConfig);
|
||||
|
||||
/*clientConfig.server_ca = serverCAIdentity.second;
|
||||
clientConfig.client_identity = dht::crypto::generateIdentity("DhtProxyTester");
|
||||
clientConfig.push_node_id = "dhtnode";*/
|
||||
clientConfig.proxy_server = "http://127.0.0.1:" + std::to_string(port); //"https://127.0.0.1:8080";
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::tearDown()
|
||||
{
|
||||
nodePeer.join();
|
||||
nodeClient.join();
|
||||
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
nodeProxy->shutdown([&] {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&] { return done; }));
|
||||
serverProxy.reset();
|
||||
nodeProxy.reset();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testGetPut()
|
||||
{
|
||||
nodeClient.run(0, clientConfig);
|
||||
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
|
||||
auto key = dht::InfoHash::get("GLaDOs");
|
||||
dht::Value val {"Hey! It's been a long time. How have you been?"};
|
||||
auto val_data = val.data;
|
||||
{
|
||||
nodePeer.put(key, std::move(val), [&](bool) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
}
|
||||
|
||||
auto vals = nodeClient.get(key).get();
|
||||
CPPUNIT_ASSERT(not vals.empty());
|
||||
CPPUNIT_ASSERT(vals.front()->data == val_data);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testListen()
|
||||
{
|
||||
nodeClient.run(0, clientConfig);
|
||||
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
auto key = dht::InfoHash::get("GLaDOs");
|
||||
bool done = false;
|
||||
|
||||
// If a peer send a value, the listen operation from the client
|
||||
// should retrieve this value
|
||||
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
|
||||
auto firstVal_data = firstVal.data;
|
||||
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
|
||||
CPPUNIT_ASSERT(ok);
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
|
||||
std::vector<dht::Blob> values;
|
||||
auto token = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
|
||||
if (not expired) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
for (const auto& value : v)
|
||||
values.emplace_back(value->data);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
// Here values should contains 1 values
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<int>(values.size()), 1);
|
||||
CPPUNIT_ASSERT(values.front() == firstVal_data);
|
||||
|
||||
// And the listen should retrieve futures values
|
||||
// All values
|
||||
dht::Value secondVal {"You're a monster"};
|
||||
auto secondVal_data = secondVal.data;
|
||||
nodePeer.put(key, std::move(secondVal));
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
nodeClient.cancelListen(key, std::move(token));
|
||||
// Here values should contains 2 values
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<int>(values.size()), 2);
|
||||
CPPUNIT_ASSERT(values.back() == secondVal_data);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testResubscribeGetValues()
|
||||
{
|
||||
clientConfig.push_token = "atlas";
|
||||
nodeClient.run(0, clientConfig);
|
||||
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
auto key = dht::InfoHash::get("GLaDOs");
|
||||
|
||||
// If a peer sent a value, the listen operation from the client
|
||||
// should retrieve this value
|
||||
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
|
||||
auto firstVal_data = firstVal.data;
|
||||
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(ok);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
|
||||
// Send a first subscribe, the value is sent via a push notification
|
||||
// So ignore values here.
|
||||
nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>&, bool) { return true; });
|
||||
cv.wait_for(lk, std::chrono::seconds(1));
|
||||
|
||||
// Reboot node (to avoid cache)
|
||||
nodeClient.join();
|
||||
clientConfig.push_token = "";
|
||||
nodeClient.run(0, clientConfig);
|
||||
|
||||
// For the second subscribe, the proxy will return the value in the body
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
auto ftoken = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
|
||||
if (not expired) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
values.insert(values.end(), v.begin(), v.end());
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
auto token = ftoken.get();
|
||||
CPPUNIT_ASSERT(token);
|
||||
nodeClient.cancelListen(key, token);
|
||||
// Here values should still contains 1 values
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
|
||||
CPPUNIT_ASSERT(firstVal_data == values.front()->data);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testPushNotification()
|
||||
{
|
||||
#ifndef OPENDHT_PUSH_NOTIFICATIONS
|
||||
fmt::print(stderr, "Push notifications are not supported in this build, skipping test\n");
|
||||
return;
|
||||
#else
|
||||
static constexpr dht::ValueType::Id PUSH_EDITABLE_TYPE_ID = 4242;
|
||||
const dht::ValueType pushEditableType {PUSH_EDITABLE_TYPE_ID,
|
||||
"push-editable",
|
||||
std::chrono::seconds(1),
|
||||
dht::ValueType::DEFAULT_STORE_POLICY,
|
||||
[](dht::InfoHash,
|
||||
const std::shared_ptr<dht::Value>&,
|
||||
std::shared_ptr<dht::Value>&,
|
||||
const dht::InfoHash&,
|
||||
const dht::SockAddr&) { return true; }};
|
||||
nodePeer.registerType(pushEditableType);
|
||||
nodeProxy->registerType(pushEditableType);
|
||||
|
||||
clientConfig.push_token = "atlas";
|
||||
nodeClient.run(0, clientConfig);
|
||||
nodeClient.registerType(pushEditableType);
|
||||
|
||||
bool gotValues = false;
|
||||
bool gotExpiredValues = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
auto key = dht::InfoHash::get("GLaDOs");
|
||||
auto valueAuthor = dht::crypto::generateIdentity("PushNotificationAuthor");
|
||||
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
std::vector<std::shared_ptr<dht::Value>> expiredValues;
|
||||
auto ftoken = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
if (expired) {
|
||||
expiredValues.insert(expiredValues.end(), v.begin(), v.end());
|
||||
gotExpiredValues = true;
|
||||
} else {
|
||||
values.insert(values.end(), v.begin(), v.end());
|
||||
gotValues = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
return true;
|
||||
});
|
||||
|
||||
// Wait for listen to be sent to proxy
|
||||
cv.wait_for(lk, std::chrono::seconds(1));
|
||||
|
||||
// Peer puts a value
|
||||
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
|
||||
firstVal.type = PUSH_EDITABLE_TYPE_ID;
|
||||
firstVal.id = 1337;
|
||||
firstVal.seq = 1;
|
||||
firstVal.sign(*valueAuthor.first);
|
||||
auto firstVal_data = firstVal.data;
|
||||
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(ok);
|
||||
gotValues = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return gotValues; }));
|
||||
gotValues = false;
|
||||
|
||||
// Simulate push notification
|
||||
std::map<std::string, std::string> push_data;
|
||||
push_data["key"] = key.toString();
|
||||
push_data["t"] = std::to_string(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
|
||||
.count());
|
||||
CPPUNIT_ASSERT(nodeClient.pushNotificationReceived(push_data).get() == dht::PushNotificationResult::Values);
|
||||
|
||||
// Wait for client to receive the value
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return gotValues; }));
|
||||
gotValues = false;
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
|
||||
CPPUNIT_ASSERT(firstVal_data == values.front()->data);
|
||||
|
||||
// Peer edits the existing value (same id, higher seq)
|
||||
dht::Value secondVal {"You're a monster"};
|
||||
secondVal.type = PUSH_EDITABLE_TYPE_ID;
|
||||
secondVal.id = 1337;
|
||||
secondVal.seq = 2;
|
||||
secondVal.sign(*valueAuthor.first);
|
||||
auto secondVal_data = secondVal.data;
|
||||
nodePeer.put(key, std::move(secondVal), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(ok);
|
||||
gotValues = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return gotValues; }));
|
||||
gotValues = false;
|
||||
|
||||
// Simulate push notification again
|
||||
push_data["t"] = std::to_string(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
|
||||
.count());
|
||||
CPPUNIT_ASSERT(nodeClient.pushNotificationReceived(push_data).get() == dht::PushNotificationResult::Values);
|
||||
|
||||
// Wait for client to receive the edited value
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return gotValues; }));
|
||||
gotValues = false;
|
||||
|
||||
// OpValueCache should update the existing id with the new sequence/data
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 2u, values.size());
|
||||
CPPUNIT_ASSERT_EQUAL((dht::Value::Id) 1337, values.back()->id);
|
||||
CPPUNIT_ASSERT_EQUAL((uint16_t) 2, values.back()->seq);
|
||||
CPPUNIT_ASSERT(secondVal_data == values.back()->data);
|
||||
|
||||
// Simulate expiration push notification for the edited value
|
||||
push_data["exp"] = std::to_string((dht::Value::Id) 1337);
|
||||
push_data["t"] = std::to_string(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
|
||||
.count());
|
||||
CPPUNIT_ASSERT(nodeClient.pushNotificationReceived(push_data).get() == dht::PushNotificationResult::ValuesExpired);
|
||||
|
||||
cv.wait_for(lk, 500ms, [&] { return gotExpiredValues; });
|
||||
|
||||
auto token = ftoken.get();
|
||||
CPPUNIT_ASSERT(token);
|
||||
nodeClient.cancelListen(key, token);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testProxyServerClientFullChain()
|
||||
{
|
||||
#ifndef OPENDHT_PUSH_NOTIFICATIONS
|
||||
fmt::print(stderr, "Push notifications are not supported in this build, skipping test\n");
|
||||
return;
|
||||
#else
|
||||
static constexpr dht::ValueType::Id CHAIN_EDITABLE_TYPE_ID = 4343;
|
||||
const dht::ValueType chainEditableType {CHAIN_EDITABLE_TYPE_ID,
|
||||
"chain-editable",
|
||||
std::chrono::seconds(30),
|
||||
dht::ValueType::DEFAULT_STORE_POLICY,
|
||||
[](dht::InfoHash,
|
||||
const std::shared_ptr<dht::Value>&,
|
||||
std::shared_ptr<dht::Value>&,
|
||||
const dht::InfoHash&,
|
||||
const dht::SockAddr&) { return true; }};
|
||||
|
||||
uint16_t pushPort = 1024 + (std::rand() % (65535 - 1024));
|
||||
uint16_t proxyPort = 1024 + (std::rand() % (65535 - 1024));
|
||||
|
||||
std::atomic_bool relayStop {false};
|
||||
std::atomic_size_t relayedPushCount {0};
|
||||
std::thread pushRelay([&] {
|
||||
try {
|
||||
asio::io_context io;
|
||||
asio::ip::tcp::acceptor acceptor(io, asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), pushPort));
|
||||
acceptor.non_blocking(true);
|
||||
|
||||
while (not relayStop.load()) {
|
||||
asio::ip::tcp::socket socket(io);
|
||||
std::error_code ec;
|
||||
acceptor.accept(socket, ec);
|
||||
if (ec == asio::error::would_block || ec == asio::error::try_again) {
|
||||
std::this_thread::sleep_for(5ms);
|
||||
continue;
|
||||
}
|
||||
if (ec)
|
||||
continue;
|
||||
|
||||
socket.non_blocking(true);
|
||||
|
||||
std::string raw;
|
||||
raw.reserve(4096);
|
||||
auto deadline = std::chrono::steady_clock::now() + 2s;
|
||||
while (raw.find("\r\n\r\n") == std::string::npos && std::chrono::steady_clock::now() < deadline
|
||||
&& not relayStop.load()) {
|
||||
std::array<char, 2048> chunk {};
|
||||
size_t n = socket.read_some(asio::buffer(chunk), ec);
|
||||
if (!ec) {
|
||||
raw.append(chunk.data(), n);
|
||||
} else if (ec == asio::error::would_block || ec == asio::error::try_again) {
|
||||
std::this_thread::sleep_for(2ms);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (raw.find("\r\n\r\n") == std::string::npos)
|
||||
continue;
|
||||
|
||||
auto sep = raw.find("\r\n\r\n");
|
||||
std::string head = raw.substr(0, sep + 4);
|
||||
auto clPos = head.find("Content-Length:");
|
||||
size_t contentLength = 0;
|
||||
if (clPos != std::string::npos) {
|
||||
clPos += std::string("Content-Length:").size();
|
||||
while (clPos < head.size() && (head[clPos] == ' ' || head[clPos] == '\t'))
|
||||
++clPos;
|
||||
size_t clEnd = head.find("\r\n", clPos);
|
||||
contentLength = std::stoul(head.substr(clPos, clEnd - clPos));
|
||||
}
|
||||
|
||||
std::string body = raw.substr(sep + 4);
|
||||
while (body.size() < contentLength && std::chrono::steady_clock::now() < deadline
|
||||
&& not relayStop.load()) {
|
||||
std::array<char, 2048> chunk {};
|
||||
size_t n = socket.read_some(asio::buffer(chunk), ec);
|
||||
if (!ec) {
|
||||
body.append(chunk.data(), n);
|
||||
} else if (ec == asio::error::would_block || ec == asio::error::try_again) {
|
||||
std::this_thread::sleep_for(2ms);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (body.size() > contentLength)
|
||||
body.resize(contentLength);
|
||||
|
||||
Json::Value payload;
|
||||
std::string err;
|
||||
auto reader = std::unique_ptr<Json::CharReader>(Json::CharReaderBuilder().newCharReader());
|
||||
if (reader->parse(body.data(), body.data() + body.size(), &payload, &err)
|
||||
&& payload.isMember("notifications") && payload["notifications"].isArray()
|
||||
&& !payload["notifications"].empty() && payload["notifications"][0].isMember("data")) {
|
||||
const auto& data = payload["notifications"][0]["data"];
|
||||
std::map<std::string, std::string> notif;
|
||||
for (const auto& name : data.getMemberNames()) {
|
||||
const auto& value = data[name];
|
||||
if (value.isString())
|
||||
notif[name] = value.asString();
|
||||
else if (value.isBool())
|
||||
notif[name] = value.asBool() ? "true" : "false";
|
||||
else if (value.isUInt64())
|
||||
notif[name] = std::to_string(value.asUInt64());
|
||||
else if (value.isInt64())
|
||||
notif[name] = std::to_string(value.asInt64());
|
||||
}
|
||||
nodeClient.pushNotificationReceived(notif);
|
||||
relayedPushCount++;
|
||||
}
|
||||
|
||||
static const std::string okResp = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK";
|
||||
asio::write(socket, asio::buffer(okResp), ec);
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
struct RelayGuard
|
||||
{
|
||||
std::atomic_bool& stop;
|
||||
std::thread& th;
|
||||
~RelayGuard()
|
||||
{
|
||||
stop = true;
|
||||
if (th.joinable())
|
||||
th.join();
|
||||
}
|
||||
} relayGuard {relayStop, pushRelay};
|
||||
|
||||
serverProxy.reset();
|
||||
dht::ProxyServerConfig serverConfig;
|
||||
serverConfig.port = proxyPort;
|
||||
serverConfig.pushServer = "127.0.0.1:" + std::to_string(pushPort);
|
||||
serverProxy = std::make_unique<dht::DhtProxyServer>(nodeProxy, serverConfig);
|
||||
|
||||
clientConfig.proxy_server = "http://127.0.0.1:" + std::to_string(proxyPort);
|
||||
clientConfig.push_token = "relay-token";
|
||||
clientConfig.push_platform = "android";
|
||||
|
||||
nodePeer.registerType(chainEditableType);
|
||||
nodeProxy->registerType(chainEditableType);
|
||||
|
||||
nodeClient.run(0, clientConfig);
|
||||
nodeClient.registerType(chainEditableType);
|
||||
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
|
||||
auto key = dht::InfoHash::get("proxy-full-chain");
|
||||
auto valueAuthor = dht::crypto::generateIdentity("ProxyChainAuthor");
|
||||
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
auto token = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& incoming, bool expired) {
|
||||
if (expired)
|
||||
return true;
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
values.insert(values.end(), incoming.begin(), incoming.end());
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
return true;
|
||||
});
|
||||
|
||||
dht::Value firstVal {"first-version"};
|
||||
firstVal.type = CHAIN_EDITABLE_TYPE_ID;
|
||||
firstVal.id = 424242;
|
||||
firstVal.seq = 1;
|
||||
firstVal.sign(*valueAuthor.first);
|
||||
auto firstData = firstVal.data;
|
||||
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(ok);
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
|
||||
CPPUNIT_ASSERT_EQUAL((dht::Value::Id) 424242, values.back()->id);
|
||||
CPPUNIT_ASSERT_EQUAL((uint16_t) 1, values.back()->seq);
|
||||
CPPUNIT_ASSERT(firstData == values.back()->data);
|
||||
|
||||
dht::Value secondVal {"second-version"};
|
||||
secondVal.type = CHAIN_EDITABLE_TYPE_ID;
|
||||
secondVal.id = 424242;
|
||||
secondVal.seq = 2;
|
||||
secondVal.sign(*valueAuthor.first);
|
||||
auto secondData = secondVal.data;
|
||||
nodePeer.put(key, std::move(secondVal), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(ok);
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 2u, values.size());
|
||||
CPPUNIT_ASSERT_EQUAL((dht::Value::Id) 424242, values.back()->id);
|
||||
CPPUNIT_ASSERT_EQUAL((uint16_t) 2, values.back()->seq);
|
||||
CPPUNIT_ASSERT(secondData == values.back()->data);
|
||||
CPPUNIT_ASSERT(relayedPushCount.load() >= 2);
|
||||
|
||||
// Older sequence should not update current cached value
|
||||
dht::Value staleVal {"stale-version"};
|
||||
staleVal.type = CHAIN_EDITABLE_TYPE_ID;
|
||||
staleVal.id = 424242;
|
||||
staleVal.seq = 1;
|
||||
staleVal.sign(*valueAuthor.first);
|
||||
nodePeer.put(key, std::move(staleVal), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(ok);
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(not cv.wait_for(lk, 1s, [&] { return done; }));
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 2u, values.size());
|
||||
CPPUNIT_ASSERT_EQUAL((uint16_t) 2, values.back()->seq);
|
||||
CPPUNIT_ASSERT(secondData == values.back()->data);
|
||||
|
||||
// New id should be delivered as an additional value
|
||||
dht::Value thirdVal {"third-value"};
|
||||
thirdVal.type = CHAIN_EDITABLE_TYPE_ID;
|
||||
thirdVal.id = 424243;
|
||||
thirdVal.seq = 1;
|
||||
thirdVal.sign(*valueAuthor.first);
|
||||
auto thirdData = thirdVal.data;
|
||||
nodePeer.put(key, std::move(thirdVal), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(ok);
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 3u, values.size());
|
||||
CPPUNIT_ASSERT_EQUAL((dht::Value::Id) 424243, values.back()->id);
|
||||
CPPUNIT_ASSERT_EQUAL((uint16_t) 1, values.back()->seq);
|
||||
CPPUNIT_ASSERT(thirdData == values.back()->data);
|
||||
CPPUNIT_ASSERT(relayedPushCount.load() >= 3);
|
||||
|
||||
nodeClient.cancelListen(key, std::move(token));
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testPutGet40KChars()
|
||||
{
|
||||
nodeClient.run(0, clientConfig);
|
||||
constexpr size_t N = 40000;
|
||||
|
||||
// Arrange
|
||||
auto key = dht::InfoHash::get("testPutGet40KChars");
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
std::vector<uint8_t> mtu;
|
||||
mtu.reserve(N);
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mtu.emplace_back((i % 2) ? 'T' : 'M');
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
bool done_put = false;
|
||||
bool done_get = false;
|
||||
|
||||
// Act
|
||||
dht::Value val {mtu};
|
||||
nodePeer.put(key, std::move(val), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done_put = ok;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done_put; }));
|
||||
|
||||
nodeClient.get(
|
||||
key,
|
||||
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
|
||||
values.insert(values.end(), vals.begin(), vals.end());
|
||||
return true;
|
||||
},
|
||||
[&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done_get = ok;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done_get; }));
|
||||
|
||||
// Assert
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
|
||||
for (const auto& value : values)
|
||||
CPPUNIT_ASSERT(value->data == mtu);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testFuzzy()
|
||||
{
|
||||
constexpr size_t N = 40000;
|
||||
|
||||
// Arrange
|
||||
auto key = dht::InfoHash::get("testFuzzy");
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
std::vector<uint8_t> mtu;
|
||||
mtu.reserve(N);
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mtu.emplace_back((i % 2) ? 'T' : 'M');
|
||||
|
||||
// Act
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
auto nodeTest = std::make_shared<dht::DhtRunner>();
|
||||
nodeTest->run(0, clientConfig);
|
||||
nodeTest->put(key, dht::Value(mtu), [&](bool ok) { CPPUNIT_ASSERT(ok); });
|
||||
nodeTest->get(
|
||||
key,
|
||||
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
|
||||
values.insert(values.end(), vals.begin(), vals.end());
|
||||
return true;
|
||||
},
|
||||
[&](bool ok) { CPPUNIT_ASSERT(ok); });
|
||||
std::this_thread::sleep_for(5ms);
|
||||
}
|
||||
|
||||
// Assert
|
||||
for (const auto& value : values)
|
||||
CPPUNIT_ASSERT(value->data == mtu);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testShutdownStop()
|
||||
{
|
||||
constexpr size_t N = 40000;
|
||||
constexpr unsigned C = 100;
|
||||
|
||||
// Arrange
|
||||
auto key = dht::InfoHash::get("testShutdownStop");
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
std::vector<uint8_t> mtu;
|
||||
mtu.reserve(N);
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mtu.emplace_back((i % 2) ? 'T' : 'M');
|
||||
|
||||
std::atomic_uint callback_count {0};
|
||||
|
||||
// Act
|
||||
for (size_t i = 0; i < C; i++) {
|
||||
auto nodeTest = std::make_shared<dht::DhtRunner>();
|
||||
nodeTest->run(0, clientConfig);
|
||||
nodeTest->put(key, dht::Value(mtu), [&](bool /*ok*/) { callback_count++; });
|
||||
nodeTest->get(
|
||||
key,
|
||||
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
|
||||
values.insert(values.end(), vals.begin(), vals.end());
|
||||
return true;
|
||||
},
|
||||
[&](bool /*ok*/) { callback_count++; });
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
nodeTest->shutdown(
|
||||
[&] {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
},
|
||||
true);
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
}
|
||||
CPPUNIT_ASSERT_EQUAL(2 * C, callback_count.load());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -18,6 +18,8 @@ class DhtProxyTester : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testGetPut);
|
||||
CPPUNIT_TEST(testListen);
|
||||
CPPUNIT_TEST(testResubscribeGetValues);
|
||||
CPPUNIT_TEST(testPushNotification);
|
||||
CPPUNIT_TEST(testProxyServerClientFullChain);
|
||||
CPPUNIT_TEST(testPutGet40KChars);
|
||||
CPPUNIT_TEST(testFuzzy);
|
||||
CPPUNIT_TEST(testShutdownStop);
|
||||
@@ -46,6 +48,14 @@ public:
|
||||
* it should retrieve existant values
|
||||
*/
|
||||
void testResubscribeGetValues();
|
||||
/**
|
||||
* Test push notification mechanism and OpValueCache
|
||||
*/
|
||||
void testPushNotification();
|
||||
/**
|
||||
* Test end-to-end chain through proxy server and proxy client
|
||||
*/
|
||||
void testProxyServerClientFullChain();
|
||||
/**
|
||||
* Test MTU put/get on dht
|
||||
*/
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_dhtrunner.h"
|
||||
#include "dhtrunnertester.h"
|
||||
|
||||
#include <opendht/thread_pool.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
using namespace std::chrono_literals;
|
||||
@@ -15,14 +14,6 @@ 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()
|
||||
{
|
||||
@@ -81,14 +72,49 @@ 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(getFutureValue(std::move(future)));
|
||||
auto vals = getFutureValue(node1.get(key));
|
||||
CPPUNIT_ASSERT(p.get_future().get());
|
||||
auto vals = node1.get(key).get();
|
||||
CPPUNIT_ASSERT(not vals.empty());
|
||||
CPPUNIT_ASSERT(vals.front()->data == val_data);
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunnerTester::testStoreEmptyValue()
|
||||
{
|
||||
dht::DhtRunner::Config config;
|
||||
config.dht_config.node_config.max_peer_req_per_sec = -1;
|
||||
config.dht_config.node_config.max_req_per_sec = -1;
|
||||
config.dht_config.node_config.max_store_size = 100; // Small size to force expireStore()
|
||||
config.dht_config.node_config.max_store_keys = -1;
|
||||
|
||||
dht::DhtRunner testNode;
|
||||
testNode.run(0, config);
|
||||
testNode.bootstrap(node1.getBound());
|
||||
|
||||
auto key = testNode.getId();
|
||||
auto val = std::make_shared<dht::Value>();
|
||||
val->data.clear(); // Ensure size is 0
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 0, val->size());
|
||||
|
||||
std::promise<bool> p;
|
||||
testNode.put(key, val, [&](bool ok) { p.set_value(ok); });
|
||||
p.get_future().get(); // Wait for put to finish
|
||||
|
||||
// Put a large value to exceed max_store_size and trigger expireStore()
|
||||
auto key2 = dht::InfoHash::get("large_value_test");
|
||||
auto val2 = std::make_shared<dht::Value>();
|
||||
val2->data = std::vector<uint8_t>(200, 0); // 200 bytes
|
||||
std::promise<bool> p2;
|
||||
testNode.put(key2, val2, [&](bool ok) { p2.set_value(ok); });
|
||||
p2.get_future().get(); // Wait for put to finish
|
||||
|
||||
// Wait a bit to ensure the value is processed
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
testNode.join();
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunnerTester::testPutDuplicate()
|
||||
{
|
||||
@@ -98,15 +124,13 @@ 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 = getFutureValue(std::move(p1future));
|
||||
auto p2ret = getFutureValue(std::move(p2future));
|
||||
auto p1ret = p1.get_future().get();
|
||||
auto p2ret = p2.get_future().get();
|
||||
CPPUNIT_ASSERT(p1ret);
|
||||
CPPUNIT_ASSERT(p2ret);
|
||||
auto vals = getFutureValue(node1.get(key));
|
||||
auto vals = node1.get(key).get();
|
||||
CPPUNIT_ASSERT(not vals.empty());
|
||||
CPPUNIT_ASSERT(vals.size() == 1);
|
||||
CPPUNIT_ASSERT(vals.front()->data == val_data);
|
||||
@@ -124,345 +148,18 @@ 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 = getFutureValue(std::move(p1future));
|
||||
auto p2ret = getFutureValue(std::move(p2future));
|
||||
auto p1ret = p1.get_future().get();
|
||||
auto p2ret = p2.get_future().get();
|
||||
CPPUNIT_ASSERT(!p1ret);
|
||||
CPPUNIT_ASSERT(p2ret);
|
||||
auto vals = getFutureValue(node1.get(key));
|
||||
auto vals = node1.get(key).get();
|
||||
CPPUNIT_ASSERT(not vals.empty());
|
||||
CPPUNIT_ASSERT(vals.size() == 1);
|
||||
CPPUNIT_ASSERT(vals.front()->data == val_data);
|
||||
}
|
||||
|
||||
void
|
||||
DhtRunnerTester::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()
|
||||
{
|
||||
@@ -562,9 +259,9 @@ DhtRunnerTester::testListen()
|
||||
CPPUNIT_ASSERT(ftokenc.valid());
|
||||
CPPUNIT_ASSERT(ftokend.valid());
|
||||
|
||||
auto tokena = getFutureValue(std::move(ftokena));
|
||||
auto tokenc = getFutureValue(std::move(ftokenc));
|
||||
auto tokend = getFutureValue(std::move(ftokend));
|
||||
auto tokena = ftokena.get();
|
||||
auto tokenc = ftokenc.get();
|
||||
auto tokend = ftokend.get();
|
||||
// tokenb might be 0 since the callback returns false.
|
||||
|
||||
CPPUNIT_ASSERT(tokena);
|
||||
@@ -604,7 +301,7 @@ DhtRunnerTester::testIdOps()
|
||||
};
|
||||
|
||||
node2.join();
|
||||
node2.run(0, config2, std::move(context2));
|
||||
node2.run(42232, config2, std::move(context2));
|
||||
auto bound = node1.getBound();
|
||||
if (bound.isUnspecified())
|
||||
bound.setLoopback();
|
||||
@@ -615,7 +312,7 @@ DhtRunnerTester::testIdOps()
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return identityCount == 1; }));
|
||||
}
|
||||
|
||||
node1.findCertificate(node2.getPublicKey()->getLongId(), [&](const std::shared_ptr<dht::crypto::Certificate>& crt) {
|
||||
node1.findCertificate(node2.getId(), [&](const std::shared_ptr<dht::crypto::Certificate>& crt) {
|
||||
CPPUNIT_ASSERT(crt);
|
||||
std::lock_guard lk(mutex);
|
||||
valueCount++;
|
||||
@@ -638,21 +335,21 @@ DhtRunnerTester::testIdOps()
|
||||
|
||||
config2.dht_config.id = dht::crypto::generateIdentity();
|
||||
node1.join();
|
||||
node1.run(0, config2, std::move(context1));
|
||||
node1.run(42222, config2, std::move(context1));
|
||||
bound = node2.getBound();
|
||||
if (bound.isUnspecified())
|
||||
bound.setLoopback();
|
||||
node1.bootstrap(bound);
|
||||
|
||||
auto key = dht::InfoHash::get("key");
|
||||
node1.putEncrypted(key, node2.getPublicKey()->getLongId(), std::make_shared<dht::Value>("yo"), [&](bool ok) {
|
||||
node1.putEncrypted(key, node2.getId(), dht::Value("yo"), [&](bool ok) {
|
||||
CPPUNIT_ASSERT(ok);
|
||||
std::lock_guard lk(mutex);
|
||||
valueCount++;
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
node1.putEncrypted(key, node2.getPublicKey(), dht::Value("yo"), [&](bool ok) {
|
||||
node1.putEncrypted(key, node2.getPublicKey(), std::make_shared<dht::Value>("yo"), [&](bool ok) {
|
||||
CPPUNIT_ASSERT(ok);
|
||||
std::lock_guard lk(mutex);
|
||||
valueCount++;
|
||||
@@ -770,7 +467,7 @@ DhtRunnerTester::testListenLotOfBytes()
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return valueCount == N; }));
|
||||
}
|
||||
|
||||
node3.cancelListen(foo, getFutureValue(std::move(ftokenfoo)));
|
||||
node3.cancelListen(foo, ftokenfoo.get());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -19,13 +19,8 @@ class DhtRunnerTester : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testPutOverride);
|
||||
CPPUNIT_TEST(testListen);
|
||||
CPPUNIT_TEST(testListenLotOfBytes);
|
||||
CPPUNIT_TEST(testStoreEmptyValue);
|
||||
CPPUNIT_TEST(testIdOps);
|
||||
CPPUNIT_TEST(testImportValuesPreservesRemoteQuota);
|
||||
CPPUNIT_TEST(testImportValuesPreservesStoredExpiration);
|
||||
CPPUNIT_TEST(testBootstrapSetsConnectingState);
|
||||
CPPUNIT_TEST(testBootstrapThenPutNoRace);
|
||||
CPPUNIT_TEST(testBootstrapMissingNodeThenPutFails);
|
||||
CPPUNIT_TEST(testShutdownCompletesWithPendingPut);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
dht::DhtRunner node1 {};
|
||||
@@ -64,16 +59,14 @@ 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
|
||||
*/
|
||||
void testListenLotOfBytes();
|
||||
/**
|
||||
* Test storing an empty value
|
||||
*/
|
||||
void testStoreEmptyValue();
|
||||
/**
|
||||
* Test multithread
|
||||
*/
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_http.h"
|
||||
#include "httptester.h"
|
||||
|
||||
#include <opendht/value.h>
|
||||
#include <opendht/dhtrunner.h>
|
||||
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_infohash.h"
|
||||
#include "infohashtester.h"
|
||||
|
||||
// std
|
||||
#include <iostream>
|
||||
@@ -22,60 +22,47 @@ void
|
||||
InfoHashTester::testConstructors()
|
||||
{
|
||||
// Default constructor creates a null infohash
|
||||
constexpr auto nullHash = dht::InfoHash();
|
||||
auto nullHash = dht::InfoHash();
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 20u, nullHash.size());
|
||||
CPPUNIT_ASSERT(!nullHash);
|
||||
// Build from a uint8_t. if length to short, should get a null infohash
|
||||
constexpr uint8_t too_short[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
|
||||
auto infohash = dht::InfoHash(too_short, 8);
|
||||
uint8_t to_short[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8};
|
||||
auto infohash = dht::InfoHash(to_short, 8);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 20u, infohash.size());
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("0000000000000000000000000000000000000000"), infohash.toString());
|
||||
// Build from a uint8_t. if length is enough, data should contains the uint8_t
|
||||
constexpr uint8_t enough[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
|
||||
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa};
|
||||
uint8_t enough[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa,
|
||||
0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa};
|
||||
infohash = dht::InfoHash(enough, 20);
|
||||
static_assert(dht::InfoHash(enough, 20) != nullHash);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 20u, infohash.size());
|
||||
CPPUNIT_ASSERT(infohash.size() == 20);
|
||||
const auto* data = infohash.data();
|
||||
for (auto i = 0; i < 20; ++i) {
|
||||
CPPUNIT_ASSERT_EQUAL(enough[i], data[i]);
|
||||
}
|
||||
// if too long, should be cutted to 20
|
||||
constexpr uint8_t tooLong[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x1,
|
||||
0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb0};
|
||||
uint8_t tooLong[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x1,
|
||||
0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb0};
|
||||
infohash = dht::InfoHash(tooLong, 21);
|
||||
CPPUNIT_ASSERT(infohash.size() == 20);
|
||||
const auto* data2 = infohash.data();
|
||||
for (auto i = 0; i < 20; ++i) {
|
||||
CPPUNIT_ASSERT_EQUAL(enough[i], data2[i]);
|
||||
}
|
||||
// Build from string
|
||||
constexpr auto infohashFromStr = dht::InfoHash("0102030405060708090A0102030405060708090A");
|
||||
auto infohashFromStr = dht::InfoHash("0102030405060708090A0102030405060708090A");
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 20u, infohashFromStr.size());
|
||||
const auto* dataStr = infohashFromStr.data();
|
||||
for (auto i = 0; i < 20; ++i) {
|
||||
CPPUNIT_ASSERT_EQUAL((int) dataStr[i], (int) data[i]);
|
||||
}
|
||||
|
||||
// invalid strings
|
||||
constexpr auto invalidHash = dht::InfoHash("invalid_string0102030405060708090A010203");
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 20u, invalidHash.size());
|
||||
CPPUNIT_ASSERT(!invalidHash);
|
||||
CPPUNIT_ASSERT_EQUAL(nullHash, invalidHash);
|
||||
static_assert(invalidHash == nullHash, "Invalid hash string should result in null hash");
|
||||
|
||||
constexpr auto tooShortHash = dht::InfoHash("0123456789abcdef0123456789abcdef012345");
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 20u, tooShortHash.size());
|
||||
CPPUNIT_ASSERT(!tooShortHash);
|
||||
CPPUNIT_ASSERT_EQUAL(nullHash, tooShortHash);
|
||||
static_assert(tooShortHash == nullHash, "Too short hash string should result in null hash");
|
||||
}
|
||||
|
||||
void
|
||||
InfoHashTester::testComparators()
|
||||
{
|
||||
constexpr auto nullHash = dht::InfoHash();
|
||||
constexpr auto minHash = dht::InfoHash("0000000000000000000000000000000000111110");
|
||||
constexpr auto maxHash = dht::InfoHash("0111110000000000000000000000000000000000");
|
||||
auto nullHash = dht::InfoHash();
|
||||
auto minHash = dht::InfoHash("0000000000000000000000000000000000111110");
|
||||
auto maxHash = dht::InfoHash("0111110000000000000000000000000000000000");
|
||||
// operator ==
|
||||
CPPUNIT_ASSERT_EQUAL(minHash, minHash);
|
||||
CPPUNIT_ASSERT_EQUAL(minHash, dht::InfoHash("0000000000000000000000000000000000111110"));
|
||||
File renamed without changes.
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: GitHub Copilot
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "opcachetester.h"
|
||||
|
||||
// Hack to test internal class
|
||||
#include "../src/op_cache.cpp"
|
||||
|
||||
namespace test {
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(OpCacheTester);
|
||||
|
||||
void
|
||||
OpCacheTester::setUp() {
|
||||
}
|
||||
|
||||
void
|
||||
OpCacheTester::tearDown() {
|
||||
}
|
||||
|
||||
void
|
||||
OpCacheTester::testUpdateRefCount() {
|
||||
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 1;
|
||||
v1->seq = 1;
|
||||
std::string data1 = "data1";
|
||||
v1->data = {data1.begin(), data1.end()};
|
||||
|
||||
auto v2 = std::make_shared<dht::Value>();
|
||||
v2->id = 1;
|
||||
v2->seq = 2;
|
||||
std::string data2 = "data2";
|
||||
v2->data = {data2.begin(), data2.end()};
|
||||
|
||||
// Add v1
|
||||
cache.onValuesAdded({v1});
|
||||
// Should have 1 value
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
|
||||
auto stored = cache.get(1);
|
||||
CPPUNIT_ASSERT(stored);
|
||||
CPPUNIT_ASSERT_EQUAL(v1->seq, stored->seq);
|
||||
|
||||
// Add v2 (update)
|
||||
cache.onValuesAdded({v2});
|
||||
// Should still have 1 value (updated)
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
|
||||
stored = cache.get(1);
|
||||
CPPUNIT_ASSERT(stored);
|
||||
CPPUNIT_ASSERT_EQUAL(v2->seq, stored->seq);
|
||||
|
||||
// Now expire v1
|
||||
// Note: onValuesExpired uses ID to find value.
|
||||
cache.onValuesExpired({v1});
|
||||
|
||||
// If refCount was incremented on update, we should still have the value (refCount=1).
|
||||
// If not, it will be removed (refCount=0).
|
||||
|
||||
// Check if value exists
|
||||
stored = cache.get(1);
|
||||
|
||||
// This assertion will fail if the bug exists
|
||||
CPPUNIT_ASSERT_MESSAGE("Value should still exist after expiring one source", stored != nullptr);
|
||||
|
||||
if (stored) {
|
||||
CPPUNIT_ASSERT_EQUAL(v2->seq, stored->seq);
|
||||
}
|
||||
|
||||
// Expire v2
|
||||
cache.onValuesExpired({v2});
|
||||
stored = cache.get(1);
|
||||
CPPUNIT_ASSERT_MESSAGE("Value should be removed after expiring all sources", stored == nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
OpCacheTester::testBasicAddExpire() {
|
||||
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 10;
|
||||
v1->data = {'A'};
|
||||
|
||||
cache.onValuesAdded({v1});
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
|
||||
CPPUNIT_ASSERT(cache.get(10));
|
||||
|
||||
std::vector<dht::Value::Id> ids = {v1->id};
|
||||
cache.onValuesExpired(ids);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)0, cache.size());
|
||||
CPPUNIT_ASSERT(!cache.get(10));
|
||||
}
|
||||
|
||||
void
|
||||
OpCacheTester::testMultipleSources() {
|
||||
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 20;
|
||||
v1->data = {'B'};
|
||||
|
||||
// Add same value twice (simulating two sources)
|
||||
cache.onValuesAdded({v1});
|
||||
cache.onValuesAdded({v1});
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
|
||||
|
||||
// Expire once
|
||||
cache.onValuesExpired({v1});
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
|
||||
CPPUNIT_ASSERT(cache.get(20));
|
||||
|
||||
// Expire twice
|
||||
cache.onValuesExpired({v1});
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)0, cache.size());
|
||||
}
|
||||
|
||||
void
|
||||
OpCacheTester::testUpdateSequence() {
|
||||
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 30;
|
||||
v1->seq = 10;
|
||||
v1->data = {'C'};
|
||||
|
||||
auto v2 = std::make_shared<dht::Value>();
|
||||
v2->id = 30;
|
||||
v2->seq = 20;
|
||||
v2->data = {'D'};
|
||||
|
||||
auto v3 = std::make_shared<dht::Value>();
|
||||
v3->id = 30;
|
||||
v3->seq = 5;
|
||||
v3->data = {'E'};
|
||||
|
||||
// Add seq 10
|
||||
cache.onValuesAdded({v1});
|
||||
auto stored = cache.get(30);
|
||||
CPPUNIT_ASSERT_EQUAL((uint16_t)10, stored->seq);
|
||||
|
||||
// Add seq 20 (update)
|
||||
cache.onValuesAdded({v2});
|
||||
stored = cache.get(30);
|
||||
CPPUNIT_ASSERT_EQUAL((uint16_t)20, stored->seq);
|
||||
|
||||
// Add seq 5 (older, should not update content but increment refCount)
|
||||
cache.onValuesAdded({v3});
|
||||
stored = cache.get(30);
|
||||
CPPUNIT_ASSERT_EQUAL((uint16_t)20, stored->seq);
|
||||
|
||||
// We added 3 times. Should need 3 expires to remove.
|
||||
cache.onValuesExpired({v1}); // 2 left
|
||||
CPPUNIT_ASSERT(cache.get(30));
|
||||
cache.onValuesExpired({v2}); // 1 left
|
||||
CPPUNIT_ASSERT(cache.get(30));
|
||||
cache.onValuesExpired({v3}); // 0 left
|
||||
CPPUNIT_ASSERT(!cache.get(30));
|
||||
}
|
||||
|
||||
void
|
||||
OpCacheTester::testCallbacks() {
|
||||
int addCount = 0;
|
||||
int expireCount = 0;
|
||||
dht::OpValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired){
|
||||
if (expired) expireCount += vals.size();
|
||||
else addCount += vals.size();
|
||||
return true;
|
||||
});
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 40;
|
||||
|
||||
cache.onValuesAdded({v1});
|
||||
CPPUNIT_ASSERT_EQUAL(1, addCount);
|
||||
CPPUNIT_ASSERT_EQUAL(0, expireCount);
|
||||
|
||||
// Adding same value again should NOT trigger callback (no new value added)
|
||||
// Wait, looking at code:
|
||||
// if (viop.second) { newValues.emplace_back(v); } ...
|
||||
// return newValues.empty() ? true : callback(newValues, false);
|
||||
// So if it's a duplicate or update, does it callback?
|
||||
// If update: newValues.emplace_back(v) IS called.
|
||||
// If duplicate (refCount++ only): newValues is NOT added.
|
||||
|
||||
cache.onValuesAdded({v1});
|
||||
CPPUNIT_ASSERT_EQUAL(1, addCount); // Should still be 1
|
||||
|
||||
auto v2 = std::make_shared<dht::Value>();
|
||||
v2->id = 40;
|
||||
v2->seq = 100; // Update
|
||||
v2->data = {'M', 'O', 'D'}; // Change data to ensure inequality
|
||||
|
||||
cache.onValuesAdded({v2});
|
||||
CPPUNIT_ASSERT_EQUAL(2, addCount); // Should be 2 now (update triggers callback)
|
||||
|
||||
cache.onValuesExpired({v1});
|
||||
CPPUNIT_ASSERT_EQUAL(0, expireCount); // RefCount dec, not removed yet
|
||||
|
||||
cache.onValuesExpired({v1});
|
||||
CPPUNIT_ASSERT_EQUAL(0, expireCount); // RefCount dec, not removed yet
|
||||
|
||||
cache.onValuesExpired({v1});
|
||||
CPPUNIT_ASSERT_EQUAL(1, expireCount); // Removed now
|
||||
}
|
||||
|
||||
void
|
||||
OpCacheTester::testFilters() {
|
||||
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 50;
|
||||
v1->type = 1;
|
||||
|
||||
auto v2 = std::make_shared<dht::Value>();
|
||||
v2->id = 51;
|
||||
v2->type = 2;
|
||||
|
||||
cache.onValuesAdded({v1, v2});
|
||||
|
||||
auto f1 = dht::Value::TypeFilter(1);
|
||||
auto res1 = cache.get(f1);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, res1.size());
|
||||
CPPUNIT_ASSERT_EQUAL(v1->id, res1[0]->id);
|
||||
|
||||
auto f2 = dht::Value::TypeFilter(2);
|
||||
auto res2 = cache.get(f2);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, res2.size());
|
||||
CPPUNIT_ASSERT_EQUAL(v2->id, res2[0]->id);
|
||||
|
||||
auto all = cache.getValues();
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)2, all.size());
|
||||
}
|
||||
|
||||
void
|
||||
OpCacheTester::testSyncStatus() {
|
||||
dht::OpValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){ return true; });
|
||||
|
||||
// Initial state
|
||||
CPPUNIT_ASSERT(!cache.isSynced());
|
||||
|
||||
// Add a node
|
||||
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
|
||||
// 1 node, 0 synced -> not synced
|
||||
CPPUNIT_ASSERT(!cache.isSynced());
|
||||
|
||||
// Sync the node
|
||||
cache.onNodeChanged(dht::ListenSyncStatus::SYNCED);
|
||||
// 1 node, 1 synced -> synced
|
||||
CPPUNIT_ASSERT(cache.isSynced());
|
||||
|
||||
// Add another node
|
||||
cache.onNodeChanged(dht::ListenSyncStatus::ADDED);
|
||||
// 2 nodes, 1 synced -> not synced
|
||||
CPPUNIT_ASSERT(!cache.isSynced());
|
||||
|
||||
// Sync second node
|
||||
cache.onNodeChanged(dht::ListenSyncStatus::SYNCED);
|
||||
// 2 nodes, 2 synced -> synced
|
||||
CPPUNIT_ASSERT(cache.isSynced());
|
||||
|
||||
// Unsync one
|
||||
cache.onNodeChanged(dht::ListenSyncStatus::UNSYNCED);
|
||||
CPPUNIT_ASSERT(!cache.isSynced());
|
||||
|
||||
// Remove one (the unsynced one)
|
||||
cache.onNodeChanged(dht::ListenSyncStatus::REMOVED);
|
||||
// 1 node, 1 synced -> synced
|
||||
CPPUNIT_ASSERT(cache.isSynced());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: GitHub Copilot
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
namespace test {
|
||||
|
||||
class OpCacheTester : public CppUnit::TestFixture {
|
||||
CPPUNIT_TEST_SUITE(OpCacheTester);
|
||||
CPPUNIT_TEST(testUpdateRefCount);
|
||||
CPPUNIT_TEST(testBasicAddExpire);
|
||||
CPPUNIT_TEST(testMultipleSources);
|
||||
CPPUNIT_TEST(testUpdateSequence);
|
||||
CPPUNIT_TEST(testCallbacks);
|
||||
CPPUNIT_TEST(testFilters);
|
||||
CPPUNIT_TEST(testSyncStatus);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void testUpdateRefCount();
|
||||
void testBasicAddExpire();
|
||||
void testMultipleSources();
|
||||
void testUpdateSequence();
|
||||
void testCallbacks();
|
||||
void testFilters();
|
||||
void testSyncStatus();
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_peerdiscovery.h"
|
||||
#include "peerdiscoverytester.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 "test_storage.h"
|
||||
#include "storagetester.h"
|
||||
|
||||
#include <opendht/thread_pool.h>
|
||||
|
||||
File renamed without changes.
@@ -1,330 +0,0 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_dhtproxy.h"
|
||||
|
||||
// std
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace test {
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(DhtProxyTester);
|
||||
|
||||
void
|
||||
DhtProxyTester::setUp()
|
||||
{
|
||||
clientConfig.dht_config.node_config.max_peer_req_per_sec = -1;
|
||||
clientConfig.dht_config.node_config.max_req_per_sec = -1;
|
||||
|
||||
nodePeer.run(0, clientConfig);
|
||||
|
||||
nodeProxy = std::make_shared<dht::DhtRunner>();
|
||||
nodeProxy->run(0, clientConfig);
|
||||
|
||||
auto bound = nodePeer.getBound();
|
||||
if (bound.isUnspecified())
|
||||
bound.setLoopback();
|
||||
nodeProxy->bootstrap(bound);
|
||||
|
||||
// auto serverCAIdentity = dht::crypto::generateEcIdentity("DHT Node CA");
|
||||
|
||||
uint16_t port = 1024 + (std::rand() % (65535 - 1024));
|
||||
|
||||
dht::ProxyServerConfig serverConfig;
|
||||
// serverConfig.identity = dht::crypto::generateIdentity("DHT Node", serverCAIdentity);
|
||||
serverConfig.port = port;
|
||||
// serverConfig.pushServer = "127.0.0.1:8090";
|
||||
serverProxy = std::make_unique<dht::DhtProxyServer>(nodeProxy, serverConfig);
|
||||
|
||||
/*clientConfig.server_ca = serverCAIdentity.second;
|
||||
clientConfig.client_identity = dht::crypto::generateIdentity("DhtProxyTester");
|
||||
clientConfig.push_node_id = "dhtnode";*/
|
||||
clientConfig.proxy_server = "http://127.0.0.1:" + std::to_string(port); //"https://127.0.0.1:8080";
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::tearDown()
|
||||
{
|
||||
nodePeer.join();
|
||||
nodeClient.join();
|
||||
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
nodeProxy->shutdown([&] {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 15s, [&] { return done; }));
|
||||
serverProxy.reset();
|
||||
nodeProxy.reset();
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testGetPut()
|
||||
{
|
||||
nodeClient.run(0, clientConfig);
|
||||
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
|
||||
auto key = dht::InfoHash::get("GLaDOs");
|
||||
dht::Value val {"Hey! It's been a long time. How have you been?"};
|
||||
auto val_data = val.data;
|
||||
{
|
||||
nodePeer.put(key, std::move(val), [&](bool) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
}
|
||||
|
||||
auto vals = nodeClient.get(key).get();
|
||||
CPPUNIT_ASSERT(not vals.empty());
|
||||
CPPUNIT_ASSERT(vals.front()->data == val_data);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testListen()
|
||||
{
|
||||
nodeClient.run(0, clientConfig);
|
||||
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
auto key = dht::InfoHash::get("GLaDOs");
|
||||
bool done = false;
|
||||
|
||||
// If a peer send a value, the listen operation from the client
|
||||
// should retrieve this value
|
||||
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
|
||||
auto firstVal_data = firstVal.data;
|
||||
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
|
||||
CPPUNIT_ASSERT(ok);
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
|
||||
std::vector<dht::Blob> values;
|
||||
auto token = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
|
||||
if (not expired) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
for (const auto& value : v)
|
||||
values.emplace_back(value->data);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
// Here values should contains 1 values
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<int>(values.size()), 1);
|
||||
CPPUNIT_ASSERT(values.front() == firstVal_data);
|
||||
|
||||
// And the listen should retrieve futures values
|
||||
// All values
|
||||
dht::Value secondVal {"You're a monster"};
|
||||
auto secondVal_data = secondVal.data;
|
||||
nodePeer.put(key, std::move(secondVal));
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
nodeClient.cancelListen(key, std::move(token));
|
||||
// Here values should contains 2 values
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<int>(values.size()), 2);
|
||||
CPPUNIT_ASSERT(values.back() == secondVal_data);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testResubscribeGetValues()
|
||||
{
|
||||
clientConfig.push_token = "atlas";
|
||||
nodeClient.run(0, clientConfig);
|
||||
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
auto key = dht::InfoHash::get("GLaDOs");
|
||||
|
||||
// If a peer sent a value, the listen operation from the client
|
||||
// should retrieve this value
|
||||
dht::Value firstVal {"Hey! It's been a long time. How have you been?"};
|
||||
auto firstVal_data = firstVal.data;
|
||||
nodePeer.put(key, std::move(firstVal), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(ok);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
done = false;
|
||||
|
||||
// Send a first subscribe, the value is sent via a push notification
|
||||
// So ignore values here.
|
||||
nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>&, bool) { return true; });
|
||||
cv.wait_for(lk, std::chrono::seconds(1));
|
||||
|
||||
// Reboot node (to avoid cache)
|
||||
nodeClient.join();
|
||||
clientConfig.push_token = "";
|
||||
nodeClient.run(0, clientConfig);
|
||||
|
||||
// For the second subscribe, the proxy will return the value in the body
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
auto ftoken = nodeClient.listen(key, [&](const std::vector<std::shared_ptr<dht::Value>>& v, bool expired) {
|
||||
if (not expired) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
values.insert(values.end(), v.begin(), v.end());
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
auto token = ftoken.get();
|
||||
CPPUNIT_ASSERT(token);
|
||||
nodeClient.cancelListen(key, token);
|
||||
// Here values should still contains 1 values
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
|
||||
CPPUNIT_ASSERT(firstVal_data == values.front()->data);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testPutGet40KChars()
|
||||
{
|
||||
nodeClient.run(0, clientConfig);
|
||||
constexpr size_t N = 40000;
|
||||
|
||||
// Arrange
|
||||
auto key = dht::InfoHash::get("testPutGet40KChars");
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
std::vector<uint8_t> mtu;
|
||||
mtu.reserve(N);
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mtu.emplace_back((i % 2) ? 'T' : 'M');
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
bool done_put = false;
|
||||
bool done_get = false;
|
||||
|
||||
// Act
|
||||
dht::Value val {mtu};
|
||||
nodePeer.put(key, std::move(val), [&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done_put = ok;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done_put; }));
|
||||
|
||||
nodeClient.get(
|
||||
key,
|
||||
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
|
||||
values.insert(values.end(), vals.begin(), vals.end());
|
||||
return true;
|
||||
},
|
||||
[&](bool ok) {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done_get = ok;
|
||||
cv.notify_all();
|
||||
});
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done_get; }));
|
||||
|
||||
// Assert
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 1u, values.size());
|
||||
for (const auto& value : values)
|
||||
CPPUNIT_ASSERT(value->data == mtu);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testFuzzy()
|
||||
{
|
||||
constexpr size_t N = 40000;
|
||||
|
||||
// Arrange
|
||||
auto key = dht::InfoHash::get("testFuzzy");
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
std::vector<uint8_t> mtu;
|
||||
mtu.reserve(N);
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mtu.emplace_back((i % 2) ? 'T' : 'M');
|
||||
|
||||
// Act
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
auto nodeTest = std::make_shared<dht::DhtRunner>();
|
||||
nodeTest->run(0, clientConfig);
|
||||
nodeTest->put(key, dht::Value(mtu), [&](bool ok) { CPPUNIT_ASSERT(ok); });
|
||||
nodeTest->get(
|
||||
key,
|
||||
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
|
||||
values.insert(values.end(), vals.begin(), vals.end());
|
||||
return true;
|
||||
},
|
||||
[&](bool ok) { CPPUNIT_ASSERT(ok); });
|
||||
std::this_thread::sleep_for(5ms);
|
||||
}
|
||||
|
||||
// Assert
|
||||
for (const auto& value : values)
|
||||
CPPUNIT_ASSERT(value->data == mtu);
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyTester::testShutdownStop()
|
||||
{
|
||||
constexpr size_t N = 40000;
|
||||
constexpr unsigned C = 100;
|
||||
|
||||
// Arrange
|
||||
auto key = dht::InfoHash::get("testShutdownStop");
|
||||
std::vector<std::shared_ptr<dht::Value>> values;
|
||||
std::vector<uint8_t> mtu;
|
||||
mtu.reserve(N);
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mtu.emplace_back((i % 2) ? 'T' : 'M');
|
||||
|
||||
std::atomic_uint callback_count {0};
|
||||
|
||||
// Act
|
||||
for (size_t i = 0; i < C; i++) {
|
||||
auto nodeTest = std::make_shared<dht::DhtRunner>();
|
||||
nodeTest->run(0, clientConfig);
|
||||
nodeTest->put(key, dht::Value(mtu), [&](bool /*ok*/) { callback_count++; });
|
||||
nodeTest->get(
|
||||
key,
|
||||
[&](const std::vector<std::shared_ptr<dht::Value>>& vals) {
|
||||
values.insert(values.end(), vals.begin(), vals.end());
|
||||
return true;
|
||||
},
|
||||
[&](bool /*ok*/) { callback_count++; });
|
||||
bool done = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
nodeTest->shutdown(
|
||||
[&] {
|
||||
std::lock_guard<std::mutex> lk(cv_m);
|
||||
done = true;
|
||||
cv.notify_all();
|
||||
},
|
||||
true);
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return done; }));
|
||||
}
|
||||
CPPUNIT_ASSERT_EQUAL(2 * C, callback_count.load());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -1,93 +0,0 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_dhtproxy_client.h"
|
||||
|
||||
#include <any>
|
||||
#include <mutex>
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wkeyword-macro"
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#endif
|
||||
#define private public
|
||||
#include "opendht/http.h"
|
||||
#include "opendht/dht_proxy_client.h"
|
||||
#undef private
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include "../src/op_cache.cpp"
|
||||
#include "../src/dht_proxy_client.cpp"
|
||||
|
||||
namespace test {
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(DhtProxyClientTester);
|
||||
|
||||
using namespace dht;
|
||||
|
||||
void
|
||||
DhtProxyClientTester::testResubscribeUsesKeyRoute()
|
||||
{
|
||||
#ifdef OPENDHT_PUSH_NOTIFICATIONS
|
||||
DhtProxyClient client({}, {}, [] {}, "http://127.0.0.1:8080", "OpenDHT-Test", "client-id", "push-token");
|
||||
|
||||
DhtProxyClient::Listener listener {OpValueCache([](const std::vector<Sp<Value>>&, bool) { return true; })};
|
||||
listener.opstate = std::make_shared<DhtProxyClient::OperationState>();
|
||||
listener.cb = [](const std::vector<Sp<Value>>&, bool, system_clock::time_point) {
|
||||
return true;
|
||||
};
|
||||
|
||||
auto key = InfoHash::get("proxy-client-resubscribe");
|
||||
client.resubscribe(key, 1, listener);
|
||||
|
||||
CPPUNIT_ASSERT(listener.request);
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("/key/") + key.toString(), std::string(listener.request->header_.request_target()));
|
||||
CPPUNIT_ASSERT(listener.request->body_.find("\"refresh\"") != std::string::npos);
|
||||
CPPUNIT_ASSERT(listener.request->body_.find("true") != std::string::npos);
|
||||
#else
|
||||
CPPUNIT_ASSERT(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DhtProxyClientTester::testSetPushNotificationTokenResubscribesWithNewToken()
|
||||
{
|
||||
#ifdef OPENDHT_PUSH_NOTIFICATIONS
|
||||
DhtProxyClient client({}, {}, [] {}, "http://127.0.0.1:8080", "OpenDHT-Test", "client-id", "old-token");
|
||||
|
||||
client.statusIpv4_ = NodeStatus::Connected;
|
||||
|
||||
auto key = InfoHash::get("proxy-client-token-rotation");
|
||||
auto& search = client.searches_[key];
|
||||
auto [it, inserted] = search.listeners.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(1),
|
||||
std::forward_as_tuple(OpValueCache(
|
||||
[](const std::vector<Sp<Value>>&, bool) { return true; })));
|
||||
CPPUNIT_ASSERT(inserted);
|
||||
|
||||
auto& listener = it->second;
|
||||
listener.opstate = std::make_shared<DhtProxyClient::OperationState>();
|
||||
listener.cb = [](const std::vector<Sp<Value>>&, bool, system_clock::time_point) {
|
||||
return true;
|
||||
};
|
||||
|
||||
client.setPushNotificationToken("new-token");
|
||||
|
||||
CPPUNIT_ASSERT(listener.request);
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("/key/") + key.toString(), std::string(listener.request->header_.request_target()));
|
||||
CPPUNIT_ASSERT(listener.request->body_.find("new-token") != std::string::npos);
|
||||
CPPUNIT_ASSERT(listener.request->body_.find("old-token") == std::string::npos);
|
||||
CPPUNIT_ASSERT(listener.request->body_.find("\"refresh\"") != std::string::npos);
|
||||
CPPUNIT_ASSERT(listener.request->body_.find("true") != std::string::npos);
|
||||
#else
|
||||
CPPUNIT_ASSERT(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
namespace test {
|
||||
|
||||
class DhtProxyClientTester : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(DhtProxyClientTester);
|
||||
CPPUNIT_TEST(testResubscribeUsesKeyRoute);
|
||||
CPPUNIT_TEST(testSetPushNotificationTokenResubscribesWithNewToken);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void testResubscribeUsesKeyRoute();
|
||||
void testSetPushNotificationTokenResubscribesWithNewToken();
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
@@ -1,549 +0,0 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_networkengine.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
namespace test {
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(NetworkEngineTester);
|
||||
|
||||
void
|
||||
NetworkEngineTester::setUp()
|
||||
{}
|
||||
|
||||
void
|
||||
NetworkEngineTester::tearDown()
|
||||
{}
|
||||
|
||||
void
|
||||
NetworkEngineTester::testDisabledOnMsvc()
|
||||
{
|
||||
CPPUNIT_ASSERT(true);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
#else
|
||||
|
||||
#include <any>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef opendht_EXPORTS
|
||||
#undef opendht_EXPORTS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "opendht/node.h"
|
||||
#include "opendht/network_engine.h"
|
||||
#include "opendht/network_utils.h"
|
||||
#include "opendht/utils.h"
|
||||
#include "opendht/value.h"
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wkeyword-macro"
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#endif
|
||||
#define private public
|
||||
#include "opendht/dht.h"
|
||||
#undef private
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include "../src/parsed_message.h"
|
||||
#include "../src/search.h"
|
||||
|
||||
// Hack to test internal classes
|
||||
#include "../src/op_cache.cpp"
|
||||
#include "../src/routing_table.cpp"
|
||||
#include "../src/dht.cpp"
|
||||
#include "../src/node.cpp"
|
||||
#include "../src/node_cache.cpp"
|
||||
#include "../src/network_engine.cpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace test {
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(NetworkEngineTester);
|
||||
|
||||
using namespace dht;
|
||||
using namespace dht::net;
|
||||
|
||||
static constexpr size_t TEST_MTU {1280};
|
||||
|
||||
class TestDatagramSocket : public net::DatagramSocket
|
||||
{
|
||||
public:
|
||||
TestDatagramSocket()
|
||||
{
|
||||
bound_.setFamily(AF_INET);
|
||||
bound_.setLoopback();
|
||||
bound_.setPort(4222);
|
||||
}
|
||||
|
||||
int sendTo(const SockAddr& dest, const uint8_t* data, size_t size, bool replied) override
|
||||
{
|
||||
sends.emplace_back(SentPacket {dest, Blob(data, data + size), replied});
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool hasIPv4() const override { return true; }
|
||||
bool hasIPv6() const override { return false; }
|
||||
const SockAddr& getBoundRef(sa_family_t family = AF_UNSPEC) const override
|
||||
{
|
||||
(void) family;
|
||||
return bound_;
|
||||
}
|
||||
void stop() override {}
|
||||
|
||||
struct SentPacket
|
||||
{
|
||||
SockAddr dest;
|
||||
Blob data;
|
||||
bool replied;
|
||||
};
|
||||
|
||||
SockAddr bound_;
|
||||
std::vector<SentPacket> sends {};
|
||||
};
|
||||
|
||||
static Blob
|
||||
serializeValue(const std::string& data)
|
||||
{
|
||||
auto val = std::make_shared<Value>((const uint8_t*) data.data(), data.size());
|
||||
return packMsg(val);
|
||||
}
|
||||
|
||||
static Blob
|
||||
makeReplyHeaderPacketBlob(const InfoHash& id, Tid tid, const std::vector<size_t>& valueSizes)
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
|
||||
pk.pack_map(3);
|
||||
pk.pack(KEY_R);
|
||||
pk.pack_map(2);
|
||||
pk.pack(KEY_REQ_ID);
|
||||
pk.pack(id);
|
||||
pk.pack(KEY_REQ_VALUES);
|
||||
pk.pack_array(valueSizes.size());
|
||||
for (const auto& size : valueSizes)
|
||||
pk.pack(size);
|
||||
pk.pack(KEY_TID);
|
||||
pk.pack(tid);
|
||||
pk.pack(KEY_Y);
|
||||
pk.pack(KEY_R);
|
||||
|
||||
return {buffer.data(), buffer.data() + buffer.size()};
|
||||
}
|
||||
|
||||
static Blob
|
||||
makeValueDataPacketBlob(Tid tid, unsigned index, size_t offset, const Blob& data)
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
|
||||
pk.pack_map(3);
|
||||
pk.pack(KEY_Y);
|
||||
pk.pack(KEY_V);
|
||||
pk.pack(KEY_TID);
|
||||
pk.pack(tid);
|
||||
pk.pack(KEY_V);
|
||||
pk.pack_map(1);
|
||||
pk.pack(index);
|
||||
pk.pack_map(2);
|
||||
pk.pack("o");
|
||||
pk.pack(offset);
|
||||
pk.pack("d");
|
||||
pk.pack_bin(data.size());
|
||||
pk.pack_bin_body((const char*) data.data(), data.size());
|
||||
|
||||
return {buffer.data(), buffer.data() + buffer.size()};
|
||||
}
|
||||
|
||||
static Blob
|
||||
makeListenPacketBlob(const InfoHash& id, const InfoHash& hash, Tid tid, Tid socketId, const Blob& token)
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
|
||||
pk.pack_map(4);
|
||||
pk.pack(KEY_A);
|
||||
pk.pack_map(5);
|
||||
pk.pack(KEY_REQ_ID);
|
||||
pk.pack(id);
|
||||
pk.pack(KEY_VERSION);
|
||||
pk.pack(1);
|
||||
pk.pack(KEY_REQ_H);
|
||||
pk.pack(hash);
|
||||
pk.pack(KEY_REQ_TOKEN);
|
||||
packToken(pk, token);
|
||||
pk.pack(KEY_REQ_SID);
|
||||
pk.pack(socketId);
|
||||
pk.pack(KEY_Q);
|
||||
pk.pack(QUERY_LISTEN);
|
||||
pk.pack(KEY_TID);
|
||||
pk.pack(tid);
|
||||
pk.pack(KEY_Y);
|
||||
pk.pack(KEY_Q);
|
||||
|
||||
return {buffer.data(), buffer.data() + buffer.size()};
|
||||
}
|
||||
|
||||
static SockAddr
|
||||
makeIPv4(const char* ip, in_port_t port)
|
||||
{
|
||||
SockAddr addr;
|
||||
addr.setFamily(AF_INET);
|
||||
addr.setAddress(ip);
|
||||
addr.setPort(port);
|
||||
return addr;
|
||||
}
|
||||
|
||||
static net::NetworkEngine
|
||||
makeEngine(std::unique_ptr<TestDatagramSocket>&& socket,
|
||||
Scheduler& scheduler,
|
||||
InfoHash& myid,
|
||||
std::mt19937_64& rd,
|
||||
int& onNewNodeCalls)
|
||||
{
|
||||
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
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
namespace test {
|
||||
|
||||
class NetworkEngineTester : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(NetworkEngineTester);
|
||||
#ifdef _MSC_VER
|
||||
CPPUNIT_TEST(testDisabledOnMsvc);
|
||||
#else
|
||||
CPPUNIT_TEST(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
|
||||
@@ -1,540 +0,0 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_parsedmessage.h"
|
||||
|
||||
#include "opendht/value.h"
|
||||
#include "opendht/node.h"
|
||||
#include "opendht/utils.h"
|
||||
|
||||
#include "../src/parsed_message.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
namespace test {
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(ParsedMessageTester);
|
||||
|
||||
using namespace dht;
|
||||
using namespace dht::net;
|
||||
|
||||
static constexpr size_t TEST_MTU {1280};
|
||||
|
||||
// Helper: create a ParsedMessage with a single value_parts entry of given total_size
|
||||
static ParsedMessage
|
||||
makeHeader(unsigned index, size_t total_size)
|
||||
{
|
||||
ParsedMessage msg;
|
||||
msg.value_parts[index].total_size = total_size;
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Helper: create a ParsedMessage fragment (as if parsed from ValueData)
|
||||
static ParsedMessage
|
||||
makeFragment(unsigned index, size_t offset, const Blob& data)
|
||||
{
|
||||
ParsedMessage frag;
|
||||
frag.fragment_parts[index] = {offset, data};
|
||||
return frag;
|
||||
}
|
||||
|
||||
// Helper: serialize a Value to a Blob
|
||||
static Blob
|
||||
serializeValue(const std::string& data)
|
||||
{
|
||||
auto val = std::make_shared<Value>((const uint8_t*) data.data(), data.size());
|
||||
return packMsg(val);
|
||||
}
|
||||
|
||||
static ParsedMessage
|
||||
parsePackedMessage(msgpack::sbuffer& buffer)
|
||||
{
|
||||
auto msg = msgpack::unpack(buffer.data(), buffer.size());
|
||||
ParsedMessage parsed;
|
||||
parsed.msgpack_unpack(msg.get());
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static ParsedMessage
|
||||
makeReplyHeaderPacket(Tid tid, const std::vector<size_t>& valueSizes)
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
|
||||
pk.pack_map(3);
|
||||
pk.pack(KEY_R);
|
||||
pk.pack_map(1);
|
||||
pk.pack(KEY_REQ_VALUES);
|
||||
pk.pack_array(valueSizes.size());
|
||||
for (const auto& size : valueSizes)
|
||||
pk.pack(size);
|
||||
pk.pack(KEY_TID);
|
||||
pk.pack(tid);
|
||||
pk.pack(KEY_Y);
|
||||
pk.pack(KEY_R);
|
||||
|
||||
return parsePackedMessage(buffer);
|
||||
}
|
||||
|
||||
static ParsedMessage
|
||||
makeValueDataPacket(Tid tid, unsigned index, size_t offset, const Blob& data)
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
|
||||
pk.pack_map(3);
|
||||
pk.pack(KEY_Y);
|
||||
pk.pack(KEY_V);
|
||||
pk.pack(KEY_TID);
|
||||
pk.pack(tid);
|
||||
pk.pack(KEY_V);
|
||||
pk.pack_map(1);
|
||||
pk.pack(index);
|
||||
pk.pack_map(2);
|
||||
pk.pack("o");
|
||||
pk.pack(offset);
|
||||
pk.pack("d");
|
||||
pk.pack_bin(data.size());
|
||||
pk.pack_bin_body((const char*) data.data(), data.size());
|
||||
|
||||
return parsePackedMessage(buffer);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::setUp()
|
||||
{}
|
||||
|
||||
void
|
||||
ParsedMessageTester::tearDown()
|
||||
{}
|
||||
|
||||
// --- append tests ---
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendInOrder()
|
||||
{
|
||||
Blob full = {1, 2, 3, 4, 5, 6};
|
||||
auto msg = makeHeader(0, full.size());
|
||||
|
||||
// Send first half, then second half
|
||||
Blob part1(full.begin(), full.begin() + 3);
|
||||
Blob part2(full.begin() + 3, full.end());
|
||||
|
||||
auto frag1 = makeFragment(0, 0, part1);
|
||||
auto frag2 = makeFragment(0, 3, part2);
|
||||
|
||||
CPPUNIT_ASSERT(msg.append(frag1));
|
||||
CPPUNIT_ASSERT(msg.append(frag2));
|
||||
|
||||
auto& pv = msg.value_parts[0];
|
||||
CPPUNIT_ASSERT(pv.isComplete());
|
||||
CPPUNIT_ASSERT_EQUAL(full.size(), pv.received_bytes);
|
||||
|
||||
auto reassembled = pv.reassemble();
|
||||
CPPUNIT_ASSERT(reassembled == full);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendOutOfOrder()
|
||||
{
|
||||
Blob full = {10, 20, 30, 40, 50, 60};
|
||||
auto msg = makeHeader(0, full.size());
|
||||
|
||||
Blob part1(full.begin(), full.begin() + 2); // [0..2)
|
||||
Blob part2(full.begin() + 2, full.begin() + 4); // [2..4)
|
||||
Blob part3(full.begin() + 4, full.end()); // [4..6)
|
||||
|
||||
// Send in order: part3, part1, part2
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, 4, part3)));
|
||||
CPPUNIT_ASSERT(!msg.value_parts[0].isComplete());
|
||||
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, part1)));
|
||||
CPPUNIT_ASSERT(!msg.value_parts[0].isComplete());
|
||||
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, 2, part2)));
|
||||
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
|
||||
|
||||
auto reassembled = msg.value_parts[0].reassemble();
|
||||
CPPUNIT_ASSERT(reassembled == full);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendReverseOrder()
|
||||
{
|
||||
Blob full = {1, 2, 3, 4, 5, 6, 7, 8};
|
||||
auto msg = makeHeader(0, full.size());
|
||||
|
||||
// 4 fragments of 2 bytes each, sent in reverse
|
||||
for (int i = 3; i >= 0; --i) {
|
||||
size_t offset = i * 2;
|
||||
Blob part(full.begin() + offset, full.begin() + offset + 2);
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, offset, part)));
|
||||
}
|
||||
|
||||
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
|
||||
CPPUNIT_ASSERT(msg.value_parts[0].reassemble() == full);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendDuplicateFragment()
|
||||
{
|
||||
Blob full = {1, 2, 3, 4};
|
||||
auto msg = makeHeader(0, full.size());
|
||||
|
||||
Blob part1(full.begin(), full.begin() + 2);
|
||||
Blob part2(full.begin() + 2, full.end());
|
||||
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, part1)));
|
||||
// Duplicate of first fragment should be rejected
|
||||
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 0, part1)));
|
||||
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, 2, part2)));
|
||||
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 4, msg.value_parts[0].received_bytes);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendOverlappingFragments()
|
||||
{
|
||||
Blob full = {1, 2, 3, 4, 5, 6};
|
||||
auto msg = makeHeader(0, full.size());
|
||||
|
||||
// Fragment [0..3)
|
||||
Blob part1(full.begin(), full.begin() + 3);
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, part1)));
|
||||
|
||||
// Overlapping fragment [2..5) should be rejected
|
||||
Blob overlap(full.begin() + 2, full.begin() + 5);
|
||||
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 2, overlap)));
|
||||
|
||||
// Non-overlapping fragment [3..6) should succeed
|
||||
Blob part2(full.begin() + 3, full.end());
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, 3, part2)));
|
||||
|
||||
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
|
||||
CPPUNIT_ASSERT(msg.value_parts[0].reassemble() == full);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendFragmentBeyondTotal()
|
||||
{
|
||||
auto msg = makeHeader(0, 4);
|
||||
|
||||
// Fragment at offset 3 with 2 bytes goes beyond total_size of 4
|
||||
Blob data = {1, 2};
|
||||
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 3, data)));
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 0, msg.value_parts[0].received_bytes);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendEmptyFragment()
|
||||
{
|
||||
auto msg = makeHeader(0, 4);
|
||||
|
||||
Blob empty;
|
||||
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 0, empty)));
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 0, msg.value_parts[0].received_bytes);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendUnknownIndex()
|
||||
{
|
||||
auto msg = makeHeader(0, 10);
|
||||
|
||||
// Fragment for index 1 which doesn't exist in value_parts
|
||||
Blob data = {1, 2, 3};
|
||||
CPPUNIT_ASSERT(!msg.append(makeFragment(1, 0, data)));
|
||||
// Index 0 should be untouched
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 0, msg.value_parts[0].received_bytes);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendAlreadyComplete()
|
||||
{
|
||||
Blob full = {1, 2, 3, 4};
|
||||
auto msg = makeHeader(0, full.size());
|
||||
|
||||
// Complete the value
|
||||
CPPUNIT_ASSERT(msg.append(makeFragment(0, 0, full)));
|
||||
CPPUNIT_ASSERT(msg.value_parts[0].isComplete());
|
||||
|
||||
// Further fragment should be rejected
|
||||
Blob extra = {5, 6};
|
||||
CPPUNIT_ASSERT(!msg.append(makeFragment(0, 0, extra)));
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testAppendOverflowingOffset()
|
||||
{
|
||||
auto msg = makeHeader(0, 4);
|
||||
|
||||
Blob data = {1};
|
||||
CPPUNIT_ASSERT(!msg.append(makeFragment(0, std::numeric_limits<size_t>::max(), data)));
|
||||
CPPUNIT_ASSERT_EQUAL((size_t) 0, msg.value_parts[0].received_bytes);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testParseRejectsNonMapPacket()
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
pk.pack_array(1);
|
||||
pk.pack(42);
|
||||
|
||||
auto msg = msgpack::unpack(buffer.data(), buffer.size());
|
||||
ParsedMessage parsed;
|
||||
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testParseRejectsInvalidValueDataPayload()
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
|
||||
pk.pack_map(3);
|
||||
pk.pack(KEY_Y);
|
||||
pk.pack(KEY_V);
|
||||
pk.pack(KEY_TID);
|
||||
pk.pack(1u);
|
||||
pk.pack(KEY_V);
|
||||
pk.pack_array(1);
|
||||
pk.pack(42);
|
||||
|
||||
auto msg = msgpack::unpack(buffer.data(), buffer.size());
|
||||
ParsedMessage parsed;
|
||||
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::testParseRejectsInvalidValuesField()
|
||||
{
|
||||
msgpack::sbuffer buffer;
|
||||
msgpack::packer<msgpack::sbuffer> pk(&buffer);
|
||||
|
||||
pk.pack_map(3);
|
||||
pk.pack(KEY_R);
|
||||
pk.pack_map(1);
|
||||
pk.pack(KEY_REQ_VALUES);
|
||||
pk.pack("invalid");
|
||||
pk.pack(KEY_TID);
|
||||
pk.pack(1u);
|
||||
pk.pack(KEY_Y);
|
||||
pk.pack(KEY_R);
|
||||
|
||||
auto msg = msgpack::unpack(buffer.data(), buffer.size());
|
||||
ParsedMessage parsed;
|
||||
CPPUNIT_ASSERT_THROW(parsed.msgpack_unpack(msg.get()), msgpack::type_error);
|
||||
}
|
||||
|
||||
void
|
||||
ParsedMessageTester::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
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
// cppunit
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
namespace test {
|
||||
|
||||
class ParsedMessageTester : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(ParsedMessageTester);
|
||||
CPPUNIT_TEST(testAppendInOrder);
|
||||
CPPUNIT_TEST(testAppendOutOfOrder);
|
||||
CPPUNIT_TEST(testAppendReverseOrder);
|
||||
CPPUNIT_TEST(testAppendDuplicateFragment);
|
||||
CPPUNIT_TEST(testAppendOverlappingFragments);
|
||||
CPPUNIT_TEST(testAppendFragmentBeyondTotal);
|
||||
CPPUNIT_TEST(testAppendEmptyFragment);
|
||||
CPPUNIT_TEST(testAppendUnknownIndex);
|
||||
CPPUNIT_TEST(testAppendAlreadyComplete);
|
||||
CPPUNIT_TEST(testAppendOverflowingOffset);
|
||||
CPPUNIT_TEST(testParseRejectsNonMapPacket);
|
||||
CPPUNIT_TEST(testParseRejectsInvalidValueDataPayload);
|
||||
CPPUNIT_TEST(testParseRejectsInvalidValuesField);
|
||||
CPPUNIT_TEST(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
|
||||
+2
-26
@@ -5,29 +5,12 @@
|
||||
#include <cppunit/CompilerOutputter.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
listTests(CppUnit::Test* test, const std::string& prefix = {})
|
||||
{
|
||||
const auto name = test->getName();
|
||||
const auto fullName = prefix.empty() ? name : prefix + "/" + name;
|
||||
if (test->getChildTestCount() == 0) {
|
||||
std::cout << fullName << std::endl;
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < test->getChildTestCount(); ++index)
|
||||
listTests(test->getChildTestAt(index), fullName);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
#include <gnutls/gnutls.h>
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
main(int /*argc*/, char** /*argv*/)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
if (auto err = gnutls_global_init()) {
|
||||
@@ -42,15 +25,8 @@ main(int argc, char** argv)
|
||||
std::cout << "No test cases specified for suite" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (argc > 1 && std::string(argv[1]) == "--list") {
|
||||
listTests(suite);
|
||||
return 0;
|
||||
}
|
||||
CppUnit::TextUi::TestRunner runner;
|
||||
if (argc > 1)
|
||||
runner.addTest(suite->findTest(argv[1]));
|
||||
else
|
||||
runner.addTest(suite);
|
||||
runner.addTest(suite);
|
||||
auto result = runner.run() ? 0 : 1;
|
||||
return result;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_threadpool.h"
|
||||
#include "threadpooltester.h"
|
||||
|
||||
#include "opendht/thread_pool.h"
|
||||
#include <atomic>
|
||||
File renamed without changes.
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: GitHub Copilot
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "valuecachetester.h"
|
||||
#include "../src/value_cache.h"
|
||||
|
||||
namespace test {
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(ValueCacheTester);
|
||||
|
||||
void
|
||||
ValueCacheTester::setUp() {
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::tearDown() {
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::testUpdate() {
|
||||
int addCount = 0;
|
||||
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired){
|
||||
if (!expired) addCount += vals.size();
|
||||
});
|
||||
|
||||
dht::TypeStore types;
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 1;
|
||||
v1->seq = 1;
|
||||
v1->data = {'A'};
|
||||
|
||||
auto v2 = std::make_shared<dht::Value>();
|
||||
v2->id = 1;
|
||||
v2->seq = 2;
|
||||
v2->data = {'B'};
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// Add v1
|
||||
cache.onValues({v1}, {}, {}, types, now);
|
||||
CPPUNIT_ASSERT_EQUAL(1, addCount);
|
||||
|
||||
// Update v1 -> v2
|
||||
cache.onValues({v2}, {}, {}, types, now);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("Callback should be called for update", 2, addCount);
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::testUpdateExpiration() {
|
||||
int addCount = 0;
|
||||
int expireCount = 0;
|
||||
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired){
|
||||
if (expired) expireCount += vals.size();
|
||||
else addCount += vals.size();
|
||||
});
|
||||
|
||||
dht::TypeStore types;
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 1;
|
||||
v1->seq = 1;
|
||||
v1->data = {'A'};
|
||||
|
||||
auto v2 = std::make_shared<dht::Value>();
|
||||
v2->id = 1;
|
||||
v2->seq = 2;
|
||||
v2->data = {'B'};
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// Add v1
|
||||
cache.onValues({v1}, {}, {}, types, now);
|
||||
CPPUNIT_ASSERT_EQUAL(1, addCount);
|
||||
CPPUNIT_ASSERT_EQUAL(0, expireCount);
|
||||
|
||||
// Update v1 -> v2
|
||||
cache.onValues({v2}, {}, {}, types, now);
|
||||
CPPUNIT_ASSERT_EQUAL(2, addCount);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("Old value should be expired on update", 1, expireCount);
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::testExpiration() {
|
||||
int expireCount = 0;
|
||||
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired){
|
||||
if (expired) expireCount += vals.size();
|
||||
});
|
||||
|
||||
dht::TypeStore types;
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 10;
|
||||
v1->data = {'A'};
|
||||
|
||||
// Default expiration is 10 minutes
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
cache.onValues({v1}, {}, {}, types, now);
|
||||
|
||||
// Advance time past expiration
|
||||
auto future = now + std::chrono::minutes(11);
|
||||
|
||||
// expireValues returns next expiration time
|
||||
cache.expireValues(future);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(1, expireCount);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)0, cache.size());
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::testRefresh() {
|
||||
int expireCount = 0;
|
||||
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired){
|
||||
if (expired) expireCount += vals.size();
|
||||
});
|
||||
|
||||
dht::TypeStore types;
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 20;
|
||||
v1->data = {'B'};
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
cache.onValues({v1}, {}, {}, types, now);
|
||||
|
||||
// Refresh at +5 mins
|
||||
auto later = now + std::chrono::minutes(5);
|
||||
cache.onValues({}, {v1->id}, {}, types, later);
|
||||
|
||||
// Should not expire at +11 mins (original expiration was +10, so +10 from start)
|
||||
// But refresh should extend it to +10 from 'later' = +15 from start.
|
||||
|
||||
auto checkTime = now + std::chrono::minutes(11);
|
||||
cache.expireValues(checkTime);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(0, expireCount);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, cache.size());
|
||||
|
||||
// Should expire at +16 mins
|
||||
auto future = now + std::chrono::minutes(16);
|
||||
cache.expireValues(future);
|
||||
CPPUNIT_ASSERT_EQUAL(1, expireCount);
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::testExplicitExpiration() {
|
||||
int expireCount = 0;
|
||||
dht::ValueCache cache([&](const std::vector<std::shared_ptr<dht::Value>>& vals, bool expired){
|
||||
if (expired) expireCount += vals.size();
|
||||
});
|
||||
|
||||
dht::TypeStore types;
|
||||
auto v1 = std::make_shared<dht::Value>();
|
||||
v1->id = 30;
|
||||
v1->data = {'C'};
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
cache.onValues({v1}, {}, {}, types, now);
|
||||
|
||||
cache.onValues({}, {}, {v1->id}, types, now);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(1, expireCount);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)0, cache.size());
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::testClear() {
|
||||
dht::ValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){});
|
||||
dht::TypeStore types;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
auto v1 = std::make_shared<dht::Value>(); v1->id = 1;
|
||||
auto v2 = std::make_shared<dht::Value>(); v2->id = 2;
|
||||
|
||||
cache.onValues({v1, v2}, {}, {}, types, now);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)2, cache.size());
|
||||
|
||||
auto cbs = cache.clear();
|
||||
// Execute callbacks
|
||||
for (auto& cb : cbs) cb();
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)0, cache.size());
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::testSyncStatus() {
|
||||
std::vector<dht::ListenSyncStatus> statuses;
|
||||
{
|
||||
dht::ValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){}, [&](dht::ListenSyncStatus status){
|
||||
statuses.push_back(status);
|
||||
});
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)1, statuses.size());
|
||||
CPPUNIT_ASSERT(statuses.back() == dht::ListenSyncStatus::ADDED);
|
||||
|
||||
cache.onSynced(true);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)2, statuses.size());
|
||||
CPPUNIT_ASSERT(statuses.back() == dht::ListenSyncStatus::SYNCED);
|
||||
|
||||
// Calling onSynced(true) again should not trigger the callback
|
||||
cache.onSynced(true);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)2, statuses.size());
|
||||
|
||||
cache.onSynced(false);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)3, statuses.size());
|
||||
CPPUNIT_ASSERT(statuses.back() == dht::ListenSyncStatus::UNSYNCED);
|
||||
|
||||
// Calling onSynced(false) again should not trigger the callback
|
||||
cache.onSynced(false);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)3, statuses.size());
|
||||
|
||||
// Set to synced before destruction to test UNSYNCED on destruction
|
||||
cache.onSynced(true);
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)4, statuses.size());
|
||||
CPPUNIT_ASSERT(statuses.back() == dht::ListenSyncStatus::SYNCED);
|
||||
}
|
||||
|
||||
// Destructor should have been called
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)6, statuses.size());
|
||||
CPPUNIT_ASSERT(statuses[4] == dht::ListenSyncStatus::UNSYNCED);
|
||||
CPPUNIT_ASSERT(statuses[5] == dht::ListenSyncStatus::REMOVED);
|
||||
}
|
||||
|
||||
void
|
||||
ValueCacheTester::testMaxValues() {
|
||||
dht::ValueCache cache([](const std::vector<std::shared_ptr<dht::Value>>&, bool){});
|
||||
dht::TypeStore types;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// MAX_VALUES is 4096
|
||||
for (int i = 0; i < 4100; ++i) {
|
||||
auto v = std::make_shared<dht::Value>();
|
||||
v->id = (dht::Value::Id)i + 1;
|
||||
cache.onValues({v}, {}, {}, types, now + std::chrono::milliseconds(i));
|
||||
}
|
||||
|
||||
// Should be capped at 4096
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)4096, cache.size());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: GitHub Copilot
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
namespace test {
|
||||
|
||||
class ValueCacheTester : public CppUnit::TestFixture {
|
||||
CPPUNIT_TEST_SUITE(ValueCacheTester);
|
||||
CPPUNIT_TEST(testUpdate);
|
||||
CPPUNIT_TEST(testUpdateExpiration);
|
||||
CPPUNIT_TEST(testExpiration);
|
||||
CPPUNIT_TEST(testRefresh);
|
||||
CPPUNIT_TEST(testExplicitExpiration);
|
||||
CPPUNIT_TEST(testClear);
|
||||
CPPUNIT_TEST(testSyncStatus);
|
||||
CPPUNIT_TEST(testMaxValues);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void testUpdate();
|
||||
void testUpdateExpiration();
|
||||
void testExpiration();
|
||||
void testRefresh();
|
||||
void testExplicitExpiration();
|
||||
void testClear();
|
||||
void testSyncStatus();
|
||||
void testMaxValues();
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "test_value.h"
|
||||
#include "valuetester.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
File renamed without changes.
@@ -0,0 +1,20 @@
|
||||
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
|
||||
+13
-19
@@ -12,10 +12,10 @@ extern "C" {
|
||||
}
|
||||
#include <ctime>
|
||||
|
||||
namespace dht {
|
||||
namespace tools {
|
||||
using namespace dht;
|
||||
|
||||
static std::mt19937_64 rd {dht::crypto::getSeededRandomEngine<std::mt19937_64>()};
|
||||
static std::uniform_int_distribution<dht::Value::Id> rand_id;
|
||||
|
||||
const std::string
|
||||
printTime(const std::time_t& now)
|
||||
@@ -34,18 +34,12 @@ print_usage()
|
||||
std::cout << "Report bugs to: https://opendht.net" << std::endl;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
} // namespace dht
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
using namespace dht;
|
||||
using IdDist = std::uniform_int_distribution<dht::Value::Id>;
|
||||
|
||||
auto params = tools::parseArgs(argc, argv);
|
||||
auto params = parseArgs(argc, argv);
|
||||
if (params.help) {
|
||||
tools::print_usage();
|
||||
print_usage();
|
||||
return 0;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
@@ -64,14 +58,14 @@ main(int argc, char** argv)
|
||||
if (not params.bootstrap.empty())
|
||||
dht.bootstrap(params.bootstrap);
|
||||
|
||||
tools::print_node_info(dht.getNodeInfo());
|
||||
print_node_info(dht.getNodeInfo());
|
||||
std::cout << " type 'c {hash}' to join a channel" << std::endl << std::endl;
|
||||
|
||||
bool connected {false};
|
||||
InfoHash room;
|
||||
std::future<size_t> token;
|
||||
|
||||
const auto mypk = dht.getPublicKey();
|
||||
const InfoHash myid = dht.getId();
|
||||
|
||||
#ifndef _MSC_VER
|
||||
// using the GNU History API
|
||||
@@ -80,7 +74,7 @@ main(int argc, char** argv)
|
||||
|
||||
while (true) {
|
||||
// using the GNU Readline API
|
||||
std::string line = tools::readLine(connected ? tools::PROMPT : "> ");
|
||||
std::string line = readLine(connected ? PROMPT : "> ");
|
||||
if (!line.empty() && line[0] == '\0')
|
||||
break;
|
||||
if (line.empty())
|
||||
@@ -101,12 +95,12 @@ main(int argc, char** argv)
|
||||
}
|
||||
|
||||
token = dht.listen<dht::ImMessage>(room, [&](dht::ImMessage&& msg) {
|
||||
if (msg.owner && msg.owner->getLongId() != mypk->getLongId())
|
||||
std::cout << msg.from.toString() << " at " << tools::printTime(msg.date) << " (took "
|
||||
if (msg.from != myid)
|
||||
std::cout << msg.from.toString() << " at " << printTime(msg.date) << " (took "
|
||||
<< print_duration(std::chrono::system_clock::now()
|
||||
- std::chrono::system_clock::from_time_t(msg.date))
|
||||
<< ") " << (msg.to == mypk->getId() ? "ENCRYPTED " : "") << ": " << msg.id
|
||||
<< " - " << msg.msg << std::endl;
|
||||
<< ") " << (msg.to == myid ? "ENCRYPTED " : "") << ": " << msg.id << " - "
|
||||
<< msg.msg << std::endl;
|
||||
return true;
|
||||
});
|
||||
connected = true;
|
||||
@@ -124,14 +118,14 @@ main(int argc, char** argv)
|
||||
std::getline(iss, line);
|
||||
dht.putEncrypted(room,
|
||||
InfoHash(idstr),
|
||||
dht::ImMessage(IdDist()(tools::rd), std::move(line), now),
|
||||
dht::ImMessage(rand_id(rd), std::move(line), now),
|
||||
[](bool ok) {
|
||||
// dht.cancelPut(room, id);
|
||||
if (not ok)
|
||||
std::cout << "Message publishing failed !" << std::endl;
|
||||
});
|
||||
} else {
|
||||
dht.putSigned(room, dht::ImMessage(IdDist()(tools::rd), std::move(line), now), [](bool ok) {
|
||||
dht.putSigned(room, dht::ImMessage(rand_id(rd), std::move(line), now), [](bool ok) {
|
||||
// dht.cancelPut(room, id);
|
||||
if (not ok)
|
||||
std::cout << "Message publishing failed !" << std::endl;
|
||||
|
||||
+1
-7
@@ -16,8 +16,7 @@ extern "C" {
|
||||
#include <set>
|
||||
#include <thread> // std::this_thread::sleep_for
|
||||
|
||||
namespace dht {
|
||||
namespace tools {
|
||||
using namespace dht;
|
||||
|
||||
void
|
||||
print_info()
|
||||
@@ -549,14 +548,9 @@ cmd_loop(std::shared_ptr<DhtRunner>& node,
|
||||
std::cout << std::endl << "Stopping node..." << std::endl;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
} // namespace dht
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
using namespace dht;
|
||||
using namespace dht::tools;
|
||||
#ifdef _WIN32
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
SetConsoleCP(CP_UTF8);
|
||||
|
||||
+45
-113
@@ -1,122 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Collect and aggregate statistics from DHT proxy servers."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
import requests
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
|
||||
import aiohttp
|
||||
ts = time.time()
|
||||
|
||||
stats_total = {"users":0, "pushListenersCount":0, "listenCount":0, "totalListeners":0, "totalPermanentPuts":0, "timestamp": str(ts)}
|
||||
|
||||
# Configuration
|
||||
START_PORT = 80
|
||||
END_PORT = 101
|
||||
HOST = "localhost"
|
||||
OUTPUT_FILE = "stats_proxy_total"
|
||||
REQUEST_TIMEOUT = 20 # seconds
|
||||
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')
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
|
||||
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 = {}
|
||||
# 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
|
||||
|
||||
stats['timestamp'] = str(ts)
|
||||
|
||||
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_" + str(i), "a") as stat_file:
|
||||
# stat_file.write(str(stats))
|
||||
# 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())
|
||||
with open("stats_proxy_total", "w") as stat_file:
|
||||
stat_file.write(str(stats_total))
|
||||
stat_file.write('\n')
|
||||
@@ -15,8 +15,7 @@ extern "C" {
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
namespace dht {
|
||||
namespace tools {
|
||||
using namespace dht;
|
||||
|
||||
void
|
||||
print_usage()
|
||||
@@ -83,13 +82,9 @@ step(DhtRunner& dht, std::atomic_uint& done, std::shared_ptr<NodeSet> all_nodes,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
} // namespace dht
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
using namespace dht::tools;
|
||||
#ifdef _MSC_VER
|
||||
if (auto err = gnutls_global_init()) {
|
||||
fmt::print(stderr, "Failed to initialize GnuTLS: {}\n", gnutls_strerror(err));
|
||||
@@ -102,7 +97,7 @@ main(int argc, char** argv)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
dht::DhtRunner dht;
|
||||
DhtRunner dht;
|
||||
try {
|
||||
auto [config, context] = getDhtConfig(params);
|
||||
config.dht_config.node_config.client_mode = true;
|
||||
|
||||
+15
-18
@@ -14,14 +14,6 @@ extern "C" {
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
namespace dht {
|
||||
namespace tests {
|
||||
|
||||
constexpr unsigned PINGPONG_MAX = 2048;
|
||||
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
using duration = clock::duration;
|
||||
|
||||
void
|
||||
print_usage()
|
||||
{
|
||||
@@ -29,6 +21,14 @@ print_usage()
|
||||
std::cout << "perftest, a simple OpenDHT basic performance tester." << std::endl;
|
||||
std::cout << "Report bugs to: https://opendht.net" << std::endl;
|
||||
}
|
||||
constexpr unsigned PINGPONG_MAX = 2048;
|
||||
using namespace dht;
|
||||
|
||||
namespace tests {
|
||||
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
using duration = clock::duration;
|
||||
|
||||
duration
|
||||
benchPingPong(unsigned netSize, unsigned n_parallel)
|
||||
{
|
||||
@@ -115,13 +115,10 @@ benchPingPong(unsigned netSize, unsigned n_parallel)
|
||||
}
|
||||
|
||||
} // namespace tests
|
||||
} // namespace dht
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
using namespace dht::tools;
|
||||
using namespace dht::tests;
|
||||
#ifdef _MSC_VER
|
||||
if (auto err = gnutls_global_init()) {
|
||||
std::cerr << "Failed to initialize GnuTLS: " << gnutls_strerror(err) << std::endl;
|
||||
@@ -143,10 +140,10 @@ main(int argc, char** argv)
|
||||
results.reserve(8);
|
||||
duration total {0};
|
||||
for (unsigned i = 2; i < 32; i *= 2) {
|
||||
auto dt = benchPingPong(i - 2, nparallel);
|
||||
auto dt = tests::benchPingPong(i - 2, nparallel);
|
||||
std::cout << "Network size: " << i << std::endl;
|
||||
std::cout << max << " ping-pong done, took " << dht::print_duration(dt) << std::endl;
|
||||
std::cout << dht::print_duration(dt / max) << " per rt, " << max / std::chrono::duration<double>(dt).count()
|
||||
std::cout << max << " ping-pong done, took " << print_duration(dt) << std::endl;
|
||||
std::cout << print_duration(dt / max) << " per rt, " << max / std::chrono::duration<double>(dt).count()
|
||||
<< " ping per s" << std::endl
|
||||
<< std::endl;
|
||||
total += dt;
|
||||
@@ -158,14 +155,14 @@ main(int argc, char** argv)
|
||||
|
||||
std::cout << "Total for " << nparallel << std::endl;
|
||||
auto totNum = max * results.size();
|
||||
std::cout << totNum << " ping-pong done, took " << dht::print_duration(total) << std::endl;
|
||||
std::cout << dht::print_duration(total / totNum) << " per rt, "
|
||||
std::cout << totNum << " ping-pong done, took " << print_duration(total) << std::endl;
|
||||
std::cout << print_duration(total / totNum) << " per rt, "
|
||||
<< totNum / std::chrono::duration<double>(total).count() << " ping per s" << std::endl
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl << "Grand total: " << dht::print_duration(totalTime) << " for " << totalOps << std::endl;
|
||||
std::cout << dht::print_duration(totalTime / totalOps) << " per rt, "
|
||||
std::cout << std::endl << "Grand total: " << print_duration(totalTime) << " for " << totalOps << std::endl;
|
||||
std::cout << print_duration(totalTime / totalOps) << " per rt, "
|
||||
<< totalOps / std::chrono::duration<double>(totalTime).count() << " ping per s" << std::endl
|
||||
<< std::endl;
|
||||
|
||||
|
||||
+4
-12
@@ -54,9 +54,6 @@
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
namespace dht {
|
||||
namespace tools {
|
||||
|
||||
/*
|
||||
* The mapString shall have the following format:
|
||||
*
|
||||
@@ -196,9 +193,7 @@ getDhtConfig(dht_params& params)
|
||||
}
|
||||
if (context.logger) {
|
||||
context.statusChangedCallback = [logger = context.logger](dht::NodeStatus status4, dht::NodeStatus status6) {
|
||||
logger->warn("Connectivity changed: IPv4: {}, IPv6: {}",
|
||||
dht::statusToStr(status4),
|
||||
dht::statusToStr(status6));
|
||||
logger->w("Connectivity changed: IPv4: %s, IPv6: %s", dht::statusToStr(status4), dht::statusToStr(status6));
|
||||
};
|
||||
context.publicAddressChangedCb = [logger = context.logger](std::vector<dht::SockAddr> addrs) {
|
||||
logger->warn("Public address changed: {}", addrs);
|
||||
@@ -217,9 +212,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, " << printByteCount(info.storage_size) << std::endl;
|
||||
std::cout << "Local storage: " << info.local_storage_values << " values, "
|
||||
<< printByteCount(info.local_storage_size) << 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 << "Ongoing operations: " << info.ongoing_ops << std::endl;
|
||||
}
|
||||
|
||||
@@ -492,6 +487,3 @@ daemonize()
|
||||
close(STDERR_FILENO);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
} // namespace dht
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "opendht",
|
||||
"version-string": "3.7.6",
|
||||
"version-string": "3.7.0",
|
||||
"port-version": 1,
|
||||
"description": "OpenDHT: A lightweight C++17 Distributed Hash Table library.",
|
||||
"homepage": "https://opendht.net",
|
||||
|
||||
Reference in new issue
Block a user