luct_client/request/
tiling.rs1use 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 let (status, response) = self.client.get(&url, &[]).await?;
16 self.check_status(&url, status, &response)?;
17 let checkpoint = Checkpoint::parse_checkpoint(&response)?;
18
19 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 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 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}