mirror of
https://github.com/chatmail/core.git
synced 2026-05-05 06:16:30 +03:00
build: update iroh to 0.94
https://www.iroh.computer/blog/iroh-0-94-0-the-endpoint-takeover
This commit is contained in:
@@ -66,8 +66,8 @@ humansize = "2"
|
|||||||
hyper = "1"
|
hyper = "1"
|
||||||
hyper-util = "0.1.16"
|
hyper-util = "0.1.16"
|
||||||
image = { version = "0.25.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
image = { version = "0.25.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||||
iroh-gossip = { version = "0.92", default-features = false, features = ["net"] }
|
iroh-gossip = { version = "0.94", default-features = false, features = ["net"] }
|
||||||
iroh = { version = "0.92", default-features = false }
|
iroh = { version = "0.94", default-features = false }
|
||||||
kamadak-exif = "0.6.1"
|
kamadak-exif = "0.6.1"
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
mail-builder = { version = "0.4.4", default-features = false }
|
mail-builder = { version = "0.4.4", default-features = false }
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use std::task::Poll;
|
|||||||
|
|
||||||
use anyhow::{Context as _, Result, bail, format_err};
|
use anyhow::{Context as _, Result, bail, format_err};
|
||||||
use futures_lite::FutureExt;
|
use futures_lite::FutureExt;
|
||||||
use iroh::{Endpoint, RelayMode, Watcher as _};
|
use iroh::{Endpoint, RelayMode};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@@ -69,7 +69,7 @@ pub struct BackupProvider {
|
|||||||
_endpoint: Endpoint,
|
_endpoint: Endpoint,
|
||||||
|
|
||||||
/// iroh address.
|
/// iroh address.
|
||||||
node_addr: iroh::NodeAddr,
|
node_addr: iroh::EndpointAddr,
|
||||||
|
|
||||||
/// Authentication token that should be submitted
|
/// Authentication token that should be submitted
|
||||||
/// to retrieve the backup.
|
/// to retrieve the backup.
|
||||||
@@ -100,7 +100,7 @@ impl BackupProvider {
|
|||||||
.relay_mode(relay_mode)
|
.relay_mode(relay_mode)
|
||||||
.bind()
|
.bind()
|
||||||
.await?;
|
.await?;
|
||||||
let node_addr = endpoint.node_addr().initialized().await;
|
let node_addr = endpoint.addr();
|
||||||
|
|
||||||
// Acquire global "ongoing" mutex.
|
// Acquire global "ongoing" mutex.
|
||||||
let cancel_token = context.alloc_ongoing().await?;
|
let cancel_token = context.alloc_ongoing().await?;
|
||||||
@@ -297,7 +297,7 @@ impl Future for BackupProvider {
|
|||||||
|
|
||||||
pub async fn get_backup2(
|
pub async fn get_backup2(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
node_addr: iroh::NodeAddr,
|
node_addr: iroh::EndpointAddr,
|
||||||
auth_token: String,
|
auth_token: String,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let relay_mode = RelayMode::Disabled;
|
let relay_mode = RelayMode::Disabled;
|
||||||
|
|||||||
@@ -1545,7 +1545,7 @@ impl MimeFactory {
|
|||||||
|
|
||||||
// We should not send `null` as relay URL
|
// We should not send `null` as relay URL
|
||||||
// as this is the only way to reach the node.
|
// as this is the only way to reach the node.
|
||||||
debug_assert!(node_addr.relay_url().is_some());
|
debug_assert_eq!(node_addr.relay_urls().count(), 1);
|
||||||
headers.push((
|
headers.push((
|
||||||
HeaderDef::IrohNodeAddr.into(),
|
HeaderDef::IrohNodeAddr.into(),
|
||||||
mail_builder::headers::text::Text::new(serde_json::to_string(&node_addr)?)
|
mail_builder::headers::text::Text::new(serde_json::to_string(&node_addr)?)
|
||||||
|
|||||||
@@ -19,20 +19,22 @@
|
|||||||
//! This message contains the users relay-server and public key.
|
//! This message contains the users relay-server and public key.
|
||||||
//! Direct IP address is not included as this information can be persisted by email providers.
|
//! Direct IP address is not included as this information can be persisted by email providers.
|
||||||
//! 4. After the announcement, the sending peer joins the gossip swarm with an empty list of peer IDs (as they don't know anyone yet).
|
//! 4. After the announcement, the sending peer joins the gossip swarm with an empty list of peer IDs (as they don't know anyone yet).
|
||||||
//! 5. Upon receiving an announcement message, other peers store the sender's [NodeAddr] in the database
|
//! 5. Upon receiving an announcement message, other peers store the sender's [EndpointAddr] in the database
|
||||||
//! (scoped per WebXDC app instance/message-id). The other peers can then join the gossip with `joinRealtimeChannel().setListener()`
|
//! (scoped per WebXDC app instance/message-id). The other peers can then join the gossip with `joinRealtimeChannel().setListener()`
|
||||||
//! and `joinRealtimeChannel().send()` just like the other peers.
|
//! and `joinRealtimeChannel().send()` just like the other peers.
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, anyhow, bail};
|
use anyhow::{Context as _, Result, anyhow, bail};
|
||||||
use data_encoding::BASE32_NOPAD;
|
use data_encoding::BASE32_NOPAD;
|
||||||
use futures_lite::StreamExt;
|
use futures_lite::StreamExt;
|
||||||
use iroh::Watcher as _;
|
use iroh::discovery::static_provider::StaticProvider;
|
||||||
use iroh::{Endpoint, NodeAddr, NodeId, PublicKey, RelayMode, RelayUrl, SecretKey};
|
use iroh::{
|
||||||
|
Endpoint, EndpointAddr, EndpointId, PublicKey, RelayMode, RelayUrl, SecretKey, TransportAddr,
|
||||||
|
};
|
||||||
use iroh_gossip::api::{Event as GossipEvent, GossipReceiver, GossipSender, JoinOptions};
|
use iroh_gossip::api::{Event as GossipEvent, GossipReceiver, GossipSender, JoinOptions};
|
||||||
use iroh_gossip::net::{GOSSIP_ALPN, Gossip};
|
use iroh_gossip::net::{GOSSIP_ALPN, Gossip};
|
||||||
use iroh_gossip::proto::TopicId;
|
use iroh_gossip::proto::TopicId;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use tokio::sync::{RwLock, oneshot};
|
use tokio::sync::{RwLock, oneshot};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
@@ -56,6 +58,9 @@ pub struct Iroh {
|
|||||||
/// Iroh router needed for Iroh peer channels.
|
/// Iroh router needed for Iroh peer channels.
|
||||||
pub(crate) router: iroh::protocol::Router,
|
pub(crate) router: iroh::protocol::Router,
|
||||||
|
|
||||||
|
/// Discovery service.
|
||||||
|
pub(crate) discovery: StaticProvider,
|
||||||
|
|
||||||
/// [Gossip] needed for Iroh peer channels.
|
/// [Gossip] needed for Iroh peer channels.
|
||||||
pub(crate) gossip: Gossip,
|
pub(crate) gossip: Gossip,
|
||||||
|
|
||||||
@@ -107,7 +112,7 @@ impl Iroh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let peers = get_iroh_gossip_peers(ctx, msg_id).await?;
|
let peers = get_iroh_gossip_peers(ctx, msg_id).await?;
|
||||||
let node_ids = peers.iter().map(|p| p.node_id).collect::<Vec<_>>();
|
let node_ids = peers.iter().map(|p| p.id).collect::<Vec<_>>();
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -117,7 +122,7 @@ impl Iroh {
|
|||||||
// Inform iroh of potentially new node addresses
|
// Inform iroh of potentially new node addresses
|
||||||
for node_addr in &peers {
|
for node_addr in &peers {
|
||||||
if !node_addr.is_empty() {
|
if !node_addr.is_empty() {
|
||||||
self.router.endpoint().add_node_addr(node_addr.clone())?;
|
self.discovery.add_endpoint_info(node_addr.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,10 +147,10 @@ impl Iroh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add gossip peer to realtime channel if it is already active.
|
/// Add gossip peer to realtime channel if it is already active.
|
||||||
pub async fn maybe_add_gossip_peer(&self, topic: TopicId, peer: NodeAddr) -> Result<()> {
|
pub async fn maybe_add_gossip_peer(&self, topic: TopicId, peer: EndpointAddr) -> Result<()> {
|
||||||
if self.iroh_channels.read().await.get(&topic).is_some() {
|
if self.iroh_channels.read().await.get(&topic).is_some() {
|
||||||
self.router.endpoint().add_node_addr(peer.clone())?;
|
self.discovery.add_endpoint_info(peer.clone());
|
||||||
self.gossip.subscribe(topic, vec![peer.node_id]).await?;
|
self.gossip.subscribe(topic, vec![peer.id]).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -187,18 +192,20 @@ impl Iroh {
|
|||||||
*entry
|
*entry
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the iroh [NodeAddr] without direct IP addresses.
|
/// Get the iroh [EndpointAddr] without direct IP addresses.
|
||||||
///
|
///
|
||||||
/// The address is guaranteed to have home relay URL set
|
/// The address is guaranteed to have home relay URL set
|
||||||
/// as it is the only way to reach the node
|
/// as it is the only way to reach the node
|
||||||
/// without global discovery mechanisms.
|
/// without global discovery mechanisms.
|
||||||
pub(crate) async fn get_node_addr(&self) -> Result<NodeAddr> {
|
pub(crate) async fn get_node_addr(&self) -> Result<EndpointAddr> {
|
||||||
// Wait until home relay connection is established.
|
// Wait until home relay connection is established.
|
||||||
let _relay_url = self.router.endpoint().home_relay().initialized().await;
|
self.router.endpoint().online().await;
|
||||||
let mut addr = self.router.endpoint().node_addr().initialized().await;
|
let mut endpoint_addr = self.router.endpoint().addr();
|
||||||
addr.direct_addresses = BTreeSet::new();
|
endpoint_addr
|
||||||
debug_assert!(addr.relay_url().is_some());
|
.addrs
|
||||||
Ok(addr)
|
.retain(|addr| matches!(addr, TransportAddr::Relay(_)));
|
||||||
|
debug_assert_eq!(endpoint_addr.addrs.len(), 1);
|
||||||
|
Ok(endpoint_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Leave the realtime channel for a given topic.
|
/// Leave the realtime channel for a given topic.
|
||||||
@@ -240,7 +247,7 @@ impl Context {
|
|||||||
/// Create iroh endpoint and gossip.
|
/// Create iroh endpoint and gossip.
|
||||||
async fn init_peer_channels(&self) -> Result<Iroh> {
|
async fn init_peer_channels(&self) -> Result<Iroh> {
|
||||||
info!(self, "Initializing peer channels.");
|
info!(self, "Initializing peer channels.");
|
||||||
let secret_key = SecretKey::generate(rand_old::rngs::OsRng);
|
let secret_key = SecretKey::generate(&mut rand::rng());
|
||||||
let public_key = secret_key.public();
|
let public_key = secret_key.public();
|
||||||
|
|
||||||
let relay_mode = if let Some(relay_url) = self
|
let relay_mode = if let Some(relay_url) = self
|
||||||
@@ -257,7 +264,9 @@ impl Context {
|
|||||||
RelayMode::Default
|
RelayMode::Default
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let discovery = StaticProvider::new();
|
||||||
let endpoint = Endpoint::builder()
|
let endpoint = Endpoint::builder()
|
||||||
|
.discovery(discovery.clone())
|
||||||
.secret_key(secret_key)
|
.secret_key(secret_key)
|
||||||
.alpns(vec![GOSSIP_ALPN.to_vec()])
|
.alpns(vec![GOSSIP_ALPN.to_vec()])
|
||||||
.relay_mode(relay_mode)
|
.relay_mode(relay_mode)
|
||||||
@@ -279,6 +288,7 @@ impl Context {
|
|||||||
|
|
||||||
Ok(Iroh {
|
Ok(Iroh {
|
||||||
router,
|
router,
|
||||||
|
discovery,
|
||||||
gossip,
|
gossip,
|
||||||
sequence_numbers: Mutex::new(HashMap::new()),
|
sequence_numbers: Mutex::new(HashMap::new()),
|
||||||
iroh_channels: RwLock::new(HashMap::new()),
|
iroh_channels: RwLock::new(HashMap::new()),
|
||||||
@@ -325,11 +335,15 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn maybe_add_gossip_peer(&self, topic: TopicId, peer: NodeAddr) -> Result<()> {
|
pub(crate) async fn maybe_add_gossip_peer(
|
||||||
|
&self,
|
||||||
|
topic: TopicId,
|
||||||
|
peer: EndpointAddr,
|
||||||
|
) -> Result<()> {
|
||||||
if let Some(iroh) = &*self.iroh.read().await {
|
if let Some(iroh) = &*self.iroh.read().await {
|
||||||
info!(
|
info!(
|
||||||
self,
|
self,
|
||||||
"Adding (maybe existing) peer with id {} to {topic}.", peer.node_id
|
"Adding (maybe existing) peer with id {} to {topic}.", peer.id
|
||||||
);
|
);
|
||||||
iroh.maybe_add_gossip_peer(topic, peer).await?;
|
iroh.maybe_add_gossip_peer(topic, peer).await?;
|
||||||
}
|
}
|
||||||
@@ -337,12 +351,12 @@ impl Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cache a peers [NodeId] for one topic.
|
/// Cache a peers [EndpointId] for one topic.
|
||||||
pub(crate) async fn iroh_add_peer_for_topic(
|
pub(crate) async fn iroh_add_peer_for_topic(
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
msg_id: MsgId,
|
msg_id: MsgId,
|
||||||
topic: TopicId,
|
topic: TopicId,
|
||||||
peer: NodeId,
|
peer: EndpointId,
|
||||||
relay_server: Option<&str>,
|
relay_server: Option<&str>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
ctx.sql
|
ctx.sql
|
||||||
@@ -368,11 +382,11 @@ pub async fn add_gossip_peer_from_header(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let node_addr =
|
let node_addr =
|
||||||
serde_json::from_str::<NodeAddr>(node_addr).context("Failed to parse node address")?;
|
serde_json::from_str::<EndpointAddr>(node_addr).context("Failed to parse node address")?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
"Adding iroh peer with node id {} to the topic of {instance_id}.", node_addr.node_id
|
"Adding iroh peer with node id {} to the topic of {instance_id}.", node_addr.id
|
||||||
);
|
);
|
||||||
|
|
||||||
context.emit_event(EventType::WebxdcRealtimeAdvertisementReceived {
|
context.emit_event(EventType::WebxdcRealtimeAdvertisementReceived {
|
||||||
@@ -387,8 +401,8 @@ pub async fn add_gossip_peer_from_header(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let node_id = node_addr.node_id;
|
let node_id = node_addr.id;
|
||||||
let relay_server = node_addr.relay_url().map(|relay| relay.as_str());
|
let relay_server = node_addr.relay_urls().map(|relay| relay.as_str()).next();
|
||||||
iroh_add_peer_for_topic(context, instance_id, topic, node_id, relay_server).await?;
|
iroh_add_peer_for_topic(context, instance_id, topic, node_id, relay_server).await?;
|
||||||
|
|
||||||
context.maybe_add_gossip_peer(topic, node_addr).await?;
|
context.maybe_add_gossip_peer(topic, node_addr).await?;
|
||||||
@@ -406,8 +420,8 @@ pub(crate) async fn insert_topic_stub(ctx: &Context, msg_id: MsgId, topic: Topic
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a list of [NodeAddr]s for one webxdc.
|
/// Get a list of [EndpointAddr]s for one webxdc.
|
||||||
async fn get_iroh_gossip_peers(ctx: &Context, msg_id: MsgId) -> Result<Vec<NodeAddr>> {
|
async fn get_iroh_gossip_peers(ctx: &Context, msg_id: MsgId) -> Result<Vec<EndpointAddr>> {
|
||||||
ctx.sql
|
ctx.sql
|
||||||
.query_map(
|
.query_map(
|
||||||
"SELECT public_key, relay_server FROM iroh_gossip_peers WHERE msg_id = ? AND public_key != ?",
|
"SELECT public_key, relay_server FROM iroh_gossip_peers WHERE msg_id = ? AND public_key != ?",
|
||||||
@@ -420,11 +434,11 @@ async fn get_iroh_gossip_peers(ctx: &Context, msg_id: MsgId) -> Result<Vec<NodeA
|
|||||||
|g| {
|
|g| {
|
||||||
g.map(|data| {
|
g.map(|data| {
|
||||||
let (key, server) = data?;
|
let (key, server) = data?;
|
||||||
let server = server.map(|data| Ok::<_, url::ParseError>(RelayUrl::from(Url::parse(&data)?))).transpose()?;
|
let server: Option<TransportAddr> = server.map(|data| Ok::<_, url::ParseError>(TransportAddr::Relay(RelayUrl::from(Url::parse(&data)?)))).transpose()?;
|
||||||
let id = NodeId::from_bytes(&key.try_into()
|
let id = EndpointId::from_bytes(&key.try_into()
|
||||||
.map_err(|_| anyhow!("Can't convert sql data to [u8; 32]"))?)?;
|
.map_err(|_| anyhow!("Can't convert sql data to [u8; 32]"))?)?;
|
||||||
Ok::<_, anyhow::Error>(NodeAddr::from_parts(
|
Ok::<_, anyhow::Error>(EndpointAddr::from_parts(
|
||||||
id, server, vec![]
|
id, server
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect::<std::result::Result<Vec<_>, _>>()
|
.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
@@ -623,7 +637,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|addr| addr.node_id)
|
.map(|addr| addr.id)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -636,7 +650,7 @@ mod tests {
|
|||||||
.get_node_addr()
|
.get_node_addr()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.node_id
|
.id
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -699,7 +713,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|addr| addr.node_id)
|
.map(|addr| addr.id)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -711,7 +725,7 @@ mod tests {
|
|||||||
.get_node_addr()
|
.get_node_addr()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.node_id
|
.id
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -789,7 +803,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|addr| addr.node_id)
|
.map(|addr| addr.id)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -802,7 +816,7 @@ mod tests {
|
|||||||
.get_node_addr()
|
.get_node_addr()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.node_id
|
.id
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ pub enum Qr {
|
|||||||
/// Provides a backup that can be retrieved using iroh-net based backup transfer protocol.
|
/// Provides a backup that can be retrieved using iroh-net based backup transfer protocol.
|
||||||
Backup2 {
|
Backup2 {
|
||||||
/// Iroh node address.
|
/// Iroh node address.
|
||||||
node_addr: iroh::NodeAddr,
|
node_addr: iroh::EndpointAddr,
|
||||||
|
|
||||||
/// Authentication token.
|
/// Authentication token.
|
||||||
auth_token: String,
|
auth_token: String,
|
||||||
@@ -629,7 +629,7 @@ fn decode_backup2(qr: &str) -> Result<Qr> {
|
|||||||
.split_once('&')
|
.split_once('&')
|
||||||
.context("Backup QR code has no separator")?;
|
.context("Backup QR code has no separator")?;
|
||||||
let auth_token = auth_token.to_string();
|
let auth_token = auth_token.to_string();
|
||||||
let node_addr = serde_json::from_str::<iroh::NodeAddr>(node_addr)
|
let node_addr = serde_json::from_str::<iroh::EndpointAddr>(node_addr)
|
||||||
.context("Invalid node addr in backup QR code")?;
|
.context("Invalid node addr in backup QR code")?;
|
||||||
|
|
||||||
Ok(Qr::Backup2 {
|
Ok(Qr::Backup2 {
|
||||||
|
|||||||
@@ -865,25 +865,3 @@ async fn test_decode_socks5() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that `DCBACKUP2` QR code does not fail to deserialize
|
|
||||||
/// because iroh changes the format of `NodeAddr`
|
|
||||||
/// as happened between iroh 0.29 and iroh 0.30 before.
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
||||||
async fn test_decode_backup() -> Result<()> {
|
|
||||||
let ctx = TestContext::new().await;
|
|
||||||
|
|
||||||
let qr = check_qr(&ctx, r#"DCBACKUP2:TWSv6ZjDPa5eoxkocj7xMi8r&{"node_id":"9afc1ea5b4f543e5cdd7b7a21cd26aee7c0b1e1c2af26790896fbd8932a06e1e","relay_url":null,"direct_addresses":["192.168.1.10:12345"]}"#).await?;
|
|
||||||
assert!(matches!(qr, Qr::Backup2 { .. }));
|
|
||||||
|
|
||||||
let qr = check_qr(&ctx, r#"DCBACKUP2:AIvFjRFBt_aMiisSZ8P33JqY&{"node_id":"buzkyd4x76w66qtanjk5fm6ikeuo4quletajowsl3a3p7l6j23pa","info":{"relay_url":null,"direct_addresses":["192.168.1.5:12345"]}}"#).await?;
|
|
||||||
assert!(matches!(qr, Qr::Backup2 { .. }));
|
|
||||||
|
|
||||||
let qr = check_qr(&ctx, r#"DCBACKUP9:from-the-future"#).await?;
|
|
||||||
assert!(matches!(qr, Qr::BackupTooNew { .. }));
|
|
||||||
|
|
||||||
let qr = check_qr(&ctx, r#"DCBACKUP99:far-from-the-future"#).await?;
|
|
||||||
assert!(matches!(qr, Qr::BackupTooNew { .. }));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user