Skip to main content

trustless_protocol/
client.rs

1use secrecy::ExposeSecret as _;
2
3/// Async client for communicating with a key provider process.
4///
5/// Thread-safe via an interior `Mutex` — multiple tasks can share a single client,
6/// but requests are serialized (one at a time on the wire).
7pub struct ProviderClient<R, W> {
8    inner: tokio::sync::Mutex<ProviderClientInner<R, W>>,
9}
10
11struct ProviderClientInner<R, W> {
12    reader: tokio_util::codec::FramedRead<R, tokio_util::codec::LengthDelimitedCodec>,
13    writer: tokio_util::codec::FramedWrite<W, tokio_util::codec::LengthDelimitedCodec>,
14    next_id: u64,
15}
16
17impl<R, W> ProviderClient<R, W>
18where
19    R: tokio::io::AsyncRead + Unpin + Send + 'static,
20    W: tokio::io::AsyncWrite + Unpin + Send + 'static,
21{
22    /// Create a new client from an `AsyncRead` (provider's stdout) and `AsyncWrite` (provider's stdin).
23    pub fn new(reader: R, writer: W) -> Self {
24        let reader = crate::codec::framed_read(reader);
25        let writer = crate::codec::framed_write(writer);
26
27        Self {
28            inner: tokio::sync::Mutex::new(ProviderClientInner {
29                reader,
30                writer,
31                next_id: 1,
32            }),
33        }
34    }
35
36    /// Send an `initialize` request and return the provider's certificates.
37    pub async fn initialize(
38        &self,
39    ) -> Result<crate::message::InitializeResult, crate::error::Error> {
40        let response = self
41            .send_and_recv(|id| crate::message::Request::Initialize {
42                id,
43                params: crate::message::InitializeParams {},
44            })
45            .await?;
46        match response {
47            crate::message::Response::Success(crate::message::SuccessResponse::Initialize {
48                result,
49                ..
50            }) => Ok(result),
51            crate::message::Response::Success(_) => {
52                Err(crate::error::Error::UnexpectedResponseMethod)
53            }
54            crate::message::Response::Error(crate::message::ErrorResponse { error, .. }) => {
55                Err(error.into())
56            }
57        }
58    }
59
60    /// Send a `sign` request and return the raw signature bytes.
61    pub async fn sign(
62        &self,
63        certificate_id: &str,
64        scheme: &str,
65        blob: &[u8],
66    ) -> Result<Vec<u8>, crate::error::Error> {
67        let certificate_id = certificate_id.to_owned();
68        let scheme = scheme.to_owned();
69        let blob = blob.to_vec();
70        let response = self
71            .send_and_recv(|id| crate::message::Request::Sign {
72                id,
73                params: crate::message::SignParams {
74                    certificate_id,
75                    scheme,
76                    blob: crate::message::Base64Bytes::from(blob).into_secret(),
77                },
78            })
79            .await?;
80        match response {
81            crate::message::Response::Success(crate::message::SuccessResponse::Sign {
82                result,
83                ..
84            }) => Ok(result.signature.expose_secret().to_vec()),
85            crate::message::Response::Success(_) => {
86                Err(crate::error::Error::UnexpectedResponseMethod)
87            }
88            crate::message::Response::Error(crate::message::ErrorResponse { error, .. }) => {
89                Err(error.into())
90            }
91        }
92    }
93
94    async fn send_and_recv(
95        &self,
96        build_request: impl FnOnce(u64) -> crate::message::Request,
97    ) -> Result<crate::message::Response, crate::error::Error> {
98        let mut inner = self.inner.lock().await;
99        let id = inner.next_id;
100        inner.next_id += 1;
101
102        let request = build_request(id);
103        crate::codec::send_message(&mut inner.writer, &request).await?;
104
105        let response: crate::message::Response =
106            crate::codec::recv_message(&mut inner.reader).await?;
107
108        if response.id() != id {
109            return Err(crate::error::Error::UnexpectedResponseId {
110                expected: id,
111                got: response.id(),
112            });
113        }
114
115        Ok(response)
116    }
117}