em_node_agent_client/client/
mod.rs

1/* Copyright (c) Fortanix, Inc.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#![allow(unused_extern_crates)]
7extern crate mime;
8extern crate chrono;
9extern crate url;
10extern crate uuid;
11
12use hyper;
13use hyper::client::{Request, Response};
14use hyper::header::{Header, Headers, HeaderFormat, ContentType};
15use hyper::method::Method;
16use hyper::Url;
17use self::url::percent_encoding::{utf8_percent_encode, PATH_SEGMENT_ENCODE_SET, QUERY_ENCODE_SET};
18use futures;
19use futures::{Future, Stream};
20use futures::{future, stream};
21use std::borrow::Cow;
22use std::io::{Read, Error, ErrorKind};
23use std::error;
24use std::fmt;
25use std::path::Path;
26use std::sync::Arc;
27use std::str;
28use std::str::FromStr;
29use std::string::ToString;
30use mimetypes;
31use serde_json;
32
33#[allow(unused_imports)]
34use std::collections::{HashMap, BTreeMap};
35
36use crate::ApiError;
37
38use {
39    CertificateApi,
40    EnclaveApi,
41    SystemApi
42};
43
44use models;
45
46define_encode_set! {
47    /// This encode set is used for object IDs
48    ///
49    /// Aside from the special characters defined in the `PATH_SEGMENT_ENCODE_SET`,
50    /// the vertical bar (|) is encoded.
51    pub ID_ENCODE_SET = [PATH_SEGMENT_ENCODE_SET] | {'|'}
52}
53
54/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
55fn into_base_path(input: &str, correct_scheme: Option<&'static str>) -> Result<String, ClientInitError> {
56    // First convert to Url, since a base path is a subset of Url.
57    let url = Url::from_str(input)?;
58
59    let scheme = url.scheme();
60
61    // Check the scheme if necessary
62    if let Some(correct_scheme) = correct_scheme {
63        if scheme != correct_scheme {
64            return Err(ClientInitError::InvalidScheme);
65        }
66    }
67
68    let host = url.host().ok_or_else(|| ClientInitError::MissingHost)?;
69    let port = url.port().map(|x| format!(":{}", x)).unwrap_or_default();
70    Ok(format!("{}://{}{}", scheme, host, port))
71}
72
73/// A client that implements the API by making HTTP calls out to a server.
74pub struct Client {
75    hyper_client: Arc<hyper::client::Client>,
76    base_path: String,
77    headers: Headers,
78}
79
80impl fmt::Debug for Client {
81    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82        write!(f, "Client {{ base_path: {} }}", self.base_path)
83    }
84}
85
86impl Clone for Client {
87    fn clone(&self) -> Self {
88        Client {
89            hyper_client: self.hyper_client.clone(),
90            base_path: self.base_path.clone(),
91            headers: self.headers.clone(),
92        }
93    }
94}
95
96impl Client {
97
98    /// Create an HTTP client.
99    ///
100    /// # Arguments
101    /// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
102    pub fn try_new_http(base_path: &str) -> Result<Client, ClientInitError> {
103        Self::try_new_with_connector(
104            base_path,
105            Some("http"),
106            hyper::net::HttpConnector,
107        )
108    }
109
110    /// Create a client with a custom implementation of hyper::net::NetworkConnector.
111    ///
112    /// Intended for use with custom implementations of connect for e.g. protocol logging
113    /// or similar functionality which requires wrapping the transport layer. When wrapping a TCP connection,
114    /// this function should be used in conjunction with
115    /// `swagger::{http_connector, https_connector, https_mutual_connector}`.
116    ///
117    /// For ordinary tcp connections, prefer the use of `try_new_http`, `try_new_https`
118    /// and `try_new_https_mutual`, to avoid introducing a dependency on the underlying transport layer.
119    ///
120    /// # Arguments
121    ///
122    /// * `base_path` - base path of the client API, i.e. "www.my-api-implementation.com"
123    /// * `protocol` - Which protocol to use when constructing the request url, e.g. `Some("http")`
124    /// * `connector` - An instance of `C: hyper::net::NetworkConnection`
125    pub fn try_new_with_connector<C, S>(
126        base_path: &str,
127        protocol: Option<&'static str>,
128        connector: C,
129    ) -> Result<Client, ClientInitError>
130    where
131        C: hyper::net::NetworkConnector<Stream = S> + Send + Sync + 'static,
132        S: hyper::net::NetworkStream
133    {
134        let hyper_client = hyper::Client::with_connector(connector);
135
136        Ok(Client {
137            hyper_client: Arc::new(hyper_client),
138            base_path: into_base_path(base_path, protocol)?,
139            headers: Headers::new(),
140        })
141    }
142
143    /// Constructor for creating a `Client` by passing in a pre-made `hyper` client.
144    ///
145    /// One should avoid relying on this function if possible, since it adds a dependency on the underlying transport
146    /// implementation, which it would be better to abstract away. Therefore, using this function may lead to a loss of
147    /// code generality, which may make it harder to move the application to a serverless environment, for example.
148    ///
149    /// The reason for this function's existence is to support legacy test code, which did mocking at the hyper layer.
150    /// This is not a recommended way to write new tests. If other reasons are found for using this function, they
151    /// should be mentioned here.
152    ///
153    /// This function is deprecated in the upstream openapi-generator which
154    /// uses newer hyper. However, the suggested replacement does not exist
155    /// in hyper 0.9.
156    pub fn try_new_with_hyper_client(
157        hyper_client: Arc<hyper::client::Client>,
158        base_path: &str
159    ) -> Result<Client, ClientInitError>
160    {
161        Ok(Client {
162            hyper_client: hyper_client,
163            base_path: into_base_path(base_path, None)?,
164            headers: Headers::new(),
165        })
166    }
167
168    pub fn headers(&mut self) -> &mut Headers {
169        &mut self.headers
170    }
171}
172
173impl CertificateApi for Client {
174
175    type Error = ApiError;
176
177
178    fn get_issue_certificate_response(&self, param_task_id: uuid::Uuid) -> Result<models::IssueCertificateResponse, ApiError> {
179        let mut url = format!(
180            "{}/v1/certificate/result/{task_id}",
181            self.base_path, task_id=utf8_percent_encode(&param_task_id.to_string(), ID_ENCODE_SET)
182        );
183
184        let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
185
186        let query_string_str = query_string.finish();
187        if !query_string_str.is_empty() {
188            url += "?";
189            url += &query_string_str;
190        }
191
192        let url = match Url::from_str(&url) {
193            Ok(url) => url,
194            Err(err) => return Err(ApiError(format!("Unable to build URL: {}", err))),
195        };
196
197        let mut request = self.hyper_client.request(Method::Post, url);
198        request = request.headers(self.headers.clone());
199
200        request.send()
201            .map_err(|e| ApiError(format!("No response received: {}", e)))
202            .and_then(|mut response| {
203                match response.status.to_u16() {
204                    200 => {
205                        let mut body = Vec::new();
206                        response.read_to_end(&mut body)
207                            .map_err(|e| ApiError(format!("Failed to read response: {}", e)))?;
208
209                        str::from_utf8(&body)
210                            .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))
211                            .and_then(|body|
212                                 serde_json::from_str::<models::IssueCertificateResponse>(body)
213                                         .map_err(|e| e.into())
214
215                                 )
216                    },
217                    code => {
218                        let headers = response.headers.clone();
219                        let mut body = Vec::new();
220                        let result = response.read_to_end(&mut body);
221                        Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
222                            code,
223                            headers,
224                            match result {
225                                Ok(_) => match str::from_utf8(&body) {
226                                    Ok(body) => Cow::from(body),
227                                    Err(e) => Cow::from(format!("<Body was not UTF8: {:?}>", e)),
228                                },
229                                Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
230                            })))
231                    }
232                }
233        })
234
235    }
236
237    fn issue_certificate(&self, param_body: models::IssueCertificateRequest) -> Result<models::IssueCertificateResponse, ApiError> {
238        let mut url = format!(
239            "{}/v1/certificate/issue",
240            self.base_path
241        );
242
243        let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
244
245        let query_string_str = query_string.finish();
246        if !query_string_str.is_empty() {
247            url += "?";
248            url += &query_string_str;
249        }
250
251        let url = match Url::from_str(&url) {
252            Ok(url) => url,
253            Err(err) => return Err(ApiError(format!("Unable to build URL: {}", err))),
254        };
255
256        let mut request = self.hyper_client.request(Method::Post, url);
257        request = request.headers(self.headers.clone());
258        let body = serde_json::to_string(&param_body).expect("impossible to fail to serialize");
259            request = request.body(body.as_bytes());
260
261        request = request.header(ContentType(mimetypes::requests::ISSUE_CERTIFICATE.clone()));
262
263        request.send()
264            .map_err(|e| ApiError(format!("No response received: {}", e)))
265            .and_then(|mut response| {
266                match response.status.to_u16() {
267                    200 => {
268                        let mut body = Vec::new();
269                        response.read_to_end(&mut body)
270                            .map_err(|e| ApiError(format!("Failed to read response: {}", e)))?;
271
272                        str::from_utf8(&body)
273                            .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))
274                            .and_then(|body|
275                                 serde_json::from_str::<models::IssueCertificateResponse>(body)
276                                         .map_err(|e| e.into())
277
278                                 )
279                    },
280                    code => {
281                        let headers = response.headers.clone();
282                        let mut body = Vec::new();
283                        let result = response.read_to_end(&mut body);
284                        Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
285                            code,
286                            headers,
287                            match result {
288                                Ok(_) => match str::from_utf8(&body) {
289                                    Ok(body) => Cow::from(body),
290                                    Err(e) => Cow::from(format!("<Body was not UTF8: {:?}>", e)),
291                                },
292                                Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
293                            })))
294                    }
295                }
296        })
297
298    }
299
300}
301
302impl EnclaveApi for Client {
303
304    type Error = ApiError;
305
306
307    fn get_fortanix_attestation(&self, param_body: models::GetFortanixAttestationRequest) -> Result<models::GetFortanixAttestationResponse, ApiError> {
308        let mut url = format!(
309            "{}/v1/enclave/attest",
310            self.base_path
311        );
312
313        let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
314
315        let query_string_str = query_string.finish();
316        if !query_string_str.is_empty() {
317            url += "?";
318            url += &query_string_str;
319        }
320
321        let url = match Url::from_str(&url) {
322            Ok(url) => url,
323            Err(err) => return Err(ApiError(format!("Unable to build URL: {}", err))),
324        };
325
326        let mut request = self.hyper_client.request(Method::Post, url);
327        request = request.headers(self.headers.clone());
328        // Body parameter
329        let body = serde_json::to_string(&param_body).expect("impossible to fail to serialize");
330            request = request.body(body.as_bytes());
331
332        request = request.header(ContentType(mimetypes::requests::GET_FORTANIX_ATTESTATION.clone()));
333
334        request.send()
335            .map_err(|e| ApiError(format!("No response received: {}", e)))
336            .and_then(|mut response| {
337                match response.status.to_u16() {
338                    200 => {
339                        let mut body = Vec::new();
340                        response.read_to_end(&mut body)
341                            .map_err(|e| ApiError(format!("Failed to read response: {}", e)))?;
342
343                        str::from_utf8(&body)
344                            .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))
345                            .and_then(|body|
346                                 serde_json::from_str::<models::GetFortanixAttestationResponse>(body)
347                                         .map_err(|e| e.into())
348
349                                 )
350                    },
351                    code => {
352                        let headers = response.headers.clone();
353                        let mut body = Vec::new();
354                        let result = response.read_to_end(&mut body);
355                        Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
356                            code,
357                            headers,
358                            match result {
359                                Ok(_) => match str::from_utf8(&body) {
360                                    Ok(body) => Cow::from(body),
361                                    Err(e) => Cow::from(format!("<Body was not UTF8: {:?}>", e)),
362                                },
363                                Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
364                            })))
365                    }
366                }
367        })
368
369    }
370
371    fn get_target_info(&self) -> Result<models::TargetInfo, ApiError> {
372        let mut url = format!(
373            "{}/v1/enclave/target-info",
374            self.base_path
375        );
376
377        let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
378
379        let query_string_str = query_string.finish();
380        if !query_string_str.is_empty() {
381            url += "?";
382            url += &query_string_str;
383        }
384
385        let url = match Url::from_str(&url) {
386            Ok(url) => url,
387            Err(err) => return Err(ApiError(format!("Unable to build URL: {}", err))),
388        };
389
390        let mut request = self.hyper_client.request(Method::Get, url);
391        request = request.headers(self.headers.clone());
392
393        request.send()
394            .map_err(|e| ApiError(format!("No response received: {}", e)))
395            .and_then(|mut response| {
396                match response.status.to_u16() {
397                    200 => {
398                        let mut body = Vec::new();
399                        response.read_to_end(&mut body)
400                            .map_err(|e| ApiError(format!("Failed to read response: {}", e)))?;
401
402                        str::from_utf8(&body)
403                            .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))
404                            .and_then(|body|
405                                 serde_json::from_str::<models::TargetInfo>(body)
406                                         .map_err(|e| e.into())
407
408                                 )
409                    },
410                    code => {
411                        let headers = response.headers.clone();
412                        let mut body = Vec::new();
413                        let result = response.read_to_end(&mut body);
414                        Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
415                            code,
416                            headers,
417                            match result {
418                                Ok(_) => match str::from_utf8(&body) {
419                                    Ok(body) => Cow::from(body),
420                                    Err(e) => Cow::from(format!("<Body was not UTF8: {:?}>", e)),
421                                },
422                                Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
423                            })))
424                    }
425                }
426        })
427
428    }
429
430}
431
432impl SystemApi for Client {
433
434    type Error = ApiError;
435
436
437    fn get_agent_version(&self) -> Result<models::VersionResponse, ApiError> {
438        let mut url = format!(
439            "{}/v1/sys/version",
440            self.base_path
441        );
442
443        let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
444
445        let query_string_str = query_string.finish();
446        if !query_string_str.is_empty() {
447            url += "?";
448            url += &query_string_str;
449        }
450
451        let url = match Url::from_str(&url) {
452            Ok(url) => url,
453            Err(err) => return Err(ApiError(format!("Unable to build URL: {}", err))),
454        };
455
456        let mut request = self.hyper_client.request(Method::Get, url);
457        request = request.headers(self.headers.clone());
458
459        request.send()
460            .map_err(|e| ApiError(format!("No response received: {}", e)))
461            .and_then(|mut response| {
462                match response.status.to_u16() {
463                    200 => {
464                        let mut body = Vec::new();
465                        response.read_to_end(&mut body)
466                            .map_err(|e| ApiError(format!("Failed to read response: {}", e)))?;
467
468                        str::from_utf8(&body)
469                            .map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))
470                            .and_then(|body|
471                                 serde_json::from_str::<models::VersionResponse>(body)
472                                         .map_err(|e| e.into())
473
474                                 )
475                    },
476                    code => {
477                        let headers = response.headers.clone();
478                        let mut body = Vec::new();
479                        let result = response.read_to_end(&mut body);
480                        Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
481                            code,
482                            headers,
483                            match result {
484                                Ok(_) => match str::from_utf8(&body) {
485                                    Ok(body) => Cow::from(body),
486                                    Err(e) => Cow::from(format!("<Body was not UTF8: {:?}>", e)),
487                                },
488                                Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
489                            })))
490                    }
491                }
492        })
493
494    }
495
496}
497
498
499#[derive(Debug)]
500pub enum ClientInitError {
501    InvalidScheme,
502    InvalidUri(hyper::error::ParseError),
503    MissingHost,
504}
505
506impl From<hyper::error::ParseError> for ClientInitError {
507    fn from(err: hyper::error::ParseError) -> ClientInitError {
508        ClientInitError::InvalidUri(err)
509    }
510}
511
512impl fmt::Display for ClientInitError {
513    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
514        <Self as fmt::Debug>::fmt(self, f)
515    }
516}
517
518impl error::Error for ClientInitError {
519    fn description(&self) -> &str {
520        "Failed to produce a hyper client."
521    }
522}