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    /// VPC instance
31    Vpc,
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::Vpc => (
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        "{}\n{}\n{}\n{}\n{}\n{}",
122        http_request_method,
123        canonical_uri,
124        canonical_querystring,
125        canonical_headers,
126        signed_headers,
127        hashed_request_payload
128    );
129
130    let credential_scope = format!("{}/{}/tc3_request", date, service);
131    let hashed_canonical_request = {
132        let mut hasher = Sha256::new();
133        hasher.update(canonical_request.as_bytes());
134        format!("{:x}", hasher.finalize())
135    };
136    let string_to_sign = format!(
137        "{}\n{}\n{}\n{}",
138        algorithm, timestamp, credential_scope, hashed_canonical_request
139    );
140
141    let secret_date = sign(
142        format!("TC3{}", basic_request.secret.secret_key).as_bytes(),
143        &date,
144    );
145    let secret_service = sign(&secret_date, service);
146    let secret_signing = sign(&secret_service, "tc3_request");
147    let signature = hmac256(&secret_signing, &string_to_sign)
148        .map_err(Error::msg)
149        .map(hex::encode)?;
150
151    let authorization = format!(
152        "{} Credential={}/{}, SignedHeaders={}, Signature={}",
153        algorithm,
154        basic_request.secret.secret_id,
155        credential_scope,
156        signed_headers,
157        signature
158    );
159
160    let mut headers = HeaderMap::new();
161    headers.insert("Authorization", authorization.parse()?);
162    headers.insert("Content-Type", ct.parse()?);
163    headers.insert("Host", host.parse()?);
164    headers.insert("X-TC-Action", basic_request.action.parse()?);
165    headers.insert("X-TC-Timestamp", timestamp.to_string().parse()?);
166    headers.insert("X-TC-Version", version.parse()?);
167    headers.insert("X-TC-Region", basic_request.region.parse()?);
168
169    Ok(client
170        .post(endpoint)
171        .headers(headers)
172        .body(basic_request.payload)
173        .build()?)
174}
175
176fn to_error_response(response: &str) -> Option<SdkError> {
177    let response: Value = if let Ok(response) = from_str(response) {
178        response
179    } else {
180        return None;
181    };
182    let response = response.get("Response")?;
183    let request_id = response.get("RequestId")?.as_str()?.to_string();
184    let error = response.get("Error")?;
185    let code = error.get("Code")?.as_str()?.to_string();
186    let message = error.get("Message")?.as_str()?.to_string();
187    Some(SdkError {
188        request_id,
189        code,
190        message,
191    })
192}
193
194pub(super) fn parse_response<'a, T: Deserialize<'a>>(
195    result: &'a str,
196) -> Result<T> {
197    if let Some(error) = to_error_response(result) {
198        Err(error.into())
199    } else {
200        Ok(from_str::<T>(result)?)
201    }
202}
203
204pub(super) fn sign(key: &[u8], msg: &str) -> Vec<u8> {
205    let mut mac = Hmac::<Sha256>::new_from_slice(key).unwrap();
206    mac.update(msg.as_bytes());
207    mac.finalize().into_bytes().to_vec()
208}