Implement token and key from cloud
This commit is contained in:
parent
99819f36d9
commit
1cf3e17faa
5 changed files with 199 additions and 59 deletions
|
@ -19,3 +19,4 @@ reqwest = "0.11.20"
|
|||
base64 = "0.21.4"
|
||||
tokio = { version = "1.32.0", features=["macros", "rt-multi-thread"] }
|
||||
md5 = "0.7.0"
|
||||
futures = "0.3.28"
|
||||
|
|
194
src/cloud.rs
194
src/cloud.rs
|
@ -1,9 +1,10 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
str::FromStr,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
use anyhow::{anyhow, bail, Error, Result};
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
|
||||
use chrono::Local;
|
||||
|
@ -101,13 +102,8 @@ impl Cloud {
|
|||
.collect()
|
||||
}
|
||||
|
||||
async fn api_request(
|
||||
&self,
|
||||
endpoint: &str,
|
||||
dump_data: String,
|
||||
header: Option<HeaderMap>,
|
||||
) -> Result<String> {
|
||||
let mut header = header.unwrap_or_default();
|
||||
async fn api_request(&self, endpoint: &str, dump_data: String) -> Result<String> {
|
||||
let mut header = HeaderMap::default();
|
||||
|
||||
let url = format!("{}{}", self.api_url, endpoint);
|
||||
|
||||
|
@ -159,15 +155,11 @@ impl Cloud {
|
|||
data.insert("userType".to_string(), "0".to_string());
|
||||
data.insert("userName".to_string(), format!("{}", self.account));
|
||||
|
||||
let response = Self::parse_response(
|
||||
self.api_request(
|
||||
"/v1/multicloud/platform/user/route",
|
||||
to_string(&data)?,
|
||||
None,
|
||||
)
|
||||
let response = Response::from_str(
|
||||
&self
|
||||
.api_request("/v1/multicloud/platform/user/route", to_string(&data)?)
|
||||
.await?,
|
||||
)
|
||||
.ok_or(Error::msg("failed parsing response"))?;
|
||||
)?;
|
||||
|
||||
if let Some(api_url) = response.normal_data().get("masUrl") {
|
||||
self.api_url = api_url.clone();
|
||||
|
@ -180,11 +172,11 @@ impl Cloud {
|
|||
let mut data = self.make_general_data();
|
||||
data.insert("loginAccount".to_string(), format!("{}", self.account));
|
||||
|
||||
let response = Self::parse_response(
|
||||
self.api_request("/v1/user/login/id/get", to_string(&data)?, None)
|
||||
let response = Response::from_str(
|
||||
&self
|
||||
.api_request("/v1/user/login/id/get", to_string(&data)?)
|
||||
.await?,
|
||||
)
|
||||
.ok_or(Error::msg("failed parsing response"))?;
|
||||
)?;
|
||||
|
||||
let login_id = response
|
||||
.normal_data()
|
||||
|
@ -233,57 +225,112 @@ impl Cloud {
|
|||
|
||||
let dump_i = to_string(&i).unwrap();
|
||||
|
||||
let response =
|
||||
Self::parse_response(self.api_request("/mj/user/login", dump_i, None).await?)
|
||||
.ok_or(Error::msg("failed parsing response"))?;
|
||||
let response = Response::from_str(&self.api_request("/mj/user/login", dump_i).await?)?;
|
||||
|
||||
self.uid = Some(response.nested_data().uid.clone());
|
||||
self.access_token = Some(response.nested_data().mdata.accessToken.clone());
|
||||
self.uid = Some(response.login_data().uid.clone());
|
||||
self.access_token = Some(response.login_data().mdata.accessToken.clone());
|
||||
self.security.set_aes_keys(
|
||||
&response.nested_data().accessToken,
|
||||
&response.nested_data().randomData,
|
||||
&response.login_data().accessToken,
|
||||
&response.login_data().randomData,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_response(s: String) -> Option<Response> {
|
||||
if let Ok(r) = from_str::<ResponseNumber>(&s) {
|
||||
return Some(Response::from(r));
|
||||
pub async fn keys(&self, device_id: u64) -> Result<(String, String)> {
|
||||
for method in [1, 2] {
|
||||
let udp_id = CloudSecurity::udp_id(device_id, method);
|
||||
|
||||
let mut data = self.make_general_data();
|
||||
data.insert("udpid".to_string(), udp_id.clone());
|
||||
|
||||
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 {
|
||||
return Ok((token.token.clone(), token.key.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
bail!("no keys found")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Response {
|
||||
Normal(ResponseNumber),
|
||||
NestedMap(ResponseMap),
|
||||
NestedMap(ResponseLogin),
|
||||
TokenList(ResponseTokenList),
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn normal_data(&self) -> &HashMap<String, String> {
|
||||
pub fn code(&self) -> i32 {
|
||||
match self {
|
||||
Self::Normal(n) => &n.data,
|
||||
Self::NestedMap(_) => panic!(),
|
||||
Self::Normal(n) => n.code,
|
||||
Self::NestedMap(n) => n.code,
|
||||
Self::TokenList(n) => n.code.parse().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nested_data(&self) -> &ResponseMapData {
|
||||
pub fn normal_data(&self) -> &HashMap<String, String> {
|
||||
match self {
|
||||
Self::Normal(_) => panic!(),
|
||||
Self::NestedMap(n) => &n.data,
|
||||
Self::Normal(n) => &n.data,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn login_data(&self) -> &ResponseLoginData {
|
||||
match self {
|
||||
Self::NestedMap(n) => &n.data,
|
||||
_ => 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());
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseNumber> for Response {
|
||||
|
@ -302,12 +349,18 @@ impl From<ResponseString> for Response {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ResponseMap> for Response {
|
||||
fn from(value: ResponseMap) -> Self {
|
||||
impl From<ResponseLogin> for Response {
|
||||
fn from(value: ResponseLogin) -> Self {
|
||||
Self::NestedMap(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseTokenList> for Response {
|
||||
fn from(value: ResponseTokenList) -> Self {
|
||||
Self::TokenList(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ResponseNumber {
|
||||
|
@ -325,16 +378,39 @@ struct ResponseString {
|
|||
|
||||
#[allow(unused)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ResponseMap {
|
||||
struct ResponseLogin {
|
||||
msg: String,
|
||||
code: i32,
|
||||
pub data: ResponseMapData,
|
||||
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,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ResponseMapData {
|
||||
struct ResponseLoginData {
|
||||
randomData: String,
|
||||
uid: String,
|
||||
accountId: String,
|
||||
|
@ -376,6 +452,9 @@ struct Data<'a> {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use anyhow::Result;
|
||||
use futures::future::try_join;
|
||||
|
||||
use crate::Startup;
|
||||
|
||||
use super::Cloud;
|
||||
|
||||
|
@ -408,4 +487,17 @@ mod test {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,40 @@ impl CloudSecurity {
|
|||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
pub fn udp_id(device_id: u64, method: u8) -> String {
|
||||
let bytes_id: Vec<u8> = match method {
|
||||
0 => device_id.to_be_bytes().into_iter().rev().collect(),
|
||||
1 => {
|
||||
let mut v = device_id.to_be_bytes().to_vec();
|
||||
v.truncate(6);
|
||||
|
||||
v
|
||||
}
|
||||
1 => {
|
||||
let mut v = device_id.to_be_bytes().to_vec();
|
||||
v.truncate(6);
|
||||
|
||||
v
|
||||
}
|
||||
|
||||
_ => return String::new(),
|
||||
};
|
||||
|
||||
let mut data = Sha256::digest(&bytes_id);
|
||||
|
||||
for i in 0..16 {
|
||||
data[i] ^= data[i + 16];
|
||||
}
|
||||
|
||||
let mut v: Vec<u8> = data.into_iter().collect();
|
||||
v.truncate(16);
|
||||
|
||||
v.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -78,7 +78,7 @@ impl Startup {
|
|||
|
||||
const NUM_RETRIES: u8 = 5;
|
||||
|
||||
pub fn discover() -> Result<Vec<DeviceInfo>> {
|
||||
pub async fn discover() -> Result<Vec<DeviceInfo>> {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0")?;
|
||||
socket.set_broadcast(true)?;
|
||||
socket.set_read_timeout(Some(Duration::from_secs(2)))?;
|
||||
|
@ -184,18 +184,18 @@ mod test {
|
|||
|
||||
use super::{Device, Startup};
|
||||
|
||||
#[test]
|
||||
fn discover() -> Result<()> {
|
||||
let devices = Startup::discover()?;
|
||||
#[tokio::test]
|
||||
async fn discover() -> Result<()> {
|
||||
let devices = Startup::discover().await?;
|
||||
|
||||
println!("{devices:#?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect() -> Result<()> {
|
||||
for device_info in Startup::discover()? {
|
||||
#[tokio::test]
|
||||
async fn connect() -> Result<()> {
|
||||
for device_info in Startup::discover().await? {
|
||||
Device::connect(device_info)?;
|
||||
}
|
||||
|
||||
|
|
13
tst.json
Normal file
13
tst.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"code": "0",
|
||||
"msg": "ok",
|
||||
"data": {
|
||||
"tokenlist": [
|
||||
{
|
||||
"udpId": "a79479839b272432f3bbf971f9faed30",
|
||||
"key": "d9d856b06f5245349e96df80e063af989532e6ef03c34039b74bfa0754a0ddf0",
|
||||
"token": "4D14C31771A353EB584566243552C5CC2CE55C06162E550E71A805949569F7E8FA659B5B4B6CC097475A1270A4CC3DB75D52782DE3D1E65FEEA0945C6FFC7B3C"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue