Skip to main content

luct_client/
lib.rs

1#![forbid(unsafe_code)]
2
3use luct_core::{
4    CtLog, CtLogConfig, SignatureValidationError,
5    tiling::{ParseCheckpointError, TilingError},
6    tree::ProofValidationError,
7};
8use std::{error::Error, fmt::Debug, sync::Arc};
9use thiserror::Error;
10use url::Url;
11
12pub use impls::*;
13
14mod impls;
15mod request;
16mod util;
17
18// TODO: Fetch entries API
19// TODO: Tests with a mock client
20
21/// Wrapper around [`Client`], that implements fetching and validation logic
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct CtClient<C> {
24    log: CtLog,
25    client: C,
26}
27
28impl<C> CtClient<C> {
29    pub fn new(config: CtLogConfig, client: C) -> Self {
30        Self {
31            log: CtLog::new(config),
32            client,
33        }
34    }
35
36    pub fn log(&self) -> &CtLog {
37        &self.log
38    }
39}
40
41/// Backend client implementation trait
42///
43/// This trait needs to be implemented by clients to be used by auditors, monitors etc.
44pub trait Client: Debug {
45    /// Make a GET request to fetch some [`String`] data
46    ///
47    /// # Arguments
48    /// - `url`: the [`Url`] to connect to
49    /// - `params`: Key-value pairs of query parameters to be included in the request
50    ///
51    /// # Returns
52    /// - **On success**:
53    ///     - The HTTP status code
54    ///     - The data as a [`String`]
55    /// - **On failure**: The [`ClientError`] describing what went wrong
56    fn get(
57        &self,
58        url: &Url,
59        params: &[(&str, &str)],
60    ) -> impl Future<Output = Result<(u16, Arc<String>), ClientError>>;
61
62    /// Make a GET request to fetch some binary data
63    ///
64    /// # Arguments
65    /// - `url`: the [`Url`] to connect to
66    /// - `params`: Key-value pairs of query parameters to be included in the request
67    ///
68    /// # Returns
69    /// - **On success**:
70    ///     - The HTTP status code
71    ///     - The data as a [`Vec<u8>`]
72    /// - **On failure**: The [`ClientError`] describing what went wrong
73    fn get_bin(
74        &self,
75        url: &Url,
76        params: &[(&str, &str)],
77    ) -> impl Future<Output = Result<(u16, Arc<Vec<u8>>), ClientError>>;
78
79    // TODO(Submission support): Post calls for submission support
80}
81
82/// Error returned by [`Client`] implementation when a request fails
83#[derive(Debug, Clone, Error)]
84pub enum ClientError {
85    /// A client attempted to make a request, that is not supported by a log with this version
86    #[error("The version of the log is not supported by this client")]
87    UnsupportedVersion,
88
89    /// A request was returned but the client failed to parse the JSON in the resonse
90    #[error("Failed to parse JSON: line: {line}, column: {column}")]
91    JsonError { line: usize, column: usize },
92
93    /// Verification of a signature failed
94    #[error("Signature validation of {0} against the logs key failed: {1}")]
95    SignatureValidationFailed(&'static str, SignatureValidationError),
96
97    /// Validating a consistency proof failed
98    #[error("Failed to validate a consistency path: {0}")]
99    ConsistencyProofError(ProofValidationError),
100
101    /// Validating an audit proof failed
102    #[error("Failed to validate an audit path: {0}")]
103    AuditProofError(ProofValidationError),
104
105    // TODO: Remove
106    /// The connection failed
107    #[error("Failed to connect to host: {0}")]
108    ConnectionError(String),
109
110    /// The connection failed
111    #[error("Failed to connect to host: {0}")]
112    ConnectionErrorStd(Arc<dyn Error + Send + Sync>),
113
114    /// The request failed, the server returned a response code other than 200
115    #[error("Request to {url} returned error: {code}: {msg}")]
116    ResponseError { url: String, code: u16, msg: String },
117
118    /// Failed to parse a checkpoint note
119    #[error("Failed parsing checkpoint: {0}")]
120    Checkpoint(#[from] ParseCheckpointError),
121
122    /// An error specific to the tiling API occured
123    #[error("Tiling error: {0}")]
124    TilingError(#[from] TilingError),
125
126    // TODO: Remove
127    /// The STH could not be parsed
128    #[error("The STH could not be parsed")]
129    SthError,
130}
131
132impl From<serde_json::Error> for ClientError {
133    fn from(value: serde_json::Error) -> Self {
134        ClientError::JsonError {
135            line: value.line(),
136            column: value.column(),
137        }
138    }
139}