Compare commits

...

25 Commits

Author SHA1 Message Date
benedetta davico b8e0b597bb nym-node 1.16.0 2025-09-18 11:55:25 +02:00
benedetta davico f0d8dabb9f Merge pull request #6042 from nymtech/release/2025.16-halloumi
Merge release/2025.16-halloumi to master
2025-09-17 14:20:19 +02:00
benedetta davico 6731b89714 update rust version 2025-09-16 11:41:49 +02:00
benedetta davico 55aea37b89 typo 2025-09-16 11:29:32 +02:00
benedettadavico 737c4d79e0 update changelog 2025-09-16 09:16:40 +02:00
benedetta davico b105e5a15d TEMP remove nym-node from publishing 2025-09-16 09:15:19 +02:00
benedetta davico 87a188ca06 Bump cred-proxy version 2025-09-11 14:28:52 +02:00
benedettadavico 4d0898c633 bump versions 2025-09-08 09:30:59 +02:00
Bogdan-Ștefan Neacşu d984d085a7 Backport metadata endpoint (#6010)
* Wireguard private metadata (#5915)

* Wireguard metadata client library (#5943)
2025-09-05 11:14:37 +03:00
Jędrzej Stuczyński 2e22cad074 bugfix: make sure tables are removed in correct order to not trigger FK constraint issue (#5987) 2025-08-29 09:02:58 +01:00
benedetta davico f105bcbafe Merge pull request #5968 from nymtech/release/2025.15-gruyere
merge gruyere to master
2025-08-21 12:20:35 +02:00
benedetta davico dc0f4af2c1 Merge pull request #5937 from nymtech/release/2025.14-feta 2025-08-13 11:12:19 +02:00
benedetta davico 2a621e07a8 Merge pull request #5907 from nymtech/release/2025.13-emmental
Merge release/2025.13-emmental to master
2025-07-22 16:23:44 +02:00
benedetta davico 485aeebabd Merge pull request #5886 from nymtech/release/2025.12-dolcelatte
Merge release/2025.12-dolcelatte to master
2025-07-09 15:25:16 +02:00
benedetta davico 3b726bada9 Merge pull request #5839 from nymtech/release/2025.11-cheddar
merge release/2025.11-cheddar to master
2025-06-11 13:09:43 +02:00
benedetta davico 1d1b2e17d2 Merge pull request #5807 from nymtech/release/2025.10-brie 2025-05-28 09:38:15 +02:00
benedetta davico b5b2dbdfd8 Merge pull request #5776 from nymtech/release/2025.9-appenzeller
Release/2025.9-appenzeller to master
2025-05-16 13:23:10 +02:00
benedetta davico 82806f47d8 Merge pull request #5735 from nymtech/release/2025.8-tourist
Merge release/2025.8-tourist to master
2025-05-05 12:11:39 +02:00
benedetta davico c6f85cf23e Merge pull request #5727 from nymtech/release/2025.7-tex
Merge tex to master
2025-04-22 10:50:43 +02:00
benedetta davico ed8de7234d Merge pull request #5672 from nymtech/release/2025.6-chuckles
Merge release/2025.6-chuckles into master
2025-04-02 10:34:51 +02:00
benedetta davico e25d83b047 Merge pull request #5641 from nymtech/release/2025.5-chokito
Merge chokito to master
2025-03-24 10:14:50 +01:00
Jędrzej Stuczyński 9974d480b5 Merge pull request #5574 from nymtech/release/2025.4-dorina-patched
Release/2025.4-dorina-patched to master
2025-03-11 10:37:06 +00:00
benedetta davico 2211f13cdd Merge pull request #5551 from nymtech/release/2025.4-dorina
Merge release/2025.4-dorina to master
2025-03-04 13:55:27 +01:00
benedetta davico 4505f18a02 Merge pull request #5485 from nymtech/release/2025.3-ruta
Release/2025.3 ruta to master
2025-02-18 10:08:08 +01:00
benedetta davico a717a18948 Merge pull request #5430 from nymtech/release/2025.2-hu
Merge release/2025.2-hu to master
2025-02-06 13:58:55 +01:00
75 changed files with 5296 additions and 166 deletions
+1 -5
View File
@@ -30,13 +30,11 @@ jobs:
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
nymnode_hash: ${{ steps.binary-hashes.outputs.nymnode_hash }}
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
client_version: ${{ steps.binary-versions.outputs.client_version }}
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
nymnode_version: ${{ steps.binary-versions.outputs.nymnode_version }}
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
@@ -56,7 +54,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.88.0
override: true
- name: Build all binaries
@@ -76,7 +74,6 @@ jobs:
target/release/nym-network-requester
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
retention-days: 30
- id: create-release
@@ -91,7 +88,6 @@ jobs:
target/release/nym-network-requester
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
push-release-data-client:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
+42
View File
@@ -4,6 +4,48 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.16-halloumi] (2025-09-16)
- Backport metadata endpoint ([#6010])
- bugfix: make sure tables are removed in correct order to not trigger FK constraint issue ([#5987])
- chore: move authenticator into gateway crate ([#5982])
- Fix the ns api ci workflow ([#5981])
- Remove freshness check on testrun submit ([#5977])
- Update sysinfo to the latest ([#5976])
- bugfix: manually calculate per node work on rewarded set changes ([#5972])
- fixing the ci for ns agent ([#5965])
- Feature/testing utils ([#5963])
- bugfix: fix ci-build for linux (and use updated runner) ([#5958])
- chore: updated refs to cheddar rev of nym repo ([#5955])
- http api client adjustment ([#5953])
- chore: fix rust 1.89 clippy issues ([#5944])
- Wireguard metadata client library ([#5943])
- chore: remove unused import ([#5942])
- feat: introduce additional checks when attempting to send to bounded channels ([#5941])
- Move credential verifier in peer controller ([#5938])
- change PK/FK on expiration date signatures tables ([#5934])
- Wireguard private metadata ([#5915])
[#6010]: https://github.com/nymtech/nym/pull/6010
[#5987]: https://github.com/nymtech/nym/pull/5987
[#5982]: https://github.com/nymtech/nym/pull/5982
[#5981]: https://github.com/nymtech/nym/pull/5981
[#5977]: https://github.com/nymtech/nym/pull/5977
[#5976]: https://github.com/nymtech/nym/pull/5976
[#5972]: https://github.com/nymtech/nym/pull/5972
[#5965]: https://github.com/nymtech/nym/pull/5965
[#5963]: https://github.com/nymtech/nym/pull/5963
[#5958]: https://github.com/nymtech/nym/pull/5958
[#5955]: https://github.com/nymtech/nym/pull/5955
[#5953]: https://github.com/nymtech/nym/pull/5953
[#5944]: https://github.com/nymtech/nym/pull/5944
[#5943]: https://github.com/nymtech/nym/pull/5943
[#5942]: https://github.com/nymtech/nym/pull/5942
[#5941]: https://github.com/nymtech/nym/pull/5941
[#5938]: https://github.com/nymtech/nym/pull/5938
[#5934]: https://github.com/nymtech/nym/pull/5934
[#5915]: https://github.com/nymtech/nym/pull/5915
## [2025.15-gruyere] (2025-08-20)
- Migrate strum to 0.27.2 ([#5960])
Generated
+70 -7
View File
@@ -4825,7 +4825,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.64"
version = "1.1.65"
dependencies = [
"anyhow",
"async-trait",
@@ -5016,7 +5016,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.61"
version = "1.1.62"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5098,7 +5098,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.61"
version = "1.1.62"
dependencies = [
"bs58",
"clap",
@@ -5718,6 +5718,7 @@ dependencies = [
"nym-types",
"nym-validator-client",
"nym-wireguard",
"nym-wireguard-private-metadata-server",
"nym-wireguard-types",
"rand 0.8.5",
"serde",
@@ -6159,7 +6160,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.62"
version = "1.1.63"
dependencies = [
"addr",
"anyhow",
@@ -6209,7 +6210,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.17.0"
version = "1.18.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -6674,7 +6675,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.61"
version = "1.1.62"
dependencies = [
"bs58",
"clap",
@@ -7310,6 +7311,68 @@ dependencies = [
"x25519-dalek",
]
[[package]]
name = "nym-wireguard-private-metadata-client"
version = "1.0.0"
dependencies = [
"async-trait",
"nym-http-api-client",
"nym-wireguard-private-metadata-shared",
"tracing",
]
[[package]]
name = "nym-wireguard-private-metadata-server"
version = "1.0.0"
dependencies = [
"anyhow",
"async-trait",
"axum 0.7.9",
"futures",
"nym-credential-verification",
"nym-credentials-interface",
"nym-http-api-common",
"nym-wireguard",
"nym-wireguard-private-metadata-shared",
"tokio",
"tokio-util",
"tower-http 0.5.2",
"utoipa",
"utoipa-swagger-ui",
]
[[package]]
name = "nym-wireguard-private-metadata-shared"
version = "1.0.0"
dependencies = [
"axum 0.7.9",
"bincode",
"nym-credentials-interface",
"schemars 0.8.22",
"serde",
"thiserror 2.0.12",
"utoipa",
]
[[package]]
name = "nym-wireguard-private-metadata-tests"
version = "1.0.0"
dependencies = [
"async-trait",
"axum 0.7.9",
"nym-credential-verification",
"nym-credentials-interface",
"nym-http-api-client",
"nym-http-api-common",
"nym-wireguard",
"nym-wireguard-private-metadata-client",
"nym-wireguard-private-metadata-server",
"nym-wireguard-private-metadata-shared",
"tokio",
"tower-http 0.5.2",
"utoipa",
]
[[package]]
name = "nym-wireguard-types"
version = "0.1.0"
@@ -7327,7 +7390,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.26"
version = "0.1.27"
dependencies = [
"anyhow",
"bytes",
+4
View File
@@ -102,6 +102,10 @@ members = [
"common/wasm/storage",
"common/wasm/utils",
"common/wireguard",
"common/wireguard-private-metadata/client",
"common/wireguard-private-metadata/server",
"common/wireguard-private-metadata/shared",
"common/wireguard-private-metadata/tests",
"common/wireguard-types",
"common/zulip-client",
"documentation/autodoc",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.61"
version = "1.1.62"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.61"
version = "1.1.62"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -110,14 +110,14 @@ FROM ecash_ticketbook;
-- 6. finally swap out the old tables
-- drop old tables
DROP TABLE expiration_date_signatures;
DROP TABLE pending_issuance;
DROP TABLE ecash_ticketbook;
DROP TABLE expiration_date_signatures;
-- rename new tables
ALTER TABLE expiration_date_signatures_new
RENAME TO expiration_date_signatures;
ALTER TABLE pending_issuance_new
RENAME TO pending_issuance;
ALTER TABLE ecash_ticketbook_new
RENAME TO ecash_ticketbook;
RENAME TO ecash_ticketbook;
ALTER TABLE expiration_date_signatures_new
RENAME TO expiration_date_signatures;
+16
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::ecash::traits::EcashManager;
use async_trait::async_trait;
use bandwidth_storage_manager::BandwidthStorageManager;
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
@@ -139,3 +140,18 @@ impl CredentialVerifier {
.await)
}
}
#[async_trait]
pub trait TicketVerifier {
/// Verify that the ticket is valid and cryptographically correct.
/// If the verification succeeds, also increase the bandwidth with the ticket's
/// amount and return the latest available bandwidth
async fn verify(&mut self) -> Result<i64>;
}
#[async_trait]
impl TicketVerifier for CredentialVerifier {
async fn verify(&mut self) -> Result<i64> {
self.verify().await
}
}
+1
View File
@@ -136,6 +136,7 @@
//! ```
#![warn(missing_docs)]
pub use reqwest::ClientBuilder as ReqwestClientBuilder;
pub use reqwest::StatusCode;
use crate::path::RequestPath;
+2 -1
View File
@@ -47,7 +47,8 @@ pub mod nyx {
pub mod wireguard {
use std::net::{Ipv4Addr, Ipv6Addr};
pub const WG_PORT: u16 = 51822;
pub const WG_TUNNEL_PORT: u16 = 51822;
pub const WG_METADATA_PORT: u16 = 51830;
// The interface used to route traffic
pub const WG_TUN_BASE_NAME: &str = "nymwg";
@@ -0,0 +1,18 @@
[package]
name = "nym-wireguard-private-metadata-client"
version = "1.0.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
async-trait = { workspace = true }
tracing = { workspace = true }
nym-http-api-client = { path = "../../http-api-client" }
nym-wireguard-private-metadata-shared = { path = "../shared" }
[lints]
workspace = true
@@ -0,0 +1,58 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use tracing::instrument;
use nym_http_api_client::{ApiClient, Client, HttpClientError, NO_PARAMS};
use nym_wireguard_private_metadata_shared::{
routes, Version, {ErrorResponse, Request, Response},
};
pub type WireguardMetadataApiClientError = HttpClientError<ErrorResponse>;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait WireguardMetadataApiClient: ApiClient {
#[instrument(level = "debug", skip(self))]
async fn version(&self) -> Result<Version, WireguardMetadataApiClientError> {
let version: u64 = self
.get_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::VERSION],
NO_PARAMS,
)
.await?;
Ok(version.into())
}
#[instrument(level = "debug", skip(self))]
async fn available_bandwidth(
&self,
request_body: &Request,
) -> Result<Response, WireguardMetadataApiClientError> {
self.post_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::AVAILABLE],
NO_PARAMS,
request_body,
)
.await
}
#[instrument(level = "debug", skip(self, request_body))]
async fn topup_bandwidth(
&self,
request_body: &Request,
) -> Result<Response, WireguardMetadataApiClientError> {
self.post_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::TOPUP],
NO_PARAMS,
request_body,
)
.await
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl WireguardMetadataApiClient for Client {}
@@ -0,0 +1,43 @@
[package]
name = "nym-wireguard-private-metadata-server"
version = "1.0.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
anyhow = { workspace = true }
axum = { workspace = true, features = ["tokio", "macros"] }
futures = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
tokio-util = { workspace = true }
tower-http = { workspace = true, features = [
"cors",
"trace",
"compression-br",
"compression-deflate",
"compression-gzip",
"compression-zstd",
] }
utoipa = { workspace = true, features = ["axum_extras", "time"] }
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
nym-credentials-interface = { path = "../../credentials-interface" }
nym-credential-verification = { path = "../../credential-verification" }
nym-http-api-common = { path = "../../http-api-common", features = [
"middleware",
"utoipa",
"output",
] }
nym-wireguard = { path = "../../wireguard" }
nym-wireguard-private-metadata-shared = { path = "../shared" }
[dev-dependencies]
async-trait = { workspace = true }
[lints]
workspace = true
@@ -0,0 +1,46 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::sync::Arc;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use nym_wireguard::WgApiWrapper;
pub(crate) mod openapi;
pub(crate) mod router;
pub(crate) mod state;
/// Shutdown goes 2 directions:
/// 1. signal background tasks to gracefully finish
/// 2. signal server itself
///
/// These are done through separate shutdown handles. Of course, shut down server
/// AFTER you have shut down BG tasks (or past their grace period).
#[allow(unused)]
pub struct ShutdownHandles {
axum_shutdown_button: CancellationToken,
/// Tokio JoinHandle for axum server's task
axum_join_handle: AxumJoinHandle,
/// Wireguard API for kernel interactions
wg_api: Arc<WgApiWrapper>,
}
impl ShutdownHandles {
/// Cancellation token is given to Axum server constructor. When the token
/// receives a shutdown signal, Axum server will shut down gracefully.
pub fn new(
axum_join_handle: AxumJoinHandle,
wg_api: Arc<WgApiWrapper>,
axum_shutdown_button: CancellationToken,
) -> Self {
Self {
axum_shutdown_button,
axum_join_handle,
wg_api,
}
}
}
type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
@@ -0,0 +1,14 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use utoipa::OpenApi;
use nym_wireguard_private_metadata_shared::{Request, Response};
#[derive(OpenApi)]
#[openapi(
info(title = "Nym Wireguard Private Metadata"),
tags(),
components(schemas(Request, Response))
)]
pub(crate) struct ApiDoc;
@@ -0,0 +1,101 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use anyhow::anyhow;
use axum::response::Redirect;
use axum::routing::get;
use axum::Router;
use core::net::SocketAddr;
use nym_http_api_common::middleware::logging::log_request_info;
use tokio::net::TcpListener;
use tokio_util::sync::WaitForCancellationFutureOwned;
use tower_http::cors::CorsLayer;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use crate::http::openapi::ApiDoc;
use crate::http::state::AppState;
use crate::network::bandwidth_routes;
/// Wrapper around `axum::Router` which ensures correct [order of layers][order].
/// Add new routes as if you were working directly with `axum`.
///
/// Why? Middleware like logger, CORS, TLS which need to handle request before other
/// layers should be added last. Using this builder pattern ensures that.
///
/// [order]: https://docs.rs/axum/latest/axum/middleware/index.html#ordering
pub struct RouterBuilder {
unfinished_router: Router<AppState>,
}
impl RouterBuilder {
/// All routes should be, if possible, added here. Exceptions are e.g.
/// routes which are added conditionally in other places based on some `if`.
pub fn with_default_routes() -> Self {
let default_routes = Router::new()
.merge(SwaggerUi::new("/swagger").url("/api-docs/openapi.json", ApiDoc::openapi()))
.route("/", get(|| async { Redirect::to("/swagger") }))
.nest("/v1", Router::new().nest("/bandwidth", bandwidth_routes()));
Self {
unfinished_router: default_routes,
}
}
/// Invoke this as late as possible before constructing HTTP server
/// (after all routes were added).
pub fn with_state(self, state: AppState) -> RouterWithState {
RouterWithState {
router: self.finalize_routes().with_state(state),
}
}
/// Middleware added here intercepts the request before it gets to other routes.
fn finalize_routes(self) -> Router<AppState> {
self.unfinished_router
.layer(setup_cors())
.layer(axum::middleware::from_fn(log_request_info))
}
}
fn setup_cors() -> CorsLayer {
CorsLayer::new()
.allow_origin(tower_http::cors::Any)
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
.allow_headers(tower_http::cors::Any)
.allow_credentials(false)
}
pub struct RouterWithState {
pub router: Router,
}
impl RouterWithState {
pub async fn build_server(self, bind_address: &SocketAddr) -> anyhow::Result<ApiHttpServer> {
let listener = tokio::net::TcpListener::bind(bind_address)
.await
.map_err(|err| anyhow!("Couldn't bind to address {} due to {}", bind_address, err))?;
Ok(ApiHttpServer {
router: self.router,
listener,
})
}
}
pub struct ApiHttpServer {
router: Router,
listener: TcpListener,
}
impl ApiHttpServer {
pub async fn run(self, receiver: WaitForCancellationFutureOwned) -> Result<(), std::io::Error> {
// into_make_service_with_connect_info allows us to see client ip address
axum::serve(
self.listener,
self.router
.into_make_service_with_connect_info::<SocketAddr>(),
)
.with_graceful_shutdown(receiver)
.await
}
}
@@ -0,0 +1,35 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::net::IpAddr;
use nym_credentials_interface::CredentialSpendingData;
use crate::transceiver::PeerControllerTransceiver;
use nym_wireguard_private_metadata_shared::error::MetadataError;
#[derive(Clone, axum::extract::FromRef)]
pub struct AppState {
transceiver: PeerControllerTransceiver,
}
impl AppState {
pub fn new(transceiver: PeerControllerTransceiver) -> Self {
Self { transceiver }
}
pub async fn available_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
self.transceiver.query_bandwidth(ip).await
}
// Top up with a credential and return the afterwards available bandwidth
pub async fn topup_bandwidth(
&self,
ip: IpAddr,
credential: CredentialSpendingData,
) -> Result<i64, MetadataError> {
self.transceiver
.topup_bandwidth(ip, Box::new(credential))
.await
}
}
@@ -0,0 +1,13 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod http;
mod network;
mod transceiver;
pub use http::{
router::{ApiHttpServer, RouterBuilder, RouterWithState},
state::AppState,
ShutdownHandles,
};
pub use transceiver::PeerControllerTransceiver;
@@ -0,0 +1,111 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::net::SocketAddr;
use axum::{
extract::{ConnectInfo, Query, State},
Json, Router,
};
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_wireguard_private_metadata_shared::{
interface::{RequestData, ResponseData},
latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
};
use tower_http::compression::CompressionLayer;
use crate::http::state::AppState;
pub(crate) fn bandwidth_routes() -> Router<AppState> {
Router::new()
.route("/version", axum::routing::get(version))
.route("/available", axum::routing::post(available_bandwidth))
.route("/topup", axum::routing::post(topup_bandwidth))
.layer(CompressionLayer::new())
}
#[utoipa::path(
tag = "bandwidth",
get,
path = "/v1/bandwidth/version",
responses(
(status = 200, content(
(Response = "application/bincode")
))
),
)]
async fn version(Query(output): Query<OutputParams>) -> AxumResult<FormattedResponse<u64>> {
let output = output.output.unwrap_or_default();
Ok(output.to_response(latest::VERSION.into()))
}
#[utoipa::path(
tag = "bandwidth",
post,
request_body = Request,
path = "/v1/bandwidth/available",
responses(
(status = 200, content(
(Response = "application/bincode")
))
),
)]
async fn available_bandwidth(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Query(output): Query<OutputParams>,
State(state): State<AppState>,
Json(request): Json<Request>,
) -> AxumResult<FormattedResponse<Response>> {
let output = output.output.unwrap_or_default();
let (RequestData::AvailableBandwidth(_), version) =
request.extract().map_err(AxumErrorResponse::bad_request)?
else {
return Err(AxumErrorResponse::bad_request("incorrect request type"));
};
let available_bandwidth = state
.available_bandwidth(addr.ip())
.await
.map_err(AxumErrorResponse::bad_request)?;
let response = Response::construct(
ResponseData::AvailableBandwidth(available_bandwidth),
version,
)
.map_err(AxumErrorResponse::bad_request)?;
Ok(output.to_response(response))
}
#[utoipa::path(
tag = "bandwidth",
post,
request_body = Request,
path = "/v1/bandwidth/topup",
responses(
(status = 200, content(
(Response = "application/bincode")
))
),
)]
async fn topup_bandwidth(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Query(output): Query<OutputParams>,
State(state): State<AppState>,
Json(request): Json<Request>,
) -> AxumResult<FormattedResponse<Response>> {
let output = output.output.unwrap_or_default();
let (RequestData::TopUpBandwidth(credential), version) =
request.extract().map_err(AxumErrorResponse::bad_request)?
else {
return Err(AxumErrorResponse::bad_request("incorrect request type"));
};
let available_bandwidth = state
.topup_bandwidth(addr.ip(), *credential)
.await
.map_err(AxumErrorResponse::bad_request)?;
let response = Response::construct(ResponseData::TopUpBandwidth(available_bandwidth), version)
.map_err(AxumErrorResponse::bad_request)?;
Ok(output.to_response(response))
}
@@ -0,0 +1,307 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::net::IpAddr;
use futures::channel::oneshot;
use tokio::sync::mpsc;
use nym_credential_verification::ClientBandwidth;
use nym_credentials_interface::CredentialSpendingData;
use nym_wireguard::peer_controller::PeerControlRequest;
use nym_wireguard_private_metadata_shared::error::MetadataError;
#[derive(Clone)]
pub struct PeerControllerTransceiver {
request_tx: mpsc::Sender<PeerControlRequest>,
}
impl PeerControllerTransceiver {
pub fn new(request_tx: mpsc::Sender<PeerControlRequest>) -> Self {
Self { request_tx }
}
async fn get_client_bandwidth(&self, ip: IpAddr) -> Result<ClientBandwidth, MetadataError> {
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::GetClientBandwidthByIp { ip, response_tx };
self.request_tx
.send(msg)
.await
.map_err(|_| MetadataError::PeerInteractionStopped)?;
response_rx
.await
.map_err(|_| MetadataError::NoResponse)?
.map_err(|err| MetadataError::Unsuccessful {
reason: err.to_string(),
})
}
pub(crate) async fn query_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
Ok(self.get_client_bandwidth(ip).await?.available().await)
}
// Top up with a credential and return the afterwards available bandwidth
pub(crate) async fn topup_bandwidth(
&self,
ip: IpAddr,
credential: Box<CredentialSpendingData>,
) -> Result<i64, MetadataError> {
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::GetVerifierByIp {
ip,
credential,
response_tx,
};
self.request_tx
.send(msg)
.await
.map_err(|_| MetadataError::PeerInteractionStopped)?;
let mut verifier = response_rx
.await
.map_err(|_| MetadataError::NoResponse)?
.map_err(|err| MetadataError::Unsuccessful {
reason: err.to_string(),
})?;
let available_bandwidth =
verifier
.verify()
.await
.map_err(|err| MetadataError::CredentialVerification {
message: err.to_string(),
})?;
Ok(available_bandwidth)
}
}
#[cfg(test)]
pub(crate) mod tests {
use nym_credential_verification::TicketVerifier;
use nym_wireguard::CONTROL_CHANNEL_SIZE;
use super::*;
pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [
0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254,
16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139,
154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123,
35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1,
151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43,
90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193,
39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81,
184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195,
196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48,
92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48,
166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186,
19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38,
228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85,
88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241,
85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112,
48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31,
97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203,
71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107,
213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180,
178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1,
96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166,
30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212,
207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71,
223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22,
119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131,
59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119,
240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202,
58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144,
189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33,
30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150,
74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6,
227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85,
15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38,
161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20,
47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48,
135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170,
150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109,
55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249,
87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144,
178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32,
203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184,
96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169,
24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61,
130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82,
108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1,
32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234,
150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241,
203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217,
160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52,
147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81,
70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233,
178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98,
110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69,
131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251,
197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233,
162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106,
83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145,
192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124,
55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0,
0, 0, 0, 0, 0, 1,
];
pub(crate) struct MockVerifier {
ret: i64,
}
impl MockVerifier {
pub(crate) fn new(ret: i64) -> MockVerifier {
Self { ret }
}
}
#[async_trait::async_trait]
impl TicketVerifier for MockVerifier {
async fn verify(&mut self) -> nym_credential_verification::Result<i64> {
Ok(self.ret)
}
}
#[tokio::test]
async fn get_bandwidth() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => {
response_tx
.send(Ok(ClientBandwidth::new(Default::default())))
.ok();
}
_ => panic!("Not expected"),
}
});
let bw = transceiver
.query_bandwidth("10.0.0.42".parse().unwrap())
.await
.unwrap();
assert_eq!(bw, 0);
}
#[tokio::test]
async fn stop_peer() {
let (request_tx, request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
drop(request_rx);
let err = transceiver
.query_bandwidth("10.0.0.42".parse().unwrap())
.await
.unwrap_err();
assert_eq!(err, MetadataError::PeerInteractionStopped);
}
#[tokio::test]
async fn unresponsive_peer() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetClientBandwidthByIp {
ip: _,
response_tx: _,
} => {}
_ => panic!("Not expected"),
}
});
let err = transceiver
.query_bandwidth("10.0.0.42".parse().unwrap())
.await
.unwrap_err();
assert_eq!(err, MetadataError::NoResponse);
}
#[tokio::test]
async fn unsuccessful_query_bandwidth() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => {
response_tx
.send(Err(nym_wireguard::error::Error::Internal(
"testing".to_owned(),
)))
.ok();
}
_ => panic!("Not expected"),
}
});
let ret = transceiver
.query_bandwidth("10.0.0.42".parse().unwrap())
.await;
assert!(ret.is_err());
}
#[tokio::test]
async fn topup() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
let verifier_bw = 42;
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetVerifierByIp {
ip: _,
credential: _,
response_tx,
} => {
response_tx
.send(Ok(Box::new(MockVerifier::new(verifier_bw))))
.ok();
}
_ => panic!("Not expected"),
}
});
let bw = transceiver
.topup_bandwidth("10.0.0.42".parse().unwrap(), Box::new(credential))
.await
.unwrap();
assert_eq!(bw, verifier_bw);
}
#[tokio::test]
async fn unsuccessful_topup() {
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let transceiver = PeerControllerTransceiver::new(request_tx);
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetVerifierByIp {
ip: _,
credential: _,
response_tx,
} => {
response_tx
.send(Err(nym_wireguard::error::Error::Internal(
"testing".to_owned(),
)))
.ok();
}
_ => panic!("Not expected"),
}
});
let ret = transceiver
.topup_bandwidth("10.0.0.42".parse().unwrap(), Box::new(credential))
.await;
assert!(ret.is_err());
}
}
@@ -0,0 +1,25 @@
[package]
name = "nym-wireguard-private-metadata-shared"
version = "1.0.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
axum = { workspace = true }
bincode = { workspace = true }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true }
thiserror = { workspace = true }
utoipa = { workspace = true }
nym-credentials-interface = { path = "../../credentials-interface" }
[features]
testing = []
[lints]
workspace = true
@@ -0,0 +1,28 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum MetadataError {
#[error("peers can't be interacted with anymore")]
PeerInteractionStopped,
#[error("no response received")]
NoResponse,
#[error("query was not successful: {reason}")]
Unsuccessful { reason: String },
#[error("Models error: {message}")]
Models { message: String },
#[error("Credential verification error: {message}")]
CredentialVerification { message: String },
}
impl From<crate::models::error::Error> for MetadataError {
fn from(value: crate::models::error::Error) -> Self {
Self::Models {
message: value.to_string(),
}
}
}
@@ -0,0 +1,20 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod error;
mod models;
pub mod routes;
#[cfg(feature = "testing")]
pub use models::v0;
pub use models::{
error::Error as ModelError, interface, latest, v1, AxumErrorResponse, AxumResult, Construct,
ErrorResponse, Extract, Request, Response, Version,
};
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
bincode::DefaultOptions::new()
.with_big_endian()
.with_varint_encoding()
}
@@ -0,0 +1,30 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::Version;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Bincode(#[from] bincode::Error),
#[error("trying to deserialize from version {source_version:?} into {target_version:?}")]
InvalidVersion {
source_version: Version,
target_version: Version,
},
#[error(
"trying to deserialize from query type {source_query_type} query type {target_query_type}"
)]
InvalidQueryType {
source_query_type: String,
target_query_type: String,
},
#[error("update not possible from {from:?} to {to:?}")]
UpdateNotPossible { from: Version, to: Version },
#[error("downgrade not possible from {from:?} to {to:?}")]
DowngradeNotPossible { from: Version, to: Version },
}
@@ -0,0 +1,145 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::CredentialSpendingData;
#[cfg(feature = "testing")]
use crate::models::v0;
use crate::models::{v1, Construct, Extract, Request, Response, Version};
pub enum RequestData {
AvailableBandwidth(()),
TopUpBandwidth(Box<CredentialSpendingData>),
}
impl From<super::latest::interface::RequestData> for RequestData {
fn from(value: super::latest::interface::RequestData) -> Self {
match value {
super::latest::interface::RequestData::AvailableBandwidth(inner) => {
Self::AvailableBandwidth(inner)
}
super::latest::interface::RequestData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
impl From<RequestData> for super::latest::interface::RequestData {
fn from(value: RequestData) -> Self {
match value {
RequestData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
RequestData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
impl From<super::latest::interface::ResponseData> for ResponseData {
fn from(value: super::latest::interface::ResponseData) -> Self {
match value {
super::latest::interface::ResponseData::AvailableBandwidth(inner) => {
Self::AvailableBandwidth(inner)
}
super::latest::interface::ResponseData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
impl From<ResponseData> for super::latest::interface::ResponseData {
fn from(value: ResponseData) -> Self {
match value {
ResponseData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
ResponseData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
impl Construct<RequestData> for Request {
fn construct(info: RequestData, version: Version) -> Result<Self, super::error::Error> {
match version {
#[cfg(feature = "testing")]
Version::V0 => {
let translate_info = super::latest::interface::RequestData::from(info);
let downgrade_info = v0::interface::RequestData::try_from(translate_info)?;
let versioned_request = v0::VersionedRequest::construct(downgrade_info, version)?;
Ok(versioned_request.try_into()?)
}
Version::V1 => {
let versioned_request = v1::VersionedRequest::construct(info.into(), version)?;
Ok(versioned_request.try_into()?)
}
}
}
}
impl Extract<RequestData> for Request {
fn extract(&self) -> Result<(RequestData, Version), crate::models::Error> {
match self.version {
#[cfg(feature = "testing")]
super::Version::V0 => {
let versioned_request = v0::VersionedRequest::try_from(self.clone())?;
let (request, version) = versioned_request.extract()?;
let upgrade_request = super::latest::interface::RequestData::try_from(request)?;
Ok((upgrade_request.into(), version))
}
super::Version::V1 => {
let versioned_request = v1::VersionedRequest::try_from(self.clone())?;
let (extracted, version) = versioned_request.extract()?;
Ok((extracted.into(), version))
}
}
}
}
pub enum ResponseData {
AvailableBandwidth(i64),
TopUpBandwidth(i64),
}
impl Construct<ResponseData> for Response {
fn construct(info: ResponseData, version: Version) -> Result<Self, super::error::Error> {
match version {
#[cfg(feature = "testing")]
super::Version::V0 => {
let translate_response = super::latest::interface::ResponseData::from(info);
let downgrade_response = v0::interface::ResponseData::try_from(translate_response)?;
let versioned_response =
v0::VersionedResponse::construct(downgrade_response, version)?;
Ok(versioned_response.try_into()?)
}
Version::V1 => {
let versioned_response = v1::VersionedResponse::construct(info.into(), version)?;
Ok(versioned_response.try_into()?)
}
}
}
}
impl Extract<ResponseData> for Response {
fn extract(&self) -> Result<(ResponseData, Version), super::error::Error> {
match self.version {
#[cfg(feature = "testing")]
super::Version::V0 => {
let versioned_response = v0::VersionedResponse::try_from(self.clone())?;
let (response, version) = versioned_response.extract()?;
let upgrade_response = super::latest::interface::ResponseData::try_from(response)?;
Ok((upgrade_response.into(), version))
}
super::Version::V1 => {
let versioned_response = v1::VersionedResponse::try_from(self.clone())?;
let (extracted, version) = versioned_response.extract()?;
Ok((extracted.into(), version))
}
}
}
}
@@ -0,0 +1,175 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::{Display, Formatter};
use axum::http::StatusCode;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
pub(crate) mod error;
pub mod interface;
#[cfg(feature = "testing")]
pub mod v0; // dummy version, only for filling boilerplate code for update/downgrade and testing
pub mod v1;
pub use v1 as latest;
use crate::models::error::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub enum Version {
#[cfg(feature = "testing")]
/// only used for testing purposes, don't include it in your matching arms
V0,
V1,
}
impl From<u64> for Version {
fn from(value: u64) -> Self {
#[cfg(feature = "testing")]
let zero_version = Version::V0;
#[cfg(not(feature = "testing"))]
let zero_version = latest::VERSION;
match value {
0 => zero_version,
1 => Version::V1,
_ => latest::VERSION, // if unknown, it means we're behind, so we can use the latest we know about
}
}
}
impl From<Version> for u64 {
fn from(value: Version) -> Self {
// remember to modify the above match if you're bumping the version
match value {
#[cfg(feature = "testing")]
Version::V0 => 0,
Version::V1 => 1,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct Request {
pub version: Version,
pub(crate) inner: Vec<u8>,
}
#[derive(Clone, Serialize, Deserialize, ToSchema)]
pub struct Response {
pub version: Version,
pub(crate) inner: Vec<u8>,
}
pub trait Extract<T> {
fn extract(&self) -> Result<(T, Version), Error>;
}
pub trait Construct<T>: Sized {
fn construct(info: T, version: Version) -> Result<Self, Error>;
}
pub type AxumResult<T> = Result<T, AxumErrorResponse>;
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct ErrorResponse {
pub message: String,
}
impl Display for ErrorResponse {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.message.fmt(f)
}
}
pub struct AxumErrorResponse {
message: ErrorResponse,
status: StatusCode,
}
impl AxumErrorResponse {
pub fn bad_request(msg: impl Display) -> Self {
Self {
message: ErrorResponse {
message: msg.to_string(),
},
status: StatusCode::BAD_REQUEST,
}
}
}
impl axum::response::IntoResponse for AxumErrorResponse {
fn into_response(self) -> axum::response::Response {
(self.status, self.message.message.to_string()).into_response()
}
}
mod tests {
#[allow(dead_code)]
pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [
0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254,
16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139,
154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123,
35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1,
151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43,
90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193,
39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81,
184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195,
196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48,
92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48,
166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186,
19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38,
228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85,
88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241,
85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112,
48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31,
97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203,
71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107,
213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180,
178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1,
96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166,
30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212,
207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71,
223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22,
119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131,
59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119,
240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202,
58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144,
189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33,
30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150,
74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6,
227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85,
15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38,
161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20,
47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48,
135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170,
150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109,
55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249,
87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144,
178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32,
203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184,
96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169,
24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61,
130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82,
108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1,
32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234,
150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241,
203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217,
160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52,
147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81,
70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233,
178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98,
110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69,
131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251,
197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233,
162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106,
83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145,
192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124,
55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0,
0, 0, 0, 0, 0, 1,
];
}
@@ -0,0 +1,5 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
@@ -0,0 +1,86 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, models::Request};
use super::super::{Error, QueryType, VersionedRequest};
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InnerAvailableBandwidthRequest {}
impl TryFrom<VersionedRequest> for InnerAvailableBandwidthRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::AvailableBandwidth => {
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::AvailableBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerAvailableBandwidthRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerAvailableBandwidthRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
let req = InnerAvailableBandwidthRequest {};
let ser = VersionedRequest::try_from(req).unwrap();
assert_eq!(QueryType::AvailableBandwidth, ser.query_type);
let de = InnerAvailableBandwidthRequest::try_from(ser).unwrap();
assert_eq!(req, de);
}
#[test]
fn empty_content() {
let future_req = VersionedRequest {
query_type: QueryType::AvailableBandwidth,
inner: vec![],
};
assert!(InnerAvailableBandwidthRequest::try_from(future_req).is_ok());
}
}
@@ -0,0 +1,86 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, models::Response};
use super::super::{Error, QueryType, VersionedResponse};
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InnerAvailableBandwidthResponse {}
impl TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::AvailableBandwidth => {
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::AvailableBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerAvailableBandwidthResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
let resp = InnerAvailableBandwidthResponse {};
let ser = VersionedResponse::try_from(resp).unwrap();
assert_eq!(QueryType::AvailableBandwidth, ser.query_type);
let de = InnerAvailableBandwidthResponse::try_from(ser).unwrap();
assert_eq!(resp, de);
}
#[test]
fn empty_content() {
let future_resp = VersionedResponse {
query_type: QueryType::AvailableBandwidth,
inner: vec![],
};
assert!(InnerAvailableBandwidthResponse::try_from(future_resp).is_ok());
}
}
@@ -0,0 +1,73 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{
available_bandwidth::{
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
},
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
QueryType, VersionedRequest, VersionedResponse, VERSION,
};
use crate::models::{error::Error, Construct, Extract, Version};
#[derive(Debug, Clone, PartialEq)]
pub enum RequestData {
AvailableBandwidth(()),
TopUpBandwidth(()),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResponseData {
AvailableBandwidth(()),
TopUpBandwidth(()),
}
impl Construct<RequestData> for VersionedRequest {
fn construct(info: RequestData, _version: Version) -> Result<Self, Error> {
match info {
RequestData::AvailableBandwidth(_) => Ok(InnerAvailableBandwidthRequest {}.try_into()?),
RequestData::TopUpBandwidth(_) => Ok(InnerTopUpRequest {}.try_into()?),
}
}
}
impl Extract<RequestData> for VersionedRequest {
fn extract(&self) -> Result<(RequestData, Version), Error> {
match self.query_type {
QueryType::AvailableBandwidth => {
let _req = InnerAvailableBandwidthRequest::try_from(self.clone())?;
Ok((RequestData::AvailableBandwidth(()), VERSION))
}
QueryType::TopupBandwidth => {
let _req = InnerTopUpRequest::try_from(self.clone())?;
Ok((RequestData::TopUpBandwidth(()), VERSION))
}
}
}
}
impl Construct<ResponseData> for VersionedResponse {
fn construct(info: ResponseData, _version: Version) -> Result<Self, Error> {
match info {
ResponseData::AvailableBandwidth(()) => {
Ok(InnerAvailableBandwidthResponse {}.try_into()?)
}
ResponseData::TopUpBandwidth(()) => Ok(InnerTopUpResponse {}.try_into()?),
}
}
}
impl Extract<ResponseData> for VersionedResponse {
fn extract(&self) -> Result<(ResponseData, Version), Error> {
match self.query_type {
QueryType::AvailableBandwidth => {
let _resp = InnerAvailableBandwidthResponse::try_from(self.clone())?;
Ok((ResponseData::AvailableBandwidth(()), VERSION))
}
QueryType::TopupBandwidth => {
let _resp = InnerTopUpResponse::try_from(self.clone())?;
Ok((ResponseData::TopUpBandwidth(()), VERSION))
}
}
}
}
@@ -0,0 +1,175 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::Display;
use bincode::Options;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use super::error::Error;
use crate::{
make_bincode_serializer,
models::{Request, Response, Version},
};
pub(crate) mod available_bandwidth;
pub mod interface;
pub(crate) mod topup_bandwidth;
pub const VERSION: Version = Version::V0;
pub use available_bandwidth::{
request::InnerAvailableBandwidthRequest as AvailableBandwidthRequest,
response::InnerAvailableBandwidthResponse as AvailableBandwidthResponse,
};
pub use topup_bandwidth::{
request::InnerTopUpRequest as TopUpRequest, response::InnerTopUpResponse as TopUpResponse,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub enum QueryType {
AvailableBandwidth,
TopupBandwidth,
}
impl Display for QueryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct VersionedRequest {
query_type: QueryType,
inner: Vec<u8>,
}
impl TryFrom<VersionedRequest> for Request {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
Ok(Request {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for VersionedRequest {
type Error = Error;
fn try_from(value: Request) -> Result<Self, Self::Error> {
if value.version != VERSION {
return Err(Error::InvalidVersion {
source_version: value.version,
target_version: VERSION,
});
}
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct VersionedResponse {
query_type: QueryType,
inner: Vec<u8>,
}
impl TryFrom<VersionedResponse> for Response {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
Ok(Response {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for VersionedResponse {
type Error = Error;
fn try_from(value: Response) -> Result<Self, Self::Error> {
if value.version != VERSION {
return Err(Error::InvalidVersion {
source_version: value.version,
target_version: VERSION,
});
}
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
}
#[cfg(test)]
mod tests {
use self::{
available_bandwidth::{
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
},
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
};
use super::*;
#[test]
fn serde_request_av_bw() {
let req = VersionedRequest {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer()
.serialize(&InnerAvailableBandwidthRequest {})
.unwrap(),
};
let ser = Request::try_from(req.clone()).unwrap();
assert_eq!(VERSION, ser.version);
let de = VersionedRequest::try_from(ser).unwrap();
assert_eq!(req, de);
}
#[test]
fn serde_response_av_bw() {
let resp = VersionedResponse {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer()
.serialize(&InnerAvailableBandwidthResponse {})
.unwrap(),
};
let ser = Response::try_from(resp.clone()).unwrap();
assert_eq!(VERSION, ser.version);
let de = VersionedResponse::try_from(ser).unwrap();
assert_eq!(resp, de);
}
#[test]
fn serde_request_topup() {
let req = VersionedRequest {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer()
.serialize(&InnerTopUpRequest {})
.unwrap(),
};
let ser = Request::try_from(req.clone()).unwrap();
assert_eq!(VERSION, ser.version);
let de = VersionedRequest::try_from(ser).unwrap();
assert_eq!(req, de);
}
#[test]
fn serde_response_topup() {
let resp = VersionedResponse {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer()
.serialize(&InnerTopUpResponse {})
.unwrap(),
};
let ser = Response::try_from(resp.clone()).unwrap();
assert_eq!(VERSION, ser.version);
let de = VersionedResponse::try_from(ser).unwrap();
assert_eq!(resp, de);
}
}
@@ -0,0 +1,5 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
@@ -0,0 +1,84 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, models::Request};
use super::super::{Error, QueryType, VersionedRequest};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InnerTopUpRequest {}
impl TryFrom<VersionedRequest> for InnerTopUpRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?),
QueryType::AvailableBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::TopupBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerTopUpRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerTopUpRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerTopUpRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
let req = InnerTopUpRequest {};
let ser = VersionedRequest::try_from(req.clone()).unwrap();
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
let de = InnerTopUpRequest::try_from(ser).unwrap();
assert_eq!(req, de);
}
#[test]
fn empty_content() {
let future_req = VersionedRequest {
query_type: QueryType::TopupBandwidth,
inner: vec![],
};
assert!(InnerTopUpRequest::try_from(future_req).is_ok());
}
}
@@ -0,0 +1,84 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, models::Response};
use super::super::{Error, QueryType, VersionedResponse};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InnerTopUpResponse {}
impl TryFrom<VersionedResponse> for InnerTopUpResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?),
QueryType::AvailableBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::TopupBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerTopUpResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerTopUpResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerTopUpResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
let resp = InnerTopUpResponse {};
let ser = VersionedResponse::try_from(resp.clone()).unwrap();
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
let de = InnerTopUpResponse::try_from(ser).unwrap();
assert_eq!(resp, de);
}
#[test]
fn empty_content() {
let future_resp = VersionedResponse {
query_type: QueryType::TopupBandwidth,
inner: vec![],
};
assert!(InnerTopUpResponse::try_from(future_resp).is_ok());
}
}
@@ -0,0 +1,5 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
@@ -0,0 +1,86 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, models::Request};
use super::super::{Error, QueryType, VersionedRequest};
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InnerAvailableBandwidthRequest {}
impl TryFrom<VersionedRequest> for InnerAvailableBandwidthRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::AvailableBandwidth => {
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::AvailableBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerAvailableBandwidthRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerAvailableBandwidthRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
let req = InnerAvailableBandwidthRequest {};
let ser = VersionedRequest::try_from(req).unwrap();
assert_eq!(QueryType::AvailableBandwidth, ser.query_type);
let de = InnerAvailableBandwidthRequest::try_from(ser).unwrap();
assert_eq!(req, de);
}
#[test]
fn empty_content() {
let future_req = VersionedRequest {
query_type: QueryType::AvailableBandwidth,
inner: vec![],
};
assert!(InnerAvailableBandwidthRequest::try_from(future_req).is_ok());
}
}
@@ -0,0 +1,90 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, models::Response};
use super::super::{Error, QueryType, VersionedResponse};
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InnerAvailableBandwidthResponse {
pub available_bandwidth: i64,
}
impl TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::AvailableBandwidth => {
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::AvailableBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerAvailableBandwidthResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerAvailableBandwidthResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
let resp = InnerAvailableBandwidthResponse {
available_bandwidth: 42,
};
let ser = VersionedResponse::try_from(resp).unwrap();
assert_eq!(QueryType::AvailableBandwidth, ser.query_type);
let de = InnerAvailableBandwidthResponse::try_from(ser).unwrap();
assert_eq!(resp, de);
}
#[test]
fn invalid_content() {
let future_resp = VersionedResponse {
query_type: QueryType::AvailableBandwidth,
inner: vec![],
};
assert!(InnerAvailableBandwidthResponse::try_from(future_resp).is_err());
}
}
@@ -0,0 +1,224 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::CredentialSpendingData;
#[cfg(feature = "testing")]
use super::super::v0 as previous;
use super::{
available_bandwidth::{
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
},
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
QueryType, VersionedRequest, VersionedResponse, VERSION,
};
use crate::models::{error::Error, Construct, Extract, Version};
#[derive(Debug, Clone, PartialEq)]
pub enum RequestData {
AvailableBandwidth(()),
TopUpBandwidth(Box<CredentialSpendingData>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResponseData {
AvailableBandwidth(i64),
TopUpBandwidth(i64),
}
impl Construct<RequestData> for VersionedRequest {
fn construct(info: RequestData, _version: Version) -> Result<Self, Error> {
match info {
RequestData::AvailableBandwidth(_) => Ok(InnerAvailableBandwidthRequest {}.try_into()?),
RequestData::TopUpBandwidth(credential) => Ok(InnerTopUpRequest {
credential: *credential,
}
.try_into()?),
}
}
}
impl Extract<RequestData> for VersionedRequest {
fn extract(&self) -> Result<(RequestData, Version), Error> {
match self.query_type {
QueryType::AvailableBandwidth => {
let _req = InnerAvailableBandwidthRequest::try_from(self.clone())?;
Ok((RequestData::AvailableBandwidth(()), VERSION))
}
QueryType::TopupBandwidth => {
let req = InnerTopUpRequest::try_from(self.clone())?;
Ok((
RequestData::TopUpBandwidth(Box::new(req.credential)),
VERSION,
))
}
}
}
}
impl Construct<ResponseData> for VersionedResponse {
fn construct(info: ResponseData, _version: Version) -> Result<Self, Error> {
match info {
ResponseData::AvailableBandwidth(available_bandwidth) => {
Ok(InnerAvailableBandwidthResponse {
available_bandwidth,
}
.try_into()?)
}
ResponseData::TopUpBandwidth(available_bandwidth) => Ok(InnerTopUpResponse {
available_bandwidth,
}
.try_into()?),
}
}
}
impl Extract<ResponseData> for VersionedResponse {
fn extract(&self) -> Result<(ResponseData, Version), Error> {
match self.query_type {
QueryType::AvailableBandwidth => {
let resp = InnerAvailableBandwidthResponse::try_from(self.clone())?;
Ok((
ResponseData::AvailableBandwidth(resp.available_bandwidth),
VERSION,
))
}
QueryType::TopupBandwidth => {
let resp = InnerTopUpResponse::try_from(self.clone())?;
Ok((
ResponseData::TopUpBandwidth(resp.available_bandwidth),
VERSION,
))
}
}
}
}
// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions
#[cfg(feature = "testing")]
impl TryFrom<previous::interface::RequestData> for RequestData {
type Error = super::Error;
fn try_from(value: previous::interface::RequestData) -> Result<Self, Self::Error> {
match value {
previous::interface::RequestData::AvailableBandwidth(inner) => {
Ok(Self::AvailableBandwidth(inner))
}
previous::interface::RequestData::TopUpBandwidth(_) => {
Err(super::Error::UpdateNotPossible {
from: previous::VERSION,
to: VERSION,
})
}
}
}
}
// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions
#[cfg(feature = "testing")]
impl TryFrom<RequestData> for previous::interface::RequestData {
type Error = super::Error;
fn try_from(value: RequestData) -> Result<Self, Self::Error> {
match value {
RequestData::AvailableBandwidth(inner) => Ok(Self::AvailableBandwidth(inner)),
RequestData::TopUpBandwidth(_) => Ok(Self::TopUpBandwidth(())),
}
}
}
// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions
#[cfg(feature = "testing")]
impl TryFrom<previous::interface::ResponseData> for ResponseData {
type Error = super::Error;
fn try_from(value: previous::interface::ResponseData) -> Result<Self, Self::Error> {
match value {
previous::interface::ResponseData::AvailableBandwidth(_) => {
Err(super::Error::UpdateNotPossible {
from: previous::VERSION,
to: VERSION,
})
}
previous::interface::ResponseData::TopUpBandwidth(_) => {
Err(super::Error::UpdateNotPossible {
from: previous::VERSION,
to: VERSION,
})
}
}
}
}
// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions
#[cfg(feature = "testing")]
impl TryFrom<ResponseData> for previous::interface::ResponseData {
type Error = super::Error;
fn try_from(value: ResponseData) -> Result<Self, Self::Error> {
match value {
ResponseData::AvailableBandwidth(_) => Ok(Self::AvailableBandwidth(())),
ResponseData::TopUpBandwidth(_) => Ok(Self::TopUpBandwidth(())),
}
}
}
#[cfg(test)]
mod test {
use crate::models::tests::CREDENTIAL_BYTES;
use super::*;
#[test]
fn request_upgrade() {
assert_eq!(
RequestData::try_from(previous::interface::RequestData::AvailableBandwidth(()))
.unwrap(),
RequestData::AvailableBandwidth(())
);
assert!(
RequestData::try_from(previous::interface::RequestData::TopUpBandwidth(())).is_err(),
);
}
#[test]
fn response_upgrade() {
assert!(
ResponseData::try_from(previous::interface::ResponseData::AvailableBandwidth(()))
.is_err()
);
assert!(
ResponseData::try_from(previous::interface::ResponseData::TopUpBandwidth(())).is_err()
);
}
#[test]
fn request_downgrade() {
assert_eq!(
previous::interface::RequestData::try_from(RequestData::AvailableBandwidth(()))
.unwrap(),
previous::interface::RequestData::AvailableBandwidth(())
);
assert_eq!(
previous::interface::RequestData::try_from(RequestData::TopUpBandwidth(Box::new(
CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()
)))
.unwrap(),
previous::interface::RequestData::TopUpBandwidth(())
);
}
#[test]
fn response_downgrade() {
assert_eq!(
previous::interface::ResponseData::try_from(ResponseData::AvailableBandwidth(42))
.unwrap(),
previous::interface::ResponseData::AvailableBandwidth(())
);
assert_eq!(
previous::interface::ResponseData::try_from(ResponseData::TopUpBandwidth(42)).unwrap(),
previous::interface::ResponseData::TopUpBandwidth(())
);
}
}
@@ -0,0 +1,224 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::Display;
use bincode::Options;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use super::error::Error;
use crate::{
make_bincode_serializer,
models::{Request, Response, Version},
};
pub use available_bandwidth::{
request::InnerAvailableBandwidthRequest as AvailableBandwidthRequest,
response::InnerAvailableBandwidthResponse as AvailableBandwidthResponse,
};
pub use topup_bandwidth::{
request::InnerTopUpRequest as TopUpRequest, response::InnerTopUpResponse as TopUpResponse,
};
pub(crate) mod available_bandwidth;
pub mod interface;
pub(crate) mod topup_bandwidth;
pub const VERSION: Version = Version::V1;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub enum QueryType {
AvailableBandwidth,
TopupBandwidth,
}
impl Display for QueryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct VersionedRequest {
query_type: QueryType,
inner: Vec<u8>,
}
impl TryFrom<VersionedRequest> for Request {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
Ok(Request {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for VersionedRequest {
type Error = Error;
fn try_from(value: Request) -> Result<Self, Self::Error> {
if value.version != VERSION {
return Err(Error::InvalidVersion {
source_version: value.version,
target_version: VERSION,
});
}
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct VersionedResponse {
query_type: QueryType,
inner: Vec<u8>,
}
impl TryFrom<VersionedResponse> for Response {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
Ok(Response {
version: VERSION,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for VersionedResponse {
type Error = Error;
fn try_from(value: Response) -> Result<Self, Self::Error> {
if value.version != VERSION {
return Err(Error::InvalidVersion {
source_version: value.version,
target_version: VERSION,
});
}
Ok(make_bincode_serializer().deserialize(&value.inner)?)
}
}
#[cfg(test)]
mod tests {
use nym_credentials_interface::CredentialSpendingData;
use crate::models::tests::CREDENTIAL_BYTES;
use self::{
available_bandwidth::{
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
},
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
};
use super::*;
#[test]
fn mismatched_request_version() {
let version = Version::V0;
let future_bw = Request {
version,
inner: vec![],
};
if let Err(Error::InvalidVersion {
source_version,
target_version,
}) = VersionedRequest::try_from(future_bw)
{
assert_eq!(source_version, version);
assert_eq!(target_version, VERSION);
} else {
panic!("failed");
};
}
#[test]
fn mismatched_response_version() {
let version = Version::V0;
let future_bw = Response {
version,
inner: vec![],
};
if let Err(Error::InvalidVersion {
source_version,
target_version,
}) = VersionedResponse::try_from(future_bw)
{
assert_eq!(source_version, version);
assert_eq!(target_version, VERSION);
} else {
panic!("failed");
};
}
#[test]
fn serde_request_av_bw() {
let req = VersionedRequest {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer()
.serialize(&InnerAvailableBandwidthResponse {
available_bandwidth: 42,
})
.unwrap(),
};
let ser = Request::try_from(req.clone()).unwrap();
assert_eq!(VERSION, ser.version);
let de = VersionedRequest::try_from(ser).unwrap();
assert_eq!(req, de);
}
#[test]
fn serde_response_av_bw() {
let resp = VersionedResponse {
query_type: QueryType::AvailableBandwidth,
inner: make_bincode_serializer()
.serialize(&InnerAvailableBandwidthRequest {})
.unwrap(),
};
let ser = Response::try_from(resp.clone()).unwrap();
assert_eq!(VERSION, ser.version);
let de = VersionedResponse::try_from(ser).unwrap();
assert_eq!(resp, de);
}
#[test]
fn serde_request_topup() {
let req = VersionedRequest {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer()
.serialize(&InnerTopUpRequest {
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
})
.unwrap(),
};
let ser = Request::try_from(req.clone()).unwrap();
assert_eq!(VERSION, ser.version);
let de = VersionedRequest::try_from(ser).unwrap();
assert_eq!(req, de);
}
#[test]
fn serde_response_topup() {
let resp = VersionedResponse {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer()
.serialize(&InnerTopUpResponse {
available_bandwidth: 42,
})
.unwrap(),
};
let ser = Response::try_from(resp.clone()).unwrap();
assert_eq!(VERSION, ser.version);
let de = VersionedResponse::try_from(ser).unwrap();
assert_eq!(resp, de);
}
}
@@ -0,0 +1,5 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod request;
pub mod response;
@@ -0,0 +1,92 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use nym_credentials_interface::CredentialSpendingData;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, models::Request};
use super::super::{Error, QueryType, VersionedRequest};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InnerTopUpRequest {
/// Ecash credential
pub credential: CredentialSpendingData,
}
impl TryFrom<VersionedRequest> for InnerTopUpRequest {
type Error = Error;
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?),
QueryType::AvailableBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::TopupBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerTopUpRequest> for VersionedRequest {
type Error = Error;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Request> for InnerTopUpRequest {
type Error = crate::error::MetadataError;
fn try_from(value: Request) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerTopUpRequest> for Request {
type Error = crate::error::MetadataError;
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
VersionedRequest::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use crate::models::tests::CREDENTIAL_BYTES;
use super::*;
#[test]
fn serde() {
let req = InnerTopUpRequest {
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
};
let ser = VersionedRequest::try_from(req.clone()).unwrap();
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
let de = InnerTopUpRequest::try_from(ser).unwrap();
assert_eq!(req, de);
}
#[test]
fn invalid_content() {
let future_req = VersionedRequest {
query_type: QueryType::TopupBandwidth,
inner: vec![],
};
assert!(InnerTopUpRequest::try_from(future_req).is_err());
}
}
@@ -0,0 +1,88 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bincode::Options;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, models::Response};
use super::super::{Error, QueryType, VersionedResponse};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct InnerTopUpResponse {
pub available_bandwidth: i64,
}
impl TryFrom<VersionedResponse> for InnerTopUpResponse {
type Error = Error;
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
match value.query_type {
QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?),
QueryType::AvailableBandwidth => Err(Error::InvalidQueryType {
source_query_type: value.query_type.to_string(),
target_query_type: QueryType::TopupBandwidth.to_string(),
}),
}
}
}
impl TryFrom<InnerTopUpResponse> for VersionedResponse {
type Error = Error;
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
Ok(Self {
query_type: QueryType::TopupBandwidth,
inner: make_bincode_serializer().serialize(&value)?,
})
}
}
impl TryFrom<Response> for InnerTopUpResponse {
type Error = crate::error::MetadataError;
fn try_from(value: Response) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
impl TryFrom<InnerTopUpResponse> for Response {
type Error = crate::error::MetadataError;
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
VersionedResponse::try_from(value)?
.try_into()
.map_err(|err: Error| crate::error::MetadataError::Models {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
let resp = InnerTopUpResponse {
available_bandwidth: 42,
};
let ser = VersionedResponse::try_from(resp.clone()).unwrap();
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
let de = InnerTopUpResponse::try_from(ser).unwrap();
assert_eq!(resp, de);
}
#[test]
fn invalid_content() {
let future_resp = VersionedResponse {
query_type: QueryType::TopupBandwidth,
inner: vec![],
};
assert!(InnerTopUpResponse::try_from(future_resp).is_err());
}
}
@@ -0,0 +1,10 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const V1_API_VERSION: &str = "v1";
pub const BANDWIDTH: &str = "bandwidth";
pub const VERSION: &str = "version";
pub const AVAILABLE: &str = "available";
pub const TOPUP: &str = "topup";
@@ -0,0 +1,41 @@
[package]
name = "nym-wireguard-private-metadata-tests"
version = "1.0.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
async-trait = { workspace = true }
axum = { workspace = true, features = ["tokio", "macros"] }
nym-credential-verification = { path = "../../credential-verification" }
nym-credentials-interface = { path = "../../credentials-interface" }
nym-http-api-client = { path = "../../http-api-client" }
nym-http-api-common = { path = "../../http-api-common", features = [
"middleware",
"utoipa",
"output",
] }
nym-wireguard = { path = "../../wireguard" }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
tower-http = { workspace = true, features = [
"cors",
"trace",
"compression-br",
"compression-deflate",
"compression-gzip",
"compression-zstd",
] }
utoipa = { workspace = true, features = ["axum_extras", "time"] }
nym-wireguard-private-metadata-client = { path = "../client" }
nym-wireguard-private-metadata-shared = { path = "../shared", features = [
"testing",
] }
nym-wireguard-private-metadata-server = { path = "../server" }
[lints]
workspace = true
@@ -0,0 +1,217 @@
#[cfg(test)]
mod v0;
#[cfg(test)]
mod tests {
use std::net::SocketAddr;
use nym_credential_verification::{ClientBandwidth, TicketVerifier};
use nym_credentials_interface::CredentialSpendingData;
use nym_http_api_client::Client;
use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE};
use nym_wireguard_private_metadata_client::WireguardMetadataApiClient;
use nym_wireguard_private_metadata_server::{
AppState, PeerControllerTransceiver, RouterBuilder,
};
use nym_wireguard_private_metadata_shared::{latest, v0, v1, ErrorResponse};
use tokio::{net::TcpListener, sync::mpsc};
pub(crate) const VERIFIER_AVAILABLE_BANDWIDTH: i64 = 42;
pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [
0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254,
16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139,
154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123,
35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1,
151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43,
90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193,
39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81,
184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195,
196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48,
92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48,
166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186,
19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38,
228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85,
88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241,
85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112,
48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31,
97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203,
71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107,
213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180,
178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1,
96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166,
30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212,
207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71,
223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22,
119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131,
59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119,
240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202,
58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144,
189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33,
30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150,
74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6,
227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85,
15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38,
161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20,
47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48,
135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170,
150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109,
55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249,
87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144,
178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32,
203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184,
96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169,
24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61,
130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82,
108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1,
32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234,
150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241,
203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217,
160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52,
147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81,
70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233,
178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98,
110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69,
131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251,
197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233,
162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106,
83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145,
192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124,
55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0,
0, 0, 0, 0, 0, 1,
];
pub(crate) struct MockVerifier {
ret: i64,
}
impl MockVerifier {
pub(crate) fn new(ret: i64) -> MockVerifier {
Self { ret }
}
}
#[async_trait::async_trait]
impl TicketVerifier for MockVerifier {
async fn verify(&mut self) -> nym_credential_verification::Result<i64> {
Ok(self.ret)
}
}
pub(crate) async fn spawn_server_and_create_client() -> Client {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let router = RouterBuilder::with_default_routes()
.with_state(AppState::new(PeerControllerTransceiver::new(request_tx)))
.router;
tokio::spawn(async move {
loop {
match request_rx.recv().await {
Some(PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx }) => {
response_tx
.send(Ok(ClientBandwidth::new(Default::default())))
.ok();
}
Some(PeerControlRequest::GetVerifierByIp {
ip: _,
credential: _,
response_tx,
}) => {
response_tx
.send(Ok(Box::new(MockVerifier::new(
VERIFIER_AVAILABLE_BANDWIDTH,
))))
.ok();
}
None => break,
_ => panic!("Not expected"),
}
}
});
tokio::spawn(async move {
axum::serve(
listener,
router.into_make_service_with_connect_info::<SocketAddr>(),
)
.await
.unwrap();
});
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
}
#[tokio::test]
async fn query_latest_version() {
let client = spawn_server_and_create_client().await;
let version = client.version().await.unwrap();
assert_eq!(version, latest::VERSION);
}
#[tokio::test]
async fn query_against_server_v0() {
let client = super::v0::network::test::spawn_server_and_create_client().await;
// version check
let version = client.version().await.unwrap();
assert_eq!(version, v0::VERSION);
// v0 reqwests
let request = v0::AvailableBandwidthRequest {}.try_into().unwrap();
let response = client.available_bandwidth(&request).await.unwrap();
v0::AvailableBandwidthResponse::try_from(response).unwrap();
let request = v0::TopUpRequest {}.try_into().unwrap();
let response = client.topup_bandwidth(&request).await.unwrap();
v0::TopUpResponse::try_from(response).unwrap();
// v1 reqwests
let request = v1::AvailableBandwidthRequest {}.try_into().unwrap();
assert!(client.available_bandwidth(&request).await.is_err());
let request = v1::TopUpRequest {
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
}
.try_into()
.unwrap();
assert!(client.topup_bandwidth(&request).await.is_err());
}
#[tokio::test]
async fn query_against_server_v1() {
let client = spawn_server_and_create_client().await;
// version check
let version = client.version().await.unwrap();
assert_eq!(version, v1::VERSION);
// v0 reqwests
let request = v0::AvailableBandwidthRequest {}.try_into().unwrap();
let response = client.available_bandwidth(&request).await.unwrap();
v0::AvailableBandwidthResponse::try_from(response).unwrap();
let request = v0::TopUpRequest {}.try_into().unwrap();
assert!(client.topup_bandwidth(&request).await.is_err());
// v1 reqwests
let request = v1::AvailableBandwidthRequest {}.try_into().unwrap();
let response = client.available_bandwidth(&request).await.unwrap();
let available_bandwidth = v1::AvailableBandwidthResponse::try_from(response)
.unwrap()
.available_bandwidth;
assert_eq!(available_bandwidth, 0);
let request = v1::TopUpRequest {
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
}
.try_into()
.unwrap();
let response = client.topup_bandwidth(&request).await.unwrap();
let available_bandwidth = v1::TopUpResponse::try_from(response)
.unwrap()
.available_bandwidth;
assert_eq!(available_bandwidth, VERIFIER_AVAILABLE_BANDWIDTH);
}
}
@@ -0,0 +1,150 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_wireguard_private_metadata_shared::{
v0 as latest, Construct, Extract, Request, Response, Version,
};
pub enum RequestData {
AvailableBandwidth(()),
TopUpBandwidth(()),
}
impl From<latest::interface::RequestData> for RequestData {
fn from(value: latest::interface::RequestData) -> Self {
match value {
latest::interface::RequestData::AvailableBandwidth(inner) => {
Self::AvailableBandwidth(inner)
}
latest::interface::RequestData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
impl From<RequestData> for latest::interface::RequestData {
fn from(value: RequestData) -> Self {
match value {
RequestData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
RequestData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
impl From<latest::interface::ResponseData> for ResponseData {
fn from(value: latest::interface::ResponseData) -> Self {
match value {
latest::interface::ResponseData::AvailableBandwidth(inner) => {
Self::AvailableBandwidth(inner)
}
latest::interface::ResponseData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
impl From<ResponseData> for latest::interface::ResponseData {
fn from(value: ResponseData) -> Self {
match value {
ResponseData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
ResponseData::TopUpBandwidth(credential_spending_data) => {
Self::TopUpBandwidth(credential_spending_data)
}
}
}
}
impl Construct<RequestData> for Request {
fn construct(
info: RequestData,
version: Version,
) -> Result<Self, nym_wireguard_private_metadata_shared::ModelError> {
match version {
Version::V0 => {
let translate_info = latest::interface::RequestData::from(info);
let versioned_request =
latest::VersionedRequest::construct(translate_info, latest::VERSION)?;
Ok(versioned_request.try_into()?)
}
_ => Err(
nym_wireguard_private_metadata_shared::ModelError::DowngradeNotPossible {
from: version,
to: Version::V0,
},
),
}
}
}
impl Extract<RequestData> for Request {
fn extract(
&self,
) -> Result<(RequestData, Version), nym_wireguard_private_metadata_shared::ModelError> {
match self.version {
Version::V0 => {
let versioned_request = latest::VersionedRequest::try_from(self.clone())?;
let (request, version) = versioned_request.extract()?;
Ok((request.into(), version))
}
_ => Err(
nym_wireguard_private_metadata_shared::ModelError::UpdateNotPossible {
from: self.version,
to: Version::V0,
},
),
}
}
}
pub enum ResponseData {
AvailableBandwidth(()),
TopUpBandwidth(()),
}
impl Construct<ResponseData> for Response {
fn construct(
info: ResponseData,
version: Version,
) -> Result<Self, nym_wireguard_private_metadata_shared::ModelError> {
match version {
Version::V0 => {
let translate_response = latest::interface::ResponseData::from(info);
let versioned_response =
latest::VersionedResponse::construct(translate_response, version)?;
Ok(versioned_response.try_into()?)
}
_ => Err(
nym_wireguard_private_metadata_shared::ModelError::DowngradeNotPossible {
from: version,
to: Version::V0,
},
),
}
}
}
impl Extract<ResponseData> for Response {
fn extract(
&self,
) -> Result<(ResponseData, Version), nym_wireguard_private_metadata_shared::ModelError> {
match self.version {
Version::V0 => {
let versioned_response = latest::VersionedResponse::try_from(self.clone())?;
let (response, version) = versioned_response.extract()?;
Ok((response.into(), version))
}
_ => Err(
nym_wireguard_private_metadata_shared::ModelError::UpdateNotPossible {
from: self.version,
to: Version::V0,
},
),
}
}
}
@@ -0,0 +1,2 @@
pub(crate) mod interface;
pub(crate) mod network;
@@ -0,0 +1,146 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub(crate) mod test {
use std::net::SocketAddr;
use crate::{
tests::{MockVerifier, VERIFIER_AVAILABLE_BANDWIDTH},
v0::interface::{RequestData, ResponseData},
};
use axum::{extract::Query, Json, Router};
use nym_credential_verification::ClientBandwidth;
use nym_http_api_client::Client;
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE};
use nym_wireguard_private_metadata_server::PeerControllerTransceiver;
use nym_wireguard_private_metadata_shared::ErrorResponse;
use nym_wireguard_private_metadata_shared::{
v0 as latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
};
use tokio::{net::TcpListener, sync::mpsc};
use tower_http::compression::CompressionLayer;
use nym_wireguard_private_metadata_server::AppState;
fn bandwidth_routes() -> Router<AppState> {
Router::new()
.route("/version", axum::routing::get(version))
.route("/available", axum::routing::post(available_bandwidth))
.route("/topup", axum::routing::post(topup_bandwidth))
.layer(CompressionLayer::new())
}
#[utoipa::path(
tag = "bandwidth",
get,
path = "/v1/bandwidth/version",
responses(
(status = 200, content(
(Response = "application/bincode")
))
),
)]
async fn version(Query(output): Query<OutputParams>) -> AxumResult<FormattedResponse<u64>> {
let output = output.output.unwrap_or_default();
Ok(output.to_response(latest::VERSION.into()))
}
#[utoipa::path(
tag = "bandwidth",
post,
request_body = Request,
path = "/v1/bandwidth/available",
responses(
(status = 200, content(
(Response = "application/bincode")
))
),
)]
async fn available_bandwidth(
Query(output): Query<OutputParams>,
Json(request): Json<Request>,
) -> AxumResult<FormattedResponse<Response>> {
let output = output.output.unwrap_or_default();
let (RequestData::AvailableBandwidth(_), version) =
request.extract().map_err(AxumErrorResponse::bad_request)?
else {
return Err(AxumErrorResponse::bad_request("incorrect request type"));
};
let response = Response::construct(ResponseData::AvailableBandwidth(()), version)
.map_err(AxumErrorResponse::bad_request)?;
Ok(output.to_response(response))
}
#[utoipa::path(
tag = "bandwidth",
post,
request_body = Request,
path = "/v1/bandwidth/topup",
responses(
(status = 200, content(
(Response = "application/bincode")
))
),
)]
async fn topup_bandwidth(
Query(output): Query<OutputParams>,
Json(request): Json<Request>,
) -> AxumResult<FormattedResponse<Response>> {
let output = output.output.unwrap_or_default();
let (RequestData::TopUpBandwidth(_), version) =
request.extract().map_err(AxumErrorResponse::bad_request)?
else {
return Err(AxumErrorResponse::bad_request("incorrect request type"));
};
let response = Response::construct(ResponseData::TopUpBandwidth(()), version)
.map_err(AxumErrorResponse::bad_request)?;
Ok(output.to_response(response))
}
pub(crate) async fn spawn_server_and_create_client() -> Client {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
let router = Router::new()
.nest("/v1", Router::new().nest("/bandwidth", bandwidth_routes()))
.with_state(AppState::new(PeerControllerTransceiver::new(request_tx)));
tokio::spawn(async move {
match request_rx.recv().await.unwrap() {
PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => {
response_tx
.send(Ok(ClientBandwidth::new(Default::default())))
.ok();
}
PeerControlRequest::GetVerifierByIp {
ip: _,
credential: _,
response_tx,
} => {
response_tx
.send(Ok(Box::new(MockVerifier::new(
VERIFIER_AVAILABLE_BANDWIDTH,
))))
.ok();
}
_ => panic!("Not expected"),
}
});
tokio::spawn(async move {
axum::serve(
listener,
router.into_make_service_with_connect_info::<SocketAddr>(),
)
.await
.unwrap();
});
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
}
}
+6 -2
View File
@@ -17,9 +17,13 @@ pub struct Config {
/// default: `fc01::1`
pub private_ipv6: Ipv6Addr,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
/// Useful in the instances where the node is behind a proxy.
pub announced_port: u16,
pub announced_tunnel_port: u16,
/// Metadata port announced to external clients wishing to connect to the endpoint.
/// Useful in the instances where the node is behind a proxy.
pub announced_metadata_port: u16,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
/// The maximum value for IPv4 is 32
+15 -6
View File
@@ -18,11 +18,13 @@ use tokio::sync::mpsc::{self, Receiver, Sender};
#[cfg(target_os = "linux")]
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
pub(crate) mod error;
pub mod error;
pub mod peer_controller;
pub mod peer_handle;
pub mod peer_storage_manager;
pub const CONTROL_CHANNEL_SIZE: usize = 256;
pub struct WgApiWrapper {
inner: WGApi,
}
@@ -126,7 +128,7 @@ pub struct WireguardGatewayData {
impl WireguardGatewayData {
pub fn new(config: Config, keypair: Arc<KeyPair>) -> (Self, Receiver<PeerControlRequest>) {
let (peer_tx, peer_rx) = mpsc::channel(1);
let (peer_tx, peer_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
(
WireguardGatewayData {
config,
@@ -178,10 +180,16 @@ pub async fn start_wireguard(
let mut peer_bandwidth_managers = HashMap::with_capacity(peers.len());
for peer in peers.iter() {
let bandwidth_manager = Arc::new(RwLock::new(
PeerController::generate_bandwidth_manager(ecash_manager.storage(), &peer.public_key)
let bandwidth_manager = peer_handle::SharedBandwidthStorageManager::new(
Arc::new(RwLock::new(
PeerController::generate_bandwidth_manager(
ecash_manager.storage(),
&peer.public_key,
)
.await?,
));
)),
peer.allowed_ips.clone(),
);
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
}
@@ -190,7 +198,7 @@ pub async fn start_wireguard(
name: ifname.clone(),
prvkey: BASE64_STANDARD.encode(wireguard_data.inner.keypair().private_key().to_bytes()),
address: wireguard_data.inner.config().private_ipv4.to_string(),
port: wireguard_data.inner.config().announced_port as u32,
port: wireguard_data.inner.config().announced_tunnel_port as u32,
peers,
mtu: None,
};
@@ -233,6 +241,7 @@ pub async fn start_wireguard(
let host = wg_api.read_interface_data()?;
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
let mut controller = PeerController::new(
ecash_manager,
metrics,
+84 -23
View File
@@ -10,15 +10,18 @@ use futures::channel::oneshot;
use log::info;
use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, ecash::traits::EcashManager,
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier,
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, TicketVerifier,
};
use nym_credentials_interface::CredentialSpendingData;
use nym_gateway_requests::models::CredentialSpendingRequest;
use nym_gateway_storage::traits::BandwidthGatewayStorage;
use nym_node_metrics::NymNodeMetrics;
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
use std::time::{Duration, SystemTime};
use std::{collections::HashMap, sync::Arc};
use std::{
net::IpAddr,
time::{Duration, SystemTime},
};
use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
@@ -41,22 +44,31 @@ pub enum PeerControlRequest {
key: Key,
response_tx: oneshot::Sender<QueryPeerControlResponse>,
},
GetClientBandwidth {
GetClientBandwidthByKey {
key: Key,
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
},
GetVerifier {
GetClientBandwidthByIp {
ip: IpAddr,
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
},
GetVerifierByKey {
key: Key,
credential: Box<CredentialSpendingData>,
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
},
GetVerifierByIp {
ip: IpAddr,
credential: Box<CredentialSpendingData>,
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
},
}
pub type AddPeerControlResponse = Result<()>;
pub type RemovePeerControlResponse = Result<()>;
pub type QueryPeerControlResponse = Result<Option<Peer>>;
pub type GetClientBandwidthControlResponse = Result<ClientBandwidth>;
pub type QueryVerifierControlResponse = Result<CredentialVerifier>;
pub type QueryVerifierControlResponse = Result<Box<dyn TicketVerifier + Send + Sync>>;
pub struct PeerController {
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
@@ -77,7 +89,7 @@ pub struct PeerController {
impl PeerController {
#[allow(clippy::too_many_arguments)]
pub fn new(
pub(crate) fn new(
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
metrics: NymNodeMetrics,
wg_api: Arc<dyn WireguardInterfaceApi + Send + Sync>,
@@ -165,10 +177,13 @@ impl PeerController {
async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> {
self.wg_api.configure_peer(peer)?;
let bandwidth_storage_manager = Arc::new(RwLock::new(
Self::generate_bandwidth_manager(self.ecash_verifier.storage(), &peer.public_key)
.await?,
));
let bandwidth_storage_manager = SharedBandwidthStorageManager::new(
Arc::new(RwLock::new(
Self::generate_bandwidth_manager(self.ecash_verifier.storage(), &peer.public_key)
.await?,
)),
peer.allowed_ips.clone(),
);
let cached_peer_manager = CachedPeerManager::new(peer);
let mut handle = PeerHandle::new(
peer.public_key.clone(),
@@ -192,7 +207,20 @@ impl PeerController {
Ok(())
}
async fn handle_query_peer(&self, key: &Key) -> Result<Option<Peer>> {
async fn ip_to_key(&self, ip: IpAddr) -> Result<Option<Key>> {
Ok(self
.bw_storage_managers
.iter()
.find_map(|(key, bw_manager)| {
bw_manager
.allowed_ips()
.iter()
.find(|ip_mask| ip_mask.ip == ip)
.and(Some(key.clone()))
}))
}
async fn handle_query_peer_by_key(&self, key: &Key) -> Result<Option<Peer>> {
Ok(self
.ecash_verifier
.storage()
@@ -202,20 +230,32 @@ impl PeerController {
.transpose()?)
}
async fn handle_get_client_bandwidth(&self, key: &Key) -> Result<ClientBandwidth> {
async fn handle_get_client_bandwidth_by_key(&self, key: &Key) -> Result<ClientBandwidth> {
let bandwidth_storage_manager = self
.bw_storage_managers
.get(key)
.ok_or(Error::MissingClientBandwidthEntry)?;
Ok(bandwidth_storage_manager.read().await.client_bandwidth())
Ok(bandwidth_storage_manager
.inner()
.read()
.await
.client_bandwidth())
}
async fn handle_query_verifier(
async fn handle_get_client_bandwidth_by_ip(&self, ip: IpAddr) -> Result<ClientBandwidth> {
let Some(key) = self.ip_to_key(ip).await? else {
return Err(Error::MissingClientKernelEntry(ip.to_string()));
};
self.handle_get_client_bandwidth_by_key(&key).await
}
async fn handle_query_verifier_by_key(
&self,
key: &Key,
credential: CredentialSpendingData,
) -> Result<CredentialVerifier> {
) -> Result<Box<dyn TicketVerifier + Send + Sync>> {
let storage = self.ecash_verifier.storage();
let client_id = storage
.get_wireguard_peer(&key.to_string())
@@ -225,7 +265,11 @@ impl PeerController {
let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) else {
return Err(Error::MissingClientBandwidthEntry);
};
let client_bandwidth = bandwidth_storage_manager.read().await.client_bandwidth();
let client_bandwidth = bandwidth_storage_manager
.inner()
.read()
.await
.client_bandwidth();
let verifier = CredentialVerifier::new(
CredentialSpendingRequest::new(credential),
self.ecash_verifier.clone(),
@@ -237,7 +281,19 @@ impl PeerController {
true,
),
);
Ok(verifier)
Ok(Box::new(verifier))
}
async fn handle_query_verifier_by_ip(
&self,
ip: IpAddr,
credential: CredentialSpendingData,
) -> Result<Box<dyn TicketVerifier + Send + Sync>> {
let Some(key) = self.ip_to_key(ip).await? else {
return Err(Error::MissingClientKernelEntry(ip.to_string()));
};
self.handle_query_verifier_by_key(&key, credential).await
}
async fn update_metrics(&self, new_host: &Host) {
@@ -340,18 +396,23 @@ impl PeerController {
response_tx.send(self.remove_peer(&key).await).ok();
}
Some(PeerControlRequest::QueryPeer { key, response_tx }) => {
response_tx.send(self.handle_query_peer(&key).await).ok();
response_tx.send(self.handle_query_peer_by_key(&key).await).ok();
}
Some(PeerControlRequest::GetClientBandwidth { key, response_tx }) => {
response_tx.send(self.handle_get_client_bandwidth(&key).await).ok();
Some(PeerControlRequest::GetClientBandwidthByKey { key, response_tx }) => {
response_tx.send(self.handle_get_client_bandwidth_by_key(&key).await).ok();
}
Some(PeerControlRequest::GetVerifier { key, credential, response_tx }) => {
response_tx.send(self.handle_query_verifier(&key, *credential).await).ok();
Some(PeerControlRequest::GetClientBandwidthByIp { ip, response_tx }) => {
response_tx.send(self.handle_get_client_bandwidth_by_ip(ip).await).ok();
}
Some(PeerControlRequest::GetVerifierByKey { key, credential, response_tx }) => {
response_tx.send(self.handle_query_verifier_by_key(&key, *credential).await).ok();
}
Some(PeerControlRequest::GetVerifierByIp { ip, credential, response_tx }) => {
response_tx.send(self.handle_query_verifier_by_ip(ip, *credential).await).ok();
}
None => {
log::trace!("PeerController [main loop]: stopping since channel closed");
break;
}
}
}
+26 -4
View File
@@ -4,7 +4,7 @@
use crate::error::Error;
use crate::peer_controller::PeerControlRequest;
use crate::peer_storage_manager::{CachedPeerManager, PeerInformation};
use defguard_wireguard_rs::{host::Host, key::Key};
use defguard_wireguard_rs::{host::Host, key::Key, net::IpAddrMask};
use futures::channel::oneshot;
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
use nym_task::TaskClient;
@@ -13,7 +13,28 @@ use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
pub(crate) type SharedBandwidthStorageManager = Arc<RwLock<BandwidthStorageManager>>;
#[derive(Clone)]
pub(crate) struct SharedBandwidthStorageManager {
inner: Arc<RwLock<BandwidthStorageManager>>,
allowed_ips: Vec<IpAddrMask>,
}
impl SharedBandwidthStorageManager {
pub(crate) fn new(
inner: Arc<RwLock<BandwidthStorageManager>>,
allowed_ips: Vec<IpAddrMask>,
) -> Self {
Self { inner, allowed_ips }
}
pub(crate) fn inner(&self) -> &RwLock<BandwidthStorageManager> {
&self.inner
}
pub(crate) fn allowed_ips(&self) -> &[IpAddrMask] {
&self.allowed_ips
}
}
pub struct PeerHandle {
public_key: Key,
@@ -26,7 +47,7 @@ pub struct PeerHandle {
}
impl PeerHandle {
pub fn new(
pub(crate) fn new(
public_key: Key,
host_information: Arc<RwLock<Host>>,
cached_peer: CachedPeerManager,
@@ -120,6 +141,7 @@ impl PeerHandle {
if spent_bandwidth > 0
&& self
.bandwidth_storage_manager
.inner()
.write()
.await
.try_use_bandwidth(spent_bandwidth)
@@ -182,7 +204,7 @@ impl PeerHandle {
_ = self.task_client.recv() => {
log::trace!("PeerHandle: Received shutdown");
if let Err(e) = self.bandwidth_storage_manager.write().await.sync_storage_bandwidth().await {
if let Err(e) = self.bandwidth_storage_manager.inner().write().await.sync_storage_bandwidth().await {
log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed");
}
@@ -58,8 +58,8 @@ Options:
Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false]
--wireguard-bind-address <WIREGUARD_BIND_ADDRESS>
Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=]
--wireguard-announced-port <WIREGUARD_ANNOUNCED_PORT>
Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
--wireguard-tunnel-announced-port <WIREGUARD_TUNNEL_ANNOUNCED_PORT>
Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
--wireguard-private-network-prefix <WIREGUARD_PRIVATE_NETWORK_PREFIX>
The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=]
--verloc-bind-address <VERLOC_BIND_ADDRESS>
+1
View File
@@ -72,6 +72,7 @@ nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
nym-node-metrics = { path = "../nym-node/nym-node-metrics" }
nym-wireguard = { path = "../common/wireguard" }
nym-wireguard-private-metadata-server = { path = "../common/wireguard-private-metadata/server" }
nym-wireguard-types = { path = "../common/wireguard-types", default-features = false }
nym-authenticator-requests = { path = "../common/authenticator-requests" }
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use nym_network_defaults::{
WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, WG_TUN_DEVICE_NETMASK_V4,
WG_TUN_DEVICE_NETMASK_V6,
WG_METADATA_PORT, WG_TUNNEL_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6,
WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6,
};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
@@ -46,9 +46,9 @@ pub struct Authenticator {
/// default: `fc01::1`
pub private_ipv6: Ipv6Addr,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
/// Useful in the instances where the node is behind a proxy.
pub announced_port: u16,
pub tunnel_announced_port: u16,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
/// The maximum value for IPv4 is 32
@@ -62,10 +62,10 @@ pub struct Authenticator {
impl Default for Authenticator {
fn default() -> Self {
Self {
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT),
private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4,
private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6,
announced_port: WG_PORT,
tunnel_announced_port: WG_TUNNEL_PORT,
private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4,
private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6,
}
@@ -78,7 +78,8 @@ impl From<Authenticator> for nym_wireguard_types::Config {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
announced_port: value.announced_port,
announced_tunnel_port: value.tunnel_announced_port,
announced_metadata_port: WG_METADATA_PORT,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
@@ -291,7 +291,7 @@ impl MixnetListener {
v1::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
@@ -304,7 +304,7 @@ impl MixnetListener {
v2::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
@@ -317,7 +317,7 @@ impl MixnetListener {
v3::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
@@ -330,7 +330,7 @@ impl MixnetListener {
v4::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ips: (allowed_ipv4, allowed_ipv6).into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
request_id,
@@ -343,7 +343,7 @@ impl MixnetListener {
v5::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ips: (allowed_ipv4, allowed_ipv6).into(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
},
request_id,
)
@@ -374,7 +374,7 @@ impl MixnetListener {
let registration_data = RegistrationData {
nonce,
gateway_data: gateway_data.clone(),
wg_port: self.config.authenticator.announced_port,
wg_port: self.config.authenticator.tunnel_announced_port,
};
registred_and_free
.registration_in_progres
@@ -691,7 +691,7 @@ impl MixnetListener {
} else {
let mut verifier = self
.peer_manager
.query_verifier(msg.pub_key(), msg.credential())
.query_verifier_by_key(msg.pub_key(), msg.credential())
.await?;
let available_bandwidth = verifier.verify().await?;
self.seen_credential_cache
@@ -4,7 +4,7 @@
use crate::node::internal_service_providers::authenticator::error::AuthenticatorError;
use defguard_wireguard_rs::{host::Peer, key::Key};
use futures::channel::oneshot;
use nym_credential_verification::{ClientBandwidth, CredentialVerifier};
use nym_credential_verification::{ClientBandwidth, TicketVerifier};
use nym_credentials_interface::CredentialSpendingData;
use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData};
use nym_wireguard_types::PeerPublicKey;
@@ -99,7 +99,7 @@ impl PeerManager {
) -> Result<ClientBandwidth, AuthenticatorError> {
let key = Key::new(key.to_bytes());
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::GetClientBandwidth { key, response_tx };
let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx };
self.wireguard_gateway_data
.peer_tx()
.send(msg)
@@ -120,14 +120,14 @@ impl PeerManager {
})
}
pub async fn query_verifier(
pub async fn query_verifier_by_key(
&mut self,
key: PeerPublicKey,
credential: CredentialSpendingData,
) -> Result<CredentialVerifier, AuthenticatorError> {
) -> Result<Box<dyn TicketVerifier + Send + Sync>, AuthenticatorError> {
let key = Key::new(key.to_bytes());
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::GetVerifier {
let msg = PeerControlRequest::GetVerifierByKey {
key,
credential: Box::new(credential),
response_tx,
@@ -398,13 +398,13 @@ mod tests {
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
assert!(peer_manager
.query_verifier(public_key, credential.clone())
.query_verifier_by_key(public_key, credential.clone())
.await
.is_err());
helper_add_peer(&storage, &mut peer_manager).await;
peer_manager
.query_verifier(public_key, credential)
.query_verifier_by_key(public_key, credential)
.await
.unwrap();
+55 -17
View File
@@ -19,7 +19,7 @@ use nym_network_defaults::NymNetworkDetails;
use nym_network_requester::NRServiceProviderBuilder;
use nym_node_metrics::events::MetricEventsSender;
use nym_node_metrics::NymNodeMetrics;
use nym_task::TaskClient;
use nym_task::{ShutdownToken, TaskClient};
use nym_topology::TopologyProvider;
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
@@ -91,7 +91,9 @@ pub struct GatewayTasksBuilder {
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient,
legacy_task_client: TaskClient,
shutdown_token: ShutdownToken,
// populated and cached as necessary
ecash_manager: Option<Arc<EcashManager>>,
@@ -105,7 +107,7 @@ impl Drop for GatewayTasksBuilder {
fn drop(&mut self) {
// disarm the shutdown as it was already used to construct relevant tasks and we don't want the builder
// to cause shutdown
self.shutdown.disarm();
self.legacy_task_client.disarm();
}
}
@@ -119,7 +121,8 @@ impl GatewayTasksBuilder {
metrics_sender: MetricEventsSender,
metrics: NymNodeMetrics,
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient,
legacy_task_client: TaskClient,
shutdown_token: ShutdownToken,
) -> GatewayTasksBuilder {
GatewayTasksBuilder {
config,
@@ -133,7 +136,8 @@ impl GatewayTasksBuilder {
metrics_sender,
metrics,
mnemonic,
shutdown,
legacy_task_client,
shutdown_token,
ecash_manager: None,
wireguard_peers: None,
wireguard_networks: None,
@@ -228,7 +232,7 @@ impl GatewayTasksBuilder {
handler_config,
nyxd_client,
self.identity_keypair.public_key().to_bytes(),
self.shutdown.fork("ecash_manager"),
self.legacy_task_client.fork("ecash_manager"),
self.storage.clone(),
)
.await?,
@@ -270,7 +274,7 @@ impl GatewayTasksBuilder {
self.config.gateway.websocket_bind_address,
self.config.debug.maximum_open_connections,
shared_state,
self.shutdown.fork("websocket"),
self.legacy_task_client.fork("websocket"),
))
}
@@ -286,13 +290,14 @@ impl GatewayTasksBuilder {
let mut message_router_builder = SpMessageRouterBuilder::new(
*self.identity_keypair.public_key(),
self.mix_packet_sender.clone(),
self.shutdown.fork("network_requester_message_router"),
self.legacy_task_client
.fork("network_requester_message_router"),
);
let transceiver = message_router_builder.gateway_transceiver();
let (on_start_tx, on_start_rx) = oneshot::channel();
let mut nr_builder = NRServiceProviderBuilder::new(nr_opts.config.clone())
.with_shutdown(self.shutdown.fork("network_requester_sp"))
.with_shutdown(self.legacy_task_client.fork("network_requester_sp"))
.with_custom_gateway_transceiver(transceiver)
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
@@ -321,13 +326,13 @@ impl GatewayTasksBuilder {
let mut message_router_builder = SpMessageRouterBuilder::new(
*self.identity_keypair.public_key(),
self.mix_packet_sender.clone(),
self.shutdown.fork("ipr_message_router"),
self.legacy_task_client.fork("ipr_message_router"),
);
let transceiver = message_router_builder.gateway_transceiver();
let (on_start_tx, on_start_rx) = oneshot::channel();
let mut ip_packet_router = IpPacketRouter::new(ip_opts.config.clone())
.with_shutdown(self.shutdown.fork("ipr_sp"))
.with_shutdown(self.legacy_task_client.fork("ipr_sp"))
.with_custom_gateway_transceiver(Box::new(transceiver))
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
@@ -427,7 +432,7 @@ impl GatewayTasksBuilder {
let mut message_router_builder = SpMessageRouterBuilder::new(
*self.identity_keypair.public_key(),
self.mix_packet_sender.clone(),
self.shutdown.fork("authenticator_message_router"),
self.legacy_task_client.fork("authenticator_message_router"),
);
let transceiver = message_router_builder.gateway_transceiver();
@@ -440,7 +445,7 @@ impl GatewayTasksBuilder {
ecash_manager,
)
.with_custom_gateway_transceiver(transceiver)
.with_shutdown(self.shutdown.fork("authenticator_sp"))
.with_shutdown(self.legacy_task_client.fork("authenticator_sp"))
.with_wait_for_gateway(true)
.with_minimum_gateway_performance(0)
.with_custom_topology_provider(topology_provider)
@@ -460,7 +465,7 @@ impl GatewayTasksBuilder {
pub fn build_stale_messages_cleaner(&self) -> StaleMessagesCleaner {
StaleMessagesCleaner::new(
&self.storage,
self.shutdown.fork("stale_messages_cleaner"),
self.legacy_task_client.fork("stale_messages_cleaner"),
self.config.debug.stale_messages_max_age,
self.config.debug.stale_messages_cleaner_run_interval,
)
@@ -471,13 +476,17 @@ impl GatewayTasksBuilder {
&mut self,
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
let _ = self.metrics.clone();
let _ = self.shutdown_token.clone();
unimplemented!("wireguard is not supported on this platform")
}
#[cfg(target_os = "linux")]
pub async fn try_start_wireguard(
&mut self,
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
) -> Result<
nym_wireguard_private_metadata_server::ShutdownHandles,
Box<dyn std::error::Error + Send + Sync>,
> {
let all_peers = self.get_wireguard_peers().await?;
let Some(wireguard_data) = self.wireguard_data.take() else {
@@ -492,14 +501,43 @@ impl GatewayTasksBuilder {
);
};
let router = nym_wireguard_private_metadata_server::RouterBuilder::with_default_routes();
let router = router.with_state(nym_wireguard_private_metadata_server::AppState::new(
nym_wireguard_private_metadata_server::PeerControllerTransceiver::new(
wireguard_data.inner.peer_tx().clone(),
),
));
let bind_address = std::net::SocketAddr::new(
wireguard_data.inner.config().private_ipv4.into(),
wireguard_data.inner.config().announced_metadata_port,
);
let wg_handle = nym_wireguard::start_wireguard(
ecash_manager,
self.metrics.clone(),
all_peers,
self.shutdown.fork("wireguard"),
self.legacy_task_client.fork("wireguard"),
wireguard_data,
)
.await?;
Ok(wg_handle)
let server = router.build_server(&bind_address).await?;
let cancel_token: tokio_util::sync::CancellationToken = (*self.shutdown_token).clone();
let axum_shutdown_receiver = cancel_token.clone().cancelled_owned();
let server_handle = tokio::spawn(async move {
{
info!("Started Wireguard Axum HTTP V2 server on {bind_address}");
server.run(axum_shutdown_receiver).await
}
});
let shutdown_handles = nym_wireguard_private_metadata_server::ShutdownHandles::new(
server_handle,
wg_handle,
cancel_token,
);
Ok(shutdown_handles)
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.64"
version = "1.1.65"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
@@ -311,7 +311,10 @@ impl From<Authenticator> for AuthenticatorDetails {
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct WireguardDetails {
// NOTE: the port field is deprecated in favour of tunnel_port
pub port: u16,
pub tunnel_port: u16,
pub metadata_port: u16,
pub public_key: String,
}
@@ -320,6 +323,8 @@ impl From<Wireguard> for WireguardDetails {
fn from(value: Wireguard) -> Self {
WireguardDetails {
port: value.port,
tunnel_port: value.tunnel_port,
metadata_port: value.metadata_port,
public_key: value.public_key,
}
}
@@ -1,6 +1,6 @@
[package]
name = "nym-credential-proxy"
version = "0.1.7"
version = "0.2.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.17.0"
version = "1.16.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -16,9 +16,16 @@ pub struct Gateway {
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct Wireguard {
#[deprecated(note = "use specific port instead (tunnel or metadata service)")]
#[cfg_attr(feature = "openapi", schema(example = 51822, default = 51822))]
pub port: u16,
#[cfg_attr(feature = "openapi", schema(example = 51822, default = 51822))]
pub tunnel_port: u16,
#[cfg_attr(feature = "openapi", schema(example = 51830, default = 51830))]
pub metadata_port: u16,
pub public_key: String,
}
+4 -4
View File
@@ -276,13 +276,13 @@ pub(crate) struct WireguardArgs {
)]
pub(crate) wireguard_bind_address: Option<SocketAddr>,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
/// Useful in the instances where the node is behind a proxy.
#[clap(
long,
env = NYMNODE_WG_ANNOUNCED_PORT_ARG
)]
pub(crate) wireguard_announced_port: Option<u16>,
pub(crate) wireguard_tunnel_announced_port: Option<u16>,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard.
/// The maximum value for IPv4 is 32 and for IPv6 is 128
@@ -311,8 +311,8 @@ impl WireguardArgs {
section.bind_address = bind_address
}
if let Some(announced_port) = self.wireguard_announced_port {
section.announced_port = announced_port
if let Some(announced_tunnel_port) = self.wireguard_tunnel_announced_port {
section.announced_tunnel_port = announced_tunnel_port
}
if let Some(private_network_prefix) = self.wireguard_private_network_prefix {
+2 -1
View File
@@ -202,7 +202,8 @@ pub fn gateway_tasks_config(config: &Config) -> GatewayTasksConfig {
bind_address: config.wireguard.bind_address,
private_ipv4: config.wireguard.private_ipv4,
private_ipv6: config.wireguard.private_ipv6,
announced_port: config.wireguard.announced_port,
announced_tunnel_port: config.wireguard.announced_tunnel_port,
announced_metadata_port: config.wireguard.announced_metadata_port,
private_network_prefix_v4: config.wireguard.private_network_prefix_v4,
private_network_prefix_v6: config.wireguard.private_network_prefix_v6,
storage_paths: config.wireguard.storage_paths.clone(),
+13 -7
View File
@@ -10,7 +10,7 @@ use human_repr::HumanCount;
use nym_bin_common::logging::LoggingSettings;
use nym_config::defaults::{
mainnet, var_names, DEFAULT_MIX_LISTENING_PORT, DEFAULT_NYM_NODE_HTTP_PORT,
DEFAULT_VERLOC_LISTENING_PORT, WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4,
DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4,
WG_TUN_DEVICE_IP_ADDRESS_V6,
};
use nym_config::defaults::{WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6};
@@ -931,9 +931,13 @@ pub struct Wireguard {
/// default: `fc01::1`
pub private_ipv6: Ipv6Addr,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
/// Useful in the instances where the node is behind a proxy.
pub announced_port: u16,
pub announced_tunnel_port: u16,
/// Metadata port announced to external clients wishing to connect to the metadata endpoint.
/// Useful in the instances where the node is behind a proxy.
pub announced_metadata_port: u16,
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
/// The maximum value for IPv4 is 32
@@ -951,10 +955,11 @@ impl Wireguard {
pub fn new_default<P: AsRef<Path>>(data_dir: P) -> Self {
Wireguard {
enabled: false,
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT),
private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4,
private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6,
announced_port: WG_PORT,
announced_tunnel_port: WG_TUNNEL_PORT,
announced_metadata_port: WG_METADATA_PORT,
private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4,
private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6,
storage_paths: persistence::WireguardPaths::new(data_dir),
@@ -968,7 +973,8 @@ impl From<Wireguard> for nym_wireguard_types::Config {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
announced_port: value.announced_port,
announced_tunnel_port: value.announced_tunnel_port,
announced_metadata_port: value.announced_metadata_port,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
@@ -981,7 +987,7 @@ impl From<Wireguard> for nym_authenticator::config::Authenticator {
bind_address: value.bind_address,
private_ipv4: value.private_ipv4,
private_ipv6: value.private_ipv6,
announced_port: value.announced_port,
tunnel_announced_port: value.announced_tunnel_port,
private_network_prefix_v4: value.private_network_prefix_v4,
private_network_prefix_v6: value.private_network_prefix_v6,
}
+2
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
mod old_config_v1;
mod old_config_v10;
mod old_config_v2;
mod old_config_v3;
mod old_config_v4;
@@ -12,6 +13,7 @@ mod old_config_v8;
mod old_config_v9;
pub use old_config_v1::try_upgrade_config_v1;
pub use old_config_v10::try_upgrade_config_v10;
pub use old_config_v2::try_upgrade_config_v2;
pub use old_config_v3::try_upgrade_config_v3;
pub use old_config_v4::try_upgrade_config_v4;
File diff suppressed because it is too large Load Diff
@@ -1,29 +1,26 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::authenticator::{Authenticator, AuthenticatorDebug};
use crate::config::gateway_tasks::{
ClientBandwidthDebug, StaleMessageDebug, ZkNymTicketHandlerDebug,
use crate::config::old_configs::old_config_v10::{
AuthenticatorDebugV10, AuthenticatorPathsV10, AuthenticatorV10, ClientBandwidthDebugV10,
ConfigV10, GatewayTasksConfigDebugV10, GatewayTasksConfigV10, GatewayTasksPathsV10, HostV10,
HttpV10, IpPacketRouterDebugV10, IpPacketRouterPathsV10, IpPacketRouterV10, KeysPathsV10,
LoggingSettingsV10, MixnetDebugV10, MixnetV10, NetworkRequesterDebugV10,
NetworkRequesterPathsV10, NetworkRequesterV10, NodeModesV10, NymNodePathsV10,
ReplayProtectionDebugV10, ReplayProtectionPathsV10, ReplayProtectionV10,
ServiceProvidersConfigDebugV10, ServiceProvidersConfigV10, ServiceProvidersPathsV10,
StaleMessageDebugV10, VerlocDebugV10, VerlocV10, WireguardPathsV10, WireguardV10,
ZkNymTicketHandlerDebugV10,
};
use crate::config::persistence::{
AuthenticatorPaths, GatewayTasksPaths, IpPacketRouterPaths, KeysPaths, NetworkRequesterPaths,
NymNodePaths, ReplayProtectionPaths, ServiceProvidersPaths, WireguardPaths,
DEFAULT_PRIMARY_X25519_SPHINX_KEY_FILENAME, DEFAULT_SECONDARY_X25519_SPHINX_KEY_FILENAME,
};
use crate::config::service_providers::{
IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug,
};
use crate::config::{
gateway_tasks, service_providers, Config, GatewayTasksConfig, Host, Http, Mixnet, MixnetDebug,
NodeModes, ReplayProtection, ReplayProtectionDebug, ServiceProvidersConfig, Verloc,
VerlocDebug, Wireguard, DEFAULT_HTTP_PORT,
};
use crate::config::{NodeModes, DEFAULT_HTTP_PORT};
use crate::error::{KeyIOFailure, NymNodeError};
use crate::node::helpers::{get_current_rotation_id, load_key, store_key};
use crate::node::key_rotation::key::SphinxPrivateKey;
use celes::Country;
use clap::ValueEnum;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core_config_types::DebugConfig as ClientDebugConfig;
use nym_config::defaults::DEFAULT_VERLOC_LISTENING_PORT;
use nym_config::helpers::{in6addr_any_init, inaddr_any};
@@ -1330,7 +1327,7 @@ async fn upgrade_sphinx_key(old_cfg: &ConfigV9) -> Result<(PathBuf, PathBuf), Ny
pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
path: P,
prev_config: Option<ConfigV9>,
) -> Result<Config, NymNodeError> {
) -> Result<ConfigV10, NymNodeError> {
debug!("attempting to load v9 config...");
let old_cfg = if let Some(prev_config) = prev_config {
@@ -1342,33 +1339,33 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
let (primary_x25519_sphinx_key_file, secondary_x25519_sphinx_key_file) =
upgrade_sphinx_key(&old_cfg).await?;
let cfg = Config {
let cfg = ConfigV10 {
save_path: old_cfg.save_path,
id: old_cfg.id,
modes: NodeModes {
modes: NodeModesV10 {
mixnode: old_cfg.modes.mixnode,
entry: old_cfg.modes.entry,
exit: old_cfg.modes.exit,
},
host: Host {
host: HostV10 {
public_ips: old_cfg.host.public_ips,
hostname: old_cfg.host.hostname,
location: old_cfg.host.location,
},
mixnet: Mixnet {
mixnet: MixnetV10 {
bind_address: old_cfg.mixnet.bind_address,
announce_port: old_cfg.mixnet.announce_port,
nym_api_urls: old_cfg.mixnet.nym_api_urls,
nyxd_urls: old_cfg.mixnet.nyxd_urls,
replay_protection: ReplayProtection {
storage_paths: ReplayProtectionPaths {
replay_protection: ReplayProtectionV10 {
storage_paths: ReplayProtectionPathsV10 {
current_bloomfilters_directory: old_cfg
.mixnet
.replay_protection
.storage_paths
.current_bloomfilters_directory,
},
debug: ReplayProtectionDebug {
debug: ReplayProtectionDebugV10 {
unsafe_disabled: old_cfg.mixnet.replay_protection.debug.unsafe_disabled,
maximum_replay_detection_deferral: old_cfg
.mixnet
@@ -1404,7 +1401,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
},
},
key_rotation: Default::default(),
debug: MixnetDebug {
debug: MixnetDebugV10 {
maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay,
packet_forwarding_initial_backoff: old_cfg
.mixnet
@@ -1420,8 +1417,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
..Default::default()
},
},
storage_paths: NymNodePaths {
keys: KeysPaths {
storage_paths: NymNodePathsV10 {
keys: KeysPathsV10 {
private_ed25519_identity_key_file: old_cfg
.storage_paths
.keys
@@ -1443,7 +1440,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
},
description: old_cfg.storage_paths.description,
},
http: Http {
http: HttpV10 {
bind_address: old_cfg.http.bind_address,
landing_page_assets_path: old_cfg.http.landing_page_assets_path,
access_token: old_cfg.http.access_token,
@@ -1452,10 +1449,10 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
expose_crypto_hardware: old_cfg.http.expose_crypto_hardware,
node_load_cache_ttl: old_cfg.http.node_load_cache_ttl,
},
verloc: Verloc {
verloc: VerlocV10 {
bind_address: old_cfg.verloc.bind_address,
announce_port: old_cfg.verloc.announce_port,
debug: VerlocDebug {
debug: VerlocDebugV10 {
packets_per_node: old_cfg.verloc.debug.packets_per_node,
connection_timeout: old_cfg.verloc.debug.connection_timeout,
packet_timeout: old_cfg.verloc.debug.packet_timeout,
@@ -1465,7 +1462,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
retry_timeout: old_cfg.verloc.debug.retry_timeout,
},
},
wireguard: Wireguard {
wireguard: WireguardV10 {
enabled: old_cfg.wireguard.enabled,
bind_address: old_cfg.wireguard.bind_address,
private_ipv4: old_cfg.wireguard.private_ipv4,
@@ -1473,7 +1470,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
announced_port: old_cfg.wireguard.announced_port,
private_network_prefix_v4: old_cfg.wireguard.private_network_prefix_v4,
private_network_prefix_v6: old_cfg.wireguard.private_network_prefix_v6,
storage_paths: WireguardPaths {
storage_paths: WireguardPathsV10 {
private_diffie_hellman_key_file: old_cfg
.wireguard
.storage_paths
@@ -1484,8 +1481,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.public_diffie_hellman_key_file,
},
},
gateway_tasks: GatewayTasksConfig {
storage_paths: GatewayTasksPaths {
gateway_tasks: GatewayTasksConfigV10 {
storage_paths: GatewayTasksPathsV10 {
clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage,
stats_storage: old_cfg.gateway_tasks.storage_paths.stats_storage,
cosmos_mnemonic: old_cfg.gateway_tasks.storage_paths.cosmos_mnemonic,
@@ -1494,12 +1491,12 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
ws_bind_address: old_cfg.gateway_tasks.ws_bind_address,
announce_ws_port: old_cfg.gateway_tasks.announce_ws_port,
announce_wss_port: old_cfg.gateway_tasks.announce_wss_port,
debug: gateway_tasks::Debug {
debug: GatewayTasksConfigDebugV10 {
message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit,
maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections,
minimum_mix_performance: old_cfg.gateway_tasks.debug.minimum_mix_performance,
max_request_timestamp_skew: old_cfg.gateway_tasks.debug.max_request_timestamp_skew,
stale_messages: StaleMessageDebug {
stale_messages: StaleMessageDebugV10 {
cleaner_run_interval: old_cfg
.gateway_tasks
.debug
@@ -1507,7 +1504,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.cleaner_run_interval,
max_age: old_cfg.gateway_tasks.debug.stale_messages.max_age,
},
client_bandwidth: ClientBandwidthDebug {
client_bandwidth: ClientBandwidthDebugV10 {
max_flushing_rate: old_cfg
.gateway_tasks
.debug
@@ -1519,7 +1516,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.client_bandwidth
.max_delta_flushing_amount,
},
zk_nym_tickets: ZkNymTicketHandlerDebug {
zk_nym_tickets: ZkNymTicketHandlerDebugV10 {
revocation_bandwidth_penalty: old_cfg
.gateway_tasks
.debug
@@ -1544,11 +1541,11 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
},
},
},
service_providers: ServiceProvidersConfig {
storage_paths: ServiceProvidersPaths {
service_providers: ServiceProvidersConfigV10 {
storage_paths: ServiceProvidersPathsV10 {
clients_storage: old_cfg.service_providers.storage_paths.clients_storage,
stats_storage: old_cfg.service_providers.storage_paths.stats_storage,
network_requester: NetworkRequesterPaths {
network_requester: NetworkRequesterPathsV10 {
private_ed25519_identity_key_file: old_cfg
.service_providers
.storage_paths
@@ -1585,7 +1582,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.network_requester
.gateway_registrations,
},
ip_packet_router: IpPacketRouterPaths {
ip_packet_router: IpPacketRouterPathsV10 {
private_ed25519_identity_key_file: old_cfg
.service_providers
.storage_paths
@@ -1622,7 +1619,7 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.ip_packet_router
.gateway_registrations,
},
authenticator: AuthenticatorPaths {
authenticator: AuthenticatorPathsV10 {
private_ed25519_identity_key_file: old_cfg
.service_providers
.storage_paths
@@ -1662,8 +1659,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
},
open_proxy: old_cfg.service_providers.open_proxy,
upstream_exit_policy_url: old_cfg.service_providers.upstream_exit_policy_url,
network_requester: NetworkRequester {
debug: NetworkRequesterDebug {
network_requester: NetworkRequesterV10 {
debug: NetworkRequesterDebugV10 {
enabled: old_cfg.service_providers.network_requester.debug.enabled,
disable_poisson_rate: old_cfg
.service_providers
@@ -1677,8 +1674,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.client_debug,
},
},
ip_packet_router: IpPacketRouter {
debug: IpPacketRouterDebug {
ip_packet_router: IpPacketRouterV10 {
debug: IpPacketRouterDebugV10 {
enabled: old_cfg.service_providers.ip_packet_router.debug.enabled,
disable_poisson_rate: old_cfg
.service_providers
@@ -1692,8 +1689,8 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
.client_debug,
},
},
authenticator: Authenticator {
debug: AuthenticatorDebug {
authenticator: AuthenticatorV10 {
debug: AuthenticatorDebugV10 {
enabled: old_cfg.service_providers.authenticator.debug.enabled,
disable_poisson_rate: old_cfg
.service_providers
@@ -1703,12 +1700,12 @@ pub async fn try_upgrade_config_v9<P: AsRef<Path>>(
client_debug: old_cfg.service_providers.authenticator.debug.client_debug,
},
},
debug: service_providers::Debug {
debug: ServiceProvidersConfigDebugV10 {
message_retrieval_limit: old_cfg.service_providers.debug.message_retrieval_limit,
},
},
metrics: Default::default(),
logging: LoggingSettings {},
logging: LoggingSettingsV10 {},
debug: Default::default(),
};
Ok(cfg)
+5 -1
View File
@@ -145,7 +145,11 @@ private_ipv6 = '{{ wireguard.private_ipv6 }}'
# Port announced to external clients wishing to connect to the wireguard interface.
# Useful in the instances where the node is behind a proxy.
announced_port = {{ wireguard.announced_port }}
announced_tunnel_port = {{ wireguard.announced_tunnel_port }}
# Port announced to external clients wishing to connect to the metadata service.
# Useful in the instances where the node is behind a proxy.
announced_metadata_port = {{ wireguard.announced_metadata_port }}
# The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
# The maximum value for IPv4 is 32
+2 -1
View File
@@ -16,7 +16,8 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> {
let cfg = try_upgrade_config_v6(path, cfg).await.ok();
let cfg = try_upgrade_config_v7(path, cfg).await.ok();
let cfg = try_upgrade_config_v8(path, cfg).await.ok();
match try_upgrade_config_v9(path, cfg).await {
let cfg = try_upgrade_config_v9(path, cfg).await.ok();
match try_upgrade_config_v10(path, cfg).await {
Ok(cfg) => cfg.save(),
Err(e) => {
tracing::error!("Failed to finish upgrade: {e}");
+9 -3
View File
@@ -605,7 +605,8 @@ impl NymNode {
metrics_sender: MetricEventsSender,
active_clients_store: ActiveClientsStore,
mix_packet_sender: MixForwardingSender,
task_client: TaskClient,
legacy_task_client: TaskClient,
shutdown_token: ShutdownToken,
) -> Result<(), NymNodeError> {
let config = gateway_tasks_config(&self.config);
@@ -623,7 +624,8 @@ impl NymNode {
metrics_sender,
self.metrics.clone(),
self.entry_gateway.mnemonic.clone(),
task_client,
legacy_task_client,
shutdown_token,
);
// if we're running in entry mode, start the websocket
@@ -716,8 +718,11 @@ impl NymNode {
// entry gateway info
let wireguard = if self.config.wireguard.enabled {
#[allow(deprecated)]
Some(api_requests::v1::gateway::models::Wireguard {
port: self.config.wireguard.announced_port,
port: self.config.wireguard.announced_tunnel_port,
tunnel_port: self.config.wireguard.announced_tunnel_port,
metadata_port: self.config.wireguard.announced_metadata_port,
public_key: self.x25519_wireguard_key()?.to_string(),
})
} else {
@@ -1183,6 +1188,7 @@ impl NymNode {
active_clients_store,
mix_packet_sender,
self.shutdown_manager.subscribe_legacy("gateway-tasks"),
self.shutdown_manager.child_token("gateway-tasks"),
)
.await?;
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.62"
version = "1.1.63"
authors.workspace = true
edition.workspace = true
rust-version = "1.70"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.61"
version = "1.1.62"
authors.workspace = true
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nymvisor"
version = "0.1.26"
version = "0.1.27"
authors.workspace = true
repository.workspace = true
homepage.workspace = true