mirror of
https://github.com/chatmail/core.git
synced 2026-05-15 12:56:30 +03:00
Compare commits
12 Commits
link2xt/ca
...
link2xt/ir
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01492e1a3c | ||
|
|
7ec51dfe27 | ||
|
|
805e402c08 | ||
|
|
17c1df0fa4 | ||
|
|
8c10c9daac | ||
|
|
68bf744971 | ||
|
|
5880a6474c | ||
|
|
2e5ba7cae3 | ||
|
|
4bd898290a | ||
|
|
f83d70b05b | ||
|
|
3c8dc6efd2 | ||
|
|
44f0edbe47 |
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -23,7 +23,7 @@ env:
|
||||
RUST_VERSION: 1.95.0
|
||||
|
||||
# Minimum Supported Rust Version
|
||||
MSRV: 1.89.0
|
||||
MSRV: 1.91.0
|
||||
|
||||
jobs:
|
||||
lint_rust:
|
||||
@@ -43,7 +43,6 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Run clippy
|
||||
@@ -97,7 +96,6 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: Rustdoc
|
||||
run: cargo doc --document-private-items --no-deps
|
||||
|
||||
@@ -143,7 +141,6 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@cca35edeb1d01366c2843b68fc3ca441446d73d3
|
||||
@@ -180,10 +177,9 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Build C library
|
||||
run: cargo build -p deltachat_ffi --locked
|
||||
run: cargo build -p deltachat_ffi
|
||||
|
||||
- name: Upload C library
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -209,10 +205,9 @@ jobs:
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Build deltachat-rpc-server
|
||||
run: cargo build -p deltachat-rpc-server --locked
|
||||
run: cargo build -p deltachat-rpc-server
|
||||
|
||||
- name: Upload deltachat-rpc-server
|
||||
uses: actions/upload-artifact@v7
|
||||
|
||||
5
.github/workflows/jsonrpc.yml
vendored
5
.github/workflows/jsonrpc.yml
vendored
@@ -25,10 +25,7 @@ jobs:
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Add Rust cache
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
- name: npm install
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install
|
||||
|
||||
2587
Cargo.lock
generated
2587
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ name = "deltachat"
|
||||
version = "2.50.0-dev"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.89"
|
||||
rust-version = "1.91"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[profile.dev]
|
||||
@@ -66,8 +66,8 @@ humansize = "2"
|
||||
hyper = "1"
|
||||
hyper-util = "0.1.16"
|
||||
image = { version = "0.25.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh-gossip = { version = "0.35", default-features = false, features = ["net"] }
|
||||
iroh = { version = "0.35", default-features = false }
|
||||
iroh-gossip = { version = "0.99.0", default-features = false, features = ["net"] }
|
||||
iroh = { version = "1.0.0-rc.0", default-features = false, features = ["tls-ring"] }
|
||||
kamadak-exif = "0.6.1"
|
||||
libc = { workspace = true }
|
||||
mail-builder = { version = "0.4.4", default-features = false }
|
||||
@@ -101,7 +101,7 @@ tagger = "4.3.4"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = { workspace = true }
|
||||
tokio-io-timeout = "1.2.1"
|
||||
tokio-rustls = { version = "0.26.2", default-features = false }
|
||||
tokio-rustls = { version = "0.26.2", default-features = false, features = ["ring"] }
|
||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||
astral-tokio-tar = { version = "0.6.1", default-features = false }
|
||||
tokio-util = { workspace = true }
|
||||
|
||||
@@ -29,7 +29,7 @@ $ pip install .
|
||||
|
||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||
2. Install tox `pip install -U tox`
|
||||
3. Run `CHATMAIL_DOMAIN=ci-chatmail.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||
3. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||
|
||||
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ futures-lite = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tokio = { workspace = true, features = ["io-std"] }
|
||||
tokio = { workspace = true, features = ["io-std", "signal"] }
|
||||
tokio-util = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
yerpc = { workspace = true, features = ["anyhow_expose", "openrpc"] }
|
||||
|
||||
78
deny.toml
78
deny.toml
@@ -7,44 +7,9 @@ ignore = [
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2023-0071>
|
||||
"RUSTSEC-2023-0071",
|
||||
|
||||
# Unmaintained instant
|
||||
"RUSTSEC-2024-0384",
|
||||
|
||||
# Unmaintained paste
|
||||
"RUSTSEC-2024-0436",
|
||||
|
||||
# Unmaintained rustls-pemfile
|
||||
# It is a transitive dependency of iroh 0.35.0,
|
||||
# this should be fixed by upgrading to iroh 1.0 once it is released.
|
||||
"RUSTSEC-2025-0134",
|
||||
|
||||
# rustls-webpki v0.102.8
|
||||
# We cannot upgrade to >=0.103.10 because
|
||||
# it is a transitive dependency of iroh 0.35.0
|
||||
# which depends on ^0.102.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0049>
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0098>
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0099>
|
||||
"RUSTSEC-2026-0049",
|
||||
"RUSTSEC-2026-0098",
|
||||
"RUSTSEC-2026-0099",
|
||||
|
||||
# Panic in CRL signature checks.
|
||||
# We do not check CRL and cannot update rustls-webpki 0.102.8
|
||||
# which is a dependency of iroh 0.35.0.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0104>
|
||||
"RUSTSEC-2026-0104",
|
||||
|
||||
# hickory-proto 0.25.2 unbounded loop in DNSSEC code.
|
||||
# Dependency of iroh 0.35.0, cannot be updated as of 2026-05-02.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0118>
|
||||
"RUSTSEC-2026-0118",
|
||||
|
||||
# hickory-proto 0.25.2 quadratic complexity issue.
|
||||
# Dependency of iroh 0.35.0, cannot be updated as of 2026-05-02.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0119>
|
||||
"RUSTSEC-2026-0119",
|
||||
|
||||
# Timing side channel in ml-dsa dependency of rPGP.
|
||||
# We enable PQC for encryption rather than signatures.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2025-0144>
|
||||
@@ -58,35 +23,54 @@ ignore = [
|
||||
# Please keep this list alphabetically sorted.
|
||||
skip = [
|
||||
{ name = "async-channel", version = "1.9.0" },
|
||||
{ name = "bitflags", version = "1.3.2" },
|
||||
{ name = "constant_time_eq", version = "0.3.1" },
|
||||
{ name = "base16ct", version = "0.2.0" },
|
||||
{ name = "block-buffer", version = "0.10.4" },
|
||||
{ name = "chacha20", version = "0.9.1" },
|
||||
{ name = "const-oid", version = "0.9.6" },
|
||||
{ name = "convert_case", version = "0.5.0" },
|
||||
{ name = "core-foundation", version = "0.9.4" },
|
||||
{ name = "cpufeatures", version = "0.2.17" },
|
||||
{ name = "derive_more-impl", version = "1.0.0" },
|
||||
{ name = "derive_more", version = "1.0.0" },
|
||||
{ name = "crypto-common", version = "0.1.6" },
|
||||
{ name = "curve25519-dalek", version = "4.1.3" },
|
||||
{ name = "der", version = "0.7.9" },
|
||||
{ name = "digest", version = "0.10.7" },
|
||||
{ name = "ed25519-dalek", version = "2.1.1" },
|
||||
{ name = "ed25519", version = "2.2.3" },
|
||||
{ name = "event-listener", version = "2.5.3" },
|
||||
{ name = "fiat-crypto", version = "0.2.9" },
|
||||
{ name = "foldhash", version = "0.1.5" },
|
||||
{ name = "getrandom", version = "0.2.12" },
|
||||
{ name = "heck", version = "0.4.1" },
|
||||
{ name = "http", version = "0.2.12" },
|
||||
{ name = "getrandom", version = "0.3.3" },
|
||||
{ name = "hashbrown", version = "0.15.4" },
|
||||
{ name = "hybrid-array", version = "0.2.3" },
|
||||
{ name = "hybrid-array", version = "0.3.1" },
|
||||
{ name = "linux-raw-sys", version = "0.4.14" },
|
||||
{ name = "lru", version = "0.12.5" },
|
||||
{ name = "netlink-packet-route", version = "0.17.1" },
|
||||
{ name = "netlink-packet-route", version = "0.29.0" },
|
||||
{ name = "nom", version = "7.1.3" },
|
||||
{ name = "openssl-probe", version = "0.1.6" },
|
||||
{ name = "pem-rfc7468", version = "0.7.0" },
|
||||
{ name = "pkcs8", version = "0.10.2" },
|
||||
{ name = "rand_chacha", version = "0.3.1" },
|
||||
{ name = "rand_core", version = "0.6.4" },
|
||||
{ name = "rand_core", version = "0.9.3" },
|
||||
{ name = "rand", version = "0.8.5" },
|
||||
{ name = "rand", version = "0.9.4" },
|
||||
{ name = "r-efi", version = "5.2.0" },
|
||||
{ name = "rustix", version = "0.38.44" },
|
||||
{ name = "rustls-webpki", version = "0.102.8" },
|
||||
{ name = "security-framework", version = "2.11.1" },
|
||||
{ name = "serdect", version = "0.2.0" },
|
||||
{ name = "serdect", version = "0.3.0" },
|
||||
{ name = "sha2", version = "0.10.9"},
|
||||
{ name = "signature", version = "2.2.0"},
|
||||
{ name = "socket2", version = "0.5.9" },
|
||||
{ name = "spin", version = "0.9.8" },
|
||||
{ name = "strum_macros", version = "0.26.2" },
|
||||
{ name = "strum", version = "0.26.2" },
|
||||
{ name = "spki", version = "0.7.3"},
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
{ name = "thiserror-impl", version = "1.0.69" },
|
||||
{ name = "thiserror", version = "1.0.69" },
|
||||
{ name = "toml_datetime", version = "0.6.11" },
|
||||
{ name = "wasi", version = "0.11.0+wasi-snapshot-preview1" },
|
||||
{ name = "webpki-roots", version = "0.26.8" },
|
||||
{ name = "windows" },
|
||||
{ name = "windows_aarch64_gnullvm" },
|
||||
{ name = "windows_aarch64_msvc" },
|
||||
@@ -103,6 +87,7 @@ skip = [
|
||||
{ name = "windows_x86_64_gnu" },
|
||||
{ name = "windows_x86_64_gnullvm" },
|
||||
{ name = "windows_x86_64_msvc" },
|
||||
{ name = "wit-bindgen", version = "0.51.0" },
|
||||
]
|
||||
|
||||
|
||||
@@ -114,6 +99,7 @@ allow = [
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0", # Boost Software License 1.0
|
||||
"CC0-1.0",
|
||||
"CDLA-Permissive-2.0",
|
||||
"ISC",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
|
||||
@@ -69,7 +69,7 @@ pub struct BackupProvider {
|
||||
_endpoint: Endpoint,
|
||||
|
||||
/// iroh address.
|
||||
node_addr: iroh::NodeAddr,
|
||||
node_addr: iroh::EndpointAddr,
|
||||
|
||||
/// Authentication token that should be submitted
|
||||
/// to retrieve the backup.
|
||||
@@ -95,13 +95,12 @@ impl BackupProvider {
|
||||
/// [`Accounts::stop_io`]: crate::accounts::Accounts::stop_io
|
||||
pub async fn prepare(context: &Context) -> Result<Self> {
|
||||
let relay_mode = RelayMode::Disabled;
|
||||
let endpoint = Endpoint::builder()
|
||||
.tls_x509() // For compatibility with iroh <0.34.0
|
||||
let endpoint = Endpoint::builder(iroh::endpoint::presets::Minimal)
|
||||
.alpns(vec![BACKUP_ALPN.to_vec()])
|
||||
.relay_mode(relay_mode)
|
||||
.bind()
|
||||
.await?;
|
||||
let node_addr = endpoint.node_addr().await?;
|
||||
let node_addr = endpoint.addr();
|
||||
|
||||
// Acquire global "ongoing" mutex.
|
||||
let cancel_token = context.alloc_ongoing().await?;
|
||||
@@ -168,7 +167,7 @@ impl BackupProvider {
|
||||
|
||||
async fn handle_connection(
|
||||
context: Context,
|
||||
conn: iroh::endpoint::Connecting,
|
||||
conn: iroh::endpoint::Accepting,
|
||||
auth_token: String,
|
||||
dbfile: Arc<TempPathGuard>,
|
||||
) -> Result<()> {
|
||||
@@ -299,13 +298,12 @@ impl Future for BackupProvider {
|
||||
|
||||
pub async fn get_backup2(
|
||||
context: &Context,
|
||||
node_addr: iroh::NodeAddr,
|
||||
node_addr: iroh::EndpointAddr,
|
||||
auth_token: String,
|
||||
) -> Result<()> {
|
||||
let relay_mode = RelayMode::Disabled;
|
||||
|
||||
let endpoint = Endpoint::builder()
|
||||
.tls_x509() // For compatibility with iroh <0.34.0
|
||||
let endpoint = Endpoint::builder(iroh::endpoint::presets::Minimal)
|
||||
.relay_mode(relay_mode)
|
||||
.bind()
|
||||
.await?;
|
||||
@@ -353,7 +351,7 @@ pub async fn get_backup2(
|
||||
/// This is a long running operation which will return only when completed.
|
||||
///
|
||||
/// Using [`Qr`] as argument is a bit odd as it only accepts specific variant of it. It
|
||||
/// does avoid having [`iroh::NodeAddr`] in the primary API however, without
|
||||
/// does avoid having [`iroh::EndpointAddr`] in the primary API however, without
|
||||
/// having to revert to untyped bytes.
|
||||
pub async fn get_backup(context: &Context, qr: Qr) -> Result<()> {
|
||||
match qr {
|
||||
|
||||
@@ -1590,7 +1590,7 @@ impl MimeFactory {
|
||||
|
||||
// We should not send `null` as relay URL
|
||||
// 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((
|
||||
HeaderDef::IrohNodeAddr.into(),
|
||||
mail_builder::headers::text::Text::new(serde_json::to_string(&node_addr)?)
|
||||
@@ -1939,6 +1939,11 @@ pub(crate) fn render_outer_message(
|
||||
/// Takes the encrypted part, wraps it in a MimePart,
|
||||
/// and sets the appropriate Content-Type for the outer message
|
||||
pub(crate) fn wrap_encrypted_part(encrypted: String) -> MimePart<'static> {
|
||||
// XXX: additional newline is needed
|
||||
// to pass filtermail at
|
||||
// <https://github.com/deltachat/chatmail/blob/4d915f9800435bf13057d41af8d708abd34dbfa8/chatmaild/src/chatmaild/filtermail.py#L84-L86>:
|
||||
let encrypted = encrypted + "\n";
|
||||
|
||||
MimePart::new(
|
||||
"multipart/encrypted; protocol=\"application/pgp-encrypted\"",
|
||||
vec![
|
||||
|
||||
16
src/net.rs
16
src/net.rs
@@ -109,8 +109,8 @@ pub(crate) async fn connect_tcp_inner(
|
||||
) -> Result<Pin<Box<TimeoutStream<TcpStream>>>> {
|
||||
let tcp_stream = timeout(TIMEOUT, TcpStream::connect(addr))
|
||||
.await
|
||||
.with_context(|| format!("Connection to {addr} timed out"))?
|
||||
.with_context(|| format!("Connection to {addr} failed"))?;
|
||||
.context("Connection timeout")?
|
||||
.context("Connection failure")?;
|
||||
|
||||
// Disable Nagle's algorithm.
|
||||
tcp_stream.set_nodelay(true)?;
|
||||
@@ -180,7 +180,7 @@ where
|
||||
delay_set.spawn(tokio::time::sleep(delay));
|
||||
}
|
||||
|
||||
let mut all_errors = Vec::new();
|
||||
let mut first_error = None;
|
||||
|
||||
let res = loop {
|
||||
if let Some(fut) = futures.next() {
|
||||
@@ -200,7 +200,7 @@ where
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
// Some connection attempt failed.
|
||||
all_errors.push(err);
|
||||
first_error.get_or_insert(err);
|
||||
}
|
||||
Err(err) => {
|
||||
break Err(err);
|
||||
@@ -211,11 +211,9 @@ where
|
||||
// Out of connection attempts.
|
||||
//
|
||||
// Break out of the loop and return error.
|
||||
break if all_errors.is_empty() {
|
||||
Err(format_err!("No connection attempts were made"))
|
||||
} else {
|
||||
Err(format_err!("All connection attempts failed: {}", all_errors.into_iter().map(|err| format!("{err:#}")).collect::<Vec<String>>().join("; ")))
|
||||
};
|
||||
break Err(
|
||||
first_error.unwrap_or_else(|| format_err!("No connection attempts were made"))
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,18 +19,22 @@
|
||||
//! 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.
|
||||
//! 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()`
|
||||
//! and `joinRealtimeChannel().send()` just like the other peers.
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use data_encoding::BASE32_NOPAD;
|
||||
use futures_lite::StreamExt;
|
||||
use iroh::{Endpoint, NodeAddr, NodeId, PublicKey, RelayMode, RelayUrl, SecretKey};
|
||||
use iroh_gossip::net::{Event, GOSSIP_ALPN, Gossip, GossipEvent, JoinOptions};
|
||||
use iroh::address_lookup::MemoryLookup;
|
||||
use iroh::{
|
||||
Endpoint, EndpointAddr, EndpointId, PublicKey, RelayMode, RelayUrl, SecretKey, TransportAddr,
|
||||
};
|
||||
use iroh_gossip::api::{Event as GossipEvent, GossipReceiver, GossipSender, JoinOptions};
|
||||
use iroh_gossip::net::{GOSSIP_ALPN, Gossip};
|
||||
use iroh_gossip::proto::TopicId;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use tokio::sync::{RwLock, oneshot};
|
||||
use tokio::task::JoinHandle;
|
||||
@@ -54,6 +58,9 @@ pub struct Iroh {
|
||||
/// Iroh router needed for Iroh peer channels.
|
||||
pub(crate) router: iroh::protocol::Router,
|
||||
|
||||
/// Address lookup, called "Discovery service" before Iroh 0.96.0.
|
||||
pub(crate) address_lookup: MemoryLookup,
|
||||
|
||||
/// [Gossip] needed for Iroh peer channels.
|
||||
pub(crate) gossip: Gossip,
|
||||
|
||||
@@ -105,7 +112,7 @@ impl Iroh {
|
||||
}
|
||||
|
||||
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!(
|
||||
ctx,
|
||||
@@ -115,7 +122,7 @@ impl Iroh {
|
||||
// Inform iroh of potentially new node addresses
|
||||
for node_addr in &peers {
|
||||
if !node_addr.is_empty() {
|
||||
self.router.endpoint().add_node_addr(node_addr.clone())?;
|
||||
self.address_lookup.add_endpoint_info(node_addr.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +131,7 @@ impl Iroh {
|
||||
let (gossip_sender, gossip_receiver) = self
|
||||
.gossip
|
||||
.subscribe_with_opts(topic, JoinOptions::with_bootstrap(node_ids))
|
||||
.await?
|
||||
.split();
|
||||
|
||||
let ctx = ctx.clone();
|
||||
@@ -139,10 +147,10 @@ impl Iroh {
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
self.router.endpoint().add_node_addr(peer.clone())?;
|
||||
self.gossip.subscribe(topic, vec![peer.node_id])?;
|
||||
self.address_lookup.add_endpoint_info(peer.clone());
|
||||
self.gossip.subscribe(topic, vec![peer.id]).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -184,16 +192,20 @@ impl Iroh {
|
||||
*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
|
||||
/// as it is the only way to reach the node
|
||||
/// without global discovery mechanisms.
|
||||
pub(crate) async fn get_node_addr(&self) -> Result<NodeAddr> {
|
||||
let mut addr = self.router.endpoint().node_addr().await?;
|
||||
addr.direct_addresses = BTreeSet::new();
|
||||
debug_assert!(addr.relay_url().is_some());
|
||||
Ok(addr)
|
||||
pub(crate) async fn get_node_addr(&self) -> Result<EndpointAddr> {
|
||||
// Wait until home relay connection is established.
|
||||
self.router.endpoint().online().await;
|
||||
let mut endpoint_addr = self.router.endpoint().addr();
|
||||
endpoint_addr
|
||||
.addrs
|
||||
.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.
|
||||
@@ -219,11 +231,11 @@ pub(crate) struct ChannelState {
|
||||
/// The subscribe loop handle.
|
||||
subscribe_loop: JoinHandle<()>,
|
||||
|
||||
sender: iroh_gossip::net::GossipSender,
|
||||
sender: GossipSender,
|
||||
}
|
||||
|
||||
impl ChannelState {
|
||||
fn new(subscribe_loop: JoinHandle<()>, sender: iroh_gossip::net::GossipSender) -> Self {
|
||||
fn new(subscribe_loop: JoinHandle<()>, sender: GossipSender) -> Self {
|
||||
Self {
|
||||
subscribe_loop,
|
||||
sender,
|
||||
@@ -235,7 +247,7 @@ impl Context {
|
||||
/// Create iroh endpoint and gossip.
|
||||
async fn init_peer_channels(&self) -> Result<Iroh> {
|
||||
info!(self, "Initializing peer channels.");
|
||||
let secret_key = SecretKey::generate(rand_old::rngs::OsRng);
|
||||
let secret_key = SecretKey::generate();
|
||||
let public_key = secret_key.public();
|
||||
|
||||
let relay_mode = if let Some(relay_url) = self
|
||||
@@ -252,8 +264,9 @@ impl Context {
|
||||
RelayMode::Default
|
||||
};
|
||||
|
||||
let endpoint = Endpoint::builder()
|
||||
.tls_x509() // For compatibility with iroh <0.34.0
|
||||
let address_lookup = MemoryLookup::new();
|
||||
let endpoint = Endpoint::builder(iroh::endpoint::presets::Minimal)
|
||||
.address_lookup(address_lookup.clone())
|
||||
.secret_key(secret_key)
|
||||
.alpns(vec![GOSSIP_ALPN.to_vec()])
|
||||
.relay_mode(relay_mode)
|
||||
@@ -267,8 +280,7 @@ impl Context {
|
||||
|
||||
let gossip = Gossip::builder()
|
||||
.max_message_size(128 * 1024)
|
||||
.spawn(endpoint.clone())
|
||||
.await?;
|
||||
.spawn(endpoint.clone());
|
||||
|
||||
let router = iroh::protocol::Router::builder(endpoint)
|
||||
.accept(GOSSIP_ALPN, gossip.clone())
|
||||
@@ -276,6 +288,7 @@ impl Context {
|
||||
|
||||
Ok(Iroh {
|
||||
router,
|
||||
address_lookup,
|
||||
gossip,
|
||||
sequence_numbers: Mutex::new(HashMap::new()),
|
||||
iroh_channels: RwLock::new(HashMap::new()),
|
||||
@@ -322,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 {
|
||||
info!(
|
||||
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?;
|
||||
}
|
||||
@@ -334,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(
|
||||
ctx: &Context,
|
||||
msg_id: MsgId,
|
||||
topic: TopicId,
|
||||
peer: NodeId,
|
||||
peer: EndpointId,
|
||||
relay_server: Option<&str>,
|
||||
) -> Result<()> {
|
||||
ctx.sql
|
||||
@@ -365,11 +382,11 @@ pub async fn add_gossip_peer_from_header(
|
||||
}
|
||||
|
||||
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!(
|
||||
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 {
|
||||
@@ -384,8 +401,8 @@ pub async fn add_gossip_peer_from_header(
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let node_id = node_addr.node_id;
|
||||
let relay_server = node_addr.relay_url().map(|relay| relay.as_str());
|
||||
let node_id = node_addr.id;
|
||||
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?;
|
||||
|
||||
context.maybe_add_gossip_peer(topic, node_addr).await?;
|
||||
@@ -403,8 +420,8 @@ pub(crate) async fn insert_topic_stub(ctx: &Context, msg_id: MsgId, topic: Topic
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a list of [NodeAddr]s for one webxdc.
|
||||
async fn get_iroh_gossip_peers(ctx: &Context, msg_id: MsgId) -> Result<Vec<NodeAddr>> {
|
||||
/// Get a list of [EndpointAddr]s for one webxdc.
|
||||
async fn get_iroh_gossip_peers(ctx: &Context, msg_id: MsgId) -> Result<Vec<EndpointAddr>> {
|
||||
ctx.sql
|
||||
.query_map(
|
||||
"SELECT public_key, relay_server FROM iroh_gossip_peers WHERE msg_id = ? AND public_key != ?",
|
||||
@@ -417,11 +434,11 @@ async fn get_iroh_gossip_peers(ctx: &Context, msg_id: MsgId) -> Result<Vec<NodeA
|
||||
|g| {
|
||||
g.map(|data| {
|
||||
let (key, server) = data?;
|
||||
let server = server.map(|data| Ok::<_, url::ParseError>(RelayUrl::from(Url::parse(&data)?))).transpose()?;
|
||||
let id = NodeId::from_bytes(&key.try_into()
|
||||
let server: Option<TransportAddr> = server.map(|data| Ok::<_, url::ParseError>(TransportAddr::Relay(RelayUrl::from(Url::parse(&data)?)))).transpose()?;
|
||||
let id = EndpointId::from_bytes(&key.try_into()
|
||||
.map_err(|_| anyhow!("Can't convert sql data to [u8; 32]"))?)?;
|
||||
Ok::<_, anyhow::Error>(NodeAddr::from_parts(
|
||||
id, server, vec![]
|
||||
Ok::<_, anyhow::Error>(EndpointAddr::from_parts(
|
||||
id, server
|
||||
))
|
||||
})
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
@@ -536,45 +553,39 @@ pub(crate) fn iroh_topic_from_str(topic: &str) -> Result<TopicId> {
|
||||
#[expect(clippy::arithmetic_side_effects)]
|
||||
async fn subscribe_loop(
|
||||
context: &Context,
|
||||
mut stream: iroh_gossip::net::GossipReceiver,
|
||||
mut stream: GossipReceiver,
|
||||
topic: TopicId,
|
||||
msg_id: MsgId,
|
||||
join_tx: oneshot::Sender<()>,
|
||||
) -> Result<()> {
|
||||
let mut join_tx = Some(join_tx);
|
||||
stream.joined().await?;
|
||||
// Try to notify that at least one peer joined,
|
||||
// but ignore the error if receiver is dropped and nobody listens.
|
||||
join_tx.send(()).ok();
|
||||
|
||||
for node in stream.neighbors() {
|
||||
iroh_add_peer_for_topic(context, msg_id, topic, node, None).await?;
|
||||
}
|
||||
|
||||
while let Some(event) = stream.try_next().await? {
|
||||
match event {
|
||||
Event::Gossip(event) => match event {
|
||||
GossipEvent::Joined(nodes) => {
|
||||
if let Some(join_tx) = join_tx.take() {
|
||||
// Try to notify that at least one peer joined,
|
||||
// but ignore the error if receiver is dropped and nobody listens.
|
||||
join_tx.send(()).ok();
|
||||
}
|
||||
|
||||
for node in nodes {
|
||||
iroh_add_peer_for_topic(context, msg_id, topic, node, None).await?;
|
||||
}
|
||||
}
|
||||
GossipEvent::NeighborUp(node) => {
|
||||
info!(context, "IROH_REALTIME: NeighborUp: {}", node.to_string());
|
||||
iroh_add_peer_for_topic(context, msg_id, topic, node, None).await?;
|
||||
}
|
||||
GossipEvent::NeighborDown(_node) => {}
|
||||
GossipEvent::Received(message) => {
|
||||
info!(context, "IROH_REALTIME: Received realtime data");
|
||||
context.emit_event(EventType::WebxdcRealtimeData {
|
||||
msg_id,
|
||||
data: message
|
||||
.content
|
||||
.get(0..message.content.len() - 4 - PUBLIC_KEY_LENGTH)
|
||||
.context("too few bytes in iroh message")?
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
},
|
||||
Event::Lagged => {
|
||||
GossipEvent::NeighborUp(node) => {
|
||||
info!(context, "IROH_REALTIME: NeighborUp: {}", node.to_string());
|
||||
iroh_add_peer_for_topic(context, msg_id, topic, node, None).await?;
|
||||
}
|
||||
GossipEvent::NeighborDown(_node) => {}
|
||||
GossipEvent::Received(message) => {
|
||||
info!(context, "IROH_REALTIME: Received realtime data");
|
||||
context.emit_event(EventType::WebxdcRealtimeData {
|
||||
msg_id,
|
||||
data: message
|
||||
.content
|
||||
.get(0..message.content.len() - 4 - PUBLIC_KEY_LENGTH)
|
||||
.context("too few bytes in iroh message")?
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
GossipEvent::Lagged => {
|
||||
warn!(context, "Gossip lost some messages");
|
||||
}
|
||||
};
|
||||
@@ -639,7 +650,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|addr| addr.node_id)
|
||||
.map(|addr| addr.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
@@ -652,7 +663,7 @@ mod tests {
|
||||
.get_node_addr()
|
||||
.await
|
||||
.unwrap()
|
||||
.node_id
|
||||
.id
|
||||
]
|
||||
);
|
||||
|
||||
@@ -715,7 +726,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|addr| addr.node_id)
|
||||
.map(|addr| addr.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
@@ -727,7 +738,7 @@ mod tests {
|
||||
.get_node_addr()
|
||||
.await
|
||||
.unwrap()
|
||||
.node_id
|
||||
.id
|
||||
]
|
||||
);
|
||||
|
||||
@@ -805,7 +816,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|addr| addr.node_id)
|
||||
.map(|addr| addr.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
@@ -818,7 +829,7 @@ mod tests {
|
||||
.get_node_addr()
|
||||
.await
|
||||
.unwrap()
|
||||
.node_id
|
||||
.id
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ pub enum Qr {
|
||||
/// Provides a backup that can be retrieved using iroh-net based backup transfer protocol.
|
||||
Backup2 {
|
||||
/// Iroh node address.
|
||||
node_addr: iroh::NodeAddr,
|
||||
node_addr: iroh::EndpointAddr,
|
||||
|
||||
/// Authentication token.
|
||||
auth_token: String,
|
||||
@@ -781,7 +781,7 @@ fn decode_backup2(qr: &str) -> Result<Qr> {
|
||||
.split_once('&')
|
||||
.context("Backup QR code has no separator")?;
|
||||
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")?;
|
||||
|
||||
Ok(Qr::Backup2 {
|
||||
|
||||
@@ -955,25 +955,3 @@ async fn test_decode_socks5() -> Result<()> {
|
||||
|
||||
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