Skip to main content

pubhubs/client/
discovery.rs

1use crate::api::{self, ApiResultExt as _, NoPayload};
2use crate::servers::constellation::ConstellationOrId;
3use crate::servers::{self, Constellation, server::Server as _};
4
5impl crate::client::Client {
6    /// Retrieves [`Constellation`] from specified url, waiting for it to be set.
7    pub async fn get_constellation(&self, url: &url::Url) -> anyhow::Result<ConstellationOrId> {
8        crate::misc::task::retry(|| async {
9            // Retry calling DiscoveryInfo endpoint while it returns a retryable error or some
10            // DiscoveryInfoResp with None constellation until constellation is Some.
11            (match self.query::<api::DiscoveryInfo>(url, NoPayload)
12                .await
13                .retryable()/* <- turns retryable error Err(err) into Ok(None) */?
14            {
15                Some(inf) => Ok(inf.constellation_or_id),
16                None => Ok(None),
17            }) as anyhow::Result<Option<ConstellationOrId>>
18        })
19        .await?
20        .ok_or_else(|| {
21            anyhow::anyhow!(
22                "timeout waiting for {url} to publish constellation",
23                url = url
24            )
25        })
26    }
27
28    /// Retrieves [Constellation] from all servers, and checks they coincide; returns `None` when
29    /// there's a disagreement.
30    pub async fn try_get_stable_constellation(
31        &self,
32        phc_url: &url::Url,
33    ) -> api::Result<Option<Constellation>> {
34        log::debug!("trying to get stable constellation");
35        let phc_inf = self.query::<api::DiscoveryInfo>(phc_url, NoPayload).await?;
36        if phc_inf.constellation_or_id.is_none() {
37            log::debug!(
38                "{phc}'s constellation not yet set",
39                phc = servers::Name::PubhubsCentral
40            );
41            return Ok(None);
42        }
43
44        let Some(constellation) = phc_inf.constellation_or_id.unwrap().into_constellation() else {
45            log::error!(
46                "{phc} did not return a constellation, but just its id",
47                phc = servers::Name::PubhubsCentral
48            );
49            return Err(api::ErrorCode::InternalError);
50        };
51
52        let mut js = tokio::task::JoinSet::new();
53
54        macro_rules! get_constellation_from_server {
55            ($server:ident) => {
56                let server_name = crate::servers::$server::Server::NAME;
57                if server_name != servers::Name::PubhubsCentral {
58                    let url = constellation.url(server_name);
59                    js.spawn_local(
60                        self.query::<api::DiscoveryInfo>(url, NoPayload)
61                            .into_future(),
62                    );
63                }
64            };
65        }
66
67        crate::for_all_servers!(get_constellation_from_server);
68
69        'lp: loop {
70            match js.join_next().await {
71                None => break 'lp,
72                Some(Ok(inf_res)) => {
73                    let inf = inf_res?;
74
75                    if inf.constellation_or_id.is_none()
76                        || *inf.constellation_or_id.unwrap().id() != constellation.id
77                    {
78                        log::debug!("constellations not yet in sync");
79                        return Ok(None);
80                    }
81                }
82                Some(Err(join_err)) => {
83                    log::warn!(
84                        "unexpected join error getting constellation from server: {join_err}"
85                    );
86                    return Err(api::ErrorCode::InternalError);
87                }
88            }
89        }
90
91        log::info!("obtained stable constellation");
92        Ok(Some(constellation))
93    }
94}
95
96/// Specifies what to check about  a [api::DiscoveryInfoResp]
97pub struct DiscoveryInfoCheck<'a> {
98    pub phc_url: &'a url::Url,
99    pub name: crate::servers::Name,
100    pub self_check_code: Option<&'a str>,
101    pub constellation: Option<&'a Constellation>,
102}
103
104impl DiscoveryInfoCheck<'_> {
105    /// Checks the given [`api::DiscoveryInfoResp`] according to the [`DiscoveryInfoCheck`],
106    /// and returns it if all checks out.
107    pub fn check(
108        self,
109        inf: api::DiscoveryInfoResp,
110        source: &url::Url,
111    ) -> api::Result<api::DiscoveryInfoResp> {
112        if inf.name != self.name {
113            log::error!(
114                "supposed {} at {} returned name {}",
115                self.name,
116                source,
117                inf.name
118            );
119            return Err(api::ErrorCode::InternalError);
120        }
121
122        if &inf.phc_url != self.phc_url {
123            log::error!(
124                "{} at {} thinks PubHubs Central is at {}",
125                self.name,
126                source,
127                inf.phc_url,
128            );
129            return Err(api::ErrorCode::InternalError);
130        }
131
132        if let Some(scc) = self.self_check_code
133            && inf.self_check_code != scc
134        {
135            log::error!(
136                "{} at {} is not me! (Different self_check_code.)",
137                self.name,
138                source,
139            );
140            return Err(api::ErrorCode::InternalError);
141        }
142
143        if inf.master_enc_key_part.is_some()
144            != matches!(
145                inf.name,
146                servers::Name::PubhubsCentral | servers::Name::Transcryptor
147            )
148        {
149            log::error!(
150                "master_enc_key_part must be set by the transcryptor and pubhub central, but no other servers - url: {source}"
151            );
152            return Err(api::ErrorCode::InternalError);
153        }
154
155        if let Some(ref ic) = inf.constellation_or_id
156            && let Some(sc) = self.constellation
157            && *ic.id() != sc.id
158        {
159            log::error!(
160                "{} at {} has a different view of the constellation of PubHubs servers",
161                inf.name,
162                source,
163            );
164            return Err(api::ErrorCode::InternalError);
165        }
166
167        api::Result::Ok(inf)
168    }
169}