Compare commits

...
11 Commits
Author SHA1 Message Date
Adrien BeraudandCopilot dff27203a4 build: prepare 4.5.0 release candidate 1
Co-authored-by: Copilot <223556219+Copilot@users.nply.github.com>
2026-11-03 22:48:09 -05:00
Adrien BeraudandCopilot 506d9f9cbb ci: install MessagePack C++ headers on Ubuntu
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-03 22:44:50 -05:00
dependabot[bot] 349f4e5fc6 build(deps): bump pypa/cibuildwheel from 4.3.4 to 4.3.5
Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 4.3.4 to 4.3.5.
- [Release notes](https://github.com/pypa/cibuildwheel/releases)
- [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md)
- [Commits](https://github.com/pypa/cibuildwheel/compare/v4.3.4...v4.3.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-03 22:41:04 -05:00
dependabot[bot] 11d69d02e8 build(deps): bump actions/setup-node from 6 to 7
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-03 22:40:02 -05:00
Adrien Beraud 3c87991e92 docs: make Rust README precise and factual 2026-10-29 22:46:57 -04:00
Adrien Beraud 52287bf608 ci: use npm environment for publishing 2026-10-29 22:27:34 -04:00
Adrien Beraud 061ce2deb4 docker: build with RelWithDebInfo 2026-10-29 22:27:04 -04:00
Adrien Beraud 64b536fb53 ci: publish Python wheels from the tag release workflow 2026-10-29 21:48:33 -04:00
Adrien Beraud 67c76f17de README: add Node.js bindings 2026-10-29 21:20:25 -04:00
Adrien Beraud 25fde756e8 docs: add linked package and Docker badges 2026-10-29 21:12:35 -04:00
Adrien Beraud f6b3eda280 rust: rewrite README 2026-10-29 20:55:07 -04:00
17 changed files with 247 additions and 257 deletions

No files matched your search

+3 -3
View File
@@ -4,9 +4,9 @@ env:
LLHTTP_VERSION: "v9.2.1"
RESTINIO_VERSION: "0.7.10"
EXPECTED_LITE_URL: "https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp"
UBUNTU_FULL_DEPS: "libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
UBUNTU_MESON_DEPS: "ninja-build libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-dev python3-setuptools python3-pip libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-dev libargon2-dev"
UBUNTU_MINIMAL_DEPS: "libncurses5-dev libreadline-dev nettle-dev libfmt-dev libgnutls28-dev libcppunit-dev libmsgpack-dev libargon2-dev"
UBUNTU_FULL_DEPS: "libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-cxx-dev libargon2-dev"
UBUNTU_MESON_DEPS: "ninja-build libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-dev python3-setuptools python3-pip libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-cxx-dev libargon2-dev"
UBUNTU_MINIMAL_DEPS: "libncurses5-dev libreadline-dev nettle-dev libfmt-dev libgnutls28-dev libcppunit-dev libmsgpack-cxx-dev libargon2-dev"
DEBIAN13_DEPS: "git libtool pkg-config wget cmake libncurses5-dev libreadline-dev nettle-dev libasio-dev libgnutls28-dev libuv1-dev python3-pip python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev libfmt-dev libmsgpack-cxx-dev libargon2-dev"
on: [push, pull_request]
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
sudo apt update && \
sudo apt install libncurses5-dev libreadline-dev nettle-dev \
libgnutls28-dev libuv1-dev python3-dev python3-setuptools libcppunit-dev libjsoncpp-dev \
libfmt-dev libmsgpack-dev libargon2-dev libasio-dev \
libfmt-dev libmsgpack-cxx-dev libargon2-dev libasio-dev \
llvm llvm-dev clang clang-tools && \
sudo apt remove gcc g++
+3 -3
View File
@@ -64,7 +64,7 @@ jobs:
VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkg-binary-cache,readwrite
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
cache: npm
@@ -151,7 +151,7 @@ jobs:
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: '22'
- uses: actions/download-artifact@v8
@@ -194,7 +194,7 @@ jobs:
node: ['22', '24', '26']
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}
- uses: actions/download-artifact@v8
+3 -22
View File
@@ -4,10 +4,10 @@ permissions:
contents: read
on:
release:
types: [published]
push:
branches: ['**']
pull_request:
workflow_call:
workflow_dispatch:
jobs:
@@ -23,7 +23,7 @@ jobs:
# Run locally with: pipx run cibuildwheel --platform linux python
- name: Build wheels
uses: pypa/cibuildwheel@v4.3.4
uses: pypa/cibuildwheel@v4.3.5
with:
package-dir: python
@@ -82,22 +82,3 @@ jobs:
with:
name: opendht-wheels-win_amd64
path: ./build/wheelhouse/*.whl
publish:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [build_wheels, build_wheels_windows]
environment:
name: pypi
url: https://pypi.org/p/opendht
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v8
with:
pattern: opendht-wheels-*
path: dist
merge-multiple: true
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
+30 -1
View File
@@ -25,6 +25,12 @@ jobs:
contents: read
packages: read
python-wheels:
name: Build Python wheels
uses: ./.github/workflows/python-wheels.yml
permissions:
contents: read
build-and-push-deps-image:
name: Dependency Docker image
runs-on: ubuntu-latest
@@ -593,6 +599,7 @@ jobs:
uses: softprops/action-gh-release@v3
with:
draft: true
prerelease: ${{ contains(github.ref_name, '-') }}
files: |
downloaded_artifacts/windows_msi/*.msi
downloaded_artifacts/static_dhtnode/**/*dhtnode.exe
@@ -602,12 +609,15 @@ jobs:
name: Publish opendht to npm
needs: create-github-release
runs-on: ubuntu-26.04
environment:
name: npm
url: https://www.npmjs.com/package/opendht
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
- uses: actions/setup-node@v7
with:
node-version: '24'
registry-url: https://registry.npmjs.org/
@@ -627,6 +637,25 @@ jobs:
fi
npm publish ./dist/*.tgz --access public --provenance --tag "$dist_tag"
publish-pypi:
name: Publish opendht to PyPI
needs: [create-github-release, python-wheels]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/opendht
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@v8
with:
pattern: opendht-wheels-*
path: dist
merge-multiple: true
- name: Publish wheels
uses: pypa/gh-action-pypi-publish@release/v1
publish-crate:
name: Publish opendht to crates.io
needs: create-github-release
+1 -1
View File
@@ -133,7 +133,7 @@ the assembled npm archive on Node 22, 24, and 26 before release attachment.
Enable `OPENDHT_C=ON` to build the C ABI. With `BUILD_TESTING=ON`, CTest runs
`test_c`; Meson registers the same test as `CBindings` when `c` and `tests` are
enabled. Rust regression and callback-lifetime tests run with Cargo; see
[the Rust binding guide](rust/README.md) for build and library-path settings.
[the Rust build guide](rust/BUILD.md) for build and library-path settings.
`dht_infohash` and `dht_pkid` have `uint32_t` alignment, matching the native hash
types, while retaining their 20-byte and 32-byte sizes. Rebuild consumers against
+4 -4
View File
@@ -10,10 +10,10 @@ if(POLICY CMP0167)
endif()
set (opendht_VERSION_MAJOR 4)
set (opendht_VERSION_MINOR 4.99)
set (opendht_VERSION_MINOR 5.0)
set (opendht_VERSION ${opendht_VERSION_MAJOR}.${opendht_VERSION_MINOR})
set (PACKAGE_VERSION ${opendht_VERSION})
set (VERSION "${opendht_VERSION}")
set (PACKAGE_VERSION "${opendht_VERSION}-rc.1")
set (VERSION "${PACKAGE_VERSION}")
project (opendht
VERSION ${opendht_VERSION}
DESCRIPTION "OpenDHT: A lightweight C++17 Distributed Hash Table library."
@@ -421,7 +421,7 @@ else ()
endif ()
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSGPACK_NO_BOOST -DMSGPACK_DISABLE_LEGACY_NIL -DMSGPACK_DISABLE_LEGACY_CONVERT")
add_definitions(-DPACKAGE_VERSION="${opendht_VERSION}")
add_definitions(-DPACKAGE_VERSION="${PACKAGE_VERSION}")
if (ASIO_INCLUDE_DIR)
include_directories (SYSTEM "${ASIO_INCLUDE_DIR}")
+5 -2
View File
@@ -4,7 +4,10 @@
<a id="user-content-opendht-" class="anchor" href="/savoirfairelinux/opendht/blob/master/README.md#opendht-" aria-hidden="true"></a>OpenDHT
</h1>
![PyPI - Version](https://img.shields.io/pypi/v/opendht?style=flat)
[![PyPI - Version](https://img.shields.io/pypi/v/opendht?style=flat)](https://pypi.org/project/opendht/)
[![crates.io - Version](https://img.shields.io/crates/v/opendht?style=flat)](https://crates.io/crates/opendht)
[![npm - Version](https://img.shields.io/npm/v/opendht?style=flat)](https://www.npmjs.com/package/opendht)
[![Docker Image](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat&logo=docker&logoColor=white)](https://github.com/savoirfairelinux/opendht/pkgs/container/opendht%2Fopendht)
A lightweight C++17 Distributed Hash Table implementation.
@@ -17,7 +20,7 @@ Values are distributed over the network, with redundancy.
* Public key cryptography layer providing optional data signature and encryption (using GnuTLS)
* IPv4 and IPv6 support
* Clean and powerful **C++17** map API
* Bindings for **C, Rust & Python 3**
* Bindings for **C, Rust, Node.js & Python 3**
* REST API with optional HTTP client+server with push notification support
## Documentation
+1
View File
@@ -8,6 +8,7 @@ RUN cd opendht && mkdir build && cd build \
&& cmake -GNinja .. -DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_TESTING=Off \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DOPENDHT_C=On \
-DOPENDHT_PEER_DISCOVERY=On \
-DOPENDHT_PYTHON=On \
+1
View File
@@ -10,6 +10,7 @@ ENV DESTDIR=/install
RUN cd opendht && mkdir build && cd build \
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBUILD_TESTING=Off \
-DOPENDHT_C=On \
-DOPENDHT_PEER_DISCOVERY=On \
+1
View File
@@ -8,6 +8,7 @@ RUN cd opendht && mkdir build && cd build \
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_TESTING=Off \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DOPENDHT_C=On \
-DOPENDHT_PEER_DISCOVERY=On \
-DOPENDHT_PYTHON=On \
+1
View File
@@ -7,6 +7,7 @@ COPY . opendht
RUN cd opendht && mkdir build && cd build \
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DOPENDHT_C=On \
-DOPENDHT_PEER_DISCOVERY=On \
-DOPENDHT_PYTHON=On \
+1
View File
@@ -8,6 +8,7 @@ RUN cd opendht && mkdir build && cd build \
&& cmake .. -DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_TESTING=Off \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DOPENDHT_C=On \
-DOPENDHT_PEER_DISCOVERY=On \
-DOPENDHT_PYTHON=On \
+7 -2
View File
@@ -5,7 +5,7 @@ project(
'cython',
license: 'MIT',
license_files: 'LICENSE',
version: '4.4.99',
version: '4.5.0-rc.1',
meson_version: '>=1.1.0',
default_options: ['cpp_std=c++20', 'warning_level=3'],
)
@@ -55,7 +55,12 @@ if host_machine.system() == 'linux' and io_uring.found()
endif
conf_data = configuration_data()
add_project_arguments('-DMSGPACK_NO_BOOST', '-DASIO_STANDALONE', language: 'cpp')
add_project_arguments(
'-DMSGPACK_NO_BOOST',
'-DASIO_STANDALONE',
'-DPACKAGE_VERSION="' + meson.project_version() + '"',
language: 'cpp',
)
if io_uring.found()
add_project_arguments('-DASIO_HAS_IO_URING', '-DASIO_DISABLE_EPOLL', language: 'cpp')
conf_data.set('iouring_lib', ', liburing')
+70
View File
@@ -0,0 +1,70 @@
# Building and Testing the Rust Crate
The `opendht` crate links to the native OpenDHT C library. Cargo does not build
or bundle that library. Install Rust and the native dependencies described in
the [OpenDHT build guide](../BUILD.md).
## Install the Native Library
From the root of an OpenDHT source checkout, enable the C bindings:
```sh
cmake -S . -B build-bindings -DOPENDHT_C=ON -DBUILD_TESTING=ON
cmake --build build-bindings
ctest --test-dir build-bindings -R test_c --output-on-failure
cmake --install build-bindings
```
Installation may require administrator privileges, depending on the install
prefix. Set `CMAKE_INSTALL_PREFIX` when configuring to choose another location.
The linker and runtime loader must be able to find the installed libraries.
Applications can then depend on `opendht` from crates.io as shown in the
[README](README.md).
## Use an Uninstalled Native Build
You can use the native build directly by setting its library search paths.
For example, from the repository root on macOS:
```sh
export LIBRARY_PATH="$PWD/build-bindings${LIBRARY_PATH:+:$LIBRARY_PATH}"
export DYLD_LIBRARY_PATH="$PWD/build-bindings${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
export RUSTFLAGS="-L native=$PWD/build-bindings"
cargo test --manifest-path rust/Cargo.toml --all-targets
```
On Linux use `LD_LIBRARY_PATH` instead of `DYLD_LIBRARY_PATH`; on Windows add
the native DLL directory to `PATH`. For an application outside this checkout,
use absolute paths to the native build and run Cargo in the application directory.
When linking an AddressSanitizer native build, use the same compiler runtime.
For example, with Clang:
```sh
export RUSTFLAGS="-L native=$PWD/build-bindings -C linker=clang -C link-arg=-fsanitize=address"
```
## Build and Test the Crate
With the native library available, run from the `rust` directory:
```sh
cargo build --all-targets
cargo test --all-targets
cargo test --doc
cargo test --all-features --all-targets
cargo test --all-features --doc
```
## Run the Examples
From the `rust` directory:
```sh
cargo run --example dhtnode
cargo run --features async --example dhtnode_async
cargo run --features serde --example dhtnode_async
```
The callback example contacts a public bootstrap server. The async example uses
two loopback nodes; enabling `serde` also demonstrates typed MessagePack records.
+114 -217
View File
@@ -1,92 +1,77 @@
# Rust Bindings for OpenDHT
# OpenDHT: a Distributed Hash Table
## Build and Test
OpenDHT is a Kademlia-based distributed hash table for storing and retrieving
small values over a peer-to-peer network without a central server. Common uses
include peer discovery, service announcements, and decentralized applications.
Install Rust and the native OpenDHT C library. From this directory:
The `opendht` crate provides Rust bindings to [OpenDHT](https://opendht.net), with
callback-based and runtime-independent async APIs. Capabilities include signed
and encrypted values, filtered reads, metadata queries, and typed MessagePack
records through Serde.
```sh
cargo build --all-targets
cargo test --all-targets
cargo test --doc
cargo run --example dhtnode
## Get Started
Add the crate to your dependencies:
```toml
[dependencies]
opendht = "0.6"
```
To use an uninstalled native build, set its library search paths. For example,
from the repository root on macOS:
The native OpenDHT library with C bindings must be installed separately; it is
not bundled with the crate. See the
[build guide](https://github.com/savoirfairelinux/opendht/blob/master/rust/BUILD.md)
for native installation, library paths, and source builds.
```sh
cmake -S . -B build-bindings -DOPENDHT_C=ON -DBUILD_TESTING=ON
cmake --build build-bindings
ctest --test-dir build-bindings -R test_c --output-on-failure
LIBRARY_PATH="$PWD/build-bindings" \
DYLD_LIBRARY_PATH="$PWD/build-bindings" \
RUSTFLAGS="-L native=$PWD/build-bindings" \
cargo test --manifest-path rust/Cargo.toml --all-targets
```
`DhtRunner` manages a node, `InfoHash` identifies a DHT key, and `Value` contains
the payload and metadata stored under that key. `put` publishes values, `get`
retrieves them, and `listen` subscribes to value updates and expiration events.
On Linux use `LD_LIBRARY_PATH` instead of `DYLD_LIBRARY_PATH`; on Windows add
the native DLL directory to `PATH`. When linking an AddressSanitizer native
build, use the same compiler runtime, for example
`RUSTFLAGS="-L native=$PWD/build-bindings -C linker=clang -C link-arg=-fsanitize=address"`.
## Ownership and Errors
Startup, crypto import/generation, serialization, and fallible settings return
`std::io::Result`. Public-key `pack()` exports bytes; `unpack(bytes)` replaces a
key only after successful import. Crypto methods accept binary buffers, including
embedded NULs. Passwords and other C strings reject embedded NULs.
Callbacks are owned `Send + 'static` closures, not borrowed `&mut` closures.
Use `move` captures and channels or `Arc` for shared application state. Native
runner and crypto handles remain thread-bound. `Value` is `Send`, not `Sync`:
it can move between threads, and mutation requires exclusive `&mut` access.
Get/listen callbacks receive ordinary owned `Box<Value>` objects and can move
them directly through channels. Callback panics are contained at the FFI
boundary; a panicking value callback stops its operation.
The same `Value` type is used for construction, callback delivery, async streams,
and publication. `as_slice()` borrows the payload; `user_type()` and `push_type()`
borrow strings. `owner()` returns a retained public-key handle without exporting
it. `as_bytes()` explicitly copies the payload into a Rust `Vec<u8>`.
Received values can be edited directly and passed to `put` without converting
or cloning them. `boxed()` explicitly creates an additional independent copy.
This example starts a node on an OS-assigned port, bootstraps into the public
network, and listens for a value it publishes.
```rust
use opendht::{DhtRunner, InfoHash, Value};
use std::sync::mpsc;
use std::{sync::mpsc, time::Duration};
let mut runner = DhtRunner::new();
runner.run(0)?;
let hash = InfoHash::get("example");
let (sent, received) = mpsc::channel();
let token = runner.listen(&hash, move |value, expired| {
if !expired { let _ = sent.send(value); }
true
})?;
runner.put(&hash, Value::new("hello"), |_| {}, false);
let value = received.recv_timeout(std::time::Duration::from_secs(5))?;
assert_eq!(value.as_slice(), b"hello");
runner.cancel_listen(&hash, token);
runner.join()?;
# Ok::<(), Box<dyn std::error::Error>>(())
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut runner = DhtRunner::new();
runner.run(0)?;
runner.bootstrap("bootstrap.jami.net", 4222)?;
let key = InfoHash::get("my-app/hello");
let (sender, receiver) = mpsc::channel();
let listener = runner.listen(&key, move |value, expired| {
if !expired {
let _ = sender.send(value);
}
true
})?;
runner.put(&key, Value::new("Hello from Rust!"), |_| {}, false);
let value = receiver.recv_timeout(Duration::from_secs(5))?;
println!("{}", String::from_utf8_lossy(value.as_slice()));
runner.cancel_listen(&key, listener);
runner.join()?;
Ok(())
}
```
`join()` waits for native callbacks, releases registrations, and permits restart.
Do not invoke it from a DHT callback. `shutdown(callback)` requests graceful
shutdown; wait for its callback before `join()` when persistence is required.
Dropping a listener token frees only the token; explicitly call `cancel_listen`
to remove the registration, or call `join()` to stop all operations. Pending
get/put/query completions are reported as failure when the runner stops.
A bootstrap server supplies initial peers. Applications can use public
bootstrap servers or known peers on a private network. Values expire, and their
availability depends on participating nodes; the DHT does not provide durable
storage guarantees.
For non-threaded operation set `config.threaded = false` and repeatedly call
`loop_once()`. Priority operations can take an iteration before normal work.
`run_bind` selects IPv4/IPv6 bind addresses; `bound_port` reports OS-assigned ports.
## Optional Features
## Async API
| Feature | Capabilities |
|---------|--------------|
| Default | Callback-based `DhtRunner`, binary values, keys, certificates, and signed/encrypted operations. |
| `async` | `aio::AsyncDhtRunner` with publication futures, bounded read/update streams, and automatic listener cancellation on drop. |
| `serde` | Serde-backed MessagePack records with `put_typed`, `get_typed`, and `listen_typed`. Enables `async`. |
The optional `async` feature adds `opendht::aio::AsyncDhtRunner`. It uses futures
channels without depending on Tokio or any other executor. The callback API and
default dependency set are unchanged.
Enable `async` in the dependency configuration:
```toml
[dependencies]
@@ -94,162 +79,74 @@ opendht = { version = "0.6", features = ["async"] }
futures = "0.3"
```
Configure and start a normal `DhtRunner`, then transfer it into
`AsyncDhtRunner::new(runner)`. Clones share the same runner. Run the complete
two-node loopback example with:
Create the adapter with `AsyncDhtRunner::new(runner)` after starting a
`DhtRunner`. The adapter is runtime-independent and thread-bound; use a local
executor or Tokio's `LocalSet`. Dropping a listener stream cancels its
subscription. Streams use bounded buffers and report `WouldBlock` on overflow.
```sh
cargo run --features async --example dhtnode_async
cargo test --all-features --all-targets
cargo test --all-features --doc
```
This example waits for a bootstrap peer, publishes a value with `.await`, and
reads matching values as a stream:
With an already running async adapter:
```rust,no_run
use futures::{executor::block_on, TryStreamExt};
use opendht::{aio::AsyncDhtRunner, DhtRunner, InfoHash, Value};
use std::{io, net::ToSocketAddrs};
```rust,ignore
use futures::{StreamExt, TryStreamExt};
use opendht::{InfoHash, Value};
fn main() -> io::Result<()> {
let mut runner = DhtRunner::new();
runner.run(0)?;
let runner = AsyncDhtRunner::new(runner);
let peers: Vec<_> = ("bootstrap.jami.net", 4222).to_socket_addrs()?.collect();
let hash = InfoHash::get("example");
let mut updates = runner.listen(&hash)?;
runner.put(&hash, Value::new("hello"), false).await?;
let update = updates.next().await.transpose()?;
drop(updates);
let values: Vec<_> = runner.get(&hash).try_collect().await?;
runner.shutdown().await?;
runner.join()?;
```
block_on(async {
runner.bootstrap(&peers).await?;
let key = InfoHash::get("my-app/hello-async");
runner.put(&key, Value::new("Hello from async Rust!"), false).await?;
- `put`, `put_signed`, `put_encrypted`, and `ping` return futures resolving to
`io::Result<()>`. `bootstrap(&[SocketAddr])` contacts pre-resolved peers
concurrently and succeeds when any peer responds. Resolve hostnames through
the application's runtime before calling it.
- `get`, `get_filtered`, and `query` return fallible streams. Get/listen values
are the same owned `Box<Value>` objects delivered by the callback API,
with borrowed access to payload and metadata. Listener `ValueEvent`s also
preserve the expiration flag.
- Streams buffer at most 64 items by default; `with_capacity` changes this
limit. A full buffer stops delivery and yields `WouldBlock` after buffered
items, then ends. Restart the operation to obtain a fresh snapshot. Native
failures and runner termination also yield errors rather
than successful EOF. Duplicate native deliveries are not suppressed.
- Dropping a listener immediately queues native cancellation and releases its
token. Listeners retain the runner until dropped. Dropping get/query streams
stops delivery at the next native callback, or when the runner is joined.
Dropping a completion future abandons its result but does not cancel the
native operation or undo a permanent put.
- Runner adapters and listeners remain thread-bound. Use `block_on`, a local
executor, or Tokio `LocalSet`, not `tokio::spawn`. Received values and query rows can
be sent to other threads. Non-threaded runners still require independent
`loop_once()` calls while waiting. `join()` remains blocking; await graceful
`shutdown()` first when persistence is required.
let mut values = runner.get(&key);
while let Some(value) = values.try_next().await? {
println!("{}", String::from_utf8_lossy(value.as_slice()));
}
Received values are copied once by the native callback bridge to isolate them
from mutable DHT storage. Async streams move those exact boxes through their
channels without copying payloads or rebuilding metadata. Borrowed data remains
valid for the value's lifetime, even after the callback returns or the runner
stops. Put operations consume their box, so Rust cannot continue mutating a value
after native code retains it. Malformed UTF-8 metadata is reported when its
string accessor is called, rather than eagerly failing the entire stream.
`value.owner()` returns the existing `PublicKey` type, not a second key class or
DER bytes. Call `pack()` only when exported bytes are actually needed. Shared
const owner-key fingerprint caches are initialized on the callback thread before
transfer. Replacing a returned key through `unpack()` changes that handle, not
the value's owner key.
## Typed Records
Enable `serde` for typed MessagePack operations on `AsyncDhtRunner`. This feature
also enables `async`; neither Serde nor MessagePack is required by default.
```toml
[dependencies]
opendht = { version = "0.6", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
serde_bytes = "0.11"
futures = "0.3"
```
With an already running async adapter:
```rust,ignore
use futures::TryStreamExt;
use opendht::InfoHash;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Service {
version: u32,
name: String,
port: u16,
#[serde(with = "serde_bytes")]
payload: Vec<u8>,
runner.shutdown().await?;
runner.join()
})
}
let hash = InfoHash::get("services/chat");
let mut updates = runner.listen_typed::<Service>(&hash)?;
let record = Service {
version: 1, name: "chat".into(), port: 4222, payload: vec![0, 0x80, 0xff],
};
runner.put_typed(&hash, &record, false).await?;
if let Some(event) = updates.try_next().await? {
println!("{}: {:?}", event.value.raw.id(), event.value.data);
}
drop(updates);
let records: Vec<_> = runner.get_typed::<Service>(&hash).try_collect().await?;
```
Typed puts accept `Serialize`, including unsized values. Typed reads require
`DeserializeOwned`, without imposing `Clone`, `Send`, or `Unpin` on the record.
Decoding happens when the consumer polls, not in native callbacks. Each
`TypedValue<T>` exposes decoded `data` and the original owned `raw: Box<Value>`, retaining
payload bytes and all metadata. Decoding borrows the raw payload directly, with
no intermediate byte-buffer copy; allocations required by the decoded record
still apply. `TypedEvent<T>` preserves the expiration flag.
### Typed Records
`TypedError` distinguishes `Serialize`, `Deserialize { id, source }`, and native
`Operation` errors, preserving the underlying error through `Error::source()`.
An invalid MessagePack payload or incompatible schema yields a per-value error; keep
polling to read subsequent records. `try_collect()` short-circuits on any error.
Buffer limits, terminal native failures, and listener cancellation on drop are
unchanged from the async API.
The `serde` feature adds typed async operations and also enables `async`.
`put_typed` serializes records implementing `Serialize`; `get_typed` and
`listen_typed` decode records implementing `DeserializeOwned`. Typed reads retain
the decoded record alongside its original payload and native metadata.
`put_typed` is an ordinary lazy async method: it serializes and starts publication
only when polled. Serialization failure does not start a native put. Dropping
the future after publication starts does not undo the put. Typed puts use the
default native metadata; for custom IDs/types or signed/encrypted publication,
serialize into a `Value` and use the existing untyped methods. Raw get/listen
values can also be decoded with `TypedValue::<T>::try_from(value)` or
`TypedEvent::<T>::try_from(event)`.
Structs are serialized as compact MessagePack arrays. Field order is part of
the wire format and must remain compatible across schema changes. `serde_bytes`
encodes binary fields using MessagePack's binary representation.
The wire payload is compact MessagePack, following OpenDHT's serialization
convention. Structs are encoded as arrays in field order; field order is part
of the wire schema. No schema version or envelope is inserted; applications
control their record format and versioning. Use `#[serde(with = "serde_bytes")]`
on byte buffers (or `serde_bytes::ByteBuf`) for native MessagePack `bin` values;
plain `Vec<u8>` otherwise serializes as an integer array. Run the complete typed
loopback example with:
## Working With Values
```sh
cargo run --features serde --example dhtnode_async
```
GET and LISTEN callbacks and async streams deliver owned `Box<Value>` instances.
Received values implement `Send` but not `Sync`; they can be retained after the
runner stops, moved to another thread, modified, and republished. `as_slice()`
borrows the payload without copying; `as_bytes()` allocates a separate `Vec<u8>`.
## Extended APIs
Values expose metadata including IDs, types, sequence numbers, and owner keys.
Filters select values by metadata, and queries return selected metadata fields.
- Values expose explicit IDs, user types, value types, sequence numbers,
push priorities, and push types.
- Keys and certificates support persistence; identities support RSA and EC
generation. Private keys support binary signing and RSA decryption.
- `get_filtered`, `listen_filtered`, and `query` accept native SQL-like filters,
such as `WHERE value_type=17`, `WHERE seq=7`, and
`SELECT id,user_type WHERE value_type=17`.
Projected fields are optional owned values in `QueryResult`.
- `register_type` installs native type policy and expiration settings.
- Configuration accepts owned status and certificate-store callbacks.
Certificate lookup/registration and connectivity changes are exposed.
- Push token/topic/platform setters forward native settings.
`push_notification_received` returns a `PushNotification`; call `poll()` for
the native result. In non-threaded mode continue driving `loop_once()`.
Disabled builds return `IgnoredDisabled`; stopped runners return the native
ignored outcome. Push tokens remain valid after the runner stops.
`DhtRunner::listen` returns a token that must be passed to `cancel_listen` to
unsubscribe. Dropping the token does not cancel the registration; dropping an
async listener stream does.
`join()` stops the runner and waits for its threads to exit. It must not be
called from a callback. For graceful shutdown and configured state persistence,
wait for `shutdown` to complete before calling `join()`.
## Learn More
- [API reference](https://docs.rs/opendht)
- [Callback example](https://github.com/savoirfairelinux/opendht/blob/master/rust/examples/dhtnode.rs)
- [Async and typed two-node example](https://github.com/savoirfairelinux/opendht/blob/master/rust/examples/dhtnode_async.rs)
- [Build and test guide](https://github.com/savoirfairelinux/opendht/blob/master/rust/BUILD.md)
- [OpenDHT project and issue tracker](https://github.com/savoirfairelinux/opendht)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "opendht",
"version-string": "4.4.99",
"version-string": "4.5.0-rc.1",
"port-version": 1,
"description": "OpenDHT: A lightweight C++17 Distributed Hash Table library.",
"homepage": "https://opendht.net",