2023-09-23 09:41:49 +00:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
2023-09-24 05:10:52 +00:00
|
|
|
str::FromStr,
|
2023-09-23 14:42:59 +00:00
|
|
|
time::{SystemTime, UNIX_EPOCH},
|
2023-09-23 09:41:49 +00:00
|
|
|
};
|
|
|
|
|
2023-09-24 05:35:21 +00:00
|
|
|
use anyhow::{bail, Error, Result};
|
2023-09-23 09:41:49 +00:00
|
|
|
use base64::{engine::general_purpose, Engine};
|
2023-09-23 14:42:59 +00:00
|
|
|
|
2023-09-23 09:41:49 +00:00
|
|
|
use chrono::Local;
|
|
|
|
use rand::RngCore;
|
|
|
|
use reqwest::header::HeaderMap;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use serde_json::{from_str, to_string};
|
|
|
|
|
|
|
|
use crate::cloud_security::CloudSecurity;
|
2023-09-25 09:30:54 +00:00
|
|
|
use crate::hex;
|
2023-09-23 09:41:49 +00:00
|
|
|
|
2023-09-22 16:06:37 +00:00
|
|
|
pub struct Cloud {
|
2023-09-23 14:42:59 +00:00
|
|
|
device_id: String,
|
2023-09-23 09:41:49 +00:00
|
|
|
uid: Option<String>,
|
|
|
|
api_url: String,
|
|
|
|
access_token: Option<String>,
|
|
|
|
auth_base: String,
|
|
|
|
login_id: Option<String>,
|
|
|
|
|
|
|
|
account: String,
|
|
|
|
password: String,
|
|
|
|
|
|
|
|
security: CloudSecurity,
|
2023-09-22 16:06:37 +00:00
|
|
|
}
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
|
|
|
struct Input {
|
|
|
|
iotData: HashMap<String, String>,
|
|
|
|
data: HashMap<String, String>,
|
|
|
|
stamp: String,
|
|
|
|
reqId: String,
|
|
|
|
}
|
|
|
|
|
2023-09-22 16:06:37 +00:00
|
|
|
impl Cloud {
|
2023-09-23 09:41:49 +00:00
|
|
|
pub const APP_ID: &str = "1010";
|
2023-09-22 16:06:37 +00:00
|
|
|
pub const APP_KEY: &str = "ac21b9f9cbfe4ca5a88562ef25e2b768";
|
2023-09-23 09:41:49 +00:00
|
|
|
pub const API_URL: &str = "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=";
|
|
|
|
pub const APP_VERSION: &str = "3.0.2";
|
|
|
|
pub const SRC: &str = "10";
|
|
|
|
pub const IOT_KEY: &str = "meicloud";
|
|
|
|
pub const HMAC_KEY: &str = "PROD_VnoClJI9aikS8dyy";
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
pub fn new(account: impl ToString, password: impl ToString) -> Result<Self> {
|
|
|
|
let account = account.to_string();
|
|
|
|
|
2023-09-23 09:41:49 +00:00
|
|
|
Ok(Self {
|
2023-09-23 14:42:59 +00:00
|
|
|
device_id: CloudSecurity::device_id(&account),
|
2023-09-23 09:41:49 +00:00
|
|
|
uid: None,
|
|
|
|
api_url: "https://mp-prod.appsmb.com/mas/v5/app/proxy?alias=".to_string(),
|
|
|
|
access_token: None,
|
|
|
|
auth_base: general_purpose::STANDARD_NO_PAD
|
|
|
|
.encode(format!("{}:{}", Self::APP_KEY, Self::IOT_KEY).as_bytes()),
|
|
|
|
login_id: None,
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
account,
|
2023-09-23 09:41:49 +00:00
|
|
|
password: password.to_string(),
|
|
|
|
|
|
|
|
security: CloudSecurity::new(Self::APP_KEY, Self::IOT_KEY, Self::HMAC_KEY),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_general_data(&self) -> HashMap<String, String> {
|
|
|
|
[
|
|
|
|
("appVersion", Self::APP_VERSION),
|
|
|
|
("src", Self::SRC),
|
|
|
|
("format", "2"),
|
|
|
|
(
|
|
|
|
"stamp",
|
|
|
|
Local::now().format("%Y%m%d%H%M%S").to_string().as_str(),
|
|
|
|
),
|
|
|
|
("platformId", "1"),
|
|
|
|
("deviceId", self.device_id.to_string().as_str()),
|
|
|
|
(
|
|
|
|
"reqId",
|
|
|
|
{
|
|
|
|
let mut d = [0; 16];
|
|
|
|
rand::thread_rng().fill_bytes(&mut d);
|
|
|
|
|
|
|
|
let mut s = String::new();
|
|
|
|
|
|
|
|
for b in d {
|
|
|
|
s += format!("{b:x}").as_str();
|
|
|
|
}
|
|
|
|
|
|
|
|
s
|
|
|
|
}
|
|
|
|
.as_str(),
|
|
|
|
),
|
|
|
|
("uid", self.uid.clone().unwrap_or_default().as_str()),
|
|
|
|
("clientType", "1"),
|
|
|
|
("appId", Self::APP_ID),
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
async fn api_request(&self, endpoint: &str, dump_data: String) -> Result<String> {
|
|
|
|
let mut header = HeaderMap::default();
|
2023-09-23 09:41:49 +00:00
|
|
|
|
|
|
|
let url = format!("{}{}", self.api_url, endpoint);
|
|
|
|
|
|
|
|
let random = SystemTime::now()
|
|
|
|
.duration_since(UNIX_EPOCH)?
|
|
|
|
.as_secs()
|
|
|
|
.to_string();
|
|
|
|
let sign = self.security.sign(&dump_data, &random);
|
|
|
|
|
|
|
|
header.insert("x-recipe-app", Self::APP_ID.parse().unwrap());
|
|
|
|
header.insert(
|
|
|
|
"authorization",
|
|
|
|
format!("Basic {}", self.auth_base).parse().unwrap(),
|
|
|
|
);
|
|
|
|
header.insert(
|
|
|
|
"content-type",
|
|
|
|
"application/json; charset=utf-8".parse().unwrap(),
|
|
|
|
);
|
|
|
|
header.insert("secretVersion", "1".parse().unwrap());
|
|
|
|
header.insert("sign", sign.parse().unwrap());
|
|
|
|
header.insert("random", random.parse().unwrap());
|
|
|
|
|
|
|
|
if let Some(access_token) = &self.access_token {
|
|
|
|
header.insert("accesstoken", access_token.parse().unwrap());
|
|
|
|
}
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
if let Some(_uid) = &self.uid {
|
|
|
|
match header.get_mut("uid") {
|
|
|
|
Some(uid) => *uid = _uid.parse().unwrap(),
|
|
|
|
None => {
|
|
|
|
header.insert("uid", _uid.parse().unwrap());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-23 09:41:49 +00:00
|
|
|
Ok(reqwest::Client::new()
|
|
|
|
.post(url)
|
|
|
|
.headers(header)
|
|
|
|
.body(dump_data)
|
|
|
|
.send()
|
|
|
|
.await?
|
|
|
|
.text()
|
|
|
|
.await?)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn reroute(&mut self) -> Result<()> {
|
|
|
|
let mut data = self.make_general_data();
|
|
|
|
|
|
|
|
data.insert("userType".to_string(), "0".to_string());
|
|
|
|
data.insert("userName".to_string(), format!("{}", self.account));
|
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
let response = Response::from_str(
|
|
|
|
&self
|
|
|
|
.api_request("/v1/multicloud/platform/user/route", to_string(&data)?)
|
|
|
|
.await?,
|
|
|
|
)?;
|
2023-09-23 14:42:59 +00:00
|
|
|
|
|
|
|
if let Some(api_url) = response.normal_data().get("masUrl") {
|
2023-09-23 09:41:49 +00:00
|
|
|
self.api_url = api_url.clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
async fn request_login_id(&mut self) -> Result<()> {
|
2023-09-23 09:41:49 +00:00
|
|
|
let mut data = self.make_general_data();
|
|
|
|
data.insert("loginAccount".to_string(), format!("{}", self.account));
|
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
let response = Response::from_str(
|
|
|
|
&self
|
|
|
|
.api_request("/v1/user/login/id/get", to_string(&data)?)
|
2023-09-23 09:41:49 +00:00
|
|
|
.await?,
|
2023-09-24 05:10:52 +00:00
|
|
|
)?;
|
2023-09-23 09:41:49 +00:00
|
|
|
|
|
|
|
let login_id = response
|
2023-09-23 14:42:59 +00:00
|
|
|
.normal_data()
|
2023-09-23 09:41:49 +00:00
|
|
|
.get("loginId")
|
|
|
|
.cloned()
|
|
|
|
.ok_or(Error::msg("failed to request loginId"))?;
|
|
|
|
|
|
|
|
self.login_id = Some(login_id);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
pub async fn login(&mut self) -> Result<()> {
|
2023-09-23 09:41:49 +00:00
|
|
|
self.reroute().await?;
|
2023-09-23 14:42:59 +00:00
|
|
|
self.request_login_id().await?;
|
2023-09-23 09:41:49 +00:00
|
|
|
|
|
|
|
let mut iot_data = self.make_general_data();
|
|
|
|
iot_data.remove("uid");
|
|
|
|
iot_data.insert(
|
|
|
|
"iampwd".to_string(),
|
|
|
|
self.security
|
2023-09-23 14:42:59 +00:00
|
|
|
.encrypt_iam_password(&self.login_id.clone().unwrap(), &self.password),
|
2023-09-23 09:41:49 +00:00
|
|
|
);
|
|
|
|
iot_data.insert("loginAccount".to_string(), self.account.clone());
|
|
|
|
iot_data.insert(
|
|
|
|
"password".to_string(),
|
|
|
|
self.security
|
2023-09-23 14:42:59 +00:00
|
|
|
.encrypt_password(&self.login_id.clone().unwrap(), &self.password),
|
2023-09-23 09:41:49 +00:00
|
|
|
);
|
|
|
|
let stamp = iot_data.get("stamp").unwrap().clone();
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
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 dump_i = to_string(&i).unwrap();
|
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
let response = Response::from_str(&self.api_request("/mj/user/login", dump_i).await?)?;
|
2023-09-23 14:42:59 +00:00
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
self.uid = Some(response.login_data().uid.clone());
|
|
|
|
self.access_token = Some(response.login_data().mdata.accessToken.clone());
|
2023-09-23 09:41:49 +00:00
|
|
|
self.security.set_aes_keys(
|
2023-09-24 05:10:52 +00:00
|
|
|
&response.login_data().accessToken,
|
|
|
|
&response.login_data().randomData,
|
2023-09-23 09:41:49 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
pub async fn keys(&self, device_id: u64) -> Result<(String, String)> {
|
2023-09-25 10:27:40 +00:00
|
|
|
for method in [1, 2] {
|
2023-09-24 05:10:52 +00:00
|
|
|
let udp_id = CloudSecurity::udp_id(device_id, method);
|
2023-09-23 09:41:49 +00:00
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
let mut data = self.make_general_data();
|
|
|
|
data.insert("udpid".to_string(), udp_id.clone());
|
2023-09-23 09:41:49 +00:00
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
let response = Response::from_str(
|
|
|
|
&self
|
|
|
|
.api_request("/v1/iot/secure/getToken", to_string(&data)?)
|
|
|
|
.await?,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
for token in response.token_list() {
|
|
|
|
if token.udpId == udp_id {
|
2023-09-25 09:30:54 +00:00
|
|
|
return Ok((
|
|
|
|
Self::hex_to_lower(&token.token)?,
|
|
|
|
Self::hex_to_lower(&token.key)?,
|
|
|
|
));
|
2023-09-24 05:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-23 14:42:59 +00:00
|
|
|
}
|
2023-09-23 09:41:49 +00:00
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
bail!("no keys found")
|
2023-09-23 14:42:59 +00:00
|
|
|
}
|
2023-09-25 09:30:54 +00:00
|
|
|
|
|
|
|
fn hex_to_lower(h: &str) -> Result<String> {
|
|
|
|
let lower = hex(h)?
|
|
|
|
.iter()
|
|
|
|
.map(|b| format!("{b:02x}"))
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join("");
|
|
|
|
|
|
|
|
Ok(lower)
|
|
|
|
}
|
2023-09-23 14:42:59 +00:00
|
|
|
}
|
2023-09-23 09:41:49 +00:00
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum Response {
|
|
|
|
Normal(ResponseNumber),
|
2023-09-24 05:10:52 +00:00
|
|
|
NestedMap(ResponseLogin),
|
|
|
|
TokenList(ResponseTokenList),
|
2023-09-23 14:42:59 +00:00
|
|
|
}
|
2023-09-23 09:41:49 +00:00
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
impl Response {
|
2023-09-24 05:10:52 +00:00
|
|
|
pub fn code(&self) -> i32 {
|
|
|
|
match self {
|
|
|
|
Self::Normal(n) => n.code,
|
|
|
|
Self::NestedMap(n) => n.code,
|
|
|
|
Self::TokenList(n) => n.code.parse().unwrap(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
pub fn normal_data(&self) -> &HashMap<String, String> {
|
|
|
|
match self {
|
|
|
|
Self::Normal(n) => &n.data,
|
2023-09-24 05:10:52 +00:00
|
|
|
_ => panic!(),
|
2023-09-23 14:42:59 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-23 09:41:49 +00:00
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
pub fn login_data(&self) -> &ResponseLoginData {
|
2023-09-23 14:42:59 +00:00
|
|
|
match self {
|
|
|
|
Self::NestedMap(n) => &n.data,
|
2023-09-24 05:10:52 +00:00
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn token_list(&self) -> &[Token] {
|
|
|
|
match self {
|
|
|
|
Self::TokenList(n) => &n.data.tokenlist,
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for Response {
|
|
|
|
type Err = Error;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
|
|
let resp_fn = |s| {
|
|
|
|
if let Ok(r) = from_str::<ResponseNumber>(s) {
|
|
|
|
return Ok(Response::from(r));
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Ok(r) = from_str::<ResponseString>(s) {
|
|
|
|
return Ok(Response::from(r));
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Ok(r) = from_str::<ResponseLogin>(s) {
|
|
|
|
return Ok(Response::from(r));
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Ok(r) = from_str::<ResponseTokenList>(s) {
|
|
|
|
return Ok(Response::from(r));
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(Error::msg("failed parsing response"))
|
|
|
|
};
|
|
|
|
|
|
|
|
let response = resp_fn(s)?;
|
|
|
|
|
|
|
|
if response.code() != 0 {
|
|
|
|
bail!("Error return code: {}", response.code());
|
2023-09-23 09:41:49 +00:00
|
|
|
}
|
2023-09-24 05:10:52 +00:00
|
|
|
|
|
|
|
Ok(response)
|
2023-09-23 14:42:59 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-23 09:41:49 +00:00
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
impl From<ResponseNumber> for Response {
|
|
|
|
fn from(value: ResponseNumber) -> Self {
|
|
|
|
Self::Normal(value)
|
2023-09-23 09:41:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
impl From<ResponseString> for Response {
|
|
|
|
fn from(value: ResponseString) -> Self {
|
|
|
|
Self::Normal(ResponseNumber {
|
|
|
|
msg: value.msg,
|
|
|
|
code: value.code.parse().unwrap(),
|
|
|
|
data: value.data,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
impl From<ResponseLogin> for Response {
|
|
|
|
fn from(value: ResponseLogin) -> Self {
|
2023-09-23 14:42:59 +00:00
|
|
|
Self::NestedMap(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-24 05:10:52 +00:00
|
|
|
impl From<ResponseTokenList> for Response {
|
|
|
|
fn from(value: ResponseTokenList) -> Self {
|
|
|
|
Self::TokenList(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
#[allow(unused)]
|
2023-09-23 09:41:49 +00:00
|
|
|
#[derive(Deserialize, Debug)]
|
2023-09-23 14:42:59 +00:00
|
|
|
struct ResponseNumber {
|
2023-09-23 09:41:49 +00:00
|
|
|
msg: String,
|
|
|
|
code: i32,
|
|
|
|
pub data: HashMap<String, String>,
|
|
|
|
}
|
|
|
|
|
2023-09-23 14:42:59 +00:00
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct ResponseString {
|
|
|
|
msg: String,
|
|
|
|
code: String,
|
|
|
|
pub data: HashMap<String, String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
#[derive(Deserialize, Debug)]
|
2023-09-24 05:10:52 +00:00
|
|
|
struct ResponseLogin {
|
2023-09-23 14:42:59 +00:00
|
|
|
msg: String,
|
|
|
|
code: i32,
|
2023-09-24 05:10:52 +00:00
|
|
|
pub data: ResponseLoginData,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct ResponseTokenList {
|
|
|
|
msg: String,
|
|
|
|
code: String,
|
|
|
|
pub data: TokenList,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct TokenList {
|
|
|
|
tokenlist: Vec<Token>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct Token {
|
|
|
|
udpId: String,
|
|
|
|
key: String,
|
|
|
|
token: String,
|
2023-09-23 14:42:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
#[allow(unused)]
|
|
|
|
#[derive(Deserialize, Debug)]
|
2023-09-24 05:10:52 +00:00
|
|
|
struct ResponseLoginData {
|
2023-09-23 14:42:59 +00:00
|
|
|
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)]
|
2023-09-23 09:41:49 +00:00
|
|
|
#[derive(Serialize, Debug)]
|
|
|
|
struct Data<'a> {
|
|
|
|
appKey: &'a str,
|
|
|
|
deviceId: &'a str,
|
|
|
|
platform: &'a str,
|
2023-09-22 16:06:37 +00:00
|
|
|
}
|
2023-09-23 14:42:59 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use anyhow::Result;
|
2023-09-24 05:10:52 +00:00
|
|
|
use futures::future::try_join;
|
2023-09-25 09:30:54 +00:00
|
|
|
use serial_test::serial;
|
2023-09-24 05:10:52 +00:00
|
|
|
|
|
|
|
use crate::Startup;
|
2023-09-23 14:42:59 +00:00
|
|
|
|
|
|
|
use super::Cloud;
|
|
|
|
|
|
|
|
#[tokio::test]
|
2023-10-06 08:22:41 +00:00
|
|
|
#[serial]
|
2023-09-23 14:42:59 +00:00
|
|
|
async fn reroute() -> Result<()> {
|
|
|
|
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
|
|
|
|
|
|
|
cloud.reroute().await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
2023-10-06 08:22:41 +00:00
|
|
|
#[serial]
|
2023-09-23 14:42:59 +00:00
|
|
|
async fn login_id() -> Result<()> {
|
|
|
|
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
|
|
|
|
|
|
|
cloud.reroute().await?;
|
|
|
|
cloud.request_login_id().await?;
|
|
|
|
|
|
|
|
assert!(cloud.login_id.is_some());
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
2023-09-25 09:30:54 +00:00
|
|
|
#[serial]
|
2023-09-23 14:42:59 +00:00
|
|
|
async fn login() -> Result<()> {
|
|
|
|
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
|
|
|
|
|
|
|
cloud.login().await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-09-24 05:10:52 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
2023-09-25 09:30:54 +00:00
|
|
|
#[serial]
|
2023-09-24 05:10:52 +00:00
|
|
|
async fn keys() -> Result<()> {
|
|
|
|
let mut cloud = Cloud::new("michaelh.95@t-online.de", "Hoda.semi1")?;
|
|
|
|
|
|
|
|
let (_, devices) = try_join(cloud.login(), Startup::discover()).await?;
|
|
|
|
|
|
|
|
for device_info in devices {
|
|
|
|
let (_token, _key) = cloud.keys(device_info.id).await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-09-23 14:42:59 +00:00
|
|
|
}
|