Skip to main content

solo_lib/sdk/qcloud/
util.rs

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/// Secret
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Secret {
20    pub secret_id: String,
21    pub secret_key: String,
22}
23
24/// Machine Type
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub enum MachineType {
27    /// Lighthouse instance
28    Lighthouse,
29
30    /// Cvm instance
31    Cvm,
32}
33
34impl MachineType {
35    /// Returns (service, host, version, endpoint)
36    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/// Common response
57///
58/// The response from the API is wrapped in this struct
59#[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/// An empty struct, used for requests that don't require a payload
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct Empty {}
76
77/// Error response
78#[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, // 改为静态字符串
88    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}