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