diff --git a/pkarr/examples/README.md b/pkarr/examples/README.md new file mode 100644 index 0000000..57ad67f --- /dev/null +++ b/pkarr/examples/README.md @@ -0,0 +1,49 @@ +# Examples + +## Publish + +```sh +cargo run --example publish +``` + +## Resolve Eager + +```sh +cargo run --example resolve +``` + +## Resolve Most Recent + +```sh +cargo run --example resolve-most-recent +``` + +## Relay + +```sh +cargo run --example relay-client +``` + +## Async Publish + +```sh +cargo run --example async-publish --features="async" +``` + +## Async Resolve Eager + +```sh +cargo run --example async-resolve --features="async" +``` + +## Resolve Most Recent + +```sh +cargo run --example async-resolve-most-recent --features="async" +``` + +## Relay + +```sh +cargo run --example async-relay-client --features="relay async" +``` diff --git a/pkarr/examples/async/relay-client.rs b/pkarr/examples/async/relay-client.rs index 030d4cb..aff79c0 100644 --- a/pkarr/examples/async/relay-client.rs +++ b/pkarr/examples/async/relay-client.rs @@ -30,7 +30,7 @@ async fn main() -> Result<()> { // Publisher { - println!("\nPublishing pk:{}...\n", keypair); + println!("\nPublishing pk:{} ...\n", keypair); let mut packet = dns::Packet::new_reply(0); packet.answers.push(dns::ResourceRecord::new( dns::Name::new("_derp_region.iroh.").unwrap(), diff --git a/pkarr/examples/async/resolve.rs b/pkarr/examples/async/resolve.rs index 475e963..27f99da 100644 --- a/pkarr/examples/async/resolve.rs +++ b/pkarr/examples/async/resolve.rs @@ -29,7 +29,7 @@ async fn main() { println!("\nResolving pk:{} ...", public_key); - if let Some(signed_packet) = client.resolve(public_key).await { + if let Some(signed_packet) = client.resolve_eager(public_key).await { println!("\nResolved in {:?} {}", instant.elapsed(), signed_packet); } else { println!("\nFailed to resolve {}", str); diff --git a/pkarr/examples/relay-client.rs b/pkarr/examples/relay-client.rs index 4edd2e1..c3b8ed3 100644 --- a/pkarr/examples/relay-client.rs +++ b/pkarr/examples/relay-client.rs @@ -29,7 +29,7 @@ fn main() -> Result<()> { // Publisher { - println!("\nPublishing pk:{}...\n", keypair); + println!("\nPublishing pk:{} ...\n", keypair); let mut packet = dns::Packet::new_reply(0); packet.answers.push(dns::ResourceRecord::new( dns::Name::new("_derp_region.iroh.").unwrap(), diff --git a/pkarr/examples/resolve.rs b/pkarr/examples/resolve.rs index a0cf32a..49a97d2 100644 --- a/pkarr/examples/resolve.rs +++ b/pkarr/examples/resolve.rs @@ -28,7 +28,7 @@ fn main() { println!("\nResolving pk:{} ...", public_key); - if let Some(signed_packet) = client.resolve(public_key) { + if let Some(signed_packet) = client.resolve_eager(public_key) { println!("\nResolved in {:?} {}", instant.elapsed(), signed_packet); } else { println!("\nFailed to resolve {}", str); diff --git a/pkarr/src/README.md b/pkarr/src/README.md index 1d0aeca..96b3542 100644 --- a/pkarr/src/README.md +++ b/pkarr/src/README.md @@ -4,8 +4,8 @@ Rust implementation of [Pkarr](pkarr.org). Publish and resolve DNS packets over Mainline DHT. -## Example +**[API Docs](https://docs.rs/pkarr/latest/pkarr/)** -```bash -cargo run --example relay-client -``` +## Get started + +Check the [Examples](./examples). diff --git a/pkarr/src/lib.rs b/pkarr/src/lib.rs index 6e966ef..54709ad 100644 --- a/pkarr/src/lib.rs +++ b/pkarr/src/lib.rs @@ -2,7 +2,7 @@ #[cfg(feature = "dht")] use mainline::{ - common::{MutableItem, StoreQueryMetdata}, + common::{GetMutableResponse, MutableItem, Response, StoreQueryMetdata}, Dht, }; #[cfg(feature = "relay")] @@ -36,7 +36,7 @@ pub struct PkarrClient { http_client: reqwest::blocking::Client, #[cfg(all(feature = "relay", feature = "async"))] http_client: reqwest::Client, - pub dht: Dht, + dht: Dht, } impl PkarrClient { @@ -83,7 +83,7 @@ impl PkarrClient { } #[cfg(all(feature = "relay", feature = "async"))] - /// Async version of [relay_get](PkarrClient::relay_get) + /// Resolves a [SignedPacket] from a [relay](https://github.com/Nuhvi/pkarr/blob/main/design/relays.md). pub async fn relay_get(&self, url: &Url, public_key: PublicKey) -> Result { let url = format_relay_url(url, &public_key); @@ -101,7 +101,7 @@ impl PkarrClient { } #[cfg(all(feature = "relay", not(feature = "async")))] - /// Publishes a [SignedPacket](crate::SignedPacket) through a [relay](https://github.com/Nuhvi/pkarr/blob/main/design/relays.md). + /// Publishes a [SignedPacket] through a [relay](https://github.com/Nuhvi/pkarr/blob/main/design/relays.md). pub fn relay_put(&self, url: &Url, signed_packet: SignedPacket) -> Result<()> { let url = format_relay_url(url, signed_packet.public_key()); @@ -123,7 +123,7 @@ impl PkarrClient { } #[cfg(all(feature = "relay", feature = "async"))] - /// Async version of [relay_put](PkarrClient::relay_put) + /// Publishes a [SignedPacket] through a [relay](https://github.com/Nuhvi/pkarr/blob/main/design/relays.md). pub async fn relay_put(&self, url: &Url, signed_packet: SignedPacket) -> Result<()> { let url = format_relay_url(url, signed_packet.public_key()); @@ -156,15 +156,36 @@ impl PkarrClient { } #[cfg(all(feature = "dht", feature = "async"))] - /// Async version of [publish](PkarrClient::publish) + /// Publish a [SignedPacket] to the DHT. + /// + /// It performs a thorough lookup first to find the closest nodes, + /// before storing the signed packet to them, so it may take few seconds. pub async fn publish(&self, signed_packet: &SignedPacket) -> Result { let item: MutableItem = signed_packet.into(); self.dht.put_mutable(item).map_err(Error::MainlineError) } #[cfg(all(feature = "dht", not(feature = "async")))] - /// Returns the first resolved [SignedPacket] from the DHT. - pub fn resolve(&self, public_key: PublicKey) -> Option { + /// Return `mainline's` [Response] to have the most control over the response and access its metadata. + /// + /// Most likely you want to use [resolve_eager](PkarrClient::resolve_eager) or + /// [resolve_most_recent](PkarrClient::resolve_most_recent) instead. + pub fn resolve(&self, public_key: PublicKey) -> Response { + self.dht.get_mutable(public_key.as_bytes(), None) + } + + #[cfg(all(feature = "dht", feature = "async"))] + /// Return `mainline's` [Response] to have the most control over the response and access its metadata. + /// + /// Most likely you want to use [resolve_eager](PkarrClient::resolve_eager) or + /// [resolve_most_recent](PkarrClient::resolve_most_recent) instead. + pub async fn resolve(&self, public_key: PublicKey) -> Response { + self.dht.get_mutable(public_key.as_bytes(), None) + } + + #[cfg(all(feature = "dht", not(feature = "async")))] + /// Return the first resolved [SignedPacket] from the DHT. + pub fn resolve_eager(&self, public_key: PublicKey) -> Option { let mut response = self.dht.get_mutable(public_key.as_bytes(), None); for res in &mut response { @@ -178,8 +199,8 @@ impl PkarrClient { } #[cfg(all(feature = "dht", feature = "async"))] - /// Returns the first resolved [SignedPacket](crate::SignedPacket) from the DHT. - pub async fn resolve(&self, public_key: PublicKey) -> Option { + /// Return the first resolved [SignedPacket] from the DHT. + pub async fn resolve_eager(&self, public_key: PublicKey) -> Option { let mut response = self.dht.get_mutable(public_key.as_bytes(), None); for res in &mut response { @@ -193,7 +214,7 @@ impl PkarrClient { } #[cfg(all(feature = "dht", not(feature = "async")))] - /// Returns the most recent [SignedPacket] from the DHT. + /// Return the most recent [SignedPacket] from the DHT. /// In order to determine the most recent, it has to do a full lookup first, so /// this method may take few seconds. pub fn resolve_most_recent(&self, public_key: PublicKey) -> Option { @@ -205,15 +226,7 @@ impl PkarrClient { let signed_packet: Result = res.item.try_into(); if let Ok(signed_packet) = signed_packet { if let Some(most_recent) = &most_recent { - if signed_packet.timestamp() < most_recent.timestamp() { - continue; - } - - // In the rare ocasion of timestamp collission, - // we use the one with the largest value - if signed_packet.timestamp() == most_recent.timestamp() - && signed_packet.encoded_packet() < most_recent.encoded_packet() - { + if signed_packet.more_recent_than(most_recent) { continue; } } @@ -226,7 +239,9 @@ impl PkarrClient { } #[cfg(all(feature = "dht", feature = "async"))] - /// Async version of [resolve_most_recent](PkarrClient::resolve_most_recent). + /// Return the most recent [SignedPacket] from the DHT. + /// In order to determine the most recent, it has to do a full lookup first, so + /// this method may take few seconds. pub async fn resolve_most_recent(&self, public_key: PublicKey) -> Option { let mut response = self.dht.get_mutable(public_key.as_bytes(), None); @@ -236,17 +251,9 @@ impl PkarrClient { let signed_packet: Result = res.item.try_into(); if let Ok(signed_packet) = signed_packet { if let Some(most_recent) = &most_recent { - if signed_packet.timestamp() < most_recent.timestamp() { + if signed_packet.more_recent_than(most_recent) { continue; } - - // In the rare ocasion of timestamp collission, - // we use the one with the largest value - if signed_packet.timestamp() == most_recent.timestamp() { - if signed_packet.encoded_packet() < most_recent.encoded_packet() { - continue; - } - } } most_recent = Some(signed_packet) diff --git a/pkarr/src/signed_packet.rs b/pkarr/src/signed_packet.rs index a9d8974..f4ca19d 100644 --- a/pkarr/src/signed_packet.rs +++ b/pkarr/src/signed_packet.rs @@ -209,6 +209,27 @@ impl SignedPacket { pub fn encoded_packet(&self) -> Bytes { self.inner.borrow_owner().slice(104..) } + + // === Public Methods === + + /// Return whether this {SignedPacket] is more recent than the given one. + /// If the timestamps are erqual, the one with the largest value is considered more recent. + /// Usefel for determining which packet contains the latest information from the Dht. + /// Assumes that both packets have the same [PublicKey], you shouldn't compare packets from + /// different keys. + pub fn more_recent_than(&self, other: &SignedPacket) -> bool { + if self.timestamp() < other.timestamp() { + return false; + } + + // In the rare ocasion of timestamp collission, + // we use the one with the largest value + if self.timestamp() == other.timestamp() && self.encoded_packet() < other.encoded_packet() { + return false; + } + + true + } } fn signable(timestamp: u64, v: &Bytes) -> Bytes {