Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b10e8db21b | |||
| d28f54d13a | |||
| fb29b6fde3 | |||
| 17894e5f8b | |||
| 410dd6f725 | |||
| 130f2d690b | |||
| d078ca88d1 | |||
| 5827937040 | |||
| 3f82cf6d5f | |||
| 7f43f38879 |
Generated
+10
-24
@@ -4987,6 +4987,7 @@ dependencies = [
|
||||
"nym-network-defaults",
|
||||
"nym-service-provider-requests-common",
|
||||
"nym-sphinx",
|
||||
"nym-test-utils",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"semver 1.0.26",
|
||||
@@ -4994,6 +4995,7 @@ dependencies = [
|
||||
"sha2 0.10.9",
|
||||
"strum_macros",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
@@ -5002,21 +5004,16 @@ name = "nym-bandwidth-controller"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bip39",
|
||||
"log",
|
||||
"nym-credential-storage",
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-time",
|
||||
"nym-network-defaults",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"thiserror 2.0.12",
|
||||
"url",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5608,8 +5605,8 @@ dependencies = [
|
||||
"nym-gateway-requests",
|
||||
"nym-gateway-storage",
|
||||
"nym-task",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"si-scale",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
@@ -5649,6 +5646,7 @@ dependencies = [
|
||||
"nym-compact-ecash",
|
||||
"nym-ecash-time",
|
||||
"nym-network-defaults",
|
||||
"nym-upgrade-mode-check",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"strum",
|
||||
@@ -5799,7 +5797,6 @@ dependencies = [
|
||||
name = "nym-gateway"
|
||||
version = "1.1.36"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"bip39",
|
||||
@@ -5810,7 +5807,6 @@ dependencies = [
|
||||
"futures",
|
||||
"ipnetwork",
|
||||
"mock_instant",
|
||||
"nym-api-requests",
|
||||
"nym-authenticator-requests",
|
||||
"nym-client-core",
|
||||
"nym-credential-verification",
|
||||
@@ -5823,7 +5819,6 @@ dependencies = [
|
||||
"nym-id",
|
||||
"nym-ip-packet-router",
|
||||
"nym-mixnet-client",
|
||||
"nym-mixnode-common",
|
||||
"nym-network-defaults",
|
||||
"nym-network-requester",
|
||||
"nym-node-metrics",
|
||||
@@ -5833,20 +5828,18 @@ dependencies = [
|
||||
"nym-statistics-common",
|
||||
"nym-task",
|
||||
"nym-topology",
|
||||
"nym-types",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-validator-client",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-private-metadata-server",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
"zeroize",
|
||||
@@ -7572,17 +7565,10 @@ dependencies = [
|
||||
name = "nym-wireguard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"defguard_wireguard_rs",
|
||||
"dyn-clone",
|
||||
"futures",
|
||||
"ip_network",
|
||||
"log",
|
||||
"nym-authenticator-requests",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
@@ -7593,11 +7579,9 @@ dependencies = [
|
||||
"nym-task",
|
||||
"nym-wireguard-types",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7649,15 +7633,20 @@ version = "1.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"futures",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-private-metadata-client",
|
||||
"nym-wireguard-private-metadata-server",
|
||||
"nym-wireguard-private-metadata-shared",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-http 0.5.2",
|
||||
"utoipa",
|
||||
]
|
||||
@@ -7667,10 +7656,7 @@ name = "nym-wireguard-types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"log",
|
||||
"nym-config",
|
||||
"nym-crypto",
|
||||
"nym-network-defaults",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
|
||||
@@ -171,6 +171,7 @@ members = [
|
||||
default-members = [
|
||||
"clients/native",
|
||||
"clients/socks5",
|
||||
"nym-authenticator-client",
|
||||
"nym-api",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-node",
|
||||
|
||||
@@ -16,6 +16,7 @@ serde = { workspace = true, features = ["derive"] }
|
||||
semver = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
@@ -29,7 +30,13 @@ hmac = { workspace = true, optional = true }
|
||||
sha2 = { workspace = true, optional = true }
|
||||
x25519-dalek = { workspace = true, features = ["static_secrets"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
|
||||
[features]
|
||||
default = ["verify"]
|
||||
# this is moved to a separate feature as we really need clients to import it (especially, *cough*, wasm)
|
||||
verify = ["hmac", "sha2"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -6,9 +6,8 @@ use nym_wireguard_types::PeerPublicKey;
|
||||
|
||||
use crate::{
|
||||
AuthenticatorVersion, Error,
|
||||
latest::registration::IpPair,
|
||||
traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, Versionable},
|
||||
v2, v3, v4, v5,
|
||||
v2, v3, v4, v5, v6,
|
||||
};
|
||||
|
||||
// This is very redundant with AuthenticatorRequest and I reckon they could be smooshed.
|
||||
@@ -21,6 +20,272 @@ pub enum ClientMessage {
|
||||
TopUp(Box<dyn TopUpMessage + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
pub struct SerialisedRequest {
|
||||
pub bytes: Vec<u8>,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl SerialisedRequest {
|
||||
pub fn new(bytes: Vec<u8>, request_id: u64) -> Self {
|
||||
Self { bytes, request_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMessage {
|
||||
fn serialise_v1(&self) -> Result<SerialisedRequest, Error> {
|
||||
Err(Error::UnsupportedVersion)
|
||||
}
|
||||
|
||||
fn serialise_v2(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
|
||||
use v2::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message
|
||||
.credential()
|
||||
.and_then(|c| c.credential.into_zk_nym())
|
||||
.map(|c| *c),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
_ => Err(Error::UnsupportedMessage),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialise_v3(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
|
||||
use v3::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message
|
||||
.credential()
|
||||
.and_then(|c| c.credential.into_zk_nym())
|
||||
.map(|c| *c),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialise_v4(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
|
||||
use v4::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message
|
||||
.credential()
|
||||
.and_then(|c| c.credential.into_zk_nym())
|
||||
.map(|c| *c),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialise_v5(&self) -> Result<SerialisedRequest, Error> {
|
||||
use v5::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message
|
||||
.credential()
|
||||
.and_then(|c| c.credential.into_zk_nym())
|
||||
.map(|c| *c),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(query_message.pub_key());
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialise_v6(&self) -> Result<SerialisedRequest, Error> {
|
||||
use v6::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(query_message.pub_key());
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMessage {
|
||||
// check if message is wasteful e.g. contains a credential
|
||||
pub fn is_wasteful(&self) -> bool {
|
||||
@@ -40,205 +305,14 @@ impl ClientMessage {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes(&self, reply_to: Recipient) -> Result<(Vec<u8>, u64), Error> {
|
||||
pub fn bytes(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
|
||||
match self.version() {
|
||||
AuthenticatorVersion::V1 => Err(Error::UnsupportedVersion),
|
||||
AuthenticatorVersion::V2 => {
|
||||
use v2::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
_ => Err(Error::UnsupportedMessage),
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V3 => {
|
||||
use v3::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V4 => {
|
||||
use v4::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
}
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V5 => {
|
||||
use v5::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key());
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V1 => self.serialise_v1(),
|
||||
AuthenticatorVersion::V2 => self.serialise_v2(reply_to),
|
||||
AuthenticatorVersion::V3 => self.serialise_v3(reply_to),
|
||||
AuthenticatorVersion::V4 => self.serialise_v4(reply_to),
|
||||
AuthenticatorVersion::V5 => self.serialise_v5(),
|
||||
AuthenticatorVersion::V6 => self.serialise_v6(),
|
||||
AuthenticatorVersion::UNKNOWN => Err(Error::UnknownVersion),
|
||||
}
|
||||
}
|
||||
@@ -247,7 +321,7 @@ impl ClientMessage {
|
||||
use AuthenticatorVersion::*;
|
||||
match self.version() {
|
||||
V1 | V2 | V3 | V4 => false,
|
||||
V5 => true,
|
||||
V5 | V6 => true,
|
||||
UNKNOWN => true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Display;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -37,3 +38,13 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
Bincode(#[from] bincode::Error),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn conversion(msg: impl Into<String>) -> Self {
|
||||
Error::Conversion(msg.into())
|
||||
}
|
||||
|
||||
pub fn conversion_display(msg: impl Display) -> Self {
|
||||
Error::Conversion(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,14 @@ pub mod v2;
|
||||
pub mod v3;
|
||||
pub mod v4;
|
||||
pub mod v5;
|
||||
pub mod v6;
|
||||
|
||||
mod error;
|
||||
mod util;
|
||||
mod version;
|
||||
|
||||
pub use error::Error;
|
||||
pub use v5 as latest;
|
||||
pub use v6 as latest;
|
||||
pub use version::AuthenticatorVersion;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = latest::VERSION;
|
||||
|
||||
@@ -5,7 +5,7 @@ use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
|
||||
use crate::traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage};
|
||||
use crate::{v1, v2, v3, v4, v5};
|
||||
use crate::{v1, v2, v3, v4, v5, v6};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticatorRequest {
|
||||
@@ -202,3 +202,38 @@ impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v6::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v6::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v6::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v6::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v6::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::traits::{
|
||||
Id, PendingRegistrationResponse, RegisteredResponse, RemainingBandwidthResponse,
|
||||
TopUpBandwidthResponse,
|
||||
CurrentUpgradeModeStatus, Id, PendingRegistrationResponse, RegisteredResponse,
|
||||
RemainingBandwidthResponse, TopUpBandwidthResponse, UpgradeModeStatus,
|
||||
};
|
||||
use crate::{v2, v3, v4, v5};
|
||||
use crate::{v2, v3, v4, v5, v6};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticatorResponse {
|
||||
@@ -15,6 +15,25 @@ pub enum AuthenticatorResponse {
|
||||
TopUpBandwidth(Box<dyn TopUpBandwidthResponse + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for AuthenticatorResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
match self {
|
||||
AuthenticatorResponse::PendingRegistration(pending_registration_response) => {
|
||||
pending_registration_response.upgrade_mode_status()
|
||||
}
|
||||
AuthenticatorResponse::Registered(registered_response) => {
|
||||
registered_response.upgrade_mode_status()
|
||||
}
|
||||
AuthenticatorResponse::RemainingBandwidth(remaining_bandwidth_response) => {
|
||||
remaining_bandwidth_response.upgrade_mode_status()
|
||||
}
|
||||
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
top_up_bandwidth_response.upgrade_mode_status()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for AuthenticatorResponse {
|
||||
fn id(&self) -> u64 {
|
||||
match self {
|
||||
@@ -104,3 +123,22 @@ impl From<v5::response::AuthenticatorResponse> for AuthenticatorResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::response::AuthenticatorResponse> for AuthenticatorResponse {
|
||||
fn from(value: v6::response::AuthenticatorResponse) -> Self {
|
||||
match value.data {
|
||||
v6::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => Self::PendingRegistration(Box::new(pending_registration_response)),
|
||||
v6::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
Self::Registered(Box::new(registered_response))
|
||||
}
|
||||
v6::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
|
||||
v6::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,66 @@
|
||||
use std::fmt;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::x25519::PrivateKey;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
|
||||
use crate::latest::registration::IpPair;
|
||||
use crate::{AuthenticatorVersion, Error, v1, v2, v3, v4, v5};
|
||||
use crate::{AuthenticatorVersion, Error, v1, v2, v3, v4, v5, v6};
|
||||
use nym_credentials_interface::{
|
||||
BandwidthCredential, CredentialSpendingData, TicketType, UnknownTicketType,
|
||||
};
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum CurrentUpgradeModeStatus {
|
||||
Enabled,
|
||||
Disabled,
|
||||
// everything pre-v6
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl CurrentUpgradeModeStatus {
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
matches!(self, CurrentUpgradeModeStatus::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for CurrentUpgradeModeStatus {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
CurrentUpgradeModeStatus::Enabled
|
||||
} else {
|
||||
CurrentUpgradeModeStatus::Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CurrentUpgradeModeStatus> for Option<bool> {
|
||||
fn from(value: CurrentUpgradeModeStatus) -> Self {
|
||||
match value {
|
||||
CurrentUpgradeModeStatus::Enabled => Some(true),
|
||||
CurrentUpgradeModeStatus::Disabled => Some(false),
|
||||
CurrentUpgradeModeStatus::Unknown => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct BandwidthClaim {
|
||||
pub credential: BandwidthCredential,
|
||||
pub kind: TicketType,
|
||||
}
|
||||
|
||||
impl TryFrom<CredentialSpendingData> for BandwidthClaim {
|
||||
type Error = UnknownTicketType;
|
||||
|
||||
fn try_from(credential: CredentialSpendingData) -> Result<Self, Self::Error> {
|
||||
Ok(BandwidthClaim {
|
||||
kind: TicketType::try_from_encoded(credential.payment.t_type)?,
|
||||
credential: BandwidthCredential::from(credential),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Versionable {
|
||||
fn version(&self) -> AuthenticatorVersion;
|
||||
@@ -51,6 +105,12 @@ impl Versionable for v5::registration::InitMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v6::registration::InitMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V6
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v2::registration::FinalMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V2
|
||||
@@ -75,6 +135,12 @@ impl Versionable for v5::registration::FinalMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v6::registration::FinalMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V6
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for PeerPublicKey {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3
|
||||
@@ -98,6 +164,146 @@ impl Versionable for v5::topup::TopUpMessage {
|
||||
AuthenticatorVersion::V5
|
||||
}
|
||||
}
|
||||
impl Versionable for v6::topup::TopUpMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V6
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UpgradeModeStatus {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus;
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v1::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v1::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v1::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v2::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v2::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v2::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
impl UpgradeModeStatus for v3::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v3::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v3::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v3::response::TopUpBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v4::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v4::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v4::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v4::response::TopUpBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v5::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v5::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v5::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v5::response::TopUpBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::TopUpBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InitMessage: Versionable + fmt::Debug {
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
@@ -133,14 +339,20 @@ impl InitMessage for v5::registration::InitMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl InitMessage for v6::registration::InitMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FinalMessage: Versionable + fmt::Debug {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey;
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error>;
|
||||
fn private_ips(&self) -> IpPair;
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr>;
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr>;
|
||||
fn gateway_client_mac(&self) -> Vec<u8>;
|
||||
fn credential(&self) -> Option<CredentialSpendingData>;
|
||||
fn credential(&self) -> Option<BandwidthClaim>;
|
||||
}
|
||||
|
||||
impl FinalMessage for v1::GatewayClient {
|
||||
@@ -148,7 +360,7 @@ impl FinalMessage for v1::GatewayClient {
|
||||
self.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -171,7 +383,7 @@ impl FinalMessage for v1::GatewayClient {
|
||||
self.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -181,7 +393,7 @@ impl FinalMessage for v2::registration::FinalMessage {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -204,8 +416,12 @@ impl FinalMessage for v2::registration::FinalMessage {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone().and_then(|c| {
|
||||
c.try_into()
|
||||
.inspect_err(|err| error!("credential conversion error: {err}"))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +430,7 @@ impl FinalMessage for v3::registration::FinalMessage {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -237,8 +453,12 @@ impl FinalMessage for v3::registration::FinalMessage {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone().and_then(|c| {
|
||||
c.try_into()
|
||||
.inspect_err(|err| error!("credential conversion error: {err}"))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +467,42 @@ impl FinalMessage for v4::registration::FinalMessage {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
// v4 -> v5 -> v6
|
||||
v5::registration::IpPair::from(self.gateway_client.private_ips).into()
|
||||
}
|
||||
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
|
||||
Some(self.gateway_client.private_ips.ipv4)
|
||||
}
|
||||
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
|
||||
Some(self.gateway_client.private_ips.ipv6)
|
||||
}
|
||||
|
||||
fn gateway_client_mac(&self) -> Vec<u8> {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone().and_then(|c| {
|
||||
c.try_into()
|
||||
.inspect_err(|err| error!("credential conversion error: {err}"))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v5::registration::FinalMessage {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -267,17 +522,21 @@ impl FinalMessage for v4::registration::FinalMessage {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone().and_then(|c| {
|
||||
c.try_into()
|
||||
.inspect_err(|err| error!("credential conversion error: {err}"))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v5::registration::FinalMessage {
|
||||
impl FinalMessage for v6::registration::FinalMessage {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -297,7 +556,7 @@ impl FinalMessage for v5::registration::FinalMessage {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
@@ -347,6 +606,16 @@ impl TopUpMessage for v5::topup::TopUpMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl TopUpMessage for v6::topup::TopUpMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
|
||||
fn credential(&self) -> CredentialSpendingData {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Id {
|
||||
fn id(&self) -> u64;
|
||||
}
|
||||
@@ -375,6 +644,12 @@ impl Id for v5::response::PendingRegistrationResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v6::response::PendingRegistrationResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v2::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
@@ -399,6 +674,12 @@ impl Id for v5::response::RegisteredResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v6::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v2::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
@@ -423,6 +704,12 @@ impl Id for v5::response::RemainingBandwidthResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v6::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v3::response::TopUpBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
@@ -441,11 +728,22 @@ impl Id for v5::response::TopUpBandwidthResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PendingRegistrationResponse: Id + fmt::Debug {
|
||||
impl Id for v6::response::TopUpBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PendingRegistrationResponse: Id + UpgradeModeStatus + fmt::Debug {
|
||||
fn nonce(&self) -> u64;
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error>;
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error>;
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn private_ips(&self) -> IpPair;
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync>;
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
|
||||
@@ -453,7 +751,7 @@ impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
@@ -464,6 +762,22 @@ impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ip.into()
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v2::registration::FinalMessage {
|
||||
gateway_client: v2::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.private_ips().ipv4.into(),
|
||||
self.nonce(),
|
||||
),
|
||||
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
|
||||
@@ -471,7 +785,7 @@ impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
@@ -482,6 +796,22 @@ impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ip.into()
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v3::registration::FinalMessage {
|
||||
gateway_client: v3::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.private_ips().ipv4.into(),
|
||||
self.nonce(),
|
||||
),
|
||||
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
|
||||
@@ -489,7 +819,42 @@ impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.gateway_data.pub_key
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
// v4 -> v5 -> v6
|
||||
v5::registration::IpPair::from(self.reply.gateway_data.private_ips).into()
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v4::registration::FinalMessage {
|
||||
gateway_client: v4::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.reply.gateway_data.private_ips,
|
||||
self.nonce(),
|
||||
),
|
||||
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
|
||||
fn nonce(&self) -> u64 {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
@@ -500,14 +865,30 @@ impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ips.into()
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v5::registration::FinalMessage {
|
||||
gateway_client: v5::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.reply.gateway_data.private_ips,
|
||||
self.nonce(),
|
||||
),
|
||||
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
|
||||
impl PendingRegistrationResponse for v6::response::PendingRegistrationResponse {
|
||||
fn nonce(&self) -> u64 {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
@@ -518,9 +899,25 @@ impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ips
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v6::registration::FinalMessage {
|
||||
gateway_client: v6::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.reply.gateway_data.private_ips,
|
||||
self.nonce(),
|
||||
),
|
||||
credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RegisteredResponse: Id + fmt::Debug {
|
||||
pub trait RegisteredResponse: Id + UpgradeModeStatus + fmt::Debug {
|
||||
fn private_ips(&self) -> IpPair;
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn wg_port(&self) -> u16;
|
||||
@@ -555,7 +952,8 @@ impl RegisteredResponse for v3::response::RegisteredResponse {
|
||||
}
|
||||
impl RegisteredResponse for v4::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ips.into()
|
||||
// v4 -> v5 -> v6
|
||||
v5::registration::IpPair::from(self.reply.private_ips).into()
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
@@ -568,6 +966,20 @@ impl RegisteredResponse for v4::response::RegisteredResponse {
|
||||
}
|
||||
|
||||
impl RegisteredResponse for v5::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ips.into()
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.pub_key
|
||||
}
|
||||
|
||||
fn wg_port(&self) -> u16 {
|
||||
self.reply.wg_port
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisteredResponse for v6::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ips
|
||||
}
|
||||
@@ -581,7 +993,7 @@ impl RegisteredResponse for v5::response::RegisteredResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RemainingBandwidthResponse: Id + fmt::Debug {
|
||||
pub trait RemainingBandwidthResponse: Id + UpgradeModeStatus + fmt::Debug {
|
||||
fn available_bandwidth(&self) -> Option<i64>;
|
||||
}
|
||||
|
||||
@@ -609,7 +1021,13 @@ impl RemainingBandwidthResponse for v5::response::RemainingBandwidthResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TopUpBandwidthResponse: Id + fmt::Debug {
|
||||
impl RemainingBandwidthResponse for v6::response::RemainingBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> Option<i64> {
|
||||
self.reply.as_ref().map(|r| r.available_bandwidth)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TopUpBandwidthResponse: Id + UpgradeModeStatus + fmt::Debug {
|
||||
fn available_bandwidth(&self) -> i64;
|
||||
}
|
||||
|
||||
@@ -630,3 +1048,9 @@ impl TopUpBandwidthResponse for v5::response::TopUpBandwidthResponse {
|
||||
self.reply.available_bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
impl TopUpBandwidthResponse for v6::response::TopUpBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> i64 {
|
||||
self.reply.available_bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegistredData {
|
||||
pub struct RegisteredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -34,7 +34,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
registred_data: RegisteredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
@@ -108,7 +108,7 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
pub reply: RegisteredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -154,8 +154,8 @@ impl From<v2::registration::RegistrationData> for v1::registration::Registration
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::RegistredData> for v1::registration::RegistredData {
|
||||
fn from(value: v2::registration::RegistredData) -> Self {
|
||||
impl From<v2::registration::RegisteredData> for v1::registration::RegisteredData {
|
||||
fn from(value: v2::registration::RegisteredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ip,
|
||||
|
||||
@@ -58,7 +58,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistredData {
|
||||
pub struct RegisteredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
registred_data: RegisteredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
@@ -118,7 +118,7 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
pub reply: RegisteredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -299,8 +299,8 @@ impl From<v2::registration::RegistrationData> for v3::registration::Registration
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
|
||||
fn from(value: v3::registration::RegistredData) -> Self {
|
||||
impl From<v3::registration::RegisteredData> for v2::registration::RegisteredData {
|
||||
fn from(value: v3::registration::RegisteredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ip,
|
||||
@@ -309,8 +309,8 @@ impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::RegistredData> for v3::registration::RegistredData {
|
||||
fn from(value: v2::registration::RegistredData) -> Self {
|
||||
impl From<v2::registration::RegisteredData> for v3::registration::RegisteredData {
|
||||
fn from(value: v2::registration::RegisteredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ip,
|
||||
@@ -674,7 +674,7 @@ mod tests {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let private_ip = IpAddr::from_str("10.10.10.10").unwrap();
|
||||
let wg_port = 51822;
|
||||
let registred_data = v2::registration::RegistredData {
|
||||
let registred_data = v2::registration::RegisteredData {
|
||||
pub_key,
|
||||
private_ip,
|
||||
wg_port,
|
||||
@@ -701,7 +701,7 @@ mod tests {
|
||||
v3::response::AuthenticatorResponseData::Registered(v3::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: v3::registration::RegistredData {
|
||||
reply: v3::registration::RegisteredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ip
|
||||
@@ -715,7 +715,7 @@ mod tests {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let private_ip = IpAddr::from_str("10.10.10.10").unwrap();
|
||||
let wg_port = 51822;
|
||||
let registred_data = v3::registration::RegistredData {
|
||||
let registred_data = v3::registration::RegisteredData {
|
||||
pub_key,
|
||||
private_ip,
|
||||
wg_port,
|
||||
@@ -742,7 +742,7 @@ mod tests {
|
||||
v2::response::AuthenticatorResponseData::Registered(v2::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: v2::registration::RegistredData {
|
||||
reply: v2::registration::RegisteredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ip
|
||||
|
||||
@@ -58,7 +58,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistredData {
|
||||
pub struct RegisteredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
registred_data: RegisteredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
@@ -139,7 +139,7 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
pub reply: RegisteredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -262,8 +262,8 @@ impl From<v4::response::TopUpBandwidthResponse> for v3::response::TopUpBandwidth
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::RegistredData> for v4::registration::RegistredData {
|
||||
fn from(value: v3::registration::RegistredData) -> Self {
|
||||
impl From<v3::registration::RegisteredData> for v4::registration::RegisteredData {
|
||||
fn from(value: v3::registration::RegisteredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ips: value.private_ip.into(),
|
||||
@@ -272,8 +272,8 @@ impl From<v3::registration::RegistredData> for v4::registration::RegistredData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegistredData> for v3::registration::RegistredData {
|
||||
fn from(value: v4::registration::RegistredData) -> Self {
|
||||
impl From<v4::registration::RegisteredData> for v3::registration::RegisteredData {
|
||||
fn from(value: v4::registration::RegisteredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ips.ipv4.into(),
|
||||
@@ -565,7 +565,7 @@ mod tests {
|
||||
let private_ips =
|
||||
v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::a0a").unwrap());
|
||||
let wg_port = 51822;
|
||||
let registred_data = v3::registration::RegistredData {
|
||||
let registred_data = v3::registration::RegisteredData {
|
||||
pub_key,
|
||||
private_ip: ipv4.into(),
|
||||
wg_port,
|
||||
@@ -592,7 +592,7 @@ mod tests {
|
||||
v4::response::AuthenticatorResponseData::Registered(v4::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: v4::registration::RegistredData {
|
||||
reply: v4::registration::RegisteredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ips
|
||||
@@ -608,7 +608,7 @@ mod tests {
|
||||
let private_ips =
|
||||
v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::10").unwrap());
|
||||
let wg_port = 51822;
|
||||
let registred_data = v4::registration::RegistredData {
|
||||
let registred_data = v4::registration::RegisteredData {
|
||||
pub_key,
|
||||
private_ips,
|
||||
wg_port,
|
||||
@@ -635,7 +635,7 @@ mod tests {
|
||||
v3::response::AuthenticatorResponseData::Registered(v3::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: v3::registration::RegistredData {
|
||||
reply: v3::registration::RegisteredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ip: ipv4.into()
|
||||
|
||||
@@ -110,7 +110,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistredData {
|
||||
pub struct RegisteredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegistredData,
|
||||
registred_data: RegisteredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
@@ -139,7 +139,7 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistredData,
|
||||
pub reply: RegisteredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -186,8 +186,8 @@ impl From<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidth
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegistredData> for v5::registration::RegistredData {
|
||||
fn from(value: v4::registration::RegistredData) -> Self {
|
||||
impl From<v4::registration::RegisteredData> for v5::registration::RegisteredData {
|
||||
fn from(value: v4::registration::RegisteredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ips: value.private_ips.into(),
|
||||
@@ -405,7 +405,7 @@ mod tests {
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let private_ips = v4::registration::IpPair::new(ipv4, ipv6);
|
||||
let wg_port = 51822;
|
||||
let registred_data = v4::registration::RegistredData {
|
||||
let registred_data = v4::registration::RegisteredData {
|
||||
pub_key,
|
||||
private_ips,
|
||||
wg_port,
|
||||
@@ -431,7 +431,7 @@ mod tests {
|
||||
upgraded_msg.data,
|
||||
v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply: v5::registration::RegistredData {
|
||||
reply: v5::registration::RegisteredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ips: v5::registration::IpPair::new(ipv4, ipv6)
|
||||
|
||||
@@ -108,7 +108,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistredData {
|
||||
pub struct RegisteredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -32,7 +32,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self {
|
||||
pub fn new_registered(registred_data: RegisteredData, request_id: u64) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
@@ -116,7 +116,7 @@ pub struct PendingRegistrationResponse {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegistredData,
|
||||
pub reply: RegisteredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{v5, v6};
|
||||
|
||||
impl TryFrom<v5::request::AuthenticatorRequest> for v6::request::AuthenticatorRequest {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(
|
||||
authenticator_request: v5::request::AuthenticatorRequest,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
protocol: v6::PROTOCOL,
|
||||
data: authenticator_request.data.try_into()?,
|
||||
request_id: authenticator_request.request_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<v5::request::AuthenticatorRequestData> for v6::request::AuthenticatorRequestData {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(
|
||||
authenticator_request_data: v5::request::AuthenticatorRequestData,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match authenticator_request_data {
|
||||
v5::request::AuthenticatorRequestData::Initial(init_msg) => Ok(
|
||||
v6::request::AuthenticatorRequestData::Initial(init_msg.into()),
|
||||
),
|
||||
v5::request::AuthenticatorRequestData::Final(final_msg) => Ok(
|
||||
v6::request::AuthenticatorRequestData::Final(Box::new((*final_msg).try_into()?)),
|
||||
),
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => Ok(
|
||||
v6::request::AuthenticatorRequestData::QueryBandwidth(pub_key),
|
||||
),
|
||||
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => Ok(
|
||||
v6::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::InitMessage> for v6::registration::InitMessage {
|
||||
fn from(init_msg: v5::registration::InitMessage) -> Self {
|
||||
Self {
|
||||
pub_key: init_msg.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<v5::registration::FinalMessage> for v6::registration::FinalMessage {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(final_msg: v5::registration::FinalMessage) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
gateway_client: final_msg.gateway_client.into(),
|
||||
credential: final_msg
|
||||
.credential
|
||||
.map(TryInto::try_into)
|
||||
.transpose()
|
||||
.map_err(Self::Error::conversion_display)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::GatewayClient> for v6::registration::GatewayClient {
|
||||
fn from(gateway_client: v5::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gateway_client.pub_key,
|
||||
private_ips: gateway_client.private_ips.into(),
|
||||
mac: gateway_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::registration::GatewayClient> for v5::registration::GatewayClient {
|
||||
fn from(gateway_client: v6::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gateway_client.pub_key,
|
||||
private_ips: gateway_client.private_ips.into(),
|
||||
mac: gateway_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::ClientMac> for v6::registration::ClientMac {
|
||||
fn from(client_mac: v5::registration::ClientMac) -> Self {
|
||||
Self::new((*client_mac).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::registration::ClientMac> for v5::registration::ClientMac {
|
||||
fn from(client_mac: v6::registration::ClientMac) -> Self {
|
||||
Self::new((*client_mac).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<v5::topup::TopUpMessage>> for Box<v6::topup::TopUpMessage> {
|
||||
fn from(top_up_message: Box<v5::topup::TopUpMessage>) -> Self {
|
||||
Box::new(v6::topup::TopUpMessage {
|
||||
pub_key: top_up_message.pub_key,
|
||||
credential: top_up_message.credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::AuthenticatorResponse> for v6::response::AuthenticatorResponse {
|
||||
fn from(value: v5::response::AuthenticatorResponse) -> Self {
|
||||
Self {
|
||||
protocol: v6::PROTOCOL,
|
||||
data: value.data.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::AuthenticatorResponseData> for v6::response::AuthenticatorResponseData {
|
||||
fn from(authenticator_response_data: v5::response::AuthenticatorResponseData) -> Self {
|
||||
match authenticator_response_data {
|
||||
v5::response::AuthenticatorResponseData::PendingRegistration(pending_response) => {
|
||||
v6::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_response.into(),
|
||||
)
|
||||
}
|
||||
v5::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
v6::response::AuthenticatorResponseData::Registered(registered_response.into())
|
||||
}
|
||||
v5::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => v6::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response.into(),
|
||||
),
|
||||
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => {
|
||||
v6::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::RegisteredResponse> for v6::response::RegisteredResponse {
|
||||
fn from(value: v5::response::RegisteredResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::PendingRegistrationResponse> for v6::response::PendingRegistrationResponse {
|
||||
fn from(value: v5::response::PendingRegistrationResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::RegistrationData> for v6::registration::RegistrationData {
|
||||
fn from(value: v5::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::registration::RegistrationData> for v5::registration::RegistrationData {
|
||||
fn from(value: v6::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::RemainingBandwidthResponse> for v6::response::RemainingBandwidthResponse {
|
||||
fn from(value: v5::response::RemainingBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.map(Into::into),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::TopUpBandwidthResponse> for v6::response::TopUpBandwidthResponse {
|
||||
fn from(value: v5::response::TopUpBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::RegisteredData> for v6::registration::RegisteredData {
|
||||
fn from(value: v5::registration::RegisteredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ips: value.private_ips.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::RemainingBandwidthData> for v6::registration::RemainingBandwidthData {
|
||||
fn from(value: v5::registration::RemainingBandwidthData) -> Self {
|
||||
Self {
|
||||
available_bandwidth: value.available_bandwidth,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::IpPair> for v6::registration::IpPair {
|
||||
fn from(value: v5::registration::IpPair) -> Self {
|
||||
Self {
|
||||
ipv4: value.ipv4,
|
||||
ipv6: value.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::registration::IpPair> for v5::registration::IpPair {
|
||||
fn from(value: v6::registration::IpPair) -> Self {
|
||||
Self {
|
||||
ipv4: value.ipv4,
|
||||
ipv6: value.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData, TicketType};
|
||||
use nym_crypto::asymmetric::x25519::PrivateKey;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use super::*;
|
||||
use crate::traits::BandwidthClaim;
|
||||
use crate::{util::tests::CREDENTIAL_BYTES, v5};
|
||||
|
||||
#[test]
|
||||
fn upgrade_initial_req() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
|
||||
let (msg, _) = v5::request::AuthenticatorRequest::new_initial_request(
|
||||
v5::registration::InitMessage::new(pub_key),
|
||||
);
|
||||
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::request::AuthenticatorRequestData::Initial(v6::registration::InitMessage {
|
||||
pub_key
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_final_req() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_secret = PrivateKey::new(&mut rng);
|
||||
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
|
||||
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let ips = v5::registration::IpPair::new(ipv4, ipv6);
|
||||
let nonce = 42;
|
||||
let gateway_client = v5::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
ips,
|
||||
nonce,
|
||||
);
|
||||
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
|
||||
let final_message = v5::registration::FinalMessage {
|
||||
gateway_client: gateway_client.clone(),
|
||||
credential: Some(credential.clone()),
|
||||
};
|
||||
|
||||
let (msg, _) = v5::request::AuthenticatorRequest::new_final_request(final_message);
|
||||
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::request::AuthenticatorRequestData::Final(Box::new(
|
||||
v6::registration::FinalMessage {
|
||||
gateway_client: v6::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
v6::registration::IpPair::new(ipv4, ipv6),
|
||||
nonce
|
||||
),
|
||||
credential: Some(BandwidthClaim {
|
||||
credential: BandwidthCredential::ZkNym(Box::new(credential)),
|
||||
kind: TicketType::V1MixnetEntry,
|
||||
})
|
||||
}
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_query_req() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
|
||||
let (msg, _) = v5::request::AuthenticatorRequest::new_query_request(pub_key);
|
||||
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_pending_reg_resp() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_secret = PrivateKey::new(&mut rng);
|
||||
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
|
||||
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let ips = v5::registration::IpPair::new(ipv4, ipv6);
|
||||
let nonce = 42;
|
||||
let wg_port = 51822;
|
||||
let gateway_data = v5::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
ips,
|
||||
nonce,
|
||||
);
|
||||
let registration_data = v5::registration::RegistrationData {
|
||||
nonce,
|
||||
gateway_data,
|
||||
wg_port,
|
||||
};
|
||||
let request_id = 123;
|
||||
|
||||
let msg = v5::response::AuthenticatorResponse::new_pending_registration_success(
|
||||
registration_data,
|
||||
request_id,
|
||||
);
|
||||
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::response::AuthenticatorResponseData::PendingRegistration(
|
||||
v6::response::PendingRegistrationResponse {
|
||||
request_id,
|
||||
reply: v6::registration::RegistrationData {
|
||||
nonce,
|
||||
gateway_data: v6::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
v6::registration::IpPair::new(ipv4, ipv6),
|
||||
nonce
|
||||
),
|
||||
wg_port
|
||||
},
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_registered_resp() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let private_ips = v5::registration::IpPair::new(ipv4, ipv6);
|
||||
let wg_port = 51822;
|
||||
let registered_data = v5::registration::RegisteredData {
|
||||
pub_key,
|
||||
private_ips,
|
||||
wg_port,
|
||||
};
|
||||
let request_id = 123;
|
||||
|
||||
let msg = v5::response::AuthenticatorResponse::new_registered(registered_data, request_id);
|
||||
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::response::AuthenticatorResponseData::Registered(v6::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply: v6::registration::RegisteredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ips: v6::registration::IpPair::new(ipv4, ipv6)
|
||||
},
|
||||
upgrade_mode_enabled: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_remaining_bandwidth_resp() {
|
||||
let available_bandwidth = 42;
|
||||
let remaining_bandwidth_data = Some(v5::registration::RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
});
|
||||
let request_id = 123;
|
||||
|
||||
let msg = v5::response::AuthenticatorResponse::new_remaining_bandwidth(
|
||||
remaining_bandwidth_data,
|
||||
request_id,
|
||||
);
|
||||
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
v6::response::RemainingBandwidthResponse {
|
||||
request_id,
|
||||
reply: Some(v6::registration::RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
}),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
pub mod conversion;
|
||||
pub mod registration;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod topup;
|
||||
|
||||
pub const VERSION: u8 = 6;
|
||||
|
||||
pub const PROTOCOL: Protocol = Protocol::new(VERSION, ServiceProviderType::Authenticator);
|
||||
@@ -0,0 +1,287 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
use crate::traits::BandwidthClaim;
|
||||
#[cfg(feature = "verify")]
|
||||
use hmac::{Hmac, Mac};
|
||||
#[cfg(feature = "verify")]
|
||||
use nym_crypto::asymmetric::x25519::{PrivateKey, PublicKey};
|
||||
#[cfg(feature = "verify")]
|
||||
use sha2::Sha256;
|
||||
|
||||
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
|
||||
pub type PrivateIPs = HashMap<IpPair, Taken>;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl IpPair {
|
||||
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
|
||||
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.ipv4, self.ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpAddr> for IpPair {
|
||||
fn from(value: IpAddr) -> Self {
|
||||
let (before_last_byte, last_byte) = match value {
|
||||
IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
|
||||
IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
|
||||
};
|
||||
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
|
||||
let ipv4 = Ipv4Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
||||
before_last_byte,
|
||||
last_byte,
|
||||
);
|
||||
let ipv6 = Ipv6Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
|
||||
last_bytes,
|
||||
);
|
||||
IpPair::new(ipv4, ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InitMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
impl InitMessage {
|
||||
pub fn new(pub_key: PeerPublicKey) -> Self {
|
||||
InitMessage { pub_key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct FinalMessage {
|
||||
/// Gateway client data
|
||||
pub gateway_client: GatewayClient,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: Option<BandwidthClaim>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistrationData {
|
||||
pub nonce: u64,
|
||||
pub gateway_data: GatewayClient,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegisteredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct GatewayClient {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Assigned private IPs (v4 and v6)
|
||||
pub private_ips: IpPair,
|
||||
|
||||
/// Sha256 hmac on the data (alongside the prior nonce)
|
||||
pub mac: ClientMac,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn new(
|
||||
local_secret: &PrivateKey,
|
||||
remote_public: x25519_dalek::PublicKey,
|
||||
private_ips: IpPair,
|
||||
nonce: u64,
|
||||
) -> Self {
|
||||
let local_public = PublicKey::from(local_secret);
|
||||
let remote_public = PublicKey::from(remote_public);
|
||||
|
||||
let dh = local_secret.diffie_hellman(&remote_public);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(&dh[..])
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(local_public.as_bytes());
|
||||
mac.update(private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
GatewayClient {
|
||||
pub_key: PeerPublicKey::new(local_public.into()),
|
||||
private_ips,
|
||||
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable secret should be gateways Wireguard PK
|
||||
// Client should perform this step when generating its payload, using its own WG PK
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// use gateways key as a ref to an x25519_dalek key
|
||||
let dh = gateway_key.inner().diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(self.pub_key.as_bytes());
|
||||
mac.update(self.private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
mac.verify_slice(&self.mac)
|
||||
.map_err(|source| Error::FailedClientMacVerification {
|
||||
client: self.pub_key.to_string(),
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
|
||||
// TODO2: rely on our internal crypto/hmac
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ClientMac(Vec<u8>);
|
||||
|
||||
impl fmt::Display for ClientMac {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ClientMac {
|
||||
fn from(v: Vec<u8>) -> Self {
|
||||
ClientMac(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMac {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(mac: Vec<u8>) -> Self {
|
||||
ClientMac(mac)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ClientMac {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ClientMac {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mac_bytes: Vec<u8> =
|
||||
general_purpose::STANDARD
|
||||
.decode(s)
|
||||
.map_err(|source| Error::MalformedClientMac {
|
||||
mac: s.to_string(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(ClientMac(mac_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ClientMac {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
|
||||
serializer.serialize_str(&encoded_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientMac {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let encoded_key = String::deserialize(deserializer)?;
|
||||
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
|
||||
#[test]
|
||||
fn create_ip_pair() {
|
||||
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
|
||||
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
|
||||
|
||||
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "verify")]
|
||||
fn client_request_roundtrip() {
|
||||
let mut rng = deterministic_rng();
|
||||
|
||||
let gateway_key_pair = x25519::KeyPair::new(&mut rng);
|
||||
let client_key_pair = x25519::KeyPair::new(&mut rng);
|
||||
|
||||
let nonce = 1234567890;
|
||||
|
||||
let client = GatewayClient::new(
|
||||
client_key_pair.private_key(),
|
||||
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
|
||||
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
|
||||
nonce,
|
||||
);
|
||||
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
PROTOCOL,
|
||||
registration::{FinalMessage, InitMessage},
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
use nym_service_provider_requests_common::Protocol;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorRequestData,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl AuthenticatorRequest {
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::Initial(init_message),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::Final(Box::new(final_message)),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(Box<FinalMessage>),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
TopUpBandwidth(Box<TopUpMessage>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::VERSION;
|
||||
use super::*;
|
||||
use nym_service_provider_requests_common::ServiceProviderType;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_first_bytes_protocol() {
|
||||
let version = VERSION;
|
||||
let data = AuthenticatorRequest {
|
||||
protocol: Protocol {
|
||||
version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: AuthenticatorRequestData::Initial(InitMessage::new(
|
||||
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
|
||||
)),
|
||||
request_id: 1,
|
||||
};
|
||||
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
|
||||
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::Protocol;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::PROTOCOL;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuthenticatorResponse {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorResponseData,
|
||||
}
|
||||
|
||||
impl AuthenticatorResponse {
|
||||
pub fn new_pending_registration_success(
|
||||
registration_data: RegistrationData,
|
||||
request_id: u64,
|
||||
upgrade_mode_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
|
||||
reply: registration_data,
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registered_data: RegisteredData,
|
||||
request_id: u64,
|
||||
upgrade_mode_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registered_data,
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
request_id: u64,
|
||||
upgrade_mode_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_topup_bandwidth(
|
||||
remaining_bandwidth_data: RemainingBandwidthData,
|
||||
request_id: u64,
|
||||
upgrade_mode_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
TopUpBandwidth(TopUpBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PendingRegistrationResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegistrationData,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegisteredData,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TopUpBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RemainingBandwidthData,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct TopUpMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: CredentialSpendingData,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{v1, v2, v3, v4, v5};
|
||||
use super::{v1, v2, v3, v4, v5, v6};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::Display)]
|
||||
@@ -22,12 +22,19 @@ pub enum AuthenticatorVersion {
|
||||
/// introduced in dorina-patched release (1.6.1)
|
||||
V5,
|
||||
|
||||
/// introduced in yet to be named release, currently aiming for Leerdammer (1.22.0)
|
||||
V6,
|
||||
|
||||
/// an unknown, future, variant that can be present if running outdated software
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
impl AuthenticatorVersion {
|
||||
pub const LATEST: Self = Self::V5;
|
||||
pub const LATEST: Self = Self::V6;
|
||||
|
||||
#[deprecated(
|
||||
note = "the final version of V6 won't be known until appropriate release is scheduled. after that happens From<semver::Version> trait will have to be adjusted"
|
||||
)]
|
||||
pub const fn release_version(&self) -> semver::Version {
|
||||
match self {
|
||||
AuthenticatorVersion::V1 => semver::Version::new(1, 1, 5),
|
||||
@@ -35,6 +42,7 @@ impl AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3 => semver::Version::new(1, 1, 10),
|
||||
AuthenticatorVersion::V4 => semver::Version::new(1, 2, 0),
|
||||
AuthenticatorVersion::V5 => semver::Version::new(1, 6, 1),
|
||||
AuthenticatorVersion::V6 => semver::Version::new(1, 22, 0),
|
||||
AuthenticatorVersion::UNKNOWN => semver::Version::new(0, 0, 0),
|
||||
}
|
||||
}
|
||||
@@ -54,6 +62,8 @@ impl From<Protocol> for AuthenticatorVersion {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value.version == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else if value.version == v6::VERSION {
|
||||
AuthenticatorVersion::V6
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
@@ -72,6 +82,8 @@ impl From<u8> for AuthenticatorVersion {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else if value == v6::VERSION {
|
||||
AuthenticatorVersion::V6
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
@@ -126,11 +138,14 @@ impl From<semver::Version> for AuthenticatorVersion {
|
||||
if semver < AuthenticatorVersion::V5.release_version() {
|
||||
return Self::V4;
|
||||
}
|
||||
// if provided version is higher (or equal) to release version of V5,
|
||||
// we return the latest (i.e. v5)
|
||||
if semver < AuthenticatorVersion::V6.release_version() {
|
||||
return Self::V5;
|
||||
}
|
||||
// if provided version is higher (or equal) to release version of V6,
|
||||
// we return the latest (i.e. v6)
|
||||
|
||||
debug_assert_eq!(
|
||||
Self::V5,
|
||||
Self::V6,
|
||||
Self::LATEST,
|
||||
"a new AuthenticatorVersion variant has been introduced without adjusting the `From<semver::Version>` trait"
|
||||
);
|
||||
@@ -191,5 +206,8 @@ mod tests {
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.7.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.16.11".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.17.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.20.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V6, "1.21.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V6, "1.22.0".into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,16 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "stream_cipher", "aes", "hashing"] }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::utils::{
|
||||
ApiClientsWrapper,
|
||||
};
|
||||
use log::error;
|
||||
use nym_credential_storage::models::RetrievedTicketbook;
|
||||
use nym_credential_storage::models::{EmergencyCredential, RetrievedTicketbook};
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::ecash::bandwidth::CredentialSpendingData;
|
||||
use nym_credentials_interface::{
|
||||
@@ -220,6 +220,16 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, BandwidthControllerError>
|
||||
where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
self.storage.get_emergency_credential(typ).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, St> Clone for BandwidthController<C, St>
|
||||
|
||||
@@ -11,6 +11,9 @@ use crate::{error::BandwidthControllerError, BandwidthController, PreparedCreden
|
||||
|
||||
pub const DEFAULT_TICKETS_TO_SPEND: u32 = 1;
|
||||
|
||||
// TODO: this does not really belong here
|
||||
pub const UPGRADE_MODE_JWT_TYPE: &str = "UPGRADE_MODE_JWT";
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait BandwidthTicketProvider: Send + Sync {
|
||||
@@ -20,6 +23,8 @@ pub trait BandwidthTicketProvider: Send + Sync {
|
||||
gateway_id: ed25519::PublicKey,
|
||||
tickets_to_spend: u32,
|
||||
) -> Result<PreparedCredential, BandwidthControllerError>;
|
||||
|
||||
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError>;
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -39,4 +44,19 @@ where
|
||||
self.prepare_ecash_ticket(ticket_type, gateway_id.to_bytes(), tickets_to_spend)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
|
||||
let Some(emergency_credential) =
|
||||
self.get_emergency_credential(UPGRADE_MODE_JWT_TYPE).await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
// upgrade mode credential is just a simple stringified JWT
|
||||
let token = String::from_utf8(emergency_credential.content).map_err(|err| {
|
||||
BandwidthControllerError::CredentialStorageError(Box::new(format!(
|
||||
"malformed upgrade mode token: {err}"
|
||||
)))
|
||||
})?;
|
||||
Ok(Some(token))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,3 +88,6 @@ features = ["js"]
|
||||
|
||||
[features]
|
||||
wasm = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -26,6 +27,39 @@ pub struct ClientBandwidth {
|
||||
inner: Arc<ClientBandwidthInner>,
|
||||
}
|
||||
|
||||
// simple helper for logging purposes to accommodate 'unknown' case
|
||||
pub(crate) enum UpgradeModeEnabledWrapper {
|
||||
True,
|
||||
False,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<Option<bool>> for UpgradeModeEnabledWrapper {
|
||||
fn from(value: Option<bool>) -> Self {
|
||||
match value {
|
||||
Some(true) => UpgradeModeEnabledWrapper::True,
|
||||
Some(false) => UpgradeModeEnabledWrapper::False,
|
||||
None => UpgradeModeEnabledWrapper::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for UpgradeModeEnabledWrapper {
|
||||
fn from(value: bool) -> Self {
|
||||
Some(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UpgradeModeEnabledWrapper {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UpgradeModeEnabledWrapper::True => write!(f, "true"),
|
||||
UpgradeModeEnabledWrapper::False => write!(f, "false"),
|
||||
UpgradeModeEnabledWrapper::Unknown => write!(f, "unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ClientBandwidthInner {
|
||||
/// the actual bandwidth amount (in bytes) available
|
||||
available: AtomicI64,
|
||||
@@ -71,26 +105,41 @@ impl ClientBandwidth {
|
||||
self.inner.available.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_log_bandwidth(&self, now: Option<OffsetDateTime>) {
|
||||
pub(crate) fn maybe_log_bandwidth(
|
||||
&self,
|
||||
now: Option<OffsetDateTime>,
|
||||
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
|
||||
) {
|
||||
let last = self.last_logged();
|
||||
let now = now.unwrap_or_else(OffsetDateTime::now_utc);
|
||||
if last + Duration::from_secs(10) < now {
|
||||
self.log_bandwidth(Some(now))
|
||||
self.log_bandwidth(Some(now), upgrade_mode)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_bandwidth(&self, now: Option<OffsetDateTime>) {
|
||||
pub(crate) fn log_bandwidth(
|
||||
&self,
|
||||
now: Option<OffsetDateTime>,
|
||||
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
|
||||
) {
|
||||
let now = now.unwrap_or_else(OffsetDateTime::now_utc);
|
||||
let upgrade_mode = upgrade_mode.into();
|
||||
|
||||
let remaining = self.remaining();
|
||||
let remaining_bi2 = bibytes2(remaining as f64);
|
||||
|
||||
if remaining < 0 {
|
||||
tracing::warn!("OUT OF BANDWIDTH. remaining: {remaining_bi2}");
|
||||
tracing::warn!(
|
||||
"OUT OF BANDWIDTH. remaining: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
|
||||
);
|
||||
} else if remaining < 1_000_000 {
|
||||
tracing::info!("remaining bandwidth: {remaining_bi2}");
|
||||
tracing::info!(
|
||||
"remaining bandwidth: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
|
||||
);
|
||||
} else {
|
||||
tracing::debug!("remaining bandwidth: {remaining_bi2}");
|
||||
tracing::trace!(
|
||||
"remaining bandwidth: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
|
||||
);
|
||||
}
|
||||
|
||||
self.inner
|
||||
@@ -98,26 +147,35 @@ impl ClientBandwidth {
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub(crate) fn update_and_maybe_log(&self, remaining: i64) {
|
||||
pub(crate) fn update_and_maybe_log(
|
||||
&self,
|
||||
remaining: i64,
|
||||
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
|
||||
) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
self.inner.available.store(remaining, Ordering::Release);
|
||||
self.inner
|
||||
.last_updated_ts
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed);
|
||||
self.maybe_log_bandwidth(Some(now))
|
||||
self.maybe_log_bandwidth(Some(now), upgrade_mode)
|
||||
}
|
||||
|
||||
pub(crate) fn update_and_log(&self, remaining: i64) {
|
||||
pub(crate) fn update_and_log(
|
||||
&self,
|
||||
remaining: i64,
|
||||
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
|
||||
) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
self.inner.available.store(remaining, Ordering::Release);
|
||||
self.inner
|
||||
.last_updated_ts
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed);
|
||||
self.log_bandwidth(Some(now))
|
||||
self.log_bandwidth(Some(now), upgrade_mode)
|
||||
}
|
||||
|
||||
fn last_logged(&self) -> OffsetDateTime {
|
||||
// SAFETY: this value is always populated with valid timestamps
|
||||
#[allow(clippy::unwrap_used)]
|
||||
OffsetDateTime::from_unix_timestamp(self.inner.last_logged_ts.load(Ordering::Relaxed))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||
GatewayRequestsError, SensitiveServerResponse, ServerResponse, SharedGatewayKey,
|
||||
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
BandwidthResponse, BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersion,
|
||||
GatewayProtocolVersionExt, GatewayRequestsError, SensitiveServerResponse, ServerResponse,
|
||||
SharedGatewayKey, SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
@@ -101,8 +101,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
|
||||
// currently unused (but populated)
|
||||
negotiated_protocol: Option<u8>,
|
||||
negotiated_protocol: Option<GatewayProtocolVersion>,
|
||||
|
||||
// Callback on the fd as soon as the connection has been established
|
||||
#[cfg(unix)]
|
||||
@@ -166,10 +165,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(mut socket) => Ok((*socket).close(None).await?),
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
// SAFETY: this is only called after the caller has already recovered the connection
|
||||
unreachable!("this branch should have never been reached!")
|
||||
}
|
||||
_ => Ok(()), // no need to do anything in those cases
|
||||
@@ -177,6 +178,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(socket) => {
|
||||
@@ -184,6 +186,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Ok(())
|
||||
}
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
// SAFETY: this is only called after the caller has already recovered the connection
|
||||
unreachable!("this branch should have never been reached!")
|
||||
}
|
||||
_ => Ok(()), // no need to do anything in those cases
|
||||
@@ -458,43 +461,16 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_gateway_protocol(
|
||||
&self,
|
||||
gateway_protocol: Option<u8>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
|
||||
// right now there are no failure cases here, but this might change in the future
|
||||
match gateway_protocol {
|
||||
None => {
|
||||
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
|
||||
// note: in +1.2.0 we will have to return a hard error here
|
||||
Ok(())
|
||||
}
|
||||
Some(v) if v > CURRENT_PROTOCOL_VERSION => {
|
||||
let err = GatewayClientError::IncompatibleProtocol {
|
||||
gateway: Some(v),
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
Err(err)
|
||||
}
|
||||
|
||||
Some(_) => {
|
||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn register(
|
||||
&mut self,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
supported_gateway_protocol: Option<GatewayProtocolVersion>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
let derive_aes256_gcm_siv_key = supported_gateway_protocol.supports_aes256_gcm_siv();
|
||||
|
||||
debug_assert!(self.connection.is_available());
|
||||
log::debug!(
|
||||
"registering with gateway. using legacy key derivation: {}",
|
||||
@@ -505,14 +481,13 @@ impl<C, St> GatewayClient<C, St> {
|
||||
// and putting it into the GatewayClient struct would be a hassle
|
||||
let mut rng = OsRng;
|
||||
|
||||
let shared_key = match &mut self.connection {
|
||||
let handshake_result = match &mut self.connection {
|
||||
SocketState::Available(ws_stream) => client_handshake(
|
||||
&mut rng,
|
||||
ws_stream,
|
||||
self.local_identity.as_ref(),
|
||||
self.gateway_identity,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
derive_aes256_gcm_siv_key,
|
||||
supported_gateway_protocol,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.shutdown_token.clone(),
|
||||
)
|
||||
@@ -521,26 +496,31 @@ impl<C, St> GatewayClient<C, St> {
|
||||
_ => return Err(GatewayClientError::ConnectionInInvalidState),
|
||||
}?;
|
||||
|
||||
let (authentication_status, gateway_protocol) = match self.read_control_response().await? {
|
||||
let authentication_status = match self.read_control_response().await? {
|
||||
ServerResponse::Register {
|
||||
protocol_version,
|
||||
status,
|
||||
} => (status, protocol_version),
|
||||
upgrade_mode,
|
||||
..
|
||||
} => {
|
||||
if upgrade_mode {
|
||||
warn!("the system is currently undergoing an upgrade. some of its functionalities might be unstable")
|
||||
}
|
||||
status
|
||||
}
|
||||
ServerResponse::Error { message } => {
|
||||
return Err(GatewayClientError::GatewayError(message))
|
||||
}
|
||||
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
};
|
||||
|
||||
self.check_gateway_protocol(gateway_protocol)?;
|
||||
self.authenticated = authentication_status;
|
||||
|
||||
if self.authenticated {
|
||||
self.shared_key = Some(Arc::new(shared_key));
|
||||
self.shared_key = Some(Arc::new(handshake_result.derived_key));
|
||||
}
|
||||
|
||||
// populate the negotiated protocol for future uses
|
||||
self.negotiated_protocol = gateway_protocol;
|
||||
self.negotiated_protocol = Some(handshake_result.negotiated_protocol);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -623,13 +603,24 @@ impl<C, St> GatewayClient<C, St> {
|
||||
protocol_version,
|
||||
status,
|
||||
bandwidth_remaining,
|
||||
upgrade_mode,
|
||||
} => {
|
||||
self.check_gateway_protocol(protocol_version)?;
|
||||
if protocol_version.is_future_version() {
|
||||
// SAFETY: future version is always defined
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let version = protocol_version.unwrap();
|
||||
error!("the gateway insists on using v{version} protocol which is not supported by this client");
|
||||
return Err(GatewayClientError::AuthenticationFailure);
|
||||
}
|
||||
self.authenticated = status;
|
||||
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
|
||||
self.bandwidth
|
||||
.update_and_maybe_log(bandwidth_remaining, upgrade_mode);
|
||||
|
||||
self.negotiated_protocol = protocol_version;
|
||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||
if upgrade_mode {
|
||||
warn!("the system is currently undergoing an upgrade. some of its functionalities might be unstable")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -650,7 +641,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate(
|
||||
let msg = ClientControlRequest::new_legacy_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
@@ -659,25 +650,40 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate_v2(
|
||||
&mut self,
|
||||
requested_protocol_version: GatewayProtocolVersion,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
debug!("using v2 authentication");
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?;
|
||||
let msg = ClientControlRequest::new_authenticate_v2(
|
||||
shared_key,
|
||||
&self.local_identity,
|
||||
requested_protocol_version,
|
||||
)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate(
|
||||
&mut self,
|
||||
supported_gateway_protocol: Option<GatewayProtocolVersion>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
if use_v2 {
|
||||
self.authenticate_v2().await
|
||||
if supported_gateway_protocol.supports_authenticate_v2() {
|
||||
// use the highest possible protocol version the gateway has announced support for
|
||||
|
||||
// SAFETY: if announced protocol supports auth v2, it means it's properly set
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.authenticate_v2(supported_gateway_protocol.unwrap())
|
||||
.await
|
||||
} else {
|
||||
self.authenticate_v1().await
|
||||
}
|
||||
@@ -708,9 +714,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
};
|
||||
|
||||
debug!("supported gateway protocol: {gw_protocol:?}");
|
||||
|
||||
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||
let supports_key_rotation_info = gw_protocol.supports_key_rotation_packet();
|
||||
let supports_upgrade_mode = gw_protocol.supports_upgrade_mode();
|
||||
|
||||
if !supports_aes_gcm_siv {
|
||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
@@ -721,6 +730,16 @@ impl<C, St> GatewayClient<C, St> {
|
||||
if !supports_key_rotation_info {
|
||||
warn!("this gateway is on an old version that doesn't support key rotation packets")
|
||||
}
|
||||
if !supports_upgrade_mode {
|
||||
warn!("this gateway is on an old version that doesn't support upgrade mode")
|
||||
}
|
||||
|
||||
let gw_protocol = if gw_protocol.is_future_version() {
|
||||
warn!("we're running outdated software as gateway is announcing protocol {gw_protocol:?} whilst we're using {}. we're going to attempt to downgrade", GatewayProtocolVersion::CURRENT);
|
||||
Some(GatewayProtocolVersion::CURRENT)
|
||||
} else {
|
||||
gw_protocol
|
||||
};
|
||||
|
||||
if self.authenticated {
|
||||
debug!("Already authenticated");
|
||||
@@ -735,10 +754,11 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
if self.shared_key.is_some() {
|
||||
self.authenticate(supports_auth_v2).await?;
|
||||
self.authenticate(gw_protocol).await?;
|
||||
|
||||
if self.authenticated {
|
||||
// if we are authenticated it means we MUST have an associated shared_key
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
let requires_key_upgrade = shared_key.is_legacy() && supports_aes_gcm_siv;
|
||||
@@ -751,9 +771,10 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Err(GatewayClientError::AuthenticationFailure)
|
||||
}
|
||||
} else {
|
||||
self.register(supports_aes_gcm_siv).await?;
|
||||
self.register(gw_protocol).await?;
|
||||
|
||||
// if registration didn't return an error, we MUST have an associated shared key
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
// we're always registering with the highest supported protocol,
|
||||
@@ -783,51 +804,81 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn claim_ecash_bandwidth(
|
||||
async fn wait_for_bandwidth_response(
|
||||
&mut self,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let msg = ClientControlRequest::new_enc_ecash_credential(
|
||||
credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
)?;
|
||||
let bandwidth_remaining = match self
|
||||
msg: ClientControlRequest,
|
||||
) -> Result<BandwidthResponse, GatewayClientError> {
|
||||
let response = match self
|
||||
.send_websocket_message_with_non_send_response(msg)
|
||||
.await?
|
||||
{
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Bandwidth(response) => {
|
||||
if response.upgrade_mode {
|
||||
info!("the system is currently undergoing an upgrade. our bandwidth shouldn't have been metered")
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
ServerResponse::TypedError { error } => {
|
||||
Err(GatewayClientError::TypedGatewayError(error))
|
||||
}
|
||||
other => Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
}?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn claim_ecash_bandwidth(
|
||||
&mut self,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
// SAFETY: claiming ecash bandwidth is called as part of `claim_bandwidth` which
|
||||
// ensures the shared key is defined
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let msg = ClientControlRequest::new_enc_ecash_credential(
|
||||
credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
)?;
|
||||
let response = self.wait_for_bandwidth_response(msg).await?;
|
||||
|
||||
// TODO: create tracing span
|
||||
info!("managed to claim ecash bandwidth");
|
||||
self.bandwidth.update_and_log(bandwidth_remaining);
|
||||
self.bandwidth
|
||||
.update_and_log(response.available_total, response.upgrade_mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_upgrade_mode_jwt(&mut self, token: String) -> Result<(), GatewayClientError> {
|
||||
let msg = ClientControlRequest::new_upgrade_mode_jwt(token);
|
||||
let response = self.wait_for_bandwidth_response(msg).await?;
|
||||
|
||||
// if gateway rejected our jwt, we would have returned an error
|
||||
info!("gateway has accepted our jwt");
|
||||
if !response.upgrade_mode {
|
||||
error!("but we're not in upgrade mode - something is wrong!");
|
||||
return Err(GatewayClientError::UnexpectedUpgradeModeState);
|
||||
}
|
||||
|
||||
self.bandwidth
|
||||
.update_and_log(response.available_total, response.upgrade_mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
||||
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth;
|
||||
let bandwidth_remaining = match self
|
||||
.send_websocket_message_with_non_send_response(msg)
|
||||
.await?
|
||||
{
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
other => Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
}?;
|
||||
let response = self.wait_for_bandwidth_response(msg).await?;
|
||||
|
||||
info!("managed to claim testnet bandwidth");
|
||||
self.bandwidth.update_and_log(bandwidth_remaining);
|
||||
self.bandwidth
|
||||
.update_and_log(response.available_total, response.upgrade_mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unchecked_bandwidth_controller(&self) -> &BandwidthController<C, St> {
|
||||
// this is an unchecked method
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.bandwidth_controller.as_ref().unwrap()
|
||||
}
|
||||
|
||||
@@ -919,6 +970,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
BinaryRequest::ForwardSphinx { packet }
|
||||
};
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
req.into_ws_message(
|
||||
self.shared_key
|
||||
.as_ref()
|
||||
@@ -1025,6 +1077,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.send_with_reconnection_on_failure(msg).await
|
||||
}
|
||||
|
||||
// SAFETY: this method is only called when the connection is in `PartiallyDelegated` state
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
if self.connection.is_available() {
|
||||
return Ok(());
|
||||
@@ -1054,6 +1108,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
return Err(GatewayClientError::ConnectionInInvalidState);
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let partially_delegated =
|
||||
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
||||
SocketState::Available(conn) => {
|
||||
@@ -1069,7 +1124,13 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.shutdown_token.clone(),
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
other => {
|
||||
error!(
|
||||
"attempted to start mixnet listener whilst the connection is in {} state!",
|
||||
other.name()
|
||||
);
|
||||
return Err(GatewayClientError::ConnectionInInvalidState);
|
||||
}
|
||||
};
|
||||
|
||||
self.connection = SocketState::PartiallyDelegated(partially_delegated);
|
||||
@@ -1082,8 +1143,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
||||
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
|
||||
.await?;
|
||||
self.authenticate(self.negotiated_protocol).await?;
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
@@ -128,6 +128,9 @@ pub enum GatewayClientError {
|
||||
"this operation couldn't be completed as the program is in the process of shutting down"
|
||||
)]
|
||||
ShutdownInProgress,
|
||||
|
||||
#[error("the system is an unexpected upgrade mode state")]
|
||||
UnexpectedUpgradeModeState,
|
||||
}
|
||||
|
||||
impl From<WsError> for GatewayClientError {
|
||||
|
||||
@@ -35,6 +35,7 @@ impl PacketRouter {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
pub fn route_mixnet_messages(
|
||||
&self,
|
||||
received_messages: Vec<Vec<u8>>,
|
||||
@@ -54,6 +55,7 @@ impl PacketRouter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
pub fn route_acks(&self, received_acks: Vec<Vec<u8>>) -> Result<(), GatewayClientError> {
|
||||
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
|
||||
// check if the failure is due to the shutdown being in progress and thus the receiver channel
|
||||
|
||||
@@ -10,7 +10,9 @@ use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::{SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_gateway_requests::{
|
||||
SendResponse, SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError,
|
||||
};
|
||||
use nym_task::ShutdownToken;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
@@ -154,11 +156,12 @@ impl PartiallyDelegatedRouter {
|
||||
fn handle_text_message(&self, text: String) -> Result<(), GatewayClientError> {
|
||||
// if we fail to deserialise the response, return a hard error. we can't handle garbage
|
||||
match ServerResponse::try_from(text).map_err(|_| GatewayClientError::MalformedResponse)? {
|
||||
ServerResponse::Send {
|
||||
ServerResponse::Send(SendResponse {
|
||||
remaining_bandwidth,
|
||||
} => {
|
||||
upgrade_mode,
|
||||
}) => {
|
||||
self.client_bandwidth
|
||||
.update_and_maybe_log(remaining_bandwidth);
|
||||
.update_and_maybe_log(remaining_bandwidth, upgrade_mode);
|
||||
Ok(())
|
||||
}
|
||||
ServerResponse::Error { message } => {
|
||||
@@ -174,7 +177,7 @@ impl PartiallyDelegatedRouter {
|
||||
let available_bi2 = bibytes2(available as f64);
|
||||
let required_bi2 = bibytes2(required as f64);
|
||||
warn!("run out of bandwidth when attempting to send the message! we got {available_bi2} available, but needed at least {required_bi2} to send the previous message");
|
||||
self.client_bandwidth.update_and_log(available);
|
||||
self.client_bandwidth.update_and_log(available, None);
|
||||
// UNIMPLEMENTED: we should stop sending messages until we recover bandwidth
|
||||
Ok(())
|
||||
}
|
||||
@@ -327,6 +330,7 @@ impl PartiallyDelegatedHandle {
|
||||
Ok(self.sink_half.send_all(&mut send_stream).await?)
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
pub(crate) async fn merge(self) -> Result<WsConn, GatewayClientError> {
|
||||
let (mut stream_receiver, notify) = self.delegated_stream;
|
||||
|
||||
@@ -355,8 +359,10 @@ impl PartiallyDelegatedHandle {
|
||||
// in receive_res
|
||||
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
|
||||
let stream = stream_results?;
|
||||
|
||||
// the error is thrown when trying to reunite sink and stream that did not originate
|
||||
// from the same split which is impossible to happen here
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(self.sink_half.reunite(stream).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -387,4 +393,13 @@ impl SocketState {
|
||||
SocketState::Available(_) | SocketState::PartiallyDelegated(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> &'static str {
|
||||
match self {
|
||||
SocketState::Available(_) => "available",
|
||||
SocketState::PartiallyDelegated(_) => "partially delegated",
|
||||
SocketState::NotConnected => "not connected",
|
||||
SocketState::Invalid => "invalid",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE TABLE emergency_credential
|
||||
(
|
||||
type TEXT NOT NULL,
|
||||
|
||||
-- don't define any strict schema on the content as it might be implementation-dependant
|
||||
content BLOB NOT NULL,
|
||||
|
||||
expiration TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
|
||||
use crate::models::{
|
||||
BasicTicketbookInformation, EmergencyCredential, RetrievedPendingTicketbook,
|
||||
RetrievedTicketbook,
|
||||
};
|
||||
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
|
||||
use nym_compact_ecash::scheme::expiration_date_signatures::AnnotatedExpirationDateSignature;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
@@ -29,6 +32,7 @@ struct EcashCredentialManagerInner {
|
||||
master_vk: HashMap<u64, VerificationKeyAuth>,
|
||||
coin_indices_sigs: HashMap<u64, Vec<AnnotatedCoinIndexSignature>>,
|
||||
expiration_date_sigs: HashMap<(u64, Date), Vec<AnnotatedExpirationDateSignature>>,
|
||||
emergency_credentials: HashMap<String, Vec<EmergencyCredential>>,
|
||||
_next_id: i64,
|
||||
}
|
||||
|
||||
@@ -277,4 +281,20 @@ impl MemoryEcachTicketbookManager {
|
||||
sigs.signatures.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) async fn get_emergency_credential(&self, typ: &str) -> Option<EmergencyCredential> {
|
||||
let guard = self.inner.read().await;
|
||||
|
||||
guard.emergency_credentials.get(typ)?.first().cloned()
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_emergency_credential(&self, credential: &EmergencyCredential) {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
guard
|
||||
.emergency_credentials
|
||||
.entry(credential.typ.clone())
|
||||
.or_default()
|
||||
.push(credential.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::{
|
||||
BasicTicketbookInformation, RawCoinIndexSignatures, RawExpirationDateSignatures,
|
||||
RawVerificationKey, StoredIssuedTicketbook, StoredPendingTicketbook,
|
||||
BasicTicketbookInformation, EmergencyCredential, RawCoinIndexSignatures,
|
||||
RawExpirationDateSignatures, RawVerificationKey, StoredIssuedTicketbook,
|
||||
StoredPendingTicketbook,
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use sqlx::{Executor, Sqlite, Transaction};
|
||||
@@ -305,6 +306,44 @@ impl SqliteEcashTicketbookManager {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, sqlx::Error> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM emergency_credential
|
||||
WHERE type = ?
|
||||
AND (expiration IS NULL OR expiration > CURRENT_TIMESTAMP)
|
||||
ORDER BY expiration ASC NULLS LAST
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(typ)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredential,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO emergency_credential
|
||||
(type, content, expiration)
|
||||
VALUES (?, ?, ?)
|
||||
"#,
|
||||
credential.typ,
|
||||
credential.content,
|
||||
credential.expiration,
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_next_unspent_ticketbook<'a, E>(
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
use crate::backends::memory::MemoryEcachTicketbookManager;
|
||||
use crate::error::StorageError;
|
||||
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
|
||||
use crate::models::{
|
||||
BasicTicketbookInformation, EmergencyCredential, RetrievedPendingTicketbook,
|
||||
RetrievedTicketbook,
|
||||
};
|
||||
use crate::storage::Storage;
|
||||
use async_trait::async_trait;
|
||||
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
|
||||
@@ -218,6 +221,23 @@ impl Storage for EphemeralStorage {
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, Self::StorageError> {
|
||||
Ok(self.storage_manager.get_emergency_credential(typ).await)
|
||||
}
|
||||
|
||||
async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredential,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager
|
||||
.insert_emergency_credential(credential)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use nym_credentials::{IssuanceTicketBook, IssuedTicketBook};
|
||||
use nym_ecash_time::Date;
|
||||
use sqlx::types::time::OffsetDateTime;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub struct RetrievedTicketbook {
|
||||
@@ -78,3 +79,12 @@ pub struct RawVerificationKey {
|
||||
pub serialised_key: Vec<u8>,
|
||||
pub serialization_revision: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
|
||||
pub struct EmergencyCredential {
|
||||
#[cfg_attr(not(target_arch = "wasm32"), sqlx(rename = "type"))]
|
||||
pub typ: String,
|
||||
pub content: Vec<u8>,
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
mod legacy_helpers;
|
||||
|
||||
use crate::models::EmergencyCredential;
|
||||
use crate::{
|
||||
backends::sqlite::{
|
||||
get_next_unspent_ticketbook, increase_used_ticketbook_tickets, SqliteEcashTicketbookManager,
|
||||
@@ -401,4 +402,21 @@ impl Storage for PersistentStorage {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, Self::StorageError> {
|
||||
Ok(self.storage_manager.get_emergency_credential(typ).await?)
|
||||
}
|
||||
|
||||
async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredential,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager
|
||||
.insert_emergency_credential(credential)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
|
||||
use crate::models::{
|
||||
BasicTicketbookInformation, EmergencyCredential, RetrievedPendingTicketbook,
|
||||
RetrievedTicketbook,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
use nym_credentials::ecash::bandwidth::serialiser::keys::EpochVerificationKey;
|
||||
@@ -108,4 +111,14 @@ pub trait Storage: Clone + Send + Sync {
|
||||
&self,
|
||||
signatures: &AggregatedExpirationDateSignatures,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, Self::StorageError>;
|
||||
|
||||
async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredential,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ cosmwasm-std = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
dyn-clone = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
si-scale = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
@@ -32,3 +31,4 @@ nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-gateway-storage = { path = "../gateway-storage" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-upgrade-mode-check = { path = "../upgrade-mode-check" }
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::ClientBandwidth;
|
||||
use crate::error::*;
|
||||
use nym_credentials::ecash::utils::ecash_today;
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_gateway_requests::ServerResponse;
|
||||
use nym_gateway_storage::traits::BandwidthGatewayStorage;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use time::OffsetDateTime;
|
||||
@@ -66,7 +65,7 @@ impl BandwidthStorageManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<ServerResponse> {
|
||||
pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<i64> {
|
||||
debug!("handling testnet bandwidth request");
|
||||
|
||||
if self.only_coconut_credentials {
|
||||
@@ -76,8 +75,7 @@ impl BandwidthStorageManager {
|
||||
self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today())
|
||||
.await?;
|
||||
let available_total = self.client_bandwidth.available().await;
|
||||
|
||||
Ok(ServerResponse::Bandwidth { available_total })
|
||||
Ok(available_total)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
@@ -96,7 +94,7 @@ impl BandwidthStorageManager {
|
||||
|
||||
let available_bi2 = bibytes2(available_bandwidth as f64);
|
||||
let required_bi2 = bibytes2(required_bandwidth as f64);
|
||||
debug!(available = available_bi2, required = required_bi2);
|
||||
trace!(available = available_bi2, required = required_bi2);
|
||||
|
||||
self.consume_bandwidth(required_bandwidth).await?;
|
||||
let remaining_bandwidth = self.client_bandwidth.available().await;
|
||||
|
||||
@@ -47,6 +47,25 @@ pub enum Error {
|
||||
UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn is_out_of_bandwidth(&self) -> bool {
|
||||
matches!(self, Error::OutOfBandwidth { .. })
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OutOfBandwidthResultExt {
|
||||
fn is_out_of_bandwidth(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> OutOfBandwidthResultExt for Result<T> {
|
||||
fn is_out_of_bandwidth(&self) -> bool {
|
||||
match &self {
|
||||
Ok(_) => false,
|
||||
Err(err) => err.is_out_of_bandwidth(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EcashTicketError> for Error {
|
||||
fn from(err: EcashTicketError) -> Self {
|
||||
// don't expose storage issue details to the user
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod bandwidth_storage_manager;
|
||||
mod client_bandwidth;
|
||||
pub mod ecash;
|
||||
pub mod error;
|
||||
pub mod upgrade_mode;
|
||||
|
||||
pub struct CredentialVerifier {
|
||||
credential: CredentialSpendingRequest,
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use nym_upgrade_mode_check::{
|
||||
CREDENTIAL_PROXY_JWT_ISSUER, UpgradeModeAttestation, validate_upgrade_mode_jwt,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::{Notify, RwLock};
|
||||
use tracing::{debug, error};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UpgradeModeEnableError {
|
||||
#[error("too soon to perform another upgrade mode attestation check")]
|
||||
TooManyRecheckRequests,
|
||||
|
||||
#[error("provided upgrade mode JWT is invalid: {0}")]
|
||||
InvalidUpgradeModeJWT(#[from] nym_upgrade_mode_check::UpgradeModeCheckError),
|
||||
|
||||
#[error("the upgrade mode attestation does not appear to have been published")]
|
||||
AttestationNotPublished,
|
||||
|
||||
#[error("the provided upgrade mode attestation is different from the published one")]
|
||||
MismatchedUpgradeModeAttestation,
|
||||
}
|
||||
|
||||
// the idea behind this is as follows:
|
||||
// it's been relatively a long time since the watcher last performed its checks (since it's in 'regular' mode)
|
||||
// and some client has just sent a JWT. we have to retrieve most recent information in case upgrade mode
|
||||
// has just been enabled, and we haven't learned about it yet
|
||||
#[derive(Clone)]
|
||||
pub struct UpgradeModeCheckRequestSender(Option<UnboundedSender<CheckRequest>>);
|
||||
|
||||
impl UpgradeModeCheckRequestSender {
|
||||
pub fn new(sender: UnboundedSender<CheckRequest>) -> Self {
|
||||
UpgradeModeCheckRequestSender(Some(sender))
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
|
||||
pub(crate) fn send_request(&self, on_done: Arc<Notify>) {
|
||||
let Some(ref inner) = self.0 else {
|
||||
// make sure the caller gets notified so it doesn't wait forever
|
||||
on_done.notify_waiters();
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(not_sent) = inner.unbounded_send(CheckRequest { on_done }) {
|
||||
debug!("failed to send upgrade mode check request - {not_sent}");
|
||||
// make sure the caller gets notified so it doesn't wait forever
|
||||
not_sent.into_inner().on_done.notify_waiters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type UpgradeModeCheckRequestReceiver = UnboundedReceiver<CheckRequest>;
|
||||
|
||||
pub struct CheckRequest {
|
||||
on_done: Arc<Notify>,
|
||||
}
|
||||
|
||||
impl CheckRequest {
|
||||
pub fn finalize(self) {
|
||||
self.on_done.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UpgradeModeCheckConfig {
|
||||
/// The minimum duration since the last explicit check to allow creation of separate request.
|
||||
pub min_staleness_recheck: Duration,
|
||||
}
|
||||
|
||||
/// Full upgrade mode information, that apart from boolean flag indicating the state
|
||||
/// and the attestation information, includes channel connection to relevant
|
||||
/// attestation watcher to request state rechecks
|
||||
#[derive(Clone)]
|
||||
pub struct UpgradeModeDetails {
|
||||
pub(crate) config: UpgradeModeCheckConfig,
|
||||
pub(crate) request_checker: UpgradeModeCheckRequestSender,
|
||||
pub(crate) state: UpgradeModeState,
|
||||
}
|
||||
|
||||
impl UpgradeModeDetails {
|
||||
pub fn new(
|
||||
config: UpgradeModeCheckConfig,
|
||||
request_checker: UpgradeModeCheckRequestSender,
|
||||
state: UpgradeModeState,
|
||||
) -> Self {
|
||||
UpgradeModeDetails {
|
||||
config,
|
||||
request_checker,
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.state.upgrade_mode_enabled()
|
||||
}
|
||||
|
||||
fn since_last_query(&self) -> Duration {
|
||||
self.state.since_last_query()
|
||||
}
|
||||
|
||||
pub fn can_request_recheck(&self) -> bool {
|
||||
self.since_last_query() > self.config.min_staleness_recheck
|
||||
}
|
||||
|
||||
// explicitly request state update. this is only called when upgrade mode is NOT enabled,
|
||||
// and client has sent a JWT instead of ticket
|
||||
async fn request_recheck(&self) -> bool {
|
||||
// send request
|
||||
let on_done = Arc::new(Notify::new());
|
||||
self.request_checker.send_request(on_done.clone());
|
||||
|
||||
// wait for response - note, if we fail to send, notification will be sent regardless,
|
||||
// so that we wouldn't get stuck in here
|
||||
on_done.notified().await;
|
||||
|
||||
// check the state again
|
||||
self.enabled()
|
||||
}
|
||||
|
||||
pub async fn try_enable_via_received_jwt(
|
||||
&self,
|
||||
token: String,
|
||||
) -> Result<(), UpgradeModeEnableError> {
|
||||
// see if it's viable to perform another expedited check
|
||||
if !self.can_request_recheck() {
|
||||
return Err(UpgradeModeEnableError::TooManyRecheckRequests);
|
||||
}
|
||||
|
||||
// first validate whether the received JWT is even valid
|
||||
// note: we expect the token has been signed by our credential proxy
|
||||
// (in the future, we won't care about it, and we'll have proper key discovery endpoint. 2026™️)
|
||||
let attestation = validate_upgrade_mode_jwt(&token, Some(CREDENTIAL_PROXY_JWT_ISSUER))?;
|
||||
|
||||
// send request to revalidate internal state
|
||||
self.request_recheck().await;
|
||||
|
||||
// not strictly necessary, but check if provided attestation actually matches the one retrieved
|
||||
// (if any)
|
||||
let Some(retrieved_attestation) = self.state.attestation().await else {
|
||||
return Err(UpgradeModeEnableError::AttestationNotPublished);
|
||||
};
|
||||
if retrieved_attestation != attestation {
|
||||
return Err(UpgradeModeEnableError::MismatchedUpgradeModeAttestation);
|
||||
}
|
||||
|
||||
// note: if attestation has been returned, it means we're definitely in upgrade mode
|
||||
// (otherwise it wouldn't have existed in the state)
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Detailed upgrade mode information, that apart from boolean flag,
|
||||
/// also includes, if applicable, the associated attestation
|
||||
#[derive(Clone)]
|
||||
pub struct UpgradeModeState {
|
||||
inner: Arc<UpgradeModeStateInner>,
|
||||
}
|
||||
|
||||
/// Just a shareable flag to indicate whether upgrade mode is enabled or disabled
|
||||
#[derive(Clone, Default)]
|
||||
pub struct UpgradeModeStatus(Arc<AtomicBool>);
|
||||
|
||||
impl UpgradeModeStatus {
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.0.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn enable(&self) {
|
||||
self.0.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn disable(&self) {
|
||||
self.0.store(false, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeState {
|
||||
pub fn new_empty() -> UpgradeModeState {
|
||||
UpgradeModeState {
|
||||
inner: Arc::new(UpgradeModeStateInner {
|
||||
expected_attestation: RwLock::new(None),
|
||||
last_queried_ts: AtomicI64::new(OffsetDateTime::UNIX_EPOCH.unix_timestamp()),
|
||||
status: UpgradeModeStatus(Arc::new(AtomicBool::new(false))),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn attestation(&self) -> Option<UpgradeModeAttestation> {
|
||||
self.inner.expected_attestation.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn set_expected_attestation(
|
||||
&self,
|
||||
expected_attestation: Option<UpgradeModeAttestation>,
|
||||
) {
|
||||
let mut guard = self.inner.expected_attestation.write().await;
|
||||
// make sure to only enable upgrade mode flag AFTER we have written the expected value
|
||||
// (or still hold the exclusive lock as in this instance)
|
||||
if expected_attestation.is_some() {
|
||||
self.enable_upgrade_mode()
|
||||
} else {
|
||||
self.disable_upgrade_mode()
|
||||
}
|
||||
self.update_last_queried(OffsetDateTime::now_utc());
|
||||
*guard = expected_attestation;
|
||||
}
|
||||
|
||||
pub fn upgrade_mode_status(&self) -> UpgradeModeStatus {
|
||||
self.inner.status.clone()
|
||||
}
|
||||
|
||||
pub fn upgrade_mode_enabled(&self) -> bool {
|
||||
self.inner.status.enabled()
|
||||
}
|
||||
|
||||
pub fn enable_upgrade_mode(&self) {
|
||||
self.inner.status.enable()
|
||||
}
|
||||
|
||||
pub fn disable_upgrade_mode(&self) {
|
||||
self.inner.status.disable()
|
||||
}
|
||||
|
||||
pub fn last_queried(&self) -> OffsetDateTime {
|
||||
// SAFETY: the stored value here is always a valid unix timestamp
|
||||
#[allow(clippy::unwrap_used)]
|
||||
OffsetDateTime::from_unix_timestamp(self.inner.last_queried_ts.load(Ordering::Acquire))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn update_last_queried(&self, queried_at: OffsetDateTime) {
|
||||
self.inner
|
||||
.last_queried_ts
|
||||
.store(queried_at.unix_timestamp(), Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn since_last_query(&self) -> Duration {
|
||||
(OffsetDateTime::now_utc() - self.last_queried())
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| {
|
||||
error!("somehow our last query for upgrade mode was in the future!");
|
||||
Duration::ZERO
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct UpgradeModeStateInner {
|
||||
/// Contents of the published upgrade mode attestation, as queried by this node
|
||||
expected_attestation: RwLock<Option<UpgradeModeAttestation>>,
|
||||
|
||||
/// timestamp indicating last time this node has queried for the current upgrade mode attestation
|
||||
/// it is used to determine if an additional expedited query should be made in case client sends a JWT
|
||||
/// whilst this node is not aware of the upgrade mode
|
||||
last_queried_ts: AtomicI64,
|
||||
|
||||
/// flag indicating whether upgrade mode is currently enabled. this is to perform cheap checks
|
||||
/// that avoid having to acquire the lock
|
||||
// (and dealing with the async consequences of that)
|
||||
status: UpgradeModeStatus,
|
||||
}
|
||||
@@ -23,3 +23,5 @@ rand = { workspace = true }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-upgrade-mode-check = { path = "../upgrade-mode-check" }
|
||||
|
||||
|
||||
@@ -30,6 +30,28 @@ pub use nym_compact_ecash::{
|
||||
};
|
||||
pub use nym_ecash_time::{EcashTime, ecash_today};
|
||||
pub use nym_network_defaults::TicketTypeRepr;
|
||||
pub use nym_upgrade_mode_check::UpgradeModeAttestation;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub enum BandwidthCredential {
|
||||
ZkNym(Box<CredentialSpendingData>),
|
||||
UpgradeModeJWT { token: String },
|
||||
}
|
||||
|
||||
impl BandwidthCredential {
|
||||
pub fn into_zk_nym(self) -> Option<Box<CredentialSpendingData>> {
|
||||
match self {
|
||||
BandwidthCredential::ZkNym(credential) => Some(credential),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CredentialSpendingData> for BandwidthCredential {
|
||||
fn from(credential: CredentialSpendingData) -> Self {
|
||||
Self::ZkNym(Box::new(credential))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CredentialSigningData {
|
||||
|
||||
@@ -51,3 +51,6 @@ anyhow = { workspace = true }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -19,7 +19,9 @@ pub use shared_key::{
|
||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
||||
};
|
||||
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = EMBEDDED_KEY_ROTATION_INFO_VERSION;
|
||||
pub type GatewayProtocolVersion = u8;
|
||||
|
||||
pub const CURRENT_PROTOCOL_VERSION: GatewayProtocolVersion = UPGRADE_MODE_VERSION;
|
||||
|
||||
/// Defines the current version of the communication protocol between gateway and clients.
|
||||
/// It has to be incremented for any breaking change.
|
||||
@@ -29,35 +31,73 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = EMBEDDED_KEY_ROTATION_INFO_VERSION;
|
||||
// 3 - change to AES-GCM-SIV and non-zero IVs
|
||||
// 4 - introduction of v2 authentication protocol to prevent reply attacks
|
||||
// 5 - add key rotation information to the serialised mix packet
|
||||
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
|
||||
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
||||
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
|
||||
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: u8 = 5;
|
||||
// 6 - support for 'upgrade mode'
|
||||
pub const INITIAL_PROTOCOL_VERSION: GatewayProtocolVersion = 1;
|
||||
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: GatewayProtocolVersion = 2;
|
||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: GatewayProtocolVersion = 3;
|
||||
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: GatewayProtocolVersion = 4;
|
||||
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: GatewayProtocolVersion = 5;
|
||||
pub const UPGRADE_MODE_VERSION: GatewayProtocolVersion = 6;
|
||||
|
||||
// TODO: could using `Mac` trait here for OutputSize backfire?
|
||||
// Should hmac itself be exposed, imported and used instead?
|
||||
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
||||
|
||||
pub trait GatewayProtocolVersionExt {
|
||||
const CURRENT: GatewayProtocolVersion = CURRENT_PROTOCOL_VERSION;
|
||||
|
||||
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||
fn supports_authenticate_v2(&self) -> bool;
|
||||
fn supports_key_rotation_packet(&self) -> bool;
|
||||
fn supports_upgrade_mode(&self) -> bool;
|
||||
fn is_future_version(&self) -> bool;
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for Option<u8> {
|
||||
impl GatewayProtocolVersionExt for Option<GatewayProtocolVersion> {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.supports_aes256_gcm_siv()
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.supports_authenticate_v2()
|
||||
}
|
||||
|
||||
fn supports_key_rotation_packet(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= EMBEDDED_KEY_ROTATION_INFO_VERSION
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.supports_key_rotation_packet()
|
||||
}
|
||||
|
||||
fn supports_upgrade_mode(&self) -> bool {
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.supports_upgrade_mode()
|
||||
}
|
||||
|
||||
fn is_future_version(&self) -> bool {
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.is_future_version()
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for GatewayProtocolVersion {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
*self >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
*self >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_key_rotation_packet(&self) -> bool {
|
||||
*self >= EMBEDDED_KEY_ROTATION_INFO_VERSION
|
||||
}
|
||||
|
||||
fn supports_upgrade_mode(&self) -> bool {
|
||||
*self >= UPGRADE_MODE_VERSION
|
||||
}
|
||||
|
||||
fn is_future_version(&self) -> bool {
|
||||
*self > CURRENT_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
use crate::registration::handshake::messages::{Finalization, GatewayMaterialExchange};
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::registration::handshake::SharedGatewayKey;
|
||||
use crate::registration::handshake::HandshakeResult;
|
||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use crate::{GatewayProtocolVersionExt, INITIAL_PROTOCOL_VERSION};
|
||||
use futures::{Sink, Stream};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use tracing::info;
|
||||
use tungstenite::Message as WsMessage;
|
||||
|
||||
impl<S, R> State<'_, S, R> {
|
||||
@@ -25,10 +27,26 @@ impl<S, R> State<'_, S, R> {
|
||||
|
||||
// 2. wait for response with remote x25519 pubkey as well as encrypted signature
|
||||
// <- g^y || AES(k, sig(gate_priv, (g^y || g^x)) || MAYBE_NONCE
|
||||
let mid_res = self
|
||||
let (mid_res, gateway_protocol) = self
|
||||
.receive_handshake_message::<GatewayMaterialExchange>()
|
||||
.await?;
|
||||
|
||||
// NEGOTIATE PROTOCOL
|
||||
if gateway_protocol.is_future_version() {
|
||||
// SAFETY: future version means it's greater than CURRENT, which is always a `Some`
|
||||
#[allow(clippy::unwrap_used)]
|
||||
return Err(HandshakeError::UnsupportedProtocol {
|
||||
version: gateway_protocol.unwrap(),
|
||||
});
|
||||
}
|
||||
let gateway_protocol = gateway_protocol.unwrap_or(INITIAL_PROTOCOL_VERSION);
|
||||
|
||||
// that should never happen, but we're fine with that outcome
|
||||
if Some(gateway_protocol) != self.proposed_protocol_version() {
|
||||
info!("the gateway insists on protocol version different from the one we suggested. it wants {gateway_protocol} whilst we wanted {:?}, however, we can support it", self.proposed_protocol_version());
|
||||
self.set_protocol_version(gateway_protocol);
|
||||
}
|
||||
|
||||
// 3. derive shared keys locally
|
||||
// hkdf::<blake3>::(g^xy)
|
||||
self.derive_shared_key(&mid_res.ephemeral_dh, maybe_hkdf_salt.as_deref());
|
||||
@@ -42,14 +60,14 @@ impl<S, R> State<'_, S, R> {
|
||||
self.send_handshake_data(materials).await?;
|
||||
|
||||
// 6. wait for remote confirmation of finalizing the handshake
|
||||
let finalization = self.receive_handshake_message::<Finalization>().await?;
|
||||
let (finalization, _) = self.receive_handshake_message::<Finalization>().await?;
|
||||
finalization.ensure_success()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn perform_client_handshake(
|
||||
mut self,
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
) -> Result<HandshakeResult, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
R: CryptoRng + RngCore,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::shared_key::SharedKeyUsageError;
|
||||
use crate::GatewayProtocolVersion;
|
||||
use crate::GatewayProtocolVersionExt;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -34,4 +36,10 @@ pub enum HandshakeError {
|
||||
|
||||
#[error("timed out waiting for a handshake message")]
|
||||
Timeout,
|
||||
|
||||
#[error("Connection is in an invalid state - please send a bug report")]
|
||||
ConnectionInInvalidState,
|
||||
|
||||
#[error("the gateway requests protocol version that's not supported by this client. it wants to use v{version} whilst we only understand up to v{}", GatewayProtocolVersion::CURRENT)]
|
||||
UnsupportedProtocol { version: GatewayProtocolVersion },
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ use crate::registration::handshake::messages::{
|
||||
HandshakeMessage, Initialisation, MaterialExchange,
|
||||
};
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::registration::handshake::SharedGatewayKey;
|
||||
use crate::registration::handshake::HandshakeResult;
|
||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use crate::{GatewayProtocolVersion, GatewayProtocolVersionExt};
|
||||
use futures::{Sink, Stream};
|
||||
use tracing::{debug, warn};
|
||||
use tungstenite::Message as WsMessage;
|
||||
|
||||
impl<S, R> State<'_, S, R> {
|
||||
@@ -18,11 +20,39 @@ impl<S, R> State<'_, S, R> {
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
{
|
||||
// NEGOTIATE PROTOCOL
|
||||
// old clients were sending protocol version as defined by the following:
|
||||
/*
|
||||
fn request_protocol_version(&self) -> u8 {
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
AES_GCM_SIV_PROTOCOL_VERSION
|
||||
} else if self.expects_credential_usage {
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
|
||||
} else {
|
||||
INITIAL_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
*/
|
||||
// meaning the highest possible value they could have sent was `4` (AUTHENTICATE_V2_PROTOCOL_VERSION)
|
||||
// so if we received anything higher than that, it means they understand negotiation.
|
||||
// currently not strictly needed as we just blindly accept what they proposed,
|
||||
// but will be needed in the future.
|
||||
if self.proposed_protocol_version().is_future_version() {
|
||||
// this should never happen in a non-malicious client as it should use at most whatever version this gateway has announced
|
||||
self.set_protocol_version(GatewayProtocolVersion::CURRENT)
|
||||
} else {
|
||||
// currently we accept all protocols, i.e. legacy keys, aes128, etc. so we downgrade to whatever
|
||||
// the client has proposed. this will change in the future
|
||||
debug!(
|
||||
"using the protocol version proposed by the client: {:?}",
|
||||
self.proposed_protocol_version()
|
||||
)
|
||||
}
|
||||
|
||||
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and maybe a flag indicating non-legacy client
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_NON_LEGACY
|
||||
let init_message = Initialisation::try_from_bytes(&raw_init_message)?;
|
||||
self.update_remote_identity(init_message.identity);
|
||||
self.set_aes256_gcm_siv_key_derivation(!init_message.is_legacy());
|
||||
|
||||
// 2. derive shared keys locally
|
||||
// hkdf::<blake3>::(g^xy)
|
||||
@@ -39,7 +69,12 @@ impl<S, R> State<'_, S, R> {
|
||||
self.send_handshake_data(material).await?;
|
||||
|
||||
// 4. wait for the remote response with their own encrypted signature
|
||||
let materials = self.receive_handshake_message::<MaterialExchange>().await?;
|
||||
let (materials, client_protocol) =
|
||||
self.receive_handshake_message::<MaterialExchange>().await?;
|
||||
if client_protocol != self.proposed_protocol_version() {
|
||||
warn!("the client hasn't accepted our proposed protocol version. we suggested {:?} while it returned {client_protocol:?}", self.proposed_protocol_version());
|
||||
// TBD what to do here
|
||||
}
|
||||
|
||||
// 5. verify the received signature using the locally derived keys
|
||||
self.verify_remote_key_material(&materials, &init_message.ephemeral_dh)?;
|
||||
@@ -54,7 +89,7 @@ impl<S, R> State<'_, S, R> {
|
||||
pub(crate) async fn perform_gateway_handshake(
|
||||
mut self,
|
||||
raw_init_message: Vec<u8>,
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
) -> Result<HandshakeResult, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
{
|
||||
|
||||
@@ -24,13 +24,6 @@ pub struct Initialisation {
|
||||
pub initiator_salt: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Initialisation {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
self.initiator_salt.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MaterialExchange {
|
||||
pub signature_ciphertext: Vec<u8>,
|
||||
@@ -99,8 +92,9 @@ impl HandshakeMessage for Initialisation {
|
||||
let identity = ed25519::PublicKey::from_bytes(&bytes[..ed25519::PUBLIC_KEY_LENGTH])
|
||||
.map_err(|_| HandshakeError::MalformedRequest)?;
|
||||
|
||||
// this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
|
||||
// SAFETY: this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
|
||||
// which is impossible
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let ephemeral_dh =
|
||||
x25519::PublicKey::from_bytes(&bytes[ed25519::PUBLIC_KEY_LENGTH..legacy_len]).unwrap();
|
||||
|
||||
@@ -194,6 +188,7 @@ impl HandshakeMessage for GatewayMaterialExchange {
|
||||
|
||||
// this can only fail if the provided bytes have len different from PUBLIC_KEY_SIZE
|
||||
// which is impossible
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let ephemeral_dh =
|
||||
x25519::PublicKey::from_bytes(&bytes[..x25519::PUBLIC_KEY_SIZE]).unwrap();
|
||||
let materials = MaterialExchange::try_from_bytes(&bytes[x25519::PUBLIC_KEY_SIZE..])?;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use self::error::HandshakeError;
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::SharedGatewayKey;
|
||||
use crate::{GatewayProtocolVersion, SharedGatewayKey};
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{Sink, Stream};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
@@ -34,24 +34,29 @@ pub const KDF_SALT_LENGTH: usize = 16;
|
||||
// we do not need to worry about that.
|
||||
|
||||
pub struct GatewayHandshake<'a> {
|
||||
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
|
||||
handshake_future: BoxFuture<'a, Result<HandshakeResult, HandshakeError>>,
|
||||
}
|
||||
|
||||
impl Future for GatewayHandshake<'_> {
|
||||
type Output = Result<SharedGatewayKey, HandshakeError>;
|
||||
type Output = Result<HandshakeResult, HandshakeError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.handshake_future).poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct HandshakeResult {
|
||||
pub negotiated_protocol: GatewayProtocolVersion,
|
||||
pub derived_key: SharedGatewayKey,
|
||||
}
|
||||
|
||||
pub fn client_handshake<'a, S, R>(
|
||||
rng: &'a mut R,
|
||||
ws_stream: &'a mut S,
|
||||
identity: &'a ed25519::KeyPair,
|
||||
gateway_pubkey: ed25519::PublicKey,
|
||||
expects_credential_usage: bool,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
gateway_protocol: Option<GatewayProtocolVersion>,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown_token: ShutdownToken,
|
||||
) -> GatewayHandshake<'a>
|
||||
where
|
||||
@@ -63,11 +68,10 @@ where
|
||||
ws_stream,
|
||||
identity,
|
||||
Some(gateway_pubkey),
|
||||
gateway_protocol,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown_token,
|
||||
)
|
||||
.with_credential_usage(expects_credential_usage)
|
||||
.with_aes256_gcm_siv_key(derive_aes256_gcm_siv_key);
|
||||
);
|
||||
|
||||
GatewayHandshake {
|
||||
handshake_future: Box::pin(state.perform_client_handshake()),
|
||||
@@ -80,13 +84,21 @@ pub fn gateway_handshake<'a, S, R>(
|
||||
ws_stream: &'a mut S,
|
||||
identity: &'a ed25519::KeyPair,
|
||||
received_init_payload: Vec<u8>,
|
||||
requested_client_protocol: Option<GatewayProtocolVersion>,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> GatewayHandshake<'a>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
|
||||
R: CryptoRng + RngCore + Send,
|
||||
{
|
||||
let state = State::new(rng, ws_stream, identity, None, shutdown_token);
|
||||
let state = State::new(
|
||||
rng,
|
||||
ws_stream,
|
||||
identity,
|
||||
None,
|
||||
requested_client_protocol,
|
||||
shutdown_token,
|
||||
);
|
||||
GatewayHandshake {
|
||||
handshake_future: Box::pin(state.perform_gateway_handshake(received_init_payload)),
|
||||
}
|
||||
@@ -113,7 +125,8 @@ DONE(status)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ClientControlRequest;
|
||||
use crate::{ClientControlRequest, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION};
|
||||
use anyhow::{bail, Context};
|
||||
use futures::StreamExt;
|
||||
use nym_test_utils::helpers::u64_seeded_rng;
|
||||
use nym_test_utils::mocks::stream_sink::mock_streams;
|
||||
@@ -121,10 +134,53 @@ mod tests {
|
||||
use tokio::join;
|
||||
use tungstenite::Message;
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_handshake() -> anyhow::Result<()> {
|
||||
use anyhow::Context as _;
|
||||
trait ClientControlRequestExt {
|
||||
async fn get_handshake_init_data(&mut self) -> anyhow::Result<Vec<u8>> {
|
||||
let ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version: _,
|
||||
data,
|
||||
} = self.get_control_request().await?
|
||||
else {
|
||||
bail!("unexpected ClientControlRequest")
|
||||
};
|
||||
Ok(data)
|
||||
}
|
||||
async fn get_control_request(&mut self) -> anyhow::Result<ClientControlRequest>;
|
||||
}
|
||||
|
||||
impl<T> ClientControlRequestExt for T
|
||||
where
|
||||
T: Stream<Item = WsItem> + Unpin,
|
||||
{
|
||||
async fn get_control_request(&mut self) -> anyhow::Result<ClientControlRequest> {
|
||||
let msg = self
|
||||
.next()
|
||||
.timeboxed()
|
||||
.await
|
||||
.context("timeout")?
|
||||
.context("no message!")??
|
||||
.into_text()?
|
||||
.parse::<ClientControlRequest>()?;
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
struct Party<R: 'static, S: 'static> {
|
||||
rng: &'static mut R,
|
||||
keys: &'static mut ed25519::KeyPair,
|
||||
socket: &'static mut S,
|
||||
}
|
||||
|
||||
fn setup() -> (
|
||||
Party<
|
||||
impl CryptoRng + RngCore + Send,
|
||||
impl Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
>,
|
||||
Party<
|
||||
impl CryptoRng + RngCore + Send,
|
||||
impl Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
>,
|
||||
) {
|
||||
// solve the lifetime issue by just leaking the contents of the boxes
|
||||
// which is perfectly fine in test
|
||||
let client_rng = u64_seeded_rng(42).leak();
|
||||
@@ -142,51 +198,139 @@ mod tests {
|
||||
let client_ws = client_ws.leak();
|
||||
let gateway_ws = gateway_ws.leak();
|
||||
|
||||
(
|
||||
Party {
|
||||
rng: client_rng,
|
||||
keys: client_keys,
|
||||
socket: client_ws,
|
||||
},
|
||||
Party {
|
||||
rng: gateway_rng,
|
||||
keys: gateway_keys,
|
||||
socket: gateway_ws,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_handshake() -> anyhow::Result<()> {
|
||||
let (client, gateway) = setup();
|
||||
|
||||
let handshake_client = client_handshake(
|
||||
client_rng,
|
||||
client_ws,
|
||||
client_keys,
|
||||
*gateway_keys.public_key(),
|
||||
false,
|
||||
true,
|
||||
client.rng,
|
||||
client.socket,
|
||||
client.keys,
|
||||
*gateway.keys.public_key(),
|
||||
Some(CURRENT_PROTOCOL_VERSION),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let client_fut = handshake_client.spawn_timeboxed();
|
||||
|
||||
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
|
||||
let ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version: _,
|
||||
data,
|
||||
} = (gateway_ws.next())
|
||||
.timeboxed()
|
||||
.await
|
||||
.context("timeout")?
|
||||
.context("no message!")??
|
||||
.into_text()?
|
||||
.parse::<ClientControlRequest>()?
|
||||
else {
|
||||
panic!("bad message")
|
||||
};
|
||||
|
||||
let init_msg = data;
|
||||
let init_msg = gateway.socket.get_handshake_init_data().await?;
|
||||
|
||||
let handshake_gateway = gateway_handshake(
|
||||
gateway_rng,
|
||||
gateway_ws,
|
||||
gateway_keys,
|
||||
gateway.rng,
|
||||
gateway.socket,
|
||||
gateway.keys,
|
||||
init_msg,
|
||||
Some(CURRENT_PROTOCOL_VERSION),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let gateway_fut = handshake_gateway.spawn_timeboxed();
|
||||
let (client, gateway) = join!(client_fut, gateway_fut);
|
||||
|
||||
let client_key = client???;
|
||||
let gateway_key = gateway???;
|
||||
let client_res = client???;
|
||||
let gateway_res = gateway???;
|
||||
|
||||
// ensure the created keys are the same
|
||||
assert_eq!(client_key, gateway_key);
|
||||
assert_eq!(client_res, gateway_res);
|
||||
assert_eq!(client_res.negotiated_protocol, CURRENT_PROTOCOL_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn protocol_downgrade() -> anyhow::Result<()> {
|
||||
let (client, gateway) = setup();
|
||||
|
||||
let handshake_client = client_handshake(
|
||||
client.rng,
|
||||
client.socket,
|
||||
client.keys,
|
||||
*gateway.keys.public_key(),
|
||||
Some(CURRENT_PROTOCOL_VERSION + 42),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let client_fut = handshake_client.spawn_timeboxed();
|
||||
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
|
||||
let init_msg = gateway.socket.get_handshake_init_data().await?;
|
||||
|
||||
let handshake_gateway = gateway_handshake(
|
||||
gateway.rng,
|
||||
gateway.socket,
|
||||
gateway.keys,
|
||||
init_msg,
|
||||
Some(CURRENT_PROTOCOL_VERSION + 42),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let gateway_fut = handshake_gateway.spawn_timeboxed();
|
||||
let (client, gateway) = join!(client_fut, gateway_fut);
|
||||
|
||||
let client_res = client???;
|
||||
let gateway_res = gateway???;
|
||||
|
||||
// ensure the created keys are the same
|
||||
assert_eq!(client_res, gateway_res);
|
||||
|
||||
// and the protocol got downgraded for both parties
|
||||
assert_eq!(client_res.negotiated_protocol, CURRENT_PROTOCOL_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn protocol_upgrade() -> anyhow::Result<()> {
|
||||
let (client, gateway) = setup();
|
||||
|
||||
let handshake_client = client_handshake(
|
||||
client.rng,
|
||||
client.socket,
|
||||
client.keys,
|
||||
*gateway.keys.public_key(),
|
||||
None,
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let client_fut = handshake_client.spawn_timeboxed();
|
||||
|
||||
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
|
||||
let init_msg = gateway.socket.get_handshake_init_data().await?;
|
||||
|
||||
let handshake_gateway = gateway_handshake(
|
||||
gateway.rng,
|
||||
gateway.socket,
|
||||
gateway.keys,
|
||||
init_msg,
|
||||
None,
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let gateway_fut = handshake_gateway.spawn_timeboxed();
|
||||
let (client, gateway) = join!(client_fut, gateway_fut);
|
||||
|
||||
let client_res = client???;
|
||||
let gateway_res = gateway???;
|
||||
|
||||
// ensure the created keys are the same
|
||||
assert_eq!(client_res, gateway_res);
|
||||
|
||||
// and the protocol got upgraded to the first known version
|
||||
assert_eq!(client_res.negotiated_protocol, INITIAL_PROTOCOL_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ use crate::registration::handshake::error::HandshakeError;
|
||||
use crate::registration::handshake::messages::{
|
||||
HandshakeMessage, Initialisation, MaterialExchange,
|
||||
};
|
||||
use crate::registration::handshake::{SharedGatewayKey, WsItem, KDF_SALT_LENGTH};
|
||||
use crate::registration::handshake::{HandshakeResult, SharedGatewayKey, WsItem, KDF_SALT_LENGTH};
|
||||
use crate::shared_key::SharedKeySize;
|
||||
use crate::{
|
||||
types, LegacySharedKeySize, LegacySharedKeys, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
types, GatewayProtocolVersion, GatewayProtocolVersionExt, LegacySharedKeySize,
|
||||
LegacySharedKeys, SharedSymmetricKey, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
@@ -54,12 +54,11 @@ pub(crate) struct State<'a, S, R> {
|
||||
/// Ideally it would always be known before the handshake was initiated.
|
||||
remote_pubkey: Option<ed25519::PublicKey>,
|
||||
|
||||
// this field is really out of place here, however, we need to propagate this information somehow
|
||||
// in order to establish correct protocol for backwards compatibility reasons
|
||||
expects_credential_usage: bool,
|
||||
|
||||
/// Specifies whether the end product should be an AES128Ctr + blake3 HMAC keys (legacy) or AES256-GCM-SIV (current)
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
/// Version of the protocol to use during the handshake that also implicitly specifies
|
||||
/// additional features such as the type of derived shared keys, i.e.
|
||||
/// AES128Ctr + blake3 HMAC keys (legacy) or AES256-GCM-SIV (current)
|
||||
/// the above is decided by whether the specified protocol version supports the new variant or not.
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
|
||||
// channel to receive shutdown signal
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -72,6 +71,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
ws_stream: &'a mut S,
|
||||
identity: &'a ed25519::KeyPair,
|
||||
remote_pubkey: Option<ed25519::PublicKey>,
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown_token: ShutdownToken,
|
||||
) -> Self
|
||||
where
|
||||
@@ -84,40 +84,31 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
ephemeral_keypair,
|
||||
identity,
|
||||
remote_pubkey,
|
||||
protocol_version,
|
||||
derived_shared_keys: None,
|
||||
// later on this should become the default
|
||||
expects_credential_usage: false,
|
||||
derive_aes256_gcm_siv_key: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_credential_usage(mut self, expects_credential_usage: bool) -> Self {
|
||||
self.expects_credential_usage = expects_credential_usage;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_aes256_gcm_siv_key(mut self, derive_aes256_gcm_siv_key: bool) -> Self {
|
||||
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn set_aes256_gcm_siv_key_derivation(&mut self, derive_aes256_gcm_siv_key: bool) {
|
||||
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn local_ephemeral_key(&self) -> &x25519::PublicKey {
|
||||
self.ephemeral_keypair.public_key()
|
||||
}
|
||||
|
||||
pub(crate) fn proposed_protocol_version(&self) -> Option<GatewayProtocolVersion> {
|
||||
self.protocol_version
|
||||
}
|
||||
|
||||
pub(crate) fn set_protocol_version(&mut self, protocol_version: GatewayProtocolVersion) {
|
||||
self.protocol_version = Some(protocol_version);
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_generate_initiator_salt(&mut self) -> Option<Vec<u8>>
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
{
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
if self.protocol_version.supports_aes256_gcm_siv() {
|
||||
let mut salt = vec![0u8; KDF_SALT_LENGTH];
|
||||
self.rng.fill_bytes(&mut salt);
|
||||
Some(salt)
|
||||
@@ -154,13 +145,14 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.private_key()
|
||||
.diffie_hellman(remote_ephemeral_key);
|
||||
|
||||
let key_size = if self.derive_aes256_gcm_siv_key {
|
||||
let key_size = if self.protocol_version.supports_aes256_gcm_siv() {
|
||||
SharedKeySize::to_usize()
|
||||
} else {
|
||||
LegacySharedKeySize::to_usize()
|
||||
};
|
||||
|
||||
// there is no reason for this to fail as our okm is expected to be only 16 bytes
|
||||
// SAFETY: there is no reason for this to fail as our okm is expected to be only 16 bytes
|
||||
#[allow(clippy::expect_used)]
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
initiator_salt,
|
||||
&dh_result,
|
||||
@@ -169,11 +161,14 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
let shared_key = if self.derive_aes256_gcm_siv_key {
|
||||
// SAFETY: the okm has been expanded to the length expected by the corresponding keys
|
||||
let shared_key = if self.protocol_version.supports_aes256_gcm_siv() {
|
||||
#[allow(clippy::expect_used)]
|
||||
let current_key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
SharedGatewayKey::Current(current_key)
|
||||
} else {
|
||||
#[allow(clippy::expect_used)]
|
||||
let legacy_key = LegacySharedKeys::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
SharedGatewayKey::Legacy(legacy_key)
|
||||
@@ -196,7 +191,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.collect();
|
||||
let signature = self.identity.private_key().sign(plaintext);
|
||||
|
||||
let nonce = if self.derive_aes256_gcm_siv_key {
|
||||
let nonce = if self.protocol_version.supports_aes256_gcm_siv() {
|
||||
let mut rng = thread_rng();
|
||||
Some(random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec())
|
||||
} else {
|
||||
@@ -204,6 +199,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
};
|
||||
|
||||
// SAFETY: this function is only called after the local key has already been derived
|
||||
#[allow(clippy::expect_used)]
|
||||
let signature_ciphertext = self
|
||||
.derived_shared_keys
|
||||
.as_ref()
|
||||
@@ -222,13 +218,14 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
remote_ephemeral_key: &x25519::PublicKey,
|
||||
) -> Result<(), HandshakeError> {
|
||||
// SAFETY: this function is only called after the local key has already been derived
|
||||
#[allow(clippy::expect_used)]
|
||||
let derived_shared_key = self
|
||||
.derived_shared_keys
|
||||
.as_ref()
|
||||
.expect("shared key was not derived!");
|
||||
|
||||
// if the [client] init message contained non-legacy flag, the associated nonce MUST be present
|
||||
if self.derive_aes256_gcm_siv_key && remote_response.nonce.is_none() {
|
||||
if self.protocol_version.supports_aes256_gcm_siv() && remote_response.nonce.is_none() {
|
||||
return Err(HandshakeError::MissingNonceForCurrentKey);
|
||||
}
|
||||
|
||||
@@ -249,6 +246,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.chain(self.ephemeral_keypair.public_key().to_bytes())
|
||||
.collect();
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.remote_pubkey
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
@@ -261,7 +259,10 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
self.remote_pubkey = Some(remote_pubkey)
|
||||
}
|
||||
|
||||
fn on_wg_msg(msg: Option<WsItem>) -> Result<Option<Vec<u8>>, HandshakeError> {
|
||||
#[allow(clippy::complexity)]
|
||||
fn on_wg_msg(
|
||||
msg: Option<WsItem>,
|
||||
) -> Result<Option<(Vec<u8>, Option<GatewayProtocolVersion>)>, HandshakeError> {
|
||||
let Some(msg) = msg else {
|
||||
return Err(HandshakeError::ClosedStream);
|
||||
};
|
||||
@@ -277,9 +278,10 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
// hehe, that's a bit disgusting that the type system requires we explicitly ignore the
|
||||
// protocol_version field that we actually never attach at this point
|
||||
// yet another reason for the overdue refactor
|
||||
types::RegistrationHandshake::HandshakePayload { data, .. } => {
|
||||
Ok(Some(data))
|
||||
}
|
||||
types::RegistrationHandshake::HandshakePayload {
|
||||
protocol_version,
|
||||
data,
|
||||
} => Ok(Some((data, protocol_version))),
|
||||
types::RegistrationHandshake::HandshakeError { message } => {
|
||||
Err(HandshakeError::RemoteError(message))
|
||||
}
|
||||
@@ -299,7 +301,9 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn _receive_handshake_message_bytes(&mut self) -> Result<Vec<u8>, HandshakeError>
|
||||
async fn _receive_handshake_message_bytes(
|
||||
&mut self,
|
||||
) -> Result<(Vec<u8>, Option<GatewayProtocolVersion>), HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Unpin,
|
||||
{
|
||||
@@ -318,7 +322,9 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn _receive_handshake_message_bytes(&mut self) -> Result<Vec<u8>, HandshakeError>
|
||||
async fn _receive_handshake_message_bytes(
|
||||
&mut self,
|
||||
) -> Result<(Vec<u8>, Option<GatewayProtocolVersion>), HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Unpin,
|
||||
{
|
||||
@@ -331,20 +337,22 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn receive_handshake_message<M>(&mut self) -> Result<M, HandshakeError>
|
||||
pub(crate) async fn receive_handshake_message<M>(
|
||||
&mut self,
|
||||
) -> Result<(M, Option<GatewayProtocolVersion>), HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Unpin,
|
||||
M: HandshakeMessage,
|
||||
{
|
||||
// TODO: make timeout duration configurable
|
||||
let bytes = timeout(
|
||||
let (bytes, protocol) = timeout(
|
||||
Duration::from_secs(5),
|
||||
self._receive_handshake_message_bytes(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| HandshakeError::Timeout)??;
|
||||
|
||||
M::try_from_bytes(&bytes)
|
||||
M::try_from_bytes(&bytes).map(|msg| (msg, protocol))
|
||||
}
|
||||
|
||||
// upon receiving this, the receiver should terminate the handshake
|
||||
@@ -357,21 +365,11 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
{
|
||||
let handshake_message = types::RegistrationHandshake::new_error(message);
|
||||
self.ws_stream
|
||||
.send(WsMessage::Text(handshake_message.try_into().unwrap()))
|
||||
.send(WsMessage::Text(handshake_message.into()))
|
||||
.await
|
||||
.map_err(|_| HandshakeError::ClosedStream)
|
||||
}
|
||||
|
||||
fn request_protocol_version(&self) -> u8 {
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
AES_GCM_SIV_PROTOCOL_VERSION
|
||||
} else if self.expects_credential_usage {
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
|
||||
} else {
|
||||
INITIAL_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_handshake_data<M>(
|
||||
&mut self,
|
||||
inner_message: M,
|
||||
@@ -384,18 +382,25 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
|
||||
let handshake_message = types::RegistrationHandshake::new_payload(
|
||||
inner_message.into_bytes(),
|
||||
self.request_protocol_version(),
|
||||
self.protocol_version,
|
||||
);
|
||||
self.ws_stream
|
||||
.send(WsMessage::Text(handshake_message.try_into().unwrap()))
|
||||
.send(WsMessage::Text(handshake_message.into()))
|
||||
.await
|
||||
.map_err(|_| HandshakeError::ClosedStream)
|
||||
}
|
||||
|
||||
/// Finish the handshake, yielding the derived shared key and implicitly dropping all borrowed
|
||||
/// values.
|
||||
pub(crate) fn finalize_handshake(self) -> SharedGatewayKey {
|
||||
self.derived_shared_keys.unwrap()
|
||||
pub(crate) fn finalize_handshake(self) -> HandshakeResult {
|
||||
// SAFETY: handshake can't be finalised without deriving the shared keys
|
||||
#[allow(clippy::unwrap_used)]
|
||||
HandshakeResult {
|
||||
negotiated_protocol: self
|
||||
.proposed_protocol_version()
|
||||
.unwrap_or(INITIAL_PROTOCOL_VERSION),
|
||||
derived_key: self.derived_shared_keys.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// If any step along the way failed (that are non-network related),
|
||||
|
||||
@@ -43,6 +43,7 @@ impl LegacySharedKeys {
|
||||
rng.fill_bytes(&mut salt);
|
||||
|
||||
let legacy_bytes = Zeroizing::new(self.to_bytes());
|
||||
#[allow(clippy::expect_used)]
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(&salt),
|
||||
&legacy_bytes,
|
||||
@@ -51,6 +52,7 @@ impl LegacySharedKeys {
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
(key, salt)
|
||||
@@ -62,6 +64,7 @@ impl LegacySharedKeys {
|
||||
expected_digest: &[u8],
|
||||
) -> Option<SharedSymmetricKey> {
|
||||
let legacy_bytes = Zeroizing::new(self.to_bytes());
|
||||
#[allow(clippy::expect_used)]
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(salt),
|
||||
&legacy_bytes,
|
||||
@@ -69,6 +72,8 @@ impl LegacySharedKeys {
|
||||
SharedKeySize::to_usize(),
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
if key.digest() != expected_digest {
|
||||
|
||||
@@ -47,6 +47,8 @@ impl SharedGatewayKey {
|
||||
}
|
||||
}
|
||||
|
||||
// it is responsibility of the caller to ensure the correct variant is present
|
||||
#[allow(clippy::panic)]
|
||||
pub fn unwrap_legacy(&self) -> &LegacySharedKeys {
|
||||
match self {
|
||||
SharedGatewayKey::Current(_) => panic!("expected legacy key"),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::GatewayProtocolVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -9,7 +10,7 @@ use std::str::FromStr;
|
||||
pub enum RegistrationHandshake {
|
||||
HandshakePayload {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
HandshakeError {
|
||||
@@ -18,9 +19,9 @@ pub enum RegistrationHandshake {
|
||||
}
|
||||
|
||||
impl RegistrationHandshake {
|
||||
pub fn new_payload(data: Vec<u8>, protocol_version: u8) -> Self {
|
||||
pub fn new_payload(data: Vec<u8>, protocol_version: Option<GatewayProtocolVersion>) -> Self {
|
||||
RegistrationHandshake::HandshakePayload {
|
||||
protocol_version: Some(protocol_version),
|
||||
protocol_version,
|
||||
data,
|
||||
}
|
||||
}
|
||||
@@ -48,11 +49,11 @@ impl TryFrom<String> for RegistrationHandshake {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<String> for RegistrationHandshake {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_into(self) -> Result<String, serde_json::Error> {
|
||||
serde_json::to_string(&self)
|
||||
impl From<RegistrationHandshake> for String {
|
||||
fn from(value: RegistrationHandshake) -> Self {
|
||||
// SAFETY: we have infallible serde implementation
|
||||
#[allow(clippy::unwrap_used)]
|
||||
serde_json::to_string(&value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ mod tests {
|
||||
assert_eq!(protocol_version, Some(42));
|
||||
assert_eq!(data, handshake_data)
|
||||
}
|
||||
_ => unreachable!("this branch shouldn't have been reached!"),
|
||||
_ => panic!("this branch shouldn't have been reached!"),
|
||||
}
|
||||
|
||||
let handshake_payload_without_protocol = RegistrationHandshake::HandshakePayload {
|
||||
@@ -97,7 +98,7 @@ mod tests {
|
||||
assert!(protocol_version.is_none());
|
||||
assert_eq!(data, handshake_data)
|
||||
}
|
||||
_ => unreachable!("this branch shouldn't have been reached!"),
|
||||
_ => panic!("this branch shouldn't have been reached!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
|
||||
use crate::{
|
||||
AuthenticationFailure, GatewayProtocolVersion, GatewayRequestsError, SharedGatewayKey,
|
||||
};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::iter;
|
||||
@@ -20,7 +22,7 @@ pub struct AuthenticateRequest {
|
||||
|
||||
impl AuthenticateRequest {
|
||||
pub fn new(
|
||||
protocol_version: u8,
|
||||
protocol_version: GatewayProtocolVersion,
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<AuthenticateRequest, GatewayRequestsError> {
|
||||
@@ -98,7 +100,7 @@ impl AuthenticateRequest {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthenticateRequestContent {
|
||||
pub protocol_version: u8,
|
||||
pub protocol_version: GatewayProtocolVersion,
|
||||
|
||||
// this is identical to the client's address
|
||||
pub client_identity: ed25519::PublicKey,
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
use crate::models::CredentialSpendingRequest;
|
||||
use crate::text_request::authenticate::AuthenticateRequest;
|
||||
use crate::{
|
||||
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
INITIAL_PROTOCOL_VERSION,
|
||||
GatewayProtocolVersion, GatewayRequestsError, SharedGatewayKey, SymmetricKey,
|
||||
AES_GCM_SIV_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
@@ -46,6 +45,7 @@ impl ClientRequest {
|
||||
// - the schema is self-describing which simplifies deserialisation
|
||||
|
||||
// SAFETY: the trait has been derived correctly with no weird variants
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let plaintext = serde_json::to_vec(self).unwrap();
|
||||
let nonce = key.random_nonce_or_iv();
|
||||
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
|
||||
@@ -72,7 +72,7 @@ pub enum ClientControlRequest {
|
||||
// have the shared key derived?
|
||||
Authenticate {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
address: String,
|
||||
enc_address: String,
|
||||
iv: String,
|
||||
@@ -83,7 +83,7 @@ pub enum ClientControlRequest {
|
||||
#[serde(alias = "handshakePayload")]
|
||||
RegisterHandshakeInitRequest {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
BandwidthCredential {
|
||||
@@ -98,6 +98,10 @@ pub enum ClientControlRequest {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
UpgradeModeJWT {
|
||||
// no need to encrypt it as it's public anyway
|
||||
token: String,
|
||||
},
|
||||
ClaimFreeTestnetBandwidth,
|
||||
EncryptedRequest {
|
||||
ciphertext: Vec<u8>,
|
||||
@@ -108,12 +112,14 @@ pub enum ClientControlRequest {
|
||||
}
|
||||
|
||||
impl ClientControlRequest {
|
||||
pub fn new_authenticate(
|
||||
pub fn new_legacy_authenticate(
|
||||
address: DestinationAddressBytes,
|
||||
shared_key: &SharedGatewayKey,
|
||||
uses_credentials: bool,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're encrypting with non-legacy key, the remote must support AES256-GCM-SIV
|
||||
// since we are using legacy authentication, the gateway definitely doesn't understand the protocol downgrade,
|
||||
// so use the lowest possible version we can
|
||||
let protocol_version = if !shared_key.is_legacy() {
|
||||
Some(AES_GCM_SIV_PROTOCOL_VERSION)
|
||||
} else if uses_credentials {
|
||||
@@ -138,10 +144,8 @@ impl ClientControlRequest {
|
||||
pub fn new_authenticate_v2(
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
protocol_version: GatewayProtocolVersion,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're using v2 authentication, we must announce at least that protocol version
|
||||
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
Ok(ClientControlRequest::AuthenticateV2(Box::new(
|
||||
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
|
||||
)))
|
||||
@@ -159,6 +163,7 @@ impl ClientControlRequest {
|
||||
"BandwidthCredentialV2".to_string()
|
||||
}
|
||||
ClientControlRequest::EcashCredential { .. } => "EcashCredential".to_string(),
|
||||
ClientControlRequest::UpgradeModeJWT { .. } => "UpgradeModeJWT".to_string(),
|
||||
ClientControlRequest::ClaimFreeTestnetBandwidth => {
|
||||
"ClaimFreeTestnetBandwidth".to_string()
|
||||
}
|
||||
@@ -192,12 +197,16 @@ impl ClientControlRequest {
|
||||
CredentialSpendingRequest::try_from_bytes(credential_bytes.as_slice())
|
||||
.map_err(|_| GatewayRequestsError::MalformedEncryption)
|
||||
}
|
||||
|
||||
pub fn new_upgrade_mode_jwt(token: String) -> Self {
|
||||
ClientControlRequest::UpgradeModeJWT { token }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientControlRequest> for Message {
|
||||
fn from(req: ClientControlRequest) -> Self {
|
||||
// it should be safe to call `unwrap` here as the message is generated by the server
|
||||
// so if it fails (and consequently panics) it's a bug that should be resolved
|
||||
// SAFETY: all of the enum variants have valid (for json) serde impl
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let str_req = serde_json::to_string(&req).unwrap();
|
||||
Message::Text(str_req)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{GatewayRequestsError, SimpleGatewayRequestsError, SymmetricKey};
|
||||
use crate::{
|
||||
GatewayProtocolVersion, GatewayRequestsError, SimpleGatewayRequestsError, SymmetricKey,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tungstenite::Message;
|
||||
|
||||
@@ -26,6 +28,7 @@ impl SensitiveServerResponse {
|
||||
// - the schema is self-describing which simplifies deserialisation
|
||||
|
||||
// SAFETY: the trait has been derived correctly with no weird variants
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let plaintext = serde_json::to_vec(self).unwrap();
|
||||
let nonce = key.random_nonce_or_iv();
|
||||
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
|
||||
@@ -43,31 +46,59 @@ impl SensitiveServerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub struct BandwidthResponse {
|
||||
pub available_total: i64,
|
||||
|
||||
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
|
||||
/// (thus it will not meter bandwidth)
|
||||
#[serde(default)]
|
||||
pub upgrade_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub struct SendResponse {
|
||||
pub remaining_bandwidth: i64,
|
||||
|
||||
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
|
||||
/// (thus it will not meter bandwidth)
|
||||
#[serde(default)]
|
||||
pub upgrade_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub enum ServerResponse {
|
||||
Authenticate {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
status: bool,
|
||||
bandwidth_remaining: i64,
|
||||
|
||||
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
|
||||
/// (thus it will not meter bandwidth)
|
||||
#[serde(default)]
|
||||
upgrade_mode: bool,
|
||||
},
|
||||
Register {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
status: bool,
|
||||
|
||||
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
|
||||
/// (thus it will not meter bandwidth)
|
||||
#[serde(default)]
|
||||
upgrade_mode: bool,
|
||||
},
|
||||
EncryptedResponse {
|
||||
ciphertext: Vec<u8>,
|
||||
nonce: Vec<u8>,
|
||||
},
|
||||
Bandwidth {
|
||||
available_total: i64,
|
||||
},
|
||||
Send {
|
||||
remaining_bandwidth: i64,
|
||||
},
|
||||
Bandwidth(BandwidthResponse),
|
||||
Send(SendResponse),
|
||||
SupportedProtocol {
|
||||
version: u8,
|
||||
},
|
||||
@@ -122,6 +153,7 @@ impl From<ServerResponse> for Message {
|
||||
fn from(res: ServerResponse) -> Self {
|
||||
// it should be safe to call `unwrap` here as the message is generated by the server
|
||||
// so if it fails (and consequently panics) it's a bug that should be resolved
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let str_res = serde_json::to_string(&res).unwrap();
|
||||
Message::Text(str_res)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ pub const NYM_APIS: &[ApiUrlConst] = &[
|
||||
];
|
||||
|
||||
pub const NYM_VPN_API: &str = "https://nymvpn.com/api/";
|
||||
|
||||
pub const UPGRADE_MOST_ATTESTATION_URL: &str = "https://nym.com/upgrade-mode/attestation.json";
|
||||
|
||||
#[cfg(feature = "network")]
|
||||
pub const NYM_VPN_APIS: &[ApiUrlConst] = &[
|
||||
ApiUrlConst {
|
||||
@@ -159,6 +162,10 @@ pub fn export_to_env() {
|
||||
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
|
||||
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
|
||||
set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
|
||||
set_var_to_default(
|
||||
var_names::UPGRADE_MOST_ATTESTATION_URL,
|
||||
UPGRADE_MOST_ATTESTATION_URL,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "env", feature = "network"))]
|
||||
@@ -199,4 +206,8 @@ pub fn export_to_env_if_not_set() {
|
||||
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
|
||||
set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
|
||||
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
|
||||
set_var_conditionally_to_default(
|
||||
var_names::UPGRADE_MOST_ATTESTATION_URL,
|
||||
UPGRADE_MOST_ATTESTATION_URL,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
|
||||
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
|
||||
pub const NYM_VPN_API: &str = "NYM_VPN_API";
|
||||
pub const CLIENT_STATS_COLLECTION_PROVIDER: &str = "CLIENT_STATS_COLLECTION_PROVIDER";
|
||||
pub const UPGRADE_MOST_ATTESTATION_URL: &str = "UPGRADE_MOST_ATTESTATION_URL";
|
||||
|
||||
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
|
||||
|
||||
|
||||
@@ -82,6 +82,17 @@ pub struct Protocol {
|
||||
pub service_provider_type: ServiceProviderType,
|
||||
}
|
||||
|
||||
impl Protocol {
|
||||
pub const fn new(version: u8, service_provider_type: ServiceProviderType) -> Self {
|
||||
Self {
|
||||
version,
|
||||
service_provider_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: this only works under the assumption of using bincode for serialisation
|
||||
// with the current field layout
|
||||
impl TryFrom<&[u8; 2]> for Protocol {
|
||||
type Error = ProtocolError;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UpgradeModeCheckError {
|
||||
#[error("failed to decode jwt metadata")]
|
||||
#[error("failed to decode jwt metadata: {source}")]
|
||||
TokenMetadataDecodeFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("the jwt metadata didn't contain explicit public key")]
|
||||
@@ -21,6 +21,6 @@ pub enum UpgradeModeCheckError {
|
||||
#[error("failed to verify the jwt: {source}")]
|
||||
JwtVerificationFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("failed to retrieve attestation from {url}:{source}")]
|
||||
#[error("failed to retrieve attestation from {url}: {source}")]
|
||||
AttestationRetrievalFailure { url: String, source: reqwest::Error },
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use nym_crypto::asymmetric::ed25519;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
|
||||
pub const CREDENTIAL_PROXY_JWT_ISSUER: &str = "nym-credential-proxy";
|
||||
|
||||
// for now use static issuer such as "nym-credential-proxy"
|
||||
pub fn generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation: UpgradeModeAttestation,
|
||||
@@ -109,11 +111,11 @@ mod tests {
|
||||
attestation.clone(),
|
||||
Duration::from_secs(60 * 60),
|
||||
&unauthorised_jwt_keys,
|
||||
Some("nym-credential-proxy"),
|
||||
Some(CREDENTIAL_PROXY_JWT_ISSUER),
|
||||
);
|
||||
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("nym-credential-proxy")).is_ok());
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some(CREDENTIAL_PROXY_JWT_ISSUER)).is_ok());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, None).is_ok());
|
||||
@@ -133,7 +135,9 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, Some("nym-credential-proxy")).is_err());
|
||||
assert!(
|
||||
validate_upgrade_mode_jwt(&jwt_no_issuer, Some(CREDENTIAL_PROXY_JWT_ISSUER)).is_err()
|
||||
);
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, None).is_ok());
|
||||
|
||||
@@ -9,7 +9,10 @@ pub use attestation::{
|
||||
UpgradeModeAttestation, generate_new_attestation, generate_new_attestation_with_starting_time,
|
||||
};
|
||||
pub use error::UpgradeModeCheckError;
|
||||
pub use jwt::{generate_jwt_for_upgrade_mode_attestation, validate_upgrade_mode_jwt};
|
||||
pub use jwt::{
|
||||
CREDENTIAL_PROXY_JWT_ISSUER, generate_jwt_for_upgrade_mode_attestation,
|
||||
validate_upgrade_mode_jwt,
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use attestation::attempt_retrieve_attestation;
|
||||
|
||||
@@ -1,35 +1,112 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credential_verification::upgrade_mode::UpgradeModeDetails;
|
||||
use nym_credentials_interface::BandwidthCredential;
|
||||
use std::cmp::max;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
use crate::transceiver::PeerControllerTransceiver;
|
||||
use nym_wireguard_private_metadata_shared::error::MetadataError;
|
||||
use nym_wireguard_private_metadata_shared::interface::ResponseData;
|
||||
|
||||
// we need to be above MINIMUM_REMAINING_BANDWIDTH (500MB) plus we also have to trick the client
|
||||
// its depletion is low enough to not require sending new tickets
|
||||
const DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024 * 1024;
|
||||
|
||||
#[derive(Clone, axum::extract::FromRef)]
|
||||
pub struct AppState {
|
||||
transceiver: PeerControllerTransceiver,
|
||||
#[from_ref(skip)]
|
||||
upgrade_mode: UpgradeModeDetails,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(transceiver: PeerControllerTransceiver) -> Self {
|
||||
Self { transceiver }
|
||||
pub fn new(transceiver: PeerControllerTransceiver, upgrade_mode: UpgradeModeDetails) -> Self {
|
||||
Self {
|
||||
transceiver,
|
||||
upgrade_mode,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn available_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
|
||||
self.transceiver.query_bandwidth(ip).await
|
||||
fn upgrade_mode_bandwidth(&self, true_bandwidth: i64) -> i64 {
|
||||
// if we're undergoing upgrade mode, we don't meter bandwidth,
|
||||
// we simply return MAX of clients current bandwidth and minimum bandwidth before default
|
||||
// client would have attempted to send new ticket (hopefully)
|
||||
// the latter is to support older clients that will ignore `upgrade_mode` field in the response
|
||||
// as they're not aware of its existence
|
||||
max(DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD, true_bandwidth)
|
||||
}
|
||||
|
||||
pub async fn available_bandwidth(&self, ip: IpAddr) -> Result<ResponseData, MetadataError> {
|
||||
let upgrade_mode = self.upgrade_mode.enabled();
|
||||
|
||||
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
|
||||
let available_bandwidth = if upgrade_mode {
|
||||
self.upgrade_mode_bandwidth(true_bandwidth)
|
||||
} else {
|
||||
true_bandwidth
|
||||
};
|
||||
|
||||
Ok(ResponseData::AvailableBandwidth {
|
||||
amount: available_bandwidth,
|
||||
upgrade_mode,
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
claim: Box<BandwidthCredential>,
|
||||
) -> Result<ResponseData, MetadataError> {
|
||||
match *claim {
|
||||
BandwidthCredential::ZkNym(zk_nym) => {
|
||||
// if we got zk-nym, we just try to verify it
|
||||
let available_bandwidth = self.transceiver.topup_bandwidth(ip, zk_nym).await?;
|
||||
|
||||
// however, we still follow the same upgrade-mode logic,
|
||||
// so that the client would not attempt to needlessly send more credentials
|
||||
let upgrade_mode = self.upgrade_mode.enabled();
|
||||
let available_bandwidth = if upgrade_mode {
|
||||
self.upgrade_mode_bandwidth(available_bandwidth)
|
||||
} else {
|
||||
available_bandwidth
|
||||
};
|
||||
|
||||
Ok(ResponseData::TopUpBandwidth {
|
||||
available_bandwidth,
|
||||
upgrade_mode,
|
||||
})
|
||||
}
|
||||
BandwidthCredential::UpgradeModeJWT { token } => {
|
||||
// if we're already in the upgrade mode, don't bother validating the token
|
||||
if self.upgrade_mode.enabled() {
|
||||
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
|
||||
return Ok(ResponseData::TopUpBandwidth {
|
||||
available_bandwidth: self.upgrade_mode_bandwidth(true_bandwidth),
|
||||
upgrade_mode: true,
|
||||
});
|
||||
}
|
||||
|
||||
// if the token is valid, try to check if we're behind
|
||||
// and have to update our internal state
|
||||
self.upgrade_mode
|
||||
.try_enable_via_received_jwt(token)
|
||||
.await
|
||||
.map_err(|err| MetadataError::JWTVerification {
|
||||
message: err.to_string(),
|
||||
})?;
|
||||
|
||||
// if we didn't return an error, it means token got accepted
|
||||
// and we have transitioned into the upgrade mode
|
||||
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
|
||||
|
||||
Ok(ResponseData::TopUpBandwidth {
|
||||
available_bandwidth: self.upgrade_mode_bandwidth(true_bandwidth),
|
||||
upgrade_mode: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ use axum::{
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
|
||||
interface::{RequestData, ResponseData},
|
||||
AxumErrorResponse, AxumResult, Construct, Extract, Request, Response, interface::RequestData,
|
||||
latest,
|
||||
};
|
||||
use tower_http::compression::CompressionLayer;
|
||||
@@ -59,20 +58,17 @@ async fn available_bandwidth(
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::AvailableBandwidth(_), version) =
|
||||
let (RequestData::AvailableBandwidth, version) =
|
||||
request.extract().map_err(AxumErrorResponse::bad_request)?
|
||||
else {
|
||||
return Err(AxumErrorResponse::bad_request("incorrect request type"));
|
||||
};
|
||||
let available_bandwidth = state
|
||||
let available_bandwidth_response = 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)?;
|
||||
let response = Response::construct(available_bandwidth_response, version)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
}
|
||||
@@ -96,16 +92,16 @@ async fn topup_bandwidth(
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::TopUpBandwidth(credential), version) =
|
||||
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)
|
||||
let top_up_bandwidth_response = state
|
||||
.topup_bandwidth(addr.ip(), credential)
|
||||
.await
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
let response = Response::construct(ResponseData::TopUpBandwidth(available_bandwidth), version)
|
||||
let response = Response::construct(top_up_bandwidth_response, version)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
|
||||
@@ -37,12 +37,12 @@ impl PeerControllerTransceiver {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn query_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
|
||||
pub 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(
|
||||
pub async fn topup_bandwidth(
|
||||
&self,
|
||||
ip: IpAddr,
|
||||
credential: Box<CredentialSpendingData>,
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// A simple macro that given `TryFrom<&A> for B`, implements `TryFrom<A> for B`
|
||||
/// using the former implementation
|
||||
#[macro_export]
|
||||
macro_rules! impl_tryfrom_ref {
|
||||
($src:ty, $dst:ty, $err:ty) => {
|
||||
impl TryFrom<$src> for $dst {
|
||||
// can't use type Error = <Self as TryFrom<&$src>>::Error;
|
||||
// due to lifetime interference within macros
|
||||
type Error = $err;
|
||||
|
||||
fn try_from(value: $src) -> Result<Self, Self::Error> {
|
||||
<Self as TryFrom<&$src>>::try_from(&value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A simple macro that implements all required variants of `TryFrom`
|
||||
/// between particular versioned `VersionedRequest` and given request variant
|
||||
/// using default bincode serializer
|
||||
#[macro_export]
|
||||
macro_rules! impl_default_bincode_request_query_conversions {
|
||||
// limitation of macros - need to pass the same underlying type twice,
|
||||
// once as pattern and once as expression
|
||||
($top_req_type:ty, $inner_req_type:ty, $query_type_pat:pat, $query_type_expr:expr) => {
|
||||
$crate::impl_query_conversions!(
|
||||
$crate::Request,
|
||||
$top_req_type,
|
||||
$inner_req_type,
|
||||
$query_type_pat,
|
||||
$query_type_expr
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// A simple macro that implements all required variants of `TryFrom`
|
||||
/// between particular versioned `VersionedResponse` and given response variant
|
||||
/// using default bincode serializer
|
||||
#[macro_export]
|
||||
macro_rules! impl_default_bincode_response_query_conversions {
|
||||
// limitation of macros - need to pass the same underlying type twice,
|
||||
// once as pattern and once as expression
|
||||
($top_resp_type:ty, $inner_resp_type:ty, $query_type_pat:pat, $query_type_expr:expr) => {
|
||||
$crate::impl_query_conversions!(
|
||||
$crate::Response,
|
||||
$top_resp_type,
|
||||
$inner_resp_type,
|
||||
$query_type_pat,
|
||||
$query_type_expr
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// A simple macro that implements all required variants of `TryFrom`
|
||||
/// between [crate::models::Request] and corresponding versioned `VersionedRequest`
|
||||
/// using default bincode serializer
|
||||
#[macro_export]
|
||||
macro_rules! impl_default_bincode_request_conversions {
|
||||
($req_type:ty, $version:expr) => {
|
||||
$crate::impl_versioned_conversions!($crate::Request, $req_type, $version);
|
||||
};
|
||||
}
|
||||
|
||||
/// A simple macro that implements all required variants of `TryFrom`
|
||||
/// between [crate::models::Response] and corresponding versioned `VersionedResponse`
|
||||
/// using default bincode serializer
|
||||
#[macro_export]
|
||||
macro_rules! impl_default_bincode_response_conversions {
|
||||
($req_type:ty, $version:expr) => {
|
||||
$crate::impl_versioned_conversions!($crate::Response, $req_type, $version);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_versioned_conversions {
|
||||
(
|
||||
// is it Request or Response
|
||||
$main_type_ty:ty,
|
||||
|
||||
// e.g. VersionedResponse
|
||||
$top_type:ty,
|
||||
|
||||
// request/response version type
|
||||
$version:expr
|
||||
) => {
|
||||
impl TryFrom<&$top_type> for $main_type_ty {
|
||||
type Error = $crate::models::error::Error;
|
||||
|
||||
fn try_from(value: &$top_type) -> Result<Self, Self::Error> {
|
||||
use ::bincode::Options;
|
||||
let data = $crate::make_bincode_serializer().serialize(value)?;
|
||||
Ok(<$main_type_ty>::new($version, data))
|
||||
}
|
||||
}
|
||||
|
||||
// automatically generate `impl TryFrom<$top_type> for $main_type`
|
||||
$crate::impl_tryfrom_ref!($top_type, $main_type_ty, $crate::models::error::Error);
|
||||
|
||||
impl TryFrom<&$main_type_ty> for $top_type {
|
||||
type Error = $crate::models::error::Error;
|
||||
|
||||
fn try_from(value: &$main_type_ty) -> Result<Self, Self::Error> {
|
||||
use ::bincode::Options;
|
||||
if value.version != $version {
|
||||
return Err($crate::models::error::Error::InvalidVersion {
|
||||
source_version: value.version,
|
||||
target_version: $version,
|
||||
});
|
||||
}
|
||||
Ok($crate::make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
}
|
||||
|
||||
// automatically generate `impl TryFrom<$main_type> for $top_type`
|
||||
$crate::impl_tryfrom_ref!($main_type_ty, $top_type, $crate::models::error::Error);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_query_conversions {
|
||||
// limitation of macros - need to pass the same underlying type twice,
|
||||
// once as pattern and once as expression
|
||||
(
|
||||
// is it Request or Response
|
||||
$main_type:ty,
|
||||
|
||||
// e.g. VersionedResponse
|
||||
$top_type:ty,
|
||||
|
||||
// e.g. InnerTopUpResponse
|
||||
$inner_type:ty,
|
||||
|
||||
// e.g. QueryType::TopUpBandwidth,
|
||||
$query_type_pat:pat,
|
||||
|
||||
// e.g. QueryType::TopUpBandwidth,
|
||||
$query_type_expr:expr
|
||||
) => {
|
||||
// conversion from the versioned type into the particular typ,
|
||||
// e.g. TryFrom<&VersionedResponse> for InnerTopUpResponse
|
||||
impl TryFrom<&$top_type> for $inner_type {
|
||||
type Error = $crate::models::error::Error;
|
||||
|
||||
fn try_from(value: &$top_type) -> Result<Self, Self::Error> {
|
||||
use ::bincode::Options;
|
||||
match value.query_type {
|
||||
$query_type_pat => {
|
||||
Ok($crate::make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
other => Err($crate::models::error::Error::InvalidQueryType {
|
||||
source_query_type: other.to_string(),
|
||||
target_query_type: stringify!($query_type_pat).to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
// implementation of conversion without the referenced type, i.e.
|
||||
// e.g. TryFrom<VersionedResponse> for InnerTopUpResponse
|
||||
$crate::impl_tryfrom_ref!($top_type, $inner_type, $crate::models::error::Error);
|
||||
|
||||
// conversion back from the particular type into the versioned type, i.e.
|
||||
// e.g. TryFrom<&InnerTopUpResponse> for VersionedResponse
|
||||
impl TryFrom<&$inner_type> for $top_type {
|
||||
type Error = $crate::models::error::Error;
|
||||
|
||||
fn try_from(value: &$inner_type) -> Result<Self, Self::Error> {
|
||||
use ::bincode::Options;
|
||||
Ok(Self {
|
||||
query_type: $query_type_expr,
|
||||
inner: $crate::make_bincode_serializer().serialize(value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// implementation of conversion without the referenced type, i.e.
|
||||
// e.g. TryFrom<InnerTopUpResponse> for VersionedResponse
|
||||
$crate::impl_tryfrom_ref!($inner_type, $top_type, $crate::models::error::Error);
|
||||
|
||||
// conversion from the'main' type (Request/Response) into the particular type
|
||||
// e.g. TryFrom<&Response> for InnerTopUpResponse
|
||||
impl TryFrom<&$main_type> for $inner_type {
|
||||
type Error = $crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: &$main_type) -> Result<Self, Self::Error> {
|
||||
<$top_type>::try_from(value)?.try_into().map_err(
|
||||
|err: $crate::models::error::Error| $crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// implementation of conversion without the referenced type, i.e.
|
||||
// e.g. TryFrom<Response> for InnerTopUpResponse
|
||||
$crate::impl_tryfrom_ref!($main_type, $inner_type, $crate::error::MetadataError);
|
||||
|
||||
// conversion from the particular type into the 'main' type (Request/Response)
|
||||
// e.g. TryFrom<&InnerTopUpResponse> for Response
|
||||
impl TryFrom<&$inner_type> for $main_type {
|
||||
type Error = $crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: &$inner_type) -> Result<Self, Self::Error> {
|
||||
<$top_type>::try_from(value)?.try_into().map_err(
|
||||
|err: $crate::models::error::Error| $crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// implementation of conversion without the referenced type, i.e.
|
||||
// e.g. TryFrom<InnerTopUpResponse> for Response
|
||||
$crate::impl_tryfrom_ref!($inner_type, $main_type, $crate::error::MetadataError);
|
||||
};
|
||||
}
|
||||
@@ -17,6 +17,9 @@ pub enum MetadataError {
|
||||
|
||||
#[error("Credential verification error: {message}")]
|
||||
CredentialVerification { message: String },
|
||||
|
||||
#[error("Upgrade Mode JWT verification error: {message}")]
|
||||
JWTVerification { message: String },
|
||||
}
|
||||
|
||||
impl From<crate::models::error::Error> for MetadataError {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod conversion_helpers;
|
||||
pub mod error;
|
||||
mod models;
|
||||
pub mod routes;
|
||||
@@ -9,7 +10,7 @@ pub mod routes;
|
||||
pub use models::v0;
|
||||
pub use models::{
|
||||
AxumErrorResponse, AxumResult, Construct, ErrorResponse, Extract, Request, Response, Version,
|
||||
error::Error as ModelError, interface, latest, v1,
|
||||
error::Error as ModelError, interface, latest, v1, v2,
|
||||
};
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
|
||||
@@ -15,7 +15,7 @@ pub enum Error {
|
||||
},
|
||||
|
||||
#[error(
|
||||
"trying to deserialize from query type {source_query_type} query type {target_query_type}"
|
||||
"trying to deserialize from query type {source_query_type} into query type {target_query_type}"
|
||||
)]
|
||||
InvalidQueryType {
|
||||
source_query_type: String,
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_credentials_interface::BandwidthCredential;
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
use crate::models::v0;
|
||||
use crate::models::{Construct, Extract, Request, Response, Version, v1};
|
||||
use crate::models::{Construct, Extract, Request, Response, Version, v1, v2};
|
||||
|
||||
pub enum RequestData {
|
||||
AvailableBandwidth(()),
|
||||
TopUpBandwidth(Box<CredentialSpendingData>),
|
||||
AvailableBandwidth,
|
||||
TopUpBandwidth {
|
||||
credential: Box<BandwidthCredential>,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
super::latest::interface::RequestData::AvailableBandwidth => Self::AvailableBandwidth,
|
||||
super::latest::interface::RequestData::TopUpBandwidth { credential } => {
|
||||
Self::TopUpBandwidth { credential }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,8 @@ impl From<super::latest::interface::RequestData> for RequestData {
|
||||
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)
|
||||
}
|
||||
RequestData::AvailableBandwidth => Self::AvailableBandwidth,
|
||||
RequestData::TopUpBandwidth { credential } => Self::TopUpBandwidth { credential },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,12 +37,20 @@ impl From<RequestData> for super::latest::interface::RequestData {
|
||||
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)
|
||||
}
|
||||
super::latest::interface::ResponseData::AvailableBandwidth {
|
||||
amount,
|
||||
upgrade_mode,
|
||||
} => Self::AvailableBandwidth {
|
||||
amount,
|
||||
upgrade_mode,
|
||||
},
|
||||
super::latest::interface::ResponseData::TopUpBandwidth {
|
||||
available_bandwidth,
|
||||
upgrade_mode,
|
||||
} => Self::TopUpBandwidth {
|
||||
available_bandwidth,
|
||||
upgrade_mode,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,10 +58,20 @@ impl From<super::latest::interface::ResponseData> for ResponseData {
|
||||
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)
|
||||
}
|
||||
ResponseData::AvailableBandwidth {
|
||||
amount,
|
||||
upgrade_mode,
|
||||
} => Self::AvailableBandwidth {
|
||||
amount,
|
||||
upgrade_mode,
|
||||
},
|
||||
ResponseData::TopUpBandwidth {
|
||||
available_bandwidth,
|
||||
upgrade_mode,
|
||||
} => Self::TopUpBandwidth {
|
||||
available_bandwidth,
|
||||
upgrade_mode,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,13 +81,26 @@ impl Construct<RequestData> for Request {
|
||||
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)?;
|
||||
// attempt to go through conversion chain for `info`: v2 => v1 => v0
|
||||
let v2_info = v2::interface::RequestData::from(info);
|
||||
let v1_info = v1::interface::RequestData::try_from(v2_info)?;
|
||||
let v0_info = v0::interface::RequestData::try_from(v1_info)?;
|
||||
|
||||
let versioned_request = v0::VersionedRequest::construct(v0_info, version)?;
|
||||
Ok(versioned_request.try_into()?)
|
||||
}
|
||||
Version::V1 => {
|
||||
let versioned_request = v1::VersionedRequest::construct(info.into(), version)?;
|
||||
// attempt to go through conversion chain for `info`: v2 => v1
|
||||
let v2_info = v2::interface::RequestData::from(info);
|
||||
let v1_info = v1::interface::RequestData::try_from(v2_info)?;
|
||||
|
||||
let versioned_request = v1::VersionedRequest::construct(v1_info, version)?;
|
||||
Ok(versioned_request.try_into()?)
|
||||
}
|
||||
Version::V2 => {
|
||||
let v2_info = v2::interface::RequestData::from(info);
|
||||
|
||||
let versioned_request = v2::VersionedRequest::construct(v2_info, version)?;
|
||||
Ok(versioned_request.try_into()?)
|
||||
}
|
||||
}
|
||||
@@ -84,24 +113,42 @@ impl Extract<RequestData> for Request {
|
||||
#[cfg(feature = "testing")]
|
||||
super::Version::V0 => {
|
||||
let versioned_request = v0::VersionedRequest::try_from(self.clone())?;
|
||||
let (request, version) = versioned_request.extract()?;
|
||||
let (extracted_v0_info, version) = versioned_request.extract()?;
|
||||
|
||||
let upgrade_request = super::latest::interface::RequestData::try_from(request)?;
|
||||
let v1_info = v1::interface::RequestData::try_from(extracted_v0_info)?;
|
||||
let v2_info = v2::interface::RequestData::try_from(v1_info)?;
|
||||
|
||||
Ok((upgrade_request.into(), version))
|
||||
let request_data = RequestData::from(v2_info);
|
||||
Ok((request_data, version))
|
||||
}
|
||||
super::Version::V1 => {
|
||||
let versioned_request = v1::VersionedRequest::try_from(self.clone())?;
|
||||
let (extracted, version) = versioned_request.extract()?;
|
||||
Ok((extracted.into(), version))
|
||||
let versioned_request = v1::VersionedRequest::try_from(self)?;
|
||||
let (extracted_v1_info, version) = versioned_request.extract()?;
|
||||
let v2_info = v2::interface::RequestData::try_from(extracted_v1_info)?;
|
||||
|
||||
let request_data = RequestData::from(v2_info);
|
||||
Ok((request_data, version))
|
||||
}
|
||||
super::Version::V2 => {
|
||||
let versioned_request = v2::VersionedRequest::try_from(self)?;
|
||||
let (extracted_v2_info, version) = versioned_request.extract()?;
|
||||
|
||||
let request_data = RequestData::from(extracted_v2_info);
|
||||
Ok((request_data, version))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResponseData {
|
||||
AvailableBandwidth(i64),
|
||||
TopUpBandwidth(i64),
|
||||
AvailableBandwidth {
|
||||
amount: i64,
|
||||
upgrade_mode: bool,
|
||||
},
|
||||
TopUpBandwidth {
|
||||
available_bandwidth: i64,
|
||||
upgrade_mode: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Construct<ResponseData> for Response {
|
||||
@@ -109,14 +156,26 @@ impl Construct<ResponseData> for Response {
|
||||
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)?;
|
||||
// attempt to go through conversion chain for `info`: v2 => v1 => v0
|
||||
let v2_info = v2::interface::ResponseData::from(info);
|
||||
let v1_info = v1::interface::ResponseData::try_from(v2_info)?;
|
||||
let v0_info = v0::interface::ResponseData::try_from(v1_info)?;
|
||||
|
||||
let versioned_response = v0::VersionedResponse::construct(v0_info, version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
Version::V1 => {
|
||||
let versioned_response = v1::VersionedResponse::construct(info.into(), version)?;
|
||||
// attempt to go through conversion chain for `info`: v2 => v1
|
||||
let v2_info = v2::interface::ResponseData::from(info);
|
||||
let v1_info = v1::interface::ResponseData::try_from(v2_info)?;
|
||||
|
||||
let versioned_response = v1::VersionedResponse::construct(v1_info, version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
Version::V2 => {
|
||||
let v2_info = v2::interface::ResponseData::from(info);
|
||||
|
||||
let versioned_response = v2::VersionedResponse::construct(v2_info, version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
}
|
||||
@@ -129,16 +188,27 @@ impl Extract<ResponseData> for Response {
|
||||
#[cfg(feature = "testing")]
|
||||
super::Version::V0 => {
|
||||
let versioned_response = v0::VersionedResponse::try_from(self.clone())?;
|
||||
let (response, version) = versioned_response.extract()?;
|
||||
let (extracted_v0_info, version) = versioned_response.extract()?;
|
||||
let v1_info = v1::interface::ResponseData::try_from(extracted_v0_info)?;
|
||||
let v2_info = v2::interface::ResponseData::try_from(v1_info)?;
|
||||
|
||||
let upgrade_response = super::latest::interface::ResponseData::try_from(response)?;
|
||||
|
||||
Ok((upgrade_response.into(), version))
|
||||
let response_data = ResponseData::from(v2_info);
|
||||
Ok((response_data, version))
|
||||
}
|
||||
super::Version::V1 => {
|
||||
let versioned_response = v1::VersionedResponse::try_from(self.clone())?;
|
||||
let (extracted, version) = versioned_response.extract()?;
|
||||
Ok((extracted.into(), version))
|
||||
let (extracted_v1_info, version) = versioned_response.extract()?;
|
||||
let v2_info = v2::interface::ResponseData::try_from(extracted_v1_info)?;
|
||||
|
||||
let response_data = ResponseData::from(v2_info);
|
||||
Ok((response_data, version))
|
||||
}
|
||||
super::Version::V2 => {
|
||||
let versioned_response = v2::VersionedResponse::try_from(self.clone())?;
|
||||
let (extracted_v2_info, version) = versioned_response.extract()?;
|
||||
|
||||
let response_data = ResponseData::from(extracted_v2_info);
|
||||
Ok((response_data, version))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ pub mod interface;
|
||||
pub mod v0; // dummy version, only for filling boilerplate code for update/downgrade and testing
|
||||
pub mod v1;
|
||||
|
||||
pub use v1 as latest;
|
||||
// adds upgrade mode information to bandwidth response
|
||||
pub mod v2;
|
||||
|
||||
pub use v2 as latest;
|
||||
|
||||
use crate::models::error::Error;
|
||||
|
||||
@@ -24,6 +27,7 @@ pub enum Version {
|
||||
/// only used for testing purposes, don't include it in your matching arms
|
||||
V0,
|
||||
V1,
|
||||
V2,
|
||||
}
|
||||
|
||||
impl From<u64> for Version {
|
||||
@@ -35,6 +39,7 @@ impl From<u64> for Version {
|
||||
match value {
|
||||
0 => zero_version,
|
||||
1 => Version::V1,
|
||||
2 => Version::V2,
|
||||
_ => latest::VERSION, // if unknown, it means we're behind, so we can use the latest we know about
|
||||
}
|
||||
}
|
||||
@@ -47,6 +52,7 @@ impl From<Version> for u64 {
|
||||
#[cfg(feature = "testing")]
|
||||
Version::V0 => 0,
|
||||
Version::V1 => 1,
|
||||
Version::V2 => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,12 +63,24 @@ pub struct Request {
|
||||
pub(crate) inner: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, ToSchema)]
|
||||
impl Request {
|
||||
pub fn new(version: Version, inner: Vec<u8>) -> Self {
|
||||
Request { version, inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct Response {
|
||||
pub version: Version,
|
||||
pub(crate) inner: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn new(version: Version, inner: Vec<u8>) -> Self {
|
||||
Response { version, inner }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Extract<T> {
|
||||
fn extract(&self) -> Result<(T, Version), Error>;
|
||||
}
|
||||
|
||||
+17
-54
@@ -1,66 +1,29 @@
|
||||
// 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};
|
||||
use super::super::{QueryType, VersionedRequest};
|
||||
use crate::impl_default_bincode_request_query_conversions;
|
||||
|
||||
#[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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<&Request> for InnerAvailableBandwidthRequest
|
||||
// - TryFrom<Request> for InnerAvailableBandwidthRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for Request
|
||||
// - TryFrom<InnerTopUpRequest> for Request
|
||||
impl_default_bincode_request_query_conversions!(
|
||||
VersionedRequest,
|
||||
InnerAvailableBandwidthRequest,
|
||||
QueryType::AvailableBandwidth,
|
||||
QueryType::AvailableBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
+17
-53
@@ -1,66 +1,30 @@
|
||||
// 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 crate::impl_default_bincode_response_query_conversions;
|
||||
|
||||
use super::super::{Error, QueryType, VersionedResponse};
|
||||
use super::super::{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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedResponse> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<&InnerAvailableBandwidthResponse> for VersionedResponse
|
||||
// - TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse
|
||||
// - TryFrom<&Response> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<Response> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<&InnerAvailableBandwidthResponse> for Response
|
||||
// - TryFrom<InnerAvailableBandwidthResponse> for Response
|
||||
impl_default_bincode_response_query_conversions!(
|
||||
VersionedResponse,
|
||||
InnerAvailableBandwidthResponse,
|
||||
QueryType::AvailableBandwidth,
|
||||
QueryType::AvailableBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -9,6 +9,7 @@ use super::{
|
||||
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
|
||||
};
|
||||
use crate::models::{Construct, Extract, Version, error::Error};
|
||||
use crate::{Request, Response};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RequestData {
|
||||
@@ -35,11 +36,11 @@ 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())?;
|
||||
let _req = InnerAvailableBandwidthRequest::try_from(self)?;
|
||||
Ok((RequestData::AvailableBandwidth(()), VERSION))
|
||||
}
|
||||
QueryType::TopupBandwidth => {
|
||||
let _req = InnerTopUpRequest::try_from(self.clone())?;
|
||||
QueryType::TopUpBandwidth => {
|
||||
let _req = InnerTopUpRequest::try_from(self)?;
|
||||
Ok((RequestData::TopUpBandwidth(()), VERSION))
|
||||
}
|
||||
}
|
||||
@@ -61,13 +62,46 @@ 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())?;
|
||||
let _resp = InnerAvailableBandwidthResponse::try_from(self)?;
|
||||
Ok((ResponseData::AvailableBandwidth(()), VERSION))
|
||||
}
|
||||
QueryType::TopupBandwidth => {
|
||||
let _resp = InnerTopUpResponse::try_from(self.clone())?;
|
||||
QueryType::TopUpBandwidth => {
|
||||
let _resp = InnerTopUpResponse::try_from(self)?;
|
||||
Ok((ResponseData::TopUpBandwidth(()), VERSION))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
impl Extract<RequestData> for Request {
|
||||
fn extract(&self) -> Result<(RequestData, Version), Error> {
|
||||
match self.version {
|
||||
Version::V0 => {
|
||||
let versioned_request = VersionedRequest::try_from(self)?;
|
||||
versioned_request.extract()
|
||||
}
|
||||
_ => Err(Error::UpdateNotPossible {
|
||||
from: self.version,
|
||||
to: VERSION,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
impl Construct<ResponseData> for Response {
|
||||
fn construct(info: ResponseData, version: Version) -> Result<Self, Error> {
|
||||
match version {
|
||||
Version::V0 => {
|
||||
let translate_response = info;
|
||||
let versioned_response = VersionedResponse::construct(translate_response, version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
_ => Err(Error::DowngradeNotPossible {
|
||||
from: version,
|
||||
to: VERSION,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
|
||||
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},
|
||||
impl_default_bincode_request_conversions, impl_default_bincode_response_conversions,
|
||||
models::Version,
|
||||
};
|
||||
|
||||
pub(crate) mod available_bandwidth;
|
||||
@@ -31,7 +29,7 @@ pub use topup_bandwidth::{
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub enum QueryType {
|
||||
AvailableBandwidth,
|
||||
TopupBandwidth,
|
||||
TopUpBandwidth,
|
||||
}
|
||||
|
||||
impl Display for QueryType {
|
||||
@@ -45,62 +43,24 @@ 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)?)
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedRequest> for Request
|
||||
// - TryFrom<VersionedRequest> for Request
|
||||
// - TryFrom<&Request> for VersionedRequest
|
||||
// - TryFrom<Request> for VersionedRequest
|
||||
impl_default_bincode_request_conversions!(VersionedRequest, VERSION);
|
||||
|
||||
#[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)?)
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedResponse> for Response
|
||||
// - TryFrom<VersionedResponse> for Response
|
||||
// - TryFrom<&Response> for VersionedResponse
|
||||
// - TryFrom<Response> for VersionedResponse
|
||||
impl_default_bincode_response_conversions!(VersionedResponse, VERSION);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -110,8 +70,10 @@ mod tests {
|
||||
},
|
||||
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::make_bincode_serializer;
|
||||
use crate::{Request, Response};
|
||||
use bincode::Options;
|
||||
|
||||
#[test]
|
||||
fn serde_request_av_bw() {
|
||||
@@ -146,7 +108,7 @@ mod tests {
|
||||
#[test]
|
||||
fn serde_request_topup() {
|
||||
let req = VersionedRequest {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
query_type: QueryType::TopUpBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerTopUpRequest {})
|
||||
.unwrap(),
|
||||
@@ -161,7 +123,7 @@ mod tests {
|
||||
#[test]
|
||||
fn serde_response_topup() {
|
||||
let resp = VersionedResponse {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
query_type: QueryType::TopUpBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerTopUpResponse {})
|
||||
.unwrap(),
|
||||
|
||||
@@ -1,64 +1,30 @@
|
||||
// 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 crate::impl_default_bincode_request_query_conversions;
|
||||
|
||||
use super::super::{Error, QueryType, VersionedRequest};
|
||||
use super::super::{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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<&Request> for InnerTopUpRequest
|
||||
// - TryFrom<Request> for InnerTopUpRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for Request
|
||||
// - TryFrom<InnerTopUpRequest> for Request
|
||||
impl_default_bincode_request_query_conversions!(
|
||||
VersionedRequest,
|
||||
InnerTopUpRequest,
|
||||
QueryType::TopUpBandwidth,
|
||||
QueryType::TopUpBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -68,7 +34,7 @@ mod tests {
|
||||
fn serde() {
|
||||
let req = InnerTopUpRequest {};
|
||||
let ser = VersionedRequest::try_from(req.clone()).unwrap();
|
||||
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
|
||||
assert_eq!(QueryType::TopUpBandwidth, ser.query_type);
|
||||
let de = InnerTopUpRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
@@ -76,7 +42,7 @@ mod tests {
|
||||
#[test]
|
||||
fn empty_content() {
|
||||
let future_req = VersionedRequest {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
query_type: QueryType::TopUpBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerTopUpRequest::try_from(future_req).is_ok());
|
||||
|
||||
+19
-53
@@ -1,64 +1,30 @@
|
||||
// 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 crate::impl_default_bincode_response_query_conversions;
|
||||
|
||||
use super::super::{Error, QueryType, VersionedResponse};
|
||||
use super::super::{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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedResponse> for InnerTopUpResponse
|
||||
// - TryFrom<VersionedResponse> for InnerTopUpResponse
|
||||
// - TryFrom<&InnerTopUpResponse> for VersionedResponse
|
||||
// - TryFrom<InnerTopUpResponse> for VersionedResponse
|
||||
// - TryFrom<&Response> for InnerTopUpResponse
|
||||
// - TryFrom<Response> for InnerTopUpResponse
|
||||
// - TryFrom<&InnerTopUpResponse> for Response
|
||||
// - TryFrom<InnerTopUpResponse> for Response
|
||||
impl_default_bincode_response_query_conversions!(
|
||||
VersionedResponse,
|
||||
InnerTopUpResponse,
|
||||
QueryType::TopUpBandwidth,
|
||||
QueryType::TopUpBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -68,7 +34,7 @@ mod tests {
|
||||
fn serde() {
|
||||
let resp = InnerTopUpResponse {};
|
||||
let ser = VersionedResponse::try_from(resp.clone()).unwrap();
|
||||
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
|
||||
assert_eq!(QueryType::TopUpBandwidth, ser.query_type);
|
||||
let de = InnerTopUpResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
@@ -76,7 +42,7 @@ mod tests {
|
||||
#[test]
|
||||
fn empty_content() {
|
||||
let future_resp = VersionedResponse {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
query_type: QueryType::TopUpBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerTopUpResponse::try_from(future_resp).is_ok());
|
||||
|
||||
+17
-53
@@ -1,66 +1,30 @@
|
||||
// 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 crate::impl_default_bincode_request_query_conversions;
|
||||
|
||||
use super::super::{Error, QueryType, VersionedRequest};
|
||||
use super::super::{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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<&Request> for InnerAvailableBandwidthRequest
|
||||
// - TryFrom<Request> for InnerAvailableBandwidthRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for Request
|
||||
// - TryFrom<InnerTopUpRequest> for Request
|
||||
impl_default_bincode_request_query_conversions!(
|
||||
VersionedRequest,
|
||||
InnerAvailableBandwidthRequest,
|
||||
QueryType::AvailableBandwidth,
|
||||
QueryType::AvailableBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
+17
-53
@@ -1,68 +1,32 @@
|
||||
// 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 crate::impl_default_bincode_response_query_conversions;
|
||||
|
||||
use super::super::{Error, QueryType, VersionedResponse};
|
||||
use super::super::{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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedResponse> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<&InnerAvailableBandwidthResponse> for VersionedResponse
|
||||
// - TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse
|
||||
// - TryFrom<&Response> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<Response> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<&InnerAvailableBandwidthResponse> for Response
|
||||
// - TryFrom<InnerAvailableBandwidthResponse> for Response
|
||||
impl_default_bincode_response_query_conversions!(
|
||||
VersionedResponse,
|
||||
InnerAvailableBandwidthResponse,
|
||||
QueryType::AvailableBandwidth,
|
||||
QueryType::AvailableBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -5,6 +5,8 @@ use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
use super::super::v0 as previous;
|
||||
#[cfg(feature = "testing")]
|
||||
use crate::{Request, Response, v0};
|
||||
|
||||
use super::{
|
||||
QueryType, VERSION, VersionedRequest, VersionedResponse,
|
||||
@@ -46,7 +48,7 @@ impl Extract<RequestData> for VersionedRequest {
|
||||
let _req = InnerAvailableBandwidthRequest::try_from(self.clone())?;
|
||||
Ok((RequestData::AvailableBandwidth(()), VERSION))
|
||||
}
|
||||
QueryType::TopupBandwidth => {
|
||||
QueryType::TopUpBandwidth => {
|
||||
let req = InnerTopUpRequest::try_from(self.clone())?;
|
||||
Ok((
|
||||
RequestData::TopUpBandwidth(Box::new(req.credential)),
|
||||
@@ -84,7 +86,7 @@ impl Extract<ResponseData> for VersionedResponse {
|
||||
VERSION,
|
||||
))
|
||||
}
|
||||
QueryType::TopupBandwidth => {
|
||||
QueryType::TopUpBandwidth => {
|
||||
let resp = InnerTopUpResponse::try_from(self.clone())?;
|
||||
Ok((
|
||||
ResponseData::TopUpBandwidth(resp.available_bandwidth),
|
||||
@@ -98,7 +100,7 @@ impl Extract<ResponseData> for VersionedResponse {
|
||||
// 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;
|
||||
type Error = crate::models::error::Error;
|
||||
|
||||
fn try_from(value: previous::interface::RequestData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
@@ -106,7 +108,7 @@ impl TryFrom<previous::interface::RequestData> for RequestData {
|
||||
Ok(Self::AvailableBandwidth(inner))
|
||||
}
|
||||
previous::interface::RequestData::TopUpBandwidth(_) => {
|
||||
Err(super::Error::UpdateNotPossible {
|
||||
Err(crate::models::Error::UpdateNotPossible {
|
||||
from: previous::VERSION,
|
||||
to: VERSION,
|
||||
})
|
||||
@@ -118,7 +120,7 @@ impl TryFrom<previous::interface::RequestData> for RequestData {
|
||||
// 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;
|
||||
type Error = crate::models::error::Error;
|
||||
|
||||
fn try_from(value: RequestData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
@@ -131,18 +133,18 @@ impl TryFrom<RequestData> for previous::interface::RequestData {
|
||||
// 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;
|
||||
type Error = crate::models::error::Error;
|
||||
|
||||
fn try_from(value: previous::interface::ResponseData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
previous::interface::ResponseData::AvailableBandwidth(_) => {
|
||||
Err(super::Error::UpdateNotPossible {
|
||||
Err(crate::models::error::Error::UpdateNotPossible {
|
||||
from: previous::VERSION,
|
||||
to: VERSION,
|
||||
})
|
||||
}
|
||||
previous::interface::ResponseData::TopUpBandwidth(_) => {
|
||||
Err(super::Error::UpdateNotPossible {
|
||||
Err(crate::models::error::Error::UpdateNotPossible {
|
||||
from: previous::VERSION,
|
||||
to: VERSION,
|
||||
})
|
||||
@@ -154,7 +156,7 @@ impl TryFrom<previous::interface::ResponseData> for ResponseData {
|
||||
// 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;
|
||||
type Error = crate::models::error::Error;
|
||||
|
||||
fn try_from(value: ResponseData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
@@ -164,13 +166,64 @@ impl TryFrom<ResponseData> for previous::interface::ResponseData {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
impl Extract<RequestData> for Request {
|
||||
fn extract(&self) -> Result<(RequestData, Version), Error> {
|
||||
match self.version {
|
||||
Version::V0 => {
|
||||
let versioned_request = v0::VersionedRequest::try_from(self)?;
|
||||
let (extracted_v0_info, version) = versioned_request.extract()?;
|
||||
|
||||
let v1_info = RequestData::try_from(extracted_v0_info)?;
|
||||
Ok((v1_info, version))
|
||||
}
|
||||
Version::V1 => {
|
||||
let versioned_request = VersionedRequest::try_from(self)?;
|
||||
versioned_request.extract()
|
||||
}
|
||||
// a v1 server does not have any code for downgrading v2 into v1
|
||||
_ => Err(Error::DowngradeNotPossible {
|
||||
from: self.version,
|
||||
to: VERSION,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
impl Construct<ResponseData> for Response {
|
||||
fn construct(info: ResponseData, version: Version) -> Result<Self, Error> {
|
||||
match version {
|
||||
Version::V0 => {
|
||||
let v1_info = info;
|
||||
let v0_info = v0::interface::ResponseData::try_from(v1_info)?;
|
||||
|
||||
let versioned_response = v0::VersionedResponse::construct(v0_info, version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
Version::V1 => {
|
||||
let translate_response = info;
|
||||
let versioned_response = VersionedResponse::construct(translate_response, version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
// a v1 server does not have any code for downgrading v2 into v1
|
||||
_ => Err(Error::DowngradeNotPossible {
|
||||
from: version,
|
||||
to: VERSION,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[cfg(feature = "testing")]
|
||||
use super::*;
|
||||
#[cfg(feature = "testing")]
|
||||
use crate::models::tests::CREDENTIAL_BYTES;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "testing")]
|
||||
fn request_upgrade() {
|
||||
assert_eq!(
|
||||
RequestData::try_from(previous::interface::RequestData::AvailableBandwidth(()))
|
||||
@@ -183,6 +236,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "testing")]
|
||||
fn response_upgrade() {
|
||||
assert!(
|
||||
ResponseData::try_from(previous::interface::ResponseData::AvailableBandwidth(()))
|
||||
@@ -194,6 +248,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "testing")]
|
||||
fn request_downgrade() {
|
||||
assert_eq!(
|
||||
previous::interface::RequestData::try_from(RequestData::AvailableBandwidth(()))
|
||||
@@ -210,6 +265,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "testing")]
|
||||
fn response_downgrade() {
|
||||
assert_eq!(
|
||||
previous::interface::ResponseData::try_from(ResponseData::AvailableBandwidth(42))
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
|
||||
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},
|
||||
impl_default_bincode_request_conversions, impl_default_bincode_response_conversions,
|
||||
models::Version,
|
||||
};
|
||||
|
||||
pub use available_bandwidth::{
|
||||
@@ -31,7 +29,7 @@ pub const VERSION: Version = Version::V1;
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub enum QueryType {
|
||||
AvailableBandwidth,
|
||||
TopupBandwidth,
|
||||
TopUpBandwidth,
|
||||
}
|
||||
|
||||
impl Display for QueryType {
|
||||
@@ -45,82 +43,44 @@ 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)?)
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedRequest> for Request
|
||||
// - TryFrom<VersionedRequest> for Request
|
||||
// - TryFrom<&Request> for VersionedRequest
|
||||
// - TryFrom<Request> for VersionedRequest
|
||||
impl_default_bincode_request_conversions!(VersionedRequest, VERSION);
|
||||
|
||||
#[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)?)
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedResponse> for Response
|
||||
// - TryFrom<VersionedResponse> for Response
|
||||
// - TryFrom<&Response> for VersionedResponse
|
||||
// - TryFrom<Response> for VersionedResponse
|
||||
impl_default_bincode_response_conversions!(VersionedResponse, VERSION);
|
||||
|
||||
#[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 crate::models::error::Error;
|
||||
use crate::models::tests::CREDENTIAL_BYTES;
|
||||
use crate::{Request, Response, make_bincode_serializer};
|
||||
use bincode::Options;
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mismatched_request_version() {
|
||||
let version = Version::V0;
|
||||
let version = Version::V2;
|
||||
let future_bw = Request {
|
||||
version,
|
||||
inner: vec![],
|
||||
@@ -139,7 +99,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn mismatched_response_version() {
|
||||
let version = Version::V0;
|
||||
let version = Version::V2;
|
||||
let future_bw = Response {
|
||||
version,
|
||||
inner: vec![],
|
||||
@@ -191,7 +151,7 @@ mod tests {
|
||||
#[test]
|
||||
fn serde_request_topup() {
|
||||
let req = VersionedRequest {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
query_type: QueryType::TopUpBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerTopUpRequest {
|
||||
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
|
||||
@@ -208,7 +168,7 @@ mod tests {
|
||||
#[test]
|
||||
fn serde_response_topup() {
|
||||
let resp = VersionedResponse {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
query_type: QueryType::TopUpBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerTopUpResponse {
|
||||
available_bandwidth: 42,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// 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 crate::impl_default_bincode_request_query_conversions;
|
||||
|
||||
use super::super::{Error, QueryType, VersionedRequest};
|
||||
use super::super::{QueryType, VersionedRequest};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InnerTopUpRequest {
|
||||
@@ -15,54 +14,21 @@ pub struct InnerTopUpRequest {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<&Request> for InnerTopUpRequest
|
||||
// - TryFrom<Request> for InnerTopUpRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for Request
|
||||
// - TryFrom<InnerTopUpRequest> for Request
|
||||
impl_default_bincode_request_query_conversions!(
|
||||
VersionedRequest,
|
||||
InnerTopUpRequest,
|
||||
QueryType::TopUpBandwidth,
|
||||
QueryType::TopUpBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -76,7 +42,7 @@ mod tests {
|
||||
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
|
||||
};
|
||||
let ser = VersionedRequest::try_from(req.clone()).unwrap();
|
||||
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
|
||||
assert_eq!(QueryType::TopUpBandwidth, ser.query_type);
|
||||
let de = InnerTopUpRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
@@ -84,7 +50,7 @@ mod tests {
|
||||
#[test]
|
||||
fn invalid_content() {
|
||||
let future_req = VersionedRequest {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
query_type: QueryType::TopUpBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerTopUpRequest::try_from(future_req).is_err());
|
||||
|
||||
+19
-53
@@ -1,66 +1,32 @@
|
||||
// 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 crate::impl_default_bincode_response_query_conversions;
|
||||
|
||||
use super::super::{Error, QueryType, VersionedResponse};
|
||||
use super::super::{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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedResponse> for InnerTopUpResponse
|
||||
// - TryFrom<VersionedResponse> for InnerTopUpResponse
|
||||
// - TryFrom<&InnerTopUpResponse> for VersionedResponse
|
||||
// - TryFrom<InnerTopUpResponse> for VersionedResponse
|
||||
// - TryFrom<&Response> for InnerTopUpResponse
|
||||
// - TryFrom<Response> for InnerTopUpResponse
|
||||
// - TryFrom<&InnerTopUpResponse> for Response
|
||||
// - TryFrom<InnerTopUpResponse> for Response
|
||||
impl_default_bincode_response_query_conversions!(
|
||||
VersionedResponse,
|
||||
InnerTopUpResponse,
|
||||
QueryType::TopUpBandwidth,
|
||||
QueryType::TopUpBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -72,7 +38,7 @@ mod tests {
|
||||
available_bandwidth: 42,
|
||||
};
|
||||
let ser = VersionedResponse::try_from(resp.clone()).unwrap();
|
||||
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
|
||||
assert_eq!(QueryType::TopUpBandwidth, ser.query_type);
|
||||
let de = InnerTopUpResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
@@ -80,7 +46,7 @@ mod tests {
|
||||
#[test]
|
||||
fn invalid_content() {
|
||||
let future_resp = VersionedResponse {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
query_type: QueryType::TopUpBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerTopUpResponse::try_from(future_resp).is_err());
|
||||
|
||||
@@ -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,50 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::impl_default_bincode_request_query_conversions;
|
||||
|
||||
use super::super::{QueryType, VersionedRequest};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct InnerAvailableBandwidthRequest {}
|
||||
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<VersionedRequest> for InnerTopUpRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<InnerTopUpRequest> for VersionedRequest
|
||||
// - TryFrom<&Request> for InnerAvailableBandwidthRequest
|
||||
// - TryFrom<Request> for InnerAvailableBandwidthRequest
|
||||
// - TryFrom<&InnerTopUpRequest> for Request
|
||||
// - TryFrom<InnerTopUpRequest> for Request
|
||||
impl_default_bincode_request_query_conversions!(
|
||||
VersionedRequest,
|
||||
InnerAvailableBandwidthRequest,
|
||||
QueryType::AvailableBandwidth,
|
||||
QueryType::AvailableBandwidth
|
||||
);
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::impl_default_bincode_response_query_conversions;
|
||||
|
||||
use super::super::{QueryType, VersionedResponse};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct InnerAvailableBandwidthResponse {
|
||||
pub available_bandwidth: i64,
|
||||
pub upgrade_mode: bool,
|
||||
}
|
||||
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedResponse> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<&InnerAvailableBandwidthResponse> for VersionedResponse
|
||||
// - TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse
|
||||
// - TryFrom<&Response> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<Response> for InnerAvailableBandwidthResponse
|
||||
// - TryFrom<&InnerAvailableBandwidthResponse> for Response
|
||||
// - TryFrom<InnerAvailableBandwidthResponse> for Response
|
||||
impl_default_bincode_response_query_conversions!(
|
||||
VersionedResponse,
|
||||
InnerAvailableBandwidthResponse,
|
||||
QueryType::AvailableBandwidth,
|
||||
QueryType::AvailableBandwidth
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let resp = InnerAvailableBandwidthResponse {
|
||||
available_bandwidth: 42,
|
||||
upgrade_mode: false,
|
||||
};
|
||||
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,257 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::BandwidthCredential;
|
||||
|
||||
use super::super::v1 as previous;
|
||||
|
||||
use super::{
|
||||
QueryType, VERSION, VersionedRequest, VersionedResponse,
|
||||
available_bandwidth::{
|
||||
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
|
||||
},
|
||||
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
|
||||
};
|
||||
use crate::models::{Construct, Extract, Version, error::Error};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RequestData {
|
||||
AvailableBandwidth,
|
||||
TopUpBandwidth {
|
||||
credential: Box<BandwidthCredential>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ResponseData {
|
||||
AvailableBandwidth {
|
||||
amount: i64,
|
||||
upgrade_mode: bool,
|
||||
},
|
||||
TopUpBandwidth {
|
||||
available_bandwidth: i64,
|
||||
upgrade_mode: bool,
|
||||
},
|
||||
}
|
||||
|
||||
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)?;
|
||||
Ok((RequestData::AvailableBandwidth, VERSION))
|
||||
}
|
||||
QueryType::TopUpBandwidth => {
|
||||
let req = InnerTopUpRequest::try_from(self)?;
|
||||
Ok((
|
||||
RequestData::TopUpBandwidth {
|
||||
credential: Box::new(req.credential),
|
||||
},
|
||||
VERSION,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Construct<ResponseData> for VersionedResponse {
|
||||
fn construct(info: ResponseData, _version: Version) -> Result<Self, Error> {
|
||||
match info {
|
||||
ResponseData::AvailableBandwidth {
|
||||
amount,
|
||||
upgrade_mode,
|
||||
} => Ok(InnerAvailableBandwidthResponse {
|
||||
available_bandwidth: amount,
|
||||
upgrade_mode,
|
||||
}
|
||||
.try_into()?),
|
||||
ResponseData::TopUpBandwidth {
|
||||
available_bandwidth,
|
||||
upgrade_mode,
|
||||
} => Ok(InnerTopUpResponse {
|
||||
available_bandwidth,
|
||||
upgrade_mode,
|
||||
}
|
||||
.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)?;
|
||||
Ok((
|
||||
ResponseData::AvailableBandwidth {
|
||||
amount: resp.available_bandwidth,
|
||||
upgrade_mode: resp.upgrade_mode,
|
||||
},
|
||||
VERSION,
|
||||
))
|
||||
}
|
||||
QueryType::TopUpBandwidth => {
|
||||
let resp = InnerTopUpResponse::try_from(self)?;
|
||||
Ok((
|
||||
ResponseData::TopUpBandwidth {
|
||||
available_bandwidth: resp.available_bandwidth,
|
||||
upgrade_mode: resp.upgrade_mode,
|
||||
},
|
||||
VERSION,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(_) => Ok(Self::AvailableBandwidth),
|
||||
previous::interface::RequestData::TopUpBandwidth(zk_nym) => Ok(Self::TopUpBandwidth {
|
||||
credential: Box::new((*zk_nym).into()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RequestData> for previous::interface::RequestData {
|
||||
type Error = super::Error;
|
||||
|
||||
fn try_from(value: RequestData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
RequestData::AvailableBandwidth => Ok(Self::AvailableBandwidth(())),
|
||||
RequestData::TopUpBandwidth { credential } => match *credential {
|
||||
BandwidthCredential::ZkNym(zk_nym) => Ok(Self::TopUpBandwidth(zk_nym)),
|
||||
BandwidthCredential::UpgradeModeJWT { .. } => {
|
||||
Err(super::Error::DowngradeNotPossible {
|
||||
from: VERSION,
|
||||
to: previous::VERSION,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ResponseData> for previous::interface::ResponseData {
|
||||
type Error = super::Error;
|
||||
|
||||
fn try_from(value: ResponseData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
ResponseData::AvailableBandwidth { amount, .. } => Ok(Self::AvailableBandwidth(amount)),
|
||||
ResponseData::TopUpBandwidth {
|
||||
available_bandwidth,
|
||||
..
|
||||
} => Ok(Self::TopUpBandwidth(available_bandwidth)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::models::tests::CREDENTIAL_BYTES;
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
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(Box::new(
|
||||
CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()
|
||||
)))
|
||||
.is_err(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_upgrade() {
|
||||
assert!(
|
||||
ResponseData::try_from(previous::interface::ResponseData::AvailableBandwidth(42))
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
ResponseData::try_from(previous::interface::ResponseData::TopUpBandwidth(42)).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 {
|
||||
credential: Box::new(BandwidthCredential::from(
|
||||
CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()
|
||||
))
|
||||
})
|
||||
.unwrap(),
|
||||
previous::interface::RequestData::TopUpBandwidth(Box::new(
|
||||
CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_downgrade() {
|
||||
assert_eq!(
|
||||
previous::interface::ResponseData::try_from(ResponseData::AvailableBandwidth {
|
||||
amount: 42,
|
||||
upgrade_mode: true
|
||||
})
|
||||
.unwrap(),
|
||||
previous::interface::ResponseData::AvailableBandwidth(42)
|
||||
);
|
||||
assert_eq!(
|
||||
previous::interface::ResponseData::try_from(ResponseData::TopUpBandwidth {
|
||||
available_bandwidth: 42,
|
||||
upgrade_mode: true,
|
||||
})
|
||||
.unwrap(),
|
||||
previous::interface::ResponseData::TopUpBandwidth(42)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::error::Error;
|
||||
use crate::{
|
||||
impl_default_bincode_request_conversions, impl_default_bincode_response_conversions,
|
||||
models::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::V2;
|
||||
|
||||
#[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>,
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedRequest> for Request
|
||||
// - TryFrom<VersionedRequest> for Request
|
||||
// - TryFrom<&Request> for VersionedRequest
|
||||
// - TryFrom<Request> for VersionedRequest
|
||||
impl_default_bincode_request_conversions!(VersionedRequest, VERSION);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct VersionedResponse {
|
||||
query_type: QueryType,
|
||||
inner: Vec<u8>,
|
||||
}
|
||||
// Implements:
|
||||
// - TryFrom<&VersionedResponse> for Response
|
||||
// - TryFrom<VersionedResponse> for Response
|
||||
// - TryFrom<&Response> for VersionedResponse
|
||||
// - TryFrom<Response> for VersionedResponse
|
||||
impl_default_bincode_response_conversions!(VersionedResponse, VERSION);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use self::{
|
||||
available_bandwidth::{
|
||||
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
|
||||
},
|
||||
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
|
||||
};
|
||||
use crate::models::tests::CREDENTIAL_BYTES;
|
||||
use crate::{Request, Response, make_bincode_serializer};
|
||||
use bincode::Options;
|
||||
use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mismatched_request_version() {
|
||||
let version = Version::V1;
|
||||
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::V1;
|
||||
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,
|
||||
upgrade_mode: true,
|
||||
})
|
||||
.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: BandwidthCredential::from(
|
||||
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,
|
||||
upgrade_mode: true,
|
||||
})
|
||||
.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;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user