1use std::{result::Result::Ok, time::SystemTime};
2
3use anyhow::{Error, Result};
4use chrono::Utc;
5use hmac::{Hmac, Mac};
6use http::HeaderMap;
7use reqwest::{Client, Request};
8use serde::{Deserialize, Serialize};
9use serde_json::{Value, from_str};
10use sha2::{Digest, Sha256};
11
12use crate::{
13 error::SdkError,
14 util::{hmac256, sha256_hex},
15};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Secret {
20 pub secret_id: String,
21 pub secret_key: String,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub enum MachineType {
27 Lighthouse,
29
30 Cvm,
32}
33
34impl MachineType {
35 fn service_info(
37 &self,
38 ) -> (&'static str, &'static str, &'static str, &'static str) {
39 match self {
40 MachineType::Lighthouse => (
41 "lighthouse",
42 "lighthouse.tencentcloudapi.com",
43 "2020-03-24",
44 "https://lighthouse.tencentcloudapi.com",
45 ),
46 MachineType::Cvm => (
47 "vpc",
48 "vpc.tencentcloudapi.com",
49 "2017-03-12",
50 "https://vpc.tencentcloudapi.com",
51 ),
52 }
53 }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct CommonResponse<T> {
61 #[serde(rename = "Response")]
62 pub response: ResponseWrapper<T>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ResponseWrapper<T> {
67 #[serde(flatten)]
68 pub data: T,
69 #[serde(rename = "RequestId")]
70 pub request_id: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct Empty {}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ErrorResponse {
80 #[serde(rename = "Error")]
81 pub error: SdkError,
82}
83
84#[derive(Debug, Clone)]
85pub(super) struct BasicRequest<'a> {
86 pub(super) machine_type: MachineType,
87 pub(super) action: &'static str, pub(super) payload: String,
89 pub(super) region: String,
90 pub(super) secret: &'a Secret,
91}
92
93pub(super) fn request_builder(
94 client: &Client,
95 basic_request: BasicRequest,
96) -> Result<Request> {
97 let (service, host, version, endpoint) =
98 basic_request.machine_type.service_info();
99
100 let algorithm = "TC3-HMAC-SHA256";
101 let timestamp = SystemTime::now()
102 .duration_since(SystemTime::UNIX_EPOCH)?
103 .as_secs();
104 let date = Utc::now().format("%Y-%m-%d").to_string();
105
106 let http_request_method = "POST";
107 let canonical_uri = "/";
108 let canonical_querystring = "";
109 let ct = "application/json";
110
111 let canonical_headers = format!(
112 "content-type:{}\nhost:{}\nx-tc-action:{}\n",
113 ct,
114 host,
115 basic_request.action.to_lowercase()
116 );
117
118 let signed_headers = "content-type;host;x-tc-action";
119 let hashed_request_payload = sha256_hex(&basic_request.payload);
120 let canonical_request = format!(
121 "{http_request_method}\n{canonical_uri}\n{canonical_querystring}\n{canonical_headers}\n{signed_headers}\n{hashed_request_payload}"
122 );
123
124 let credential_scope = format!("{date}/{service}/tc3_request");
125 let hashed_canonical_request = {
126 let mut hasher = Sha256::new();
127 hasher.update(canonical_request.as_bytes());
128 format!("{:x}", hasher.finalize())
129 };
130 let string_to_sign = format!(
131 "{algorithm}\n{timestamp}\n{credential_scope}\n{hashed_canonical_request}"
132 );
133
134 let secret_date = sign(
135 format!("TC3{}", basic_request.secret.secret_key).as_bytes(),
136 &date,
137 );
138 let secret_service = sign(&secret_date, service);
139 let secret_signing = sign(&secret_service, "tc3_request");
140 let signature = hmac256(&secret_signing, &string_to_sign)
141 .map_err(Error::msg)
142 .map(hex::encode)?;
143
144 let authorization = format!(
145 "{} Credential={}/{}, SignedHeaders={}, Signature={}",
146 algorithm,
147 basic_request.secret.secret_id,
148 credential_scope,
149 signed_headers,
150 signature
151 );
152
153 let mut headers = HeaderMap::new();
154 headers.insert("Authorization", authorization.parse()?);
155 headers.insert("Content-Type", ct.parse()?);
156 headers.insert("Host", host.parse()?);
157 headers.insert("X-TC-Action", basic_request.action.parse()?);
158 headers.insert("X-TC-Timestamp", timestamp.to_string().parse()?);
159 headers.insert("X-TC-Version", version.parse()?);
160 headers.insert("X-TC-Region", basic_request.region.parse()?);
161
162 Ok(client
163 .post(endpoint)
164 .headers(headers)
165 .body(basic_request.payload)
166 .build()?)
167}
168
169fn to_error_response(response: &str) -> Option<SdkError> {
170 let response: Value = if let Ok(response) = from_str(response) {
171 response
172 } else {
173 return None;
174 };
175 let response = response.get("Response")?;
176 let request_id = response.get("RequestId")?.as_str()?.to_string();
177 let error = response.get("Error")?;
178 let code = error.get("Code")?.as_str()?.to_string();
179 let message = error.get("Message")?.as_str()?.to_string();
180 Some(SdkError {
181 request_id,
182 code,
183 message,
184 })
185}
186
187pub(super) fn parse_response<'a, T: Deserialize<'a>>(
188 result: &'a str,
189) -> Result<T> {
190 if let Some(error) = to_error_response(result) {
191 Err(error.into())
192 } else {
193 Ok(from_str::<T>(result)?)
194 }
195}
196
197pub(super) fn sign(key: &[u8], msg: &str) -> Vec<u8> {
198 let mut mac = Hmac::<Sha256>::new_from_slice(key).unwrap();
199 mac.update(msg.as_bytes());
200 mac.finalize().into_bytes().to_vec()
201}