Implement cloud connect
This commit is contained in:
parent
e7527ddc40
commit
99819f36d9
8 changed files with 323 additions and 103 deletions
|
@ -18,3 +18,4 @@ sha2 = "0.10.7"
|
||||||
reqwest = "0.11.20"
|
reqwest = "0.11.20"
|
||||||
base64 = "0.21.4"
|
base64 = "0.21.4"
|
||||||
tokio = { version = "1.32.0", features=["macros", "rt-multi-thread"] }
|
tokio = { version = "1.32.0", features=["macros", "rt-multi-thread"] }
|
||||||
|
md5 = "0.7.0"
|
||||||
|
|
4
cloud.py
4
cloud.py
|
@ -89,6 +89,10 @@ class MideaCloud:
|
||||||
"accesstoken": self._access_token
|
"accesstoken": self._access_token
|
||||||
})
|
})
|
||||||
response:dict = {"code": -1}
|
response:dict = {"code": -1}
|
||||||
|
|
||||||
|
print(header)
|
||||||
|
print(dump_data)
|
||||||
|
|
||||||
for i in range(0, 3):
|
for i in range(0, 3):
|
||||||
try:
|
try:
|
||||||
with self._api_lock:
|
with self._api_lock:
|
||||||
|
|
18
midea.py
18
midea.py
|
@ -6,17 +6,17 @@ import discover;
|
||||||
import device;
|
import device;
|
||||||
|
|
||||||
async def test():
|
async def test():
|
||||||
devices = discover.discover()
|
cl = cloud.MSmartHomeCloud(
|
||||||
|
"MSmartHome",
|
||||||
|
aiohttp.ClientSession(),
|
||||||
|
"michaelh.95@t-online.de",
|
||||||
|
"Hoda.semi1"
|
||||||
|
)
|
||||||
|
|
||||||
for device_id in devices:
|
if await cl.login():
|
||||||
cl = cloud.MSmartHomeCloud(
|
devices = discover.discover()
|
||||||
"MSmartHome",
|
|
||||||
aiohttp.ClientSession(),
|
|
||||||
"michaelh.95@t-online.de",
|
|
||||||
"Hoda.semi1"
|
|
||||||
)
|
|
||||||
|
|
||||||
if await cl.login():
|
for device_id in devices:
|
||||||
keys = await cl.get_keys(device_id)
|
keys = await cl.get_keys(device_id)
|
||||||
|
|
||||||
for k in keys:
|
for k in keys:
|
||||||
|
|
25
security.py
25
security.py
|
@ -38,12 +38,20 @@ class CloudSecurity:
|
||||||
return hex
|
return hex
|
||||||
|
|
||||||
def encrypt_password(self, login_id, data):
|
def encrypt_password(self, login_id, data):
|
||||||
|
login_id = "bd3937c1-49ba-418c-a6e0-4b600263"
|
||||||
|
data = "Hoda.semi1"
|
||||||
|
|
||||||
m = sha256()
|
m = sha256()
|
||||||
m.update(data.encode("ascii"))
|
m.update(data.encode("ascii"))
|
||||||
login_hash = login_id + m.hexdigest() + self._login_key
|
m_hex = m.hexdigest()
|
||||||
|
|
||||||
|
login_hash = login_id + m_hex + self._login_key
|
||||||
|
|
||||||
m = sha256()
|
m = sha256()
|
||||||
m.update(login_hash.encode("ascii"))
|
m.update(login_hash.encode("ascii"))
|
||||||
return m.hexdigest()
|
m2_hex = m.hexdigest()
|
||||||
|
|
||||||
|
return m2_hex
|
||||||
|
|
||||||
def encrypt_iam_password(self, login_id, data) -> str:
|
def encrypt_iam_password(self, login_id, data) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -136,12 +144,19 @@ class MSmartCloudSecurity(CloudSecurity):
|
||||||
def encrypt_iam_password(self, login_id, data) -> str:
|
def encrypt_iam_password(self, login_id, data) -> str:
|
||||||
md = md5()
|
md = md5()
|
||||||
md.update(data.encode("ascii"))
|
md.update(data.encode("ascii"))
|
||||||
|
md_hex = md.hexdigest()
|
||||||
|
|
||||||
md_second = md5()
|
md_second = md5()
|
||||||
md_second.update(md.hexdigest().encode("ascii"))
|
md_second.update(md_hex.encode("ascii"))
|
||||||
login_hash = login_id + md_second.hexdigest() + self._login_key
|
md_second_hex = md_second.hexdigest()
|
||||||
|
|
||||||
|
login_hash = login_id + md_second_hex + self._login_key
|
||||||
|
|
||||||
sha = sha256()
|
sha = sha256()
|
||||||
sha.update(login_hash.encode("ascii"))
|
sha.update(login_hash.encode("ascii"))
|
||||||
return sha.hexdigest()
|
sha_hex = sha.hexdigest()
|
||||||
|
|
||||||
|
return sha_hex
|
||||||
|
|
||||||
def set_aes_keys(self, encrypted_key, encrypted_iv):
|
def set_aes_keys(self, encrypted_key, encrypted_iv):
|
||||||
key_digest = sha256(self._login_key.encode("ascii")).hexdigest()
|
key_digest = sha256(self._login_key.encode("ascii")).hexdigest()
|
||||||
|
|
303
src/cloud.rs
303
src/cloud.rs
|
@ -1,10 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use base64::{engine::general_purpose, Engine};
|
use base64::{engine::general_purpose, Engine};
|
||||||
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
|
@ -14,7 +15,7 @@ use serde_json::{from_str, to_string};
|
||||||
use crate::cloud_security::CloudSecurity;
|
use crate::cloud_security::CloudSecurity;
|
||||||
|
|
||||||
pub struct Cloud {
|
pub struct Cloud {
|
||||||
device_id: u64,
|
device_id: String,
|
||||||
uid: Option<String>,
|
uid: Option<String>,
|
||||||
api_url: String,
|
api_url: String,
|
||||||
access_token: Option<String>,
|
access_token: Option<String>,
|
||||||
|
@ -27,6 +28,15 @@ pub struct Cloud {
|
||||||
security: CloudSecurity,
|
security: CloudSecurity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct Input {
|
||||||
|
iotData: HashMap<String, String>,
|
||||||
|
data: HashMap<String, String>,
|
||||||
|
stamp: String,
|
||||||
|
reqId: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Cloud {
|
impl Cloud {
|
||||||
pub const APP_ID: &str = "1010";
|
pub const APP_ID: &str = "1010";
|
||||||
pub const APP_KEY: &str = "ac21b9f9cbfe4ca5a88562ef25e2b768";
|
pub const APP_KEY: &str = "ac21b9f9cbfe4ca5a88562ef25e2b768";
|
||||||
|
@ -35,12 +45,12 @@ impl Cloud {
|
||||||
pub const SRC: &str = "10";
|
pub const SRC: &str = "10";
|
||||||
pub const IOT_KEY: &str = "meicloud";
|
pub const IOT_KEY: &str = "meicloud";
|
||||||
pub const HMAC_KEY: &str = "PROD_VnoClJI9aikS8dyy";
|
pub const HMAC_KEY: &str = "PROD_VnoClJI9aikS8dyy";
|
||||||
// pub const IOT_KEY: &str = "9795516279659324117647275084689641883661667";
|
|
||||||
// pub const HMAC_KEY: &str = "117390035944627627450677220413733956185864939010425";
|
|
||||||
|
|
||||||
pub fn new(account: impl ToString, password: impl ToString, device_id: u64) -> Result<Self> {
|
pub fn new(account: impl ToString, password: impl ToString) -> Result<Self> {
|
||||||
|
let account = account.to_string();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
device_id,
|
device_id: CloudSecurity::device_id(&account),
|
||||||
uid: None,
|
uid: None,
|
||||||
api_url: "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=".to_string(),
|
api_url: "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=".to_string(),
|
||||||
access_token: None,
|
access_token: None,
|
||||||
|
@ -48,7 +58,7 @@ impl Cloud {
|
||||||
.encode(format!("{}:{}", Self::APP_KEY, Self::IOT_KEY).as_bytes()),
|
.encode(format!("{}:{}", Self::APP_KEY, Self::IOT_KEY).as_bytes()),
|
||||||
login_id: None,
|
login_id: None,
|
||||||
|
|
||||||
account: account.to_string(),
|
account,
|
||||||
password: password.to_string(),
|
password: password.to_string(),
|
||||||
|
|
||||||
security: CloudSecurity::new(Self::APP_KEY, Self::IOT_KEY, Self::HMAC_KEY),
|
security: CloudSecurity::new(Self::APP_KEY, Self::IOT_KEY, Self::HMAC_KEY),
|
||||||
|
@ -94,14 +104,13 @@ impl Cloud {
|
||||||
async fn api_request(
|
async fn api_request(
|
||||||
&self,
|
&self,
|
||||||
endpoint: &str,
|
endpoint: &str,
|
||||||
data: HashMap<String, String>,
|
dump_data: String,
|
||||||
header: Option<HeaderMap>,
|
header: Option<HeaderMap>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let mut header = header.unwrap_or_default();
|
let mut header = header.unwrap_or_default();
|
||||||
|
|
||||||
let url = format!("{}{}", self.api_url, endpoint);
|
let url = format!("{}{}", self.api_url, endpoint);
|
||||||
|
|
||||||
let dump_data = to_string(&data)?;
|
|
||||||
let random = SystemTime::now()
|
let random = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)?
|
.duration_since(UNIX_EPOCH)?
|
||||||
.as_secs()
|
.as_secs()
|
||||||
|
@ -125,11 +134,19 @@ impl Cloud {
|
||||||
header.insert("accesstoken", access_token.parse().unwrap());
|
header.insert("accesstoken", access_token.parse().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(_uid) = &self.uid {
|
||||||
|
match header.get_mut("uid") {
|
||||||
|
Some(uid) => *uid = _uid.parse().unwrap(),
|
||||||
|
None => {
|
||||||
|
header.insert("uid", _uid.parse().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(reqwest::Client::new()
|
Ok(reqwest::Client::new()
|
||||||
.post(url)
|
.post(url)
|
||||||
.headers(header)
|
.headers(header)
|
||||||
.body(dump_data)
|
.body(dump_data)
|
||||||
.timeout(Duration::from_secs(10))
|
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.text()
|
.text()
|
||||||
|
@ -142,31 +159,35 @@ impl Cloud {
|
||||||
data.insert("userType".to_string(), "0".to_string());
|
data.insert("userType".to_string(), "0".to_string());
|
||||||
data.insert("userName".to_string(), format!("{}", self.account));
|
data.insert("userName".to_string(), format!("{}", self.account));
|
||||||
|
|
||||||
let response: Response = from_str(
|
let response = Self::parse_response(
|
||||||
&self
|
self.api_request(
|
||||||
.api_request("/v1/multicloud/platform/user/route", data, None)
|
"/v1/multicloud/platform/user/route",
|
||||||
.await?,
|
to_string(&data)?,
|
||||||
)?;
|
None,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
|
.ok_or(Error::msg("failed parsing response"))?;
|
||||||
|
|
||||||
if let Some(api_url) = response.data.get("masUrl") {
|
if let Some(api_url) = response.normal_data().get("masUrl") {
|
||||||
self.api_url = api_url.clone();
|
self.api_url = api_url.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn reqest_login_id(&mut self) -> Result<()> {
|
async fn request_login_id(&mut self) -> Result<()> {
|
||||||
let mut data = self.make_general_data();
|
let mut data = self.make_general_data();
|
||||||
data.insert("loginAccount".to_string(), format!("{}", self.account));
|
data.insert("loginAccount".to_string(), format!("{}", self.account));
|
||||||
|
|
||||||
let response: Response = from_str(
|
let response = Self::parse_response(
|
||||||
&self
|
self.api_request("/v1/user/login/id/get", to_string(&data)?, None)
|
||||||
.api_request("/v1/user/login/id/get", data, None)
|
|
||||||
.await?,
|
.await?,
|
||||||
)?;
|
)
|
||||||
|
.ok_or(Error::msg("failed parsing response"))?;
|
||||||
|
|
||||||
let login_id = response
|
let login_id = response
|
||||||
.data
|
.normal_data()
|
||||||
.get("loginId")
|
.get("loginId")
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or(Error::msg("failed to request loginId"))?;
|
.ok_or(Error::msg("failed to request loginId"))?;
|
||||||
|
@ -176,70 +197,180 @@ impl Cloud {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn login(&mut self) -> Result<()> {
|
pub async fn login(&mut self) -> Result<()> {
|
||||||
self.reroute().await?;
|
self.reroute().await?;
|
||||||
self.reqest_login_id().await?;
|
self.request_login_id().await?;
|
||||||
|
|
||||||
let mut iot_data = self.make_general_data();
|
let mut iot_data = self.make_general_data();
|
||||||
iot_data.remove("uid");
|
iot_data.remove("uid");
|
||||||
iot_data.insert(
|
iot_data.insert(
|
||||||
"iampwd".to_string(),
|
"iampwd".to_string(),
|
||||||
self.security
|
self.security
|
||||||
.encrypt_iam_password(self.login_id.clone().unwrap(), self.password),
|
.encrypt_iam_password(&self.login_id.clone().unwrap(), &self.password),
|
||||||
);
|
);
|
||||||
iot_data.insert("loginAccount".to_string(), self.account.clone());
|
iot_data.insert("loginAccount".to_string(), self.account.clone());
|
||||||
iot_data.insert(
|
iot_data.insert(
|
||||||
"password".to_string(),
|
"password".to_string(),
|
||||||
self.security
|
self.security
|
||||||
.encrypt_password(self.login_id.clone().unwrap(), self.password),
|
.encrypt_password(&self.login_id.clone().unwrap(), &self.password),
|
||||||
);
|
);
|
||||||
let stamp = iot_data.get("stamp").unwrap().clone();
|
let stamp = iot_data.get("stamp").unwrap().clone();
|
||||||
|
|
||||||
let iot_data_dump = to_string(&iot_data)?;
|
let i = Input {
|
||||||
|
iotData: iot_data,
|
||||||
|
data: from_str(
|
||||||
|
&to_string(&Data {
|
||||||
|
appKey: Self::APP_KEY,
|
||||||
|
deviceId: self.device_id.to_string().as_str(),
|
||||||
|
platform: "2",
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
stamp,
|
||||||
|
reqId: "25f278357a1b1c08cf878b05ade7db26".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut data = HashMap::new();
|
let dump_i = to_string(&i).unwrap();
|
||||||
data.insert("iotData".to_string(), iot_data_dump);
|
|
||||||
data.insert(
|
|
||||||
"data".to_string(),
|
|
||||||
to_string(&Data {
|
|
||||||
appKey: Self::APP_KEY,
|
|
||||||
deviceId: self.device_id.to_string().as_str(),
|
|
||||||
platform: "2",
|
|
||||||
})?,
|
|
||||||
);
|
|
||||||
data.insert("stamp".to_string(), stamp);
|
|
||||||
|
|
||||||
let response: Response = from_str(&self.api_request("/mj/user/login", data, None).await?)?;
|
let response =
|
||||||
|
Self::parse_response(self.api_request("/mj/user/login", dump_i, None).await?)
|
||||||
|
.ok_or(Error::msg("failed parsing response"))?;
|
||||||
|
|
||||||
self.uid = Some(
|
self.uid = Some(response.nested_data().uid.clone());
|
||||||
response
|
self.access_token = Some(response.nested_data().mdata.accessToken.clone());
|
||||||
.data
|
|
||||||
.get("uid")
|
|
||||||
.cloned()
|
|
||||||
.ok_or(Error::msg("failed to request uid"))?,
|
|
||||||
);
|
|
||||||
self.access_token = Some(
|
|
||||||
response
|
|
||||||
.data
|
|
||||||
.get("accessToken")
|
|
||||||
.cloned()
|
|
||||||
.ok_or(Error::msg("failed to request uid"))?,
|
|
||||||
);
|
|
||||||
self.security.set_aes_keys(
|
self.security.set_aes_keys(
|
||||||
&response
|
&response.nested_data().accessToken,
|
||||||
.data
|
&response.nested_data().randomData,
|
||||||
.get("accessToken")
|
|
||||||
.cloned()
|
|
||||||
.ok_or(Error::msg("failed to request accessToken"))?,
|
|
||||||
&response
|
|
||||||
.data
|
|
||||||
.get("randomData")
|
|
||||||
.cloned()
|
|
||||||
.ok_or(Error::msg("failed to request randomData"))?,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_response(s: String) -> Option<Response> {
|
||||||
|
if let Ok(r) = from_str::<ResponseNumber>(&s) {
|
||||||
|
return Some(Response::from(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(r) = from_str::<ResponseString>(&s) {
|
||||||
|
return Some(Response::from(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(r) = from_str::<ResponseMap>(&s) {
|
||||||
|
return Some(Response::from(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Response {
|
||||||
|
Normal(ResponseNumber),
|
||||||
|
NestedMap(ResponseMap),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
pub fn normal_data(&self) -> &HashMap<String, String> {
|
||||||
|
match self {
|
||||||
|
Self::Normal(n) => &n.data,
|
||||||
|
Self::NestedMap(_) => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nested_data(&self) -> &ResponseMapData {
|
||||||
|
match self {
|
||||||
|
Self::Normal(_) => panic!(),
|
||||||
|
Self::NestedMap(n) => &n.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResponseNumber> for Response {
|
||||||
|
fn from(value: ResponseNumber) -> Self {
|
||||||
|
Self::Normal(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResponseString> for Response {
|
||||||
|
fn from(value: ResponseString) -> Self {
|
||||||
|
Self::Normal(ResponseNumber {
|
||||||
|
msg: value.msg,
|
||||||
|
code: value.code.parse().unwrap(),
|
||||||
|
data: value.data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResponseMap> for Response {
|
||||||
|
fn from(value: ResponseMap) -> Self {
|
||||||
|
Self::NestedMap(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct ResponseNumber {
|
||||||
|
msg: String,
|
||||||
|
code: i32,
|
||||||
|
pub data: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct ResponseString {
|
||||||
|
msg: String,
|
||||||
|
code: String,
|
||||||
|
pub data: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct ResponseMap {
|
||||||
|
msg: String,
|
||||||
|
code: i32,
|
||||||
|
pub data: ResponseMapData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct ResponseMapData {
|
||||||
|
randomData: String,
|
||||||
|
uid: String,
|
||||||
|
accountId: String,
|
||||||
|
nickname: String,
|
||||||
|
mdata: MData,
|
||||||
|
accessToken: String,
|
||||||
|
userId: String,
|
||||||
|
email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct MData {
|
||||||
|
tokenPwdInfo: TokenPwdInfo,
|
||||||
|
userInfo: HashMap<String, Option<String>>,
|
||||||
|
doDeviceBind: Option<String>,
|
||||||
|
accessToken: String,
|
||||||
|
signUnlockEnabled: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct TokenPwdInfo {
|
||||||
|
tokenPwd: String,
|
||||||
|
expiredDate: u64,
|
||||||
|
createDate: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
struct Data<'a> {
|
||||||
|
appKey: &'a str,
|
||||||
|
deviceId: &'a str,
|
||||||
|
platform: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -247,36 +378,34 @@ mod test {
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use super::Cloud;
|
use super::Cloud;
|
||||||
use crate::discover::Startup;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_login() -> Result<()> {
|
async fn reroute() -> Result<()> {
|
||||||
let devices = Startup::discover()?;
|
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
||||||
|
|
||||||
println!("{devices:#?}");
|
cloud.reroute().await?;
|
||||||
|
|
||||||
for device_info in devices {
|
Ok(())
|
||||||
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1", device_info.id)?;
|
}
|
||||||
|
|
||||||
let id = cloud.login_id().await?;
|
#[tokio::test]
|
||||||
|
async fn login_id() -> Result<()> {
|
||||||
|
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
||||||
|
|
||||||
println!("{id:?}")
|
cloud.reroute().await?;
|
||||||
}
|
cloud.request_login_id().await?;
|
||||||
|
|
||||||
|
assert!(cloud.login_id.is_some());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn login() -> Result<()> {
|
||||||
|
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
||||||
|
|
||||||
|
cloud.login().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct Response {
|
|
||||||
msg: String,
|
|
||||||
code: i32,
|
|
||||||
pub data: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
|
||||||
struct Data<'a> {
|
|
||||||
appKey: &'a str,
|
|
||||||
deviceId: &'a str,
|
|
||||||
platform: &'a str,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use sha2::Sha256;
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
pub struct CloudSecurity {
|
pub struct CloudSecurity {
|
||||||
login_key: &'static str,
|
login_key: &'static str,
|
||||||
|
@ -45,6 +45,50 @@ impl CloudSecurity {
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("")
|
.join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn device_id(account: &str) -> String {
|
||||||
|
let mut sha = Sha256::digest(format!("Hello, {account}!").as_bytes())
|
||||||
|
.iter()
|
||||||
|
.map(|b| format!("{b:02x}"))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
sha.truncate(16);
|
||||||
|
|
||||||
|
sha
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt_iam_password(&self, login_id: &str, data: &str) -> String {
|
||||||
|
let md = md5::compute(data.as_bytes());
|
||||||
|
let md_hex = format!("{:x}", md);
|
||||||
|
|
||||||
|
let md_second = md5::compute(md_hex.as_bytes());
|
||||||
|
let md_second_hex = format!("{:x}", md_second);
|
||||||
|
|
||||||
|
let login_hash = format!("{}{}{}", login_id, md_second_hex, self.login_key);
|
||||||
|
|
||||||
|
Sha256::digest(login_hash.as_bytes())
|
||||||
|
.iter()
|
||||||
|
.map(|b| format!("{b:02x}"))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt_password(&self, login_id: &str, data: &str) -> String {
|
||||||
|
let m_hex = Sha256::digest(data.as_bytes())
|
||||||
|
.iter()
|
||||||
|
.map(|b| format!("{b:02x}"))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
let login_hash = format!("{}{}{}", login_id, m_hex, self.login_key);
|
||||||
|
|
||||||
|
Sha256::digest(login_hash.as_bytes())
|
||||||
|
.iter()
|
||||||
|
.map(|b| format!("{b:02x}"))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -52,4 +96,28 @@ mod test {
|
||||||
use crate::cloud::Cloud;
|
use crate::cloud::Cloud;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn iam_password() {
|
||||||
|
let sec = CloudSecurity::new(Cloud::APP_KEY, "", "");
|
||||||
|
|
||||||
|
let sha = sec.encrypt_iam_password("bd3937c1-49ba-418c-a6e0-4b600263", "Hoda.semi1");
|
||||||
|
|
||||||
|
debug_assert_eq!(
|
||||||
|
sha,
|
||||||
|
"6cae02a0e1bc0c05ef886b1e045fc0ae662d9bc3ac60b890fa1c4bd59699c944"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn password() {
|
||||||
|
let sec = CloudSecurity::new(Cloud::APP_KEY, "", "");
|
||||||
|
|
||||||
|
let enc = sec.encrypt_password("bd3937c1-49ba-418c-a6e0-4b600263", "Hoda.semi1");
|
||||||
|
|
||||||
|
debug_assert_eq!(
|
||||||
|
enc,
|
||||||
|
"ac57663c18c81ad0423edb235d5b1059d792c2a18d5fad70a83a4afa92affabb"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn connect() -> Result<()> {
|
fn connect() -> Result<()> {
|
||||||
for device_info in Startup::discover()? {
|
for device_info in Startup::discover()? {
|
||||||
let device = Device::connect(device_info)?;
|
Device::connect(device_info)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -11,3 +11,6 @@ fn hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
|
||||||
.map(|i| u8::from_str_radix(&s[i..i + 2], 16))
|
.map(|i| u8::from_str_radix(&s[i..i + 2], 16))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use cloud::Cloud;
|
||||||
|
pub use discover::{Device, DeviceInfo, Startup};
|
||||||
|
|
Loading…
Reference in a new issue