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    ///
38    /// May be called multiple times to reload certificates from a running provider.
39    pub async fn initialize(
40        &self,
41    ) -> Result<crate::message::InitializeResult, crate::error::Error> {
42        let response = self
43            .send_and_recv(|id| crate::message::Request::Initialize {
44                id,
45                params: crate::message::InitializeParams {},
46            })
47            .await?;
48        match response {
49            crate::message::Response::Success(crate::message::SuccessResponse::Initialize {
50                result,
51                ..
52            }) => Ok(result),
53            crate::message::Response::Success(_) => {
54                Err(crate::error::Error::UnexpectedResponseMethod)
55            }
56            crate::message::Response::Error(crate::message::ErrorResponse { error, .. }) => {
57                Err(error.into())
58            }
59        }
60    }
61
62    /// Send a `sign` request and return the raw signature bytes.
63    pub async fn sign(
64        &self,
65        certificate_id: &str,
66        scheme: &str,
67        blob: &[u8],
68    ) -> Result<Vec<u8>, crate::error::Error> {
69        let certificate_id = certificate_id.to_owned();
70        let scheme = scheme.to_owned();
71        let blob = blob.to_vec();
72        let response = self
73            .send_and_recv(|id| crate::message::Request::Sign {
74                id,
75                params: crate::message::SignParams {
76                    certificate_id,
77                    scheme,
78                    blob: crate::message::Base64Bytes::from(blob).into_secret(),
79                },
80            })
81            .await?;
82        match response {
83            crate::message::Response::Success(crate::message::SuccessResponse::Sign {
84                result,
85                ..
86            }) => Ok(result.signature.expose_secret().to_vec()),
87            crate::message::Response::Success(_) => {
88                Err(crate::error::Error::UnexpectedResponseMethod)
89            }
90            crate::message::Response::Error(crate::message::ErrorResponse { error, .. }) => {
91                Err(error.into())
92            }
93        }
94    }
95
96    async fn send_and_recv(
97        &self,
98        build_request: impl FnOnce(u64) -> crate::message::Request,
99    ) -> Result<crate::message::Response, crate::error::Error> {
100        let mut inner = self.inner.lock().await;
101        let id = inner.next_id;
102        inner.next_id += 1;
103
104        let request = build_request(id);
105        crate::codec::send_message(&mut inner.writer, &request).await?;
106
107        let response: crate::message::Response =
108            crate::codec::recv_message(&mut inner.reader).await?;
109
110        if response.id() != id {
111            return Err(crate::error::Error::UnexpectedResponseId {
112                expected: id,
113                got: response.id(),
114            });
115        }
116
117        Ok(response)
118    }
119}