1use std::collections::HashMap;
2use std::result;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use hmac::{Hmac, Mac};
6use http::{request, Method, Request, Uri, Version};
7use hyper::Body;
8use sha2::Sha256;
9
10#[derive(Debug)]
11pub struct Error {}
12
13pub type Result<T> = result::Result<T, Error>;
14
15type HmacSha256 = Hmac<Sha256>;
16
17const USER_AGENT: &str = concat!("coinbase-rs/", env!("CARGO_PKG_VERSION"));
18
19#[derive(Clone, Debug, Default)]
20pub struct Parts {
21 pub method: Method,
23
24 pub uri: Uri,
26
27 pub version: Version,
29
30 pub headers: HashMap<String, String>,
32}
33
34#[derive(Clone, Debug, Default)]
35pub struct Builder {
36 auth: Option<(String, String)>,
37 parts: Parts,
38 body: Vec<u8>,
39}
40
41impl Builder {
42 pub fn new() -> Builder {
43 Builder {
44 auth: None,
45 parts: Parts {
46 method: Method::GET,
47 uri: "/".parse().unwrap(),
48 version: Version::default(),
49 headers: HashMap::new(),
50 },
51 body: Vec::new(),
52 }
53 }
54
55 pub fn new_with_auth(key: &str, secret: &str) -> Builder {
56 Builder {
57 auth: Some((key.to_string(), secret.to_string())),
58 parts: Parts {
59 method: Method::GET,
60 uri: "/".parse().unwrap(),
61 version: Version::default(),
62 headers: HashMap::new(),
63 },
64 body: Vec::new(),
65 }
66 }
67
68 pub fn method(self, method: Method) -> Builder {
69 let mut _self = self;
70 _self.parts.method = method;
71 _self
72 }
73
74 pub fn uri(self, uri: Uri) -> Builder {
75 let mut _self = self;
76 _self.parts.uri = uri;
77 _self
78 }
79
80 pub fn version(self, version: Version) -> Builder {
81 let mut _self = self;
82 _self.parts.version = version;
83 _self
84 }
85
86 pub fn header(self, key: &str, value: &str) -> Builder {
87 let mut _self = self;
88 _self.parts.headers.insert(key.into(), value.into());
89 _self
90 }
91
92 pub fn body(self, body: &Vec<u8>) -> Builder {
93 let mut _self = self;
94 _self.body = body.clone();
95 _self
96 }
97
98 pub fn build(self) -> Request<Body> {
99 let _self = if let Some((ref key, ref secret)) = self.auth {
100 let timestamp = SystemTime::now()
101 .duration_since(UNIX_EPOCH)
102 .expect("leap-second")
103 .as_secs();
104
105 let sign = Self::sign(
106 secret,
107 timestamp,
108 &self.parts.method,
109 self.parts.uri.path_and_query().unwrap().as_str(),
110 &self.body,
111 );
112
113 self.clone()
114 .header("User-Agent", USER_AGENT)
115 .header("Content-Type", "Application/JSON")
116 .header("CB-VERSION", "2021-01-01")
117 .header("CB-ACCESS-KEY", key)
118 .header("CB-ACCESS-SIGN", &sign)
119 .header("CB-ACCESS-TIMESTAMP", ×tamp.to_string())
120 } else {
121 self
122 };
123
124 let mut builder = request::Builder::new()
125 .method(_self.parts.method)
126 .uri(_self.parts.uri);
127 for (key, value) in _self.parts.headers {
128 builder = builder.header(&key, &value);
129 }
130 builder.body(_self.body.into()).unwrap()
131 }
132
133 fn sign(secret: &str, timestamp: u64, method: &Method, path: &str, body: &Vec<u8>) -> String {
134 let mut mac: Hmac<Sha256> =
135 HmacSha256::new_varkey(&secret.as_bytes()).expect("Hmac::new(secret)");
136 let input = timestamp.to_string() + method.as_str() + path;
137 mac.input(input.as_bytes());
138 mac.input(body);
139 format!("{:x}", &mac.result().code())
140 }
141}