pkarr 7.0.0

Public-Key Addressable Resource Records (Pkarr); publish and resolve DNS records over Mainline DHT
Documentation
use std::time::Duration;

use futures_buffered::FuturesUnorderedBounded;
use futures_lite::StreamExt;
use ntimestamp::Timestamp;
use url::Url;

use super::publish_result_accumulator::PublishResultAccumulator;
use super::resolve_result_accumulator::ResolveResultAccumulator;
use super::{BackendResolvePolicy, CacheContext};
use crate::client::{PublishError, ResolveError};
use crate::relay_client::{RelayClient, RelayError};
use crate::{PublicKey, ResolvePolicy, SignedPacket, StoredNodeCount};

#[derive(Clone, Debug)]
pub(in crate::client) struct RelayBackend {
    relays: Vec<RelayClient>,
}

struct RelayResolveOptions<'a> {
    relay_policy: ResolvePolicy,
    cache_context: Option<CacheContext<'a>>,
    request_lower_bound: Option<Timestamp>,
    complete_on_first_acceptable: bool,
}

impl<'a> From<BackendResolvePolicy<'a>> for RelayResolveOptions<'a> {
    fn from(policy: BackendResolvePolicy<'a>) -> Self {
        match policy {
            BackendResolvePolicy::CacheOnly => Self {
                relay_policy: ResolvePolicy::CacheOnly,
                cache_context: None,
                request_lower_bound: None,
                complete_on_first_acceptable: true,
            },
            BackendResolvePolicy::CacheFirst(context) => Self {
                relay_policy: ResolvePolicy::CacheFirst,
                cache_context: Some(context),
                request_lower_bound: context.relay_request_lower_bound(),
                complete_on_first_acceptable: true,
            },
            BackendResolvePolicy::NetworkOnly => Self {
                relay_policy: ResolvePolicy::NetworkOnly,
                cache_context: None,
                request_lower_bound: None,
                complete_on_first_acceptable: false,
            },
        }
    }
}

impl RelayBackend {
    pub(super) fn new(
        relays: Vec<Url>,
        timeout: Duration,
        client: Option<reqwest::Client>,
    ) -> Result<Self, RelayError> {
        let client = match client {
            Some(client) => client,
            None => reqwest::Client::builder()
                .build()
                .map_err(|e| RelayError::Build(e.to_string()))?,
        };
        let relays = relays
            .into_iter()
            .map(|url| RelayClient::new(url, client.clone(), timeout))
            .collect::<Result<Vec<_>, _>>()?;

        Ok(Self { relays })
    }

    pub(super) async fn publish(
        &self,
        packet: &SignedPacket,
    ) -> Result<StoredNodeCount, PublishError> {
        let mut requests: FuturesUnorderedBounded<_> = self
            .relays
            .iter()
            .map(|relay| relay.publish(packet))
            .collect();

        let mut accumulator = PublishResultAccumulator::default();
        while let Some(result) = requests.next().await {
            accumulator.record_result(result.map_err(Into::into));
        }
        accumulator.into_result()
    }

    pub(super) async fn resolve(
        &self,
        key: &PublicKey,
        policy: BackendResolvePolicy<'_>,
    ) -> Result<SignedPacket, ResolveError> {
        let options = RelayResolveOptions::from(policy);
        let mut requests: FuturesUnorderedBounded<_> = self
            .relays
            .iter()
            .map(|relay| relay.resolve(key, options.relay_policy, options.request_lower_bound))
            .collect();

        let mut accumulator = ResolveResultAccumulator::new(options.cache_context);
        while let Some(result) = requests.next().await {
            let acceptable = accumulator.record_result(result.map_err(Into::into));
            if acceptable && options.complete_on_first_acceptable {
                break;
            }
        }
        accumulator.into_result()
    }
}