1use chrono::{TimeZone, Utc};
2use reqwest::header::{HeaderMap, HeaderValue};
3use reqwest::Client;
4use serde_json::Value;
5use sha2::{Digest, Sha256};
6use std::error::Error;
7
8use crate::signing::hmac_sha256;
9use hmac::{Hmac, Mac};
10
11type HmacSha256 = Hmac<Sha256>;
12
13pub struct TencentCloudClient {
20 pub secret_id: String,
22 pub secret_key: String,
24 pub token: Option<String>,
26}
27
28impl TencentCloudClient {
29 pub fn new(secret_id: &str, secret_key: &str, token: Option<&str>) -> Self {
37 Self {
38 secret_id: secret_id.to_owned(),
39 secret_key: secret_key.to_owned(),
40 token: token.map(|s| s.to_owned()),
41 }
42 }
43
44 pub async fn request(
66 &self,
67 service: &str,
68 host: &str,
69 region: Option<&str>,
70 version: &str,
71 action: &str,
72 payload: &str,
73 ) -> Result<Value, Box<dyn Error>> {
74 let algorithm = "TC3-HMAC-SHA256";
75 let ct = "application/json; charset=utf-8";
76
77 let http_request_method = "POST";
79 let canonical_uri = "/";
80 let canonical_querystring = "";
81 let canonical_headers = format!(
82 "content-type:{}\nhost:{}\nx-tc-action:{}\n",
83 ct,
84 host,
85 action.to_lowercase()
86 );
87 let signed_headers = "content-type;host;x-tc-action";
88 let hashed_request_payload = {
89 let mut hasher = Sha256::new();
90 hasher.update(payload.as_bytes());
91 format!("{:x}", hasher.finalize())
92 };
93 let canonical_request = format!(
94 "{}\n{}\n{}\n{}\n{}\n{}",
95 http_request_method,
96 canonical_uri,
97 canonical_querystring,
98 canonical_headers,
99 signed_headers,
100 hashed_request_payload
101 );
102
103 let timestamp = Utc::now().timestamp();
105 let date = Utc.timestamp(timestamp, 0).format("%Y-%m-%d").to_string();
106 let credential_scope = format!("{}/{}/tc3_request", date, service);
107 let hashed_canonical_request = {
108 let mut hasher = Sha256::new();
109 hasher.update(canonical_request.as_bytes());
110 format!("{:x}", hasher.finalize())
111 };
112 let string_to_sign = format!(
113 "{}\n{}\n{}\n{}",
114 algorithm, timestamp, credential_scope, hashed_canonical_request
115 );
116
117 let secret_date = hmac_sha256(format!("TC3{}", self.secret_key).as_bytes(), &date);
119 let secret_service = hmac_sha256(&secret_date, service);
120 let secret_signing = hmac_sha256(&secret_service, "tc3_request");
121 let signature = {
122 let mut mac = HmacSha256::new_from_slice(&secret_signing)
123 .expect("HMAC can accept any key length");
124 mac.update(string_to_sign.as_bytes());
125 format!("{:x}", mac.finalize().into_bytes())
126 };
127
128 let authorization = format!(
130 "{} Credential={}/{}, SignedHeaders={}, Signature={}",
131 algorithm, self.secret_id, credential_scope, signed_headers, signature
132 );
133
134 let mut headers = HeaderMap::new();
136 headers.insert("Authorization", HeaderValue::from_str(&authorization)?);
137 headers.insert("Content-Type", HeaderValue::from_static(ct));
138 headers.insert("Host", HeaderValue::from_str(host)?);
139 headers.insert("X-TC-Action", HeaderValue::from_str(action)?);
140 headers.insert("X-TC-Timestamp", HeaderValue::from_str(×tamp.to_string())?);
141 headers.insert("X-TC-Version", HeaderValue::from_str(version)?);
142 if let Some(r) = region {
143 headers.insert("X-TC-Region", HeaderValue::from_str(r)?);
144 }
145 if let Some(t) = &self.token {
146 headers.insert("X-TC-Token", HeaderValue::from_str(t)?);
147 }
148
149 let url = format!("https://{}", host);
150 let client = reqwest::Client::builder()
151 .no_proxy()
152 .build()
153 .expect("build Client error");
154 let resp_json: Value = client
155 .post(&url)
156 .headers(headers)
157 .body(payload.to_owned())
158 .send()
159 .await?
160 .json()
161 .await?;
162 Ok(resp_json)
163 }
164}