barter_integration/protocol/http/private/
mod.rs

1use self::encoder::Encoder;
2use super::{BuildStrategy, rest::RestRequest};
3use crate::error::SocketError;
4use hmac::Mac;
5
6/// Implementations for encoding signatures generated by a [`RequestSigner`].
7pub mod encoder;
8
9/// API specific signing logic used by a [`RequestSigner`].
10#[allow(clippy::needless_lifetimes)]
11pub trait Signer {
12    /// Configuration required to sign the [`RestRequest`]s for this API server.
13    type Config<'a>
14    where
15        Self: 'a;
16
17    /// Generates a [`Self::Config`] for this [`RestRequest`] and
18    /// [`RequestBuilder`](reqwest::RequestBuilder).
19    ///
20    /// # Examples
21    ///
22    /// ## Private REST Request: FTX
23    /// ```rust,ignore
24    /// fn config<Request>(&self, _: Request, _: &RequestBuilder) -> Self::Config
25    /// where
26    ///     Request: RestRequest
27    /// {
28    ///     FtxSignConfig {
29    ///         api_key: self.api_key.as_str(),
30    ///         time: Utc::now(),
31    ///         method: Request::method(),
32    ///         path: Request::path()
33    ///     }
34    /// }
35    /// ```
36    fn config<'a, Request>(
37        &'a self,
38        request: Request,
39        builder: &reqwest::RequestBuilder,
40    ) -> Result<Self::Config<'a>, SocketError>
41    where
42        Request: RestRequest;
43
44    /// Generate the bytes to sign from the provided [`Self::Config`].
45    ///
46    /// # Examples
47    ///
48    /// ## Private REST Request: FTX
49    /// ```rust,ignore
50    /// fn add_bytes_to_sign<M>(mac: &mut M, config: &Self::Config) {
51    ///     mac.update(config.time.to_string().as_bytes());
52    ///     mac.update(config.method.as_str().as_bytes());
53    ///     mac.update(config.path.as_bytes());
54    /// }
55    /// ```
56    fn add_bytes_to_sign<M>(mac: &mut M, config: &Self::Config<'_>)
57    where
58        M: Mac;
59
60    /// Build a signed [`reqwest::Request`] from the provided [`Self::Config`],
61    /// [`RequestBuilder`](reqwest::RequestBuilder), and generated cryptographic signature `String`.
62    ///
63    /// # Examples
64    ///
65    /// ## Private REST Request: FTX
66    /// ```rust,ignore
67    /// fn build_signed_request(config: Self::Config, builder: RequestBuilder, signature: String) -> Result<reqwest::Request, SocketError> {
68    ///     // Add Ftx required Headers & build reqwest::Request
69    ///     builder
70    ///         .header(HEADER_FTX_KEY, &self.api_key)
71    ///         .header(HEADER_FTX_SIGN, &signature)
72    ///         .header(HEADER_FTX_TS, &time)
73    ///         .build()
74    ///         .map_err(SocketError::from)
75    /// }
76    /// ```
77    fn build_signed_request<'a>(
78        config: Self::Config<'a>,
79        builder: reqwest::RequestBuilder,
80        signature: String,
81    ) -> Result<reqwest::Request, SocketError>;
82}
83
84/// Generically signs Http [`RestRequest`]s utilising API specific [`Signer`] logic, a hashable
85/// [`Mac`], and a signature [`Encoder`].
86#[derive(Debug, Copy, Clone)]
87pub struct RequestSigner<Sig, Hmac, SigEncoder> {
88    signer: Sig,
89    mac: Hmac,
90    encoder: SigEncoder,
91}
92
93impl<Sig, Hmac, SigEncoder> BuildStrategy for RequestSigner<Sig, Hmac, SigEncoder>
94where
95    Sig: Signer,
96    Hmac: Mac + Clone,
97    SigEncoder: Encoder,
98{
99    fn build<Request>(
100        &self,
101        request: Request,
102        builder: reqwest::RequestBuilder,
103    ) -> Result<reqwest::Request, SocketError>
104    where
105        Request: RestRequest,
106    {
107        // Build configuration required for generating signed requests
108        let config = self.signer.config(request, &builder)?;
109
110        // Update Mac state & finalise bytes
111        let mut mac = self.mac.clone();
112        Sig::add_bytes_to_sign(&mut mac, &config);
113        let bytes_to_encode = mac.finalize().into_bytes();
114
115        // Encode signature from Mac bytes
116        let signature = self.encoder.encode(bytes_to_encode);
117
118        Sig::build_signed_request(config, builder, signature)
119    }
120}
121
122impl<Sig, Hmac, SigEncoder> RequestSigner<Sig, Hmac, SigEncoder> {
123    /// Construct a new [`Self`] using the provided API specific configuration.
124    pub fn new(signer: Sig, mac: Hmac, encoder: SigEncoder) -> Self {
125        Self {
126            signer,
127            mac,
128            encoder,
129        }
130    }
131}