Skip to main content

luct_client/request/
tiling.rs

1use crate::{Client, ClientError, CtClient};
2use luct_core::{
3    tiling::{Checkpoint, Tile, TileId, TilingError},
4    v1::SignedTreeHead,
5};
6use url::Url;
7
8impl<C: Client> CtClient<C> {
9    #[tracing::instrument(level = "trace")]
10    pub async fn get_checkpoint(&self) -> Result<SignedTreeHead, ClientError> {
11        self.assert_v1()?;
12        let url = self.get_url("checkpoint")?;
13
14        // Fetch and parse checkpoint
15        let (status, response) = self.client.get(&url, &[]).await?;
16        self.check_status(&url, status, &response)?;
17        let checkpoint = Checkpoint::parse_checkpoint(&response)?;
18
19        // Validate checkpoint against key
20        let sth = self
21            .log
22            .validate_checkpoint(&checkpoint)
23            .map_err(|err| ClientError::SignatureValidationFailed("checkpoint STH", err))?;
24
25        tracing::debug!(
26            "fetched and validated checkpoint: {:?} from url {}",
27            sth,
28            url
29        );
30
31        Ok(sth)
32    }
33
34    #[tracing::instrument(level = "trace")]
35    pub async fn get_tile(&self, mut tile_id: TileId) -> Result<Tile, ClientError> {
36        self.assert_v1()?;
37        let url = self.get_url(&tile_id.as_url())?;
38
39        let (mut status, mut response) = self.client.get_bin(&url, &[]).await?;
40
41        // If the partial tile can't be found, we retry with the full tile
42        if status == 404 && tile_id.is_partial() {
43            tile_id = tile_id.into_unpartial();
44            let url = self.get_url(&tile_id.as_url())?;
45            (status, response) = self.client.get_bin(&url, &[]).await?;
46        };
47
48        self.check_status_binary(&url, status, &response)?;
49
50        tracing::trace!("fetched tile {:?}, from url: {}", tile_id, url);
51
52        Ok(tile_id.with_data(response)?)
53    }
54
55    // TODO: Get Data tile
56    // TODO: Get issuer
57
58    fn get_url(&self, path: &str) -> Result<Url, ClientError> {
59        let url = self
60            .log
61            .config()
62            .tile_url()
63            .as_ref()
64            .ok_or(TilingError::NonTilingLog)?;
65        Ok(url.join(path).map_err(|_| TilingError::NonTilingLog)?)
66    }
67}
68#[cfg(all(test, feature = "reqwest"))]
69mod tests {
70    use super::*;
71    use crate::reqwest::ReqwestClient;
72    use luct_core::{CtLogConfig, tree::NodeKey};
73
74    const ARCHE2026H1: &str = "{
75          \"description\": \"Google 'Arche2026h1' log\",
76          \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ+3YKoZTMruov4cmlImbk4MckBNzEdCyMuHlwGgJ8BUrzFLlR5U0619xDDXIXespkpBgCNVQAkhMTTXakM6KMg==\",
77          \"url\": \"https://arche2026h1.staging.ct.transparency.dev/\",
78          \"tile_url\": \"https://storage.googleapis.com/static-ct-staging-arche2026h1-bucket/\",
79          \"mmd\": 60
80        }";
81
82    #[test]
83    fn get_url() {
84        let client = get_client();
85        let url = client
86            .get_url(
87                &TileId::from_node_key(&NodeKey::leaf(1), 1000)
88                    .unwrap()
89                    .as_url(),
90            )
91            .unwrap();
92
93        assert_eq!(
94            url.to_string(),
95            "https://storage.googleapis.com/static-ct-staging-arche2026h1-bucket/tile/0/000"
96        )
97    }
98
99    #[tokio::test]
100    #[ignore = "Makes an HTTP call, for manual testing only"]
101    async fn get_checkpoint() {
102        let client = get_client();
103        let _ = client.get_checkpoint().await.unwrap();
104    }
105
106    #[tokio::test]
107    #[ignore = "Makes an HTTP call, for manual testing only"]
108    async fn get_tile() {
109        let client = get_client();
110
111        let _ = client
112            .get_tile(TileId::from_node_key(&NodeKey::leaf(1), 1000).unwrap())
113            .await
114            .unwrap();
115    }
116
117    fn get_client() -> CtClient<ReqwestClient> {
118        let config: CtLogConfig = serde_json::from_str(ARCHE2026H1).unwrap();
119        let client = ReqwestClient::new("luct-test");
120        CtClient::new(config, client)
121    }
122}