From a216c65c48f6b662ce083873d6e344fbee1c556b Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 31 Oct 2026 06:50:07 +0100 Subject: pong carries time --- src/transport/identity.rs | 5 ++++- src/transport/mod.rs | 14 +++++++++----- src/transport/paths.rs | 19 ++++++++++++++++--- src/transport/wire.rs | 39 ++++++++++++++++++++++----------------- 4 files changed, 51 insertions(+), 26 deletions(-) (limited to 'src/transport') diff --git a/src/transport/identity.rs b/src/transport/identity.rs index 7ce8832..eb7bb93 100644 --- a/src/transport/identity.rs +++ b/src/transport/identity.rs @@ -244,12 +244,14 @@ impl Book { impl IdentityTransport { /// Over `transport`, whose addresses have `config.scheme`, and `config.more`. - /// `firewall`: the debug firewall, if any, which paths applies by address. + /// `firewall`: the debug firewall, if any, which paths applies by address; `clock`: + /// the node's wall clock (`Paths::new`). pub(crate) fn new( transport: Arc, config: IdentityConfig, traffic: Arc, firewall: Option>, + clock: Arc, ) -> Self { let node_id = config.key.id(); let shared = Arc::new_cyclic(|shared: &std::sync::Weak| { @@ -261,6 +263,7 @@ impl IdentityTransport { .collect(), Some(Arc::clone(&traffic)), firewall.is_some().then_some(gate), + clock, )), firewall, key: config.key, diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 903dd07..9a82cbe 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -219,8 +219,12 @@ pub(crate) enum ParsedMessage { Me(IdentityProof), /// Asks for a PONG with this nonce, over the same path (`transport::paths`). Ping(u64), - /// Answers a PING. - Pong(u64), + /// Answers a PING: its nonce, and when it was answered by the answerer's clock + /// (`at`), for comparing clocks (`transport::paths`). + Pong { + nonce: u64, + at: crate::clock::UnixMillis, + }, /// Asks a member how it is linked to its peers, for a map of the swarm /// (`swarm::links`). Links(LinksRequest), @@ -367,7 +371,7 @@ impl ParsedMessage { ParsedMessage::Who(_) => MessageKind::Who, ParsedMessage::Me(_) => MessageKind::Me, ParsedMessage::Ping(_) => MessageKind::Ping, - ParsedMessage::Pong(_) => MessageKind::Pong, + ParsedMessage::Pong { .. } => MessageKind::Pong, ParsedMessage::Links(_) => MessageKind::Links, ParsedMessage::LinksReply(_) => MessageKind::LinksReply, } @@ -389,7 +393,7 @@ impl ParsedMessage { ParsedMessage::Who(_) | ParsedMessage::Me(_) | ParsedMessage::Ping(_) - | ParsedMessage::Pong(_) => None, + | ParsedMessage::Pong { .. } => None, } } } @@ -735,7 +739,7 @@ impl std::fmt::Display for Addressed { /// consumer hands them on as soon as they are read (`hand_on_probe`), not after the /// messages read before them are handled (which can take a while: sync). pub(crate) fn is_probe(message: &ParsedMessage) -> bool { - matches!(message, ParsedMessage::Ping(_) | ParsedMessage::Pong(_)) + matches!(message, ParsedMessage::Ping(_) | ParsedMessage::Pong { .. }) } /// Hands a PING or PONG (`is_probe`) to `handler` in a task of its own; `scheme` is the diff --git a/src/transport/paths.rs b/src/transport/paths.rs index 2e7ef76..fcfadaa 100644 --- a/src/transport/paths.rs +++ b/src/transport/paths.rs @@ -47,6 +47,7 @@ use anyhow::{Context, Result, anyhow}; use tokio::time::Instant; use tracing::{debug, info}; +use crate::clock::Clock; use crate::locks::MutexExt; use crate::transport::stats::{PathCounts, PathInfo, PathsStats, TransportStats}; use crate::transport::traffic::Traffic; @@ -394,17 +395,20 @@ pub(crate) struct Paths { traffic: Option>, /// The debug firewall, if any. gate: Option>, + /// The node's wall clock, which its PONGs say the time by. + clock: Arc, /// A std mutex: never held across an await. table: Mutex, } impl Paths { /// Over `transports`, by scheme, in order of preference; its own PINGs and PONGs - /// counted in `traffic`, the firewall through `gate`. + /// counted in `traffic`, the firewall through `gate`, PONGs stamped by `clock`. pub(crate) fn new( transports: Vec<(&'static str, Arc)>, traffic: Option>, gate: Option>, + clock: Arc, ) -> Self { Self { links: transports @@ -413,6 +417,7 @@ impl Paths { .collect(), traffic, gate, + clock, table: Mutex::default(), } } @@ -766,7 +771,10 @@ impl Paths { /// (`traffic`). async fn send_probe(&self, address: &str, pong: bool, nonce: u64) -> Result<()> { let message = if pong { - ParsedMessage::Pong(nonce) + ParsedMessage::Pong { + nonce, + at: self.clock.now_millis(), + } } else { ParsedMessage::Ping(nonce) }; @@ -839,7 +847,7 @@ impl Paths { } }); } - ParsedMessage::Pong(nonce) => { + ParsedMessage::Pong { nonce, .. } => { let location = self.location(&address); let mut table = self.table.lock_or_recover(); table.located(&address, &location); @@ -959,6 +967,7 @@ mod tests { ], None, None, + Arc::default(), )) }; (on_both("a"), on_both("b")) @@ -1252,11 +1261,13 @@ mod tests { vec![("sim", net.transport("b") as Arc)], None, Some(Arc::downgrade(&gate)), + Arc::default(), )); let a = Arc::new(Paths::new( vec![("sim", net.transport("a") as Arc)], None, None, + Arc::default(), )); let received = |paths: &Arc| { let (sender, received) = tokio::sync::mpsc::unbounded_channel(); @@ -1350,11 +1361,13 @@ mod tests { ], None, None, + Arc::default(), ); let b = Arc::new(Paths::new( vec![("sim", net.transport("b") as Arc)], None, None, + Arc::default(), )); let (sender, mut received) = tokio::sync::mpsc::unbounded_channel(); let handler: Arc = Arc::new(move |from, _, _| { diff --git a/src/transport/wire.rs b/src/transport/wire.rs index 00bc739..3daba0d 100644 --- a/src/transport/wire.rs +++ b/src/transport/wire.rs @@ -14,7 +14,7 @@ use super::{EncodedMessage, MessageKind, ParsedMessage}; /// The wire format's version, the first byte of every payload. Bump it with every change /// to a message type or a codec (`golden_bytes` fails then). -pub(crate) const VERSION: u8 = 11; +pub(crate) const VERSION: u8 = 12; /// What the second byte of a payload says. fn tag(kind: MessageKind) -> u8 { @@ -81,9 +81,8 @@ pub(crate) fn encode(message: &ParsedMessage) -> Result, WireError> { ParsedMessage::Who(proof) | ParsedMessage::Me(proof) => { rmp_serde::encode::write(&mut payload, proof)? } - ParsedMessage::Ping(nonce) | ParsedMessage::Pong(nonce) => { - rmp_serde::encode::write(&mut payload, nonce)? - } + ParsedMessage::Ping(nonce) => rmp_serde::encode::write(&mut payload, nonce)?, + ParsedMessage::Pong { nonce, at } => rmp_serde::encode::write(&mut payload, &(nonce, at))?, ParsedMessage::Links(request) => rmp_serde::encode::write(&mut payload, request)?, ParsedMessage::LinksReply(reply) => rmp_serde::encode::write(&mut payload, reply)?, } @@ -117,7 +116,8 @@ pub(crate) fn decode(payload: &[u8]) -> Result { } else if is(MessageKind::Ping) { ParsedMessage::Ping(rmp_serde::from_slice(body)?) } else if is(MessageKind::Pong) { - ParsedMessage::Pong(rmp_serde::from_slice(body)?) + let (nonce, at) = rmp_serde::from_slice(body)?; + ParsedMessage::Pong { nonce, at } } else if is(MessageKind::Links) { ParsedMessage::Links(rmp_serde::from_slice(body)?) } else if is(MessageKind::LinksReply) { @@ -419,7 +419,10 @@ pub(crate) fn samples() -> Vec { ParsedMessage::Who(proof.clone()), ParsedMessage::Me(proof), ParsedMessage::Ping(7), - ParsedMessage::Pong(u64::MAX), + ParsedMessage::Pong { + nonce: u64::MAX, + at: crate::clock::UnixMillis(5), + }, ParsedMessage::Links(crate::transport::LinksRequest { epoch, epoch_seq: 1, @@ -615,7 +618,7 @@ mod tests { }); assert_eq!( hex(&encode(&hello).unwrap()), - format!("0b0097a5737761726d{epoch_hex}01c403010203c3{}07", run("04")) + format!("0c0097a5737761726d{epoch_hex}01c403010203c3{}07", run("04")) ); let reply = ParsedMessage::HelloReply(HelloReply { @@ -635,7 +638,7 @@ mod tests { assert_eq!( hex(&encode(&reply).unwrap()), format!( - "0b0199a5737761726d9192a17091a161{epoch_hex}01c400c2{}05{}", + "0c0199a5737761726d9192a17091a161{epoch_hex}01c400c2{}05{}", run("04"), run("06") ) @@ -651,7 +654,7 @@ mod tests { }); assert_eq!( hex(&encode(&status).unwrap()), - format!("0b03969290c400{epoch_hex}01c3c2c2") + format!("0c03969290c400{epoch_hex}01c3c2c2") ); let who = ParsedMessage::Who(IdentityProof { @@ -660,7 +663,7 @@ mod tests { }); assert_eq!( hex(&encode(&who).unwrap()), - format!("0b0692c420{}c440{}", "08".repeat(32), "09".repeat(64)) + format!("0c0692c420{}c440{}", "08".repeat(32), "09".repeat(64)) ); let ParsedMessage::Me(proof) = decode( &encode(&ParsedMessage::Me(IdentityProof { @@ -674,14 +677,16 @@ mod tests { }; assert_eq!((proof.key, proof.signature), ([8; 32], [9; 64])); - assert_eq!(hex(&encode(&ParsedMessage::Ping(7)).unwrap()), "0b0807"); - assert_eq!( - hex(&encode(&ParsedMessage::Pong(u64::MAX)).unwrap()), - "0b09cfffffffffffffffff" - ); - let ParsedMessage::Pong(nonce) = decode(&[VERSION, 9, 0x2a]).unwrap() else { + assert_eq!(hex(&encode(&ParsedMessage::Ping(7)).unwrap()), "0c0807"); + let pong = ParsedMessage::Pong { + nonce: u64::MAX, + at: crate::clock::UnixMillis(5), + }; + assert_eq!(hex(&encode(&pong).unwrap()), "0c0992cfffffffffffffffff05"); + let ParsedMessage::Pong { nonce, at } = decode(&[VERSION, 9, 0x92, 0x2a, 0x07]).unwrap() + else { panic!("not a PONG"); }; - assert_eq!(nonce, 42); + assert_eq!((nonce, at.as_u64()), (42, 7)); } } -- cgit v1.3.1