Implement packet builder
This commit is contained in:
parent
3d37654850
commit
d1bef25ac9
6 changed files with 192 additions and 43 deletions
28
midea.py
28
midea.py
|
@ -5,6 +5,9 @@ import asyncio;
|
|||
import discover;
|
||||
import device;
|
||||
import devicee1;
|
||||
import packet_builder;
|
||||
|
||||
import datetime;
|
||||
|
||||
async def test():
|
||||
cl = cloud.MSmartHomeCloud(
|
||||
|
@ -68,6 +71,31 @@ async def test():
|
|||
if dev.connect(True):
|
||||
return dev
|
||||
|
||||
|
||||
|
||||
t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:16]
|
||||
|
||||
t = "20230929142324212188673"[:16]
|
||||
|
||||
print(t)
|
||||
|
||||
b = bytearray()
|
||||
|
||||
for i in range(0, len(t), 2):
|
||||
|
||||
|
||||
tmp = t[i:i+2]
|
||||
d = int(tmp)
|
||||
|
||||
|
||||
b.insert(0, d)
|
||||
|
||||
|
||||
print(b)
|
||||
|
||||
b_ref = bytearray([21, 24, 23, 14, 29, 9, 23, 20])
|
||||
print(b_ref)
|
||||
|
||||
dev = asyncio.run(test())
|
||||
|
||||
print(dev)
|
||||
|
|
20
src/cloud.rs
20
src/cloud.rs
|
@ -242,35 +242,15 @@ impl Cloud {
|
|||
for method in [1, 2] {
|
||||
let udp_id = CloudSecurity::udp_id(device_id, method);
|
||||
|
||||
// 1: "a79479839b272432f3bbf971f9faed30"
|
||||
// 2: "a153e4273b29c04db85763c739d45203"
|
||||
|
||||
let mut data = self.make_general_data();
|
||||
data.insert("udpid".to_string(), udp_id.clone());
|
||||
|
||||
// {
|
||||
// 'appVersion': '3.0.2',
|
||||
// 'src': '10',
|
||||
// 'format': '2',
|
||||
// 'stamp': '20230925115743',
|
||||
// 'platformId': '1',
|
||||
// 'deviceId': 'c1bdb9d159aa18fe',
|
||||
// 'reqId': '3e593c7a186f25ccca0d010d4316af0d',
|
||||
// 'uid': '4c48146bdedaca956c465d53cf7dd9a3',
|
||||
// 'clientType': '1',
|
||||
// 'appId': '1010',
|
||||
// 'udpid': '53c18d6f4682867654bee60c9ea047f4'
|
||||
// }
|
||||
|
||||
let response = Response::from_str(
|
||||
&self
|
||||
.api_request("/v1/iot/secure/getToken", to_string(&data)?)
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
// '76697C9A0685778C6B4F487A70497F4DB3CDD2C0949138B5BA798DADC5AE0712ECF6C3559C7EE68B96B2BB2DC140CF6172D9F1587065D4A536A75D492031E22E'
|
||||
// '025f9ff7bd3c4aceb1e559ab13d5e73f6fb2358e2bcf4bb883ab62225d6b9d2d'
|
||||
|
||||
for token in response.token_list() {
|
||||
if token.udpId == udp_id {
|
||||
return Ok((
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use super::{body::Body, header::Header, Command, MessageType};
|
||||
use std::ops::Deref;
|
||||
|
||||
pub trait RequestSerializer {
|
||||
fn serialize(&self) -> Vec<u8>;
|
||||
}
|
||||
|
||||
pub struct CommandRequest {
|
||||
command: Command,
|
||||
}
|
||||
|
@ -29,16 +33,6 @@ impl CommandRequest {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let mut stream = Vec::new();
|
||||
|
||||
stream.extend_from_slice(self.command.header.raw());
|
||||
stream.extend_from_slice(self.command.body.raw());
|
||||
stream.push(Self::checksum(&stream[1..]));
|
||||
|
||||
stream
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &Header {
|
||||
&self.command.header
|
||||
}
|
||||
|
@ -52,6 +46,18 @@ impl CommandRequest {
|
|||
}
|
||||
}
|
||||
|
||||
impl RequestSerializer for CommandRequest {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
let mut stream = Vec::new();
|
||||
|
||||
stream.extend_from_slice(self.command.header.raw());
|
||||
stream.extend_from_slice(self.command.body.raw());
|
||||
stream.push(Self::checksum(&stream[1..]));
|
||||
|
||||
stream
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandQuerySubtype {
|
||||
command: CommandRequest,
|
||||
}
|
||||
|
@ -81,6 +87,12 @@ impl Deref for CommandQuerySubtype {
|
|||
}
|
||||
}
|
||||
|
||||
impl RequestSerializer for CommandQuerySubtype {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.command.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandQueryCustom {
|
||||
command: CommandRequest,
|
||||
}
|
||||
|
@ -104,3 +116,17 @@ impl Deref for CommandQueryCustom {
|
|||
&self.command
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestSerializer for CommandQueryCustom {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
self.command.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandHeartbeat;
|
||||
|
||||
impl RequestSerializer for CommandHeartbeat {
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
vec![0x00]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::{bail, Context, Error, Result};
|
||||
use serde_json::to_string;
|
||||
|
||||
use crate::{
|
||||
command::{CommandQuerySubtype, CommandRequest, CommandSubtypeResponse, MessageType},
|
||||
command::{
|
||||
CommandHeartbeat, CommandQuerySubtype, CommandRequest, CommandSubtypeResponse, MessageType,
|
||||
RequestSerializer,
|
||||
},
|
||||
devices::{e1::E1, DeviceBackend},
|
||||
hex,
|
||||
packet_builder::PacketBuilder,
|
||||
|
@ -211,13 +213,12 @@ impl Device {
|
|||
}
|
||||
|
||||
fn send_heartbeat(&self) -> Result<()> {
|
||||
let msg = PacketBuilder::builder(self.info.id, &[0x00]).finalize(0);
|
||||
let msg = PacketBuilder::builder(self.info.id, CommandHeartbeat).finalize(0);
|
||||
self.send_message(&msg)
|
||||
}
|
||||
|
||||
fn build_send(&self, cmd: CommandRequest) -> Result<()> {
|
||||
let data = PacketBuilder::builder(self.info.id, to_string(&cmd.serialize())?.as_bytes())
|
||||
.finalize(1);
|
||||
fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> {
|
||||
let data = PacketBuilder::builder(self.info.id, cmd).finalize(1);
|
||||
self.send_message(&data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,98 @@
|
|||
pub struct PacketBuilder {
|
||||
//
|
||||
use std::str::from_utf8;
|
||||
|
||||
use chrono::Local;
|
||||
|
||||
use crate::{command::RequestSerializer, security::Security};
|
||||
|
||||
pub struct PacketBuilder<S: RequestSerializer> {
|
||||
command: S,
|
||||
|
||||
packet: [u8; 40],
|
||||
}
|
||||
|
||||
impl PacketBuilder {
|
||||
pub fn builder(device_id: u64, msg: &[u8]) -> Self {
|
||||
Self {}
|
||||
impl<S: RequestSerializer> PacketBuilder<S> {
|
||||
pub fn builder(device_id: u64, command: S) -> Self {
|
||||
#[rustfmt::skip]
|
||||
let mut packet = [
|
||||
// 2 bytes - StaicHeader
|
||||
0x5a, 0x5a,
|
||||
// 2 bytes - mMessageType
|
||||
0x01, 0x11,
|
||||
// 2 bytes - PacketLenght
|
||||
0x00, 0x00,
|
||||
// 2 bytes
|
||||
0x20, 0x00,
|
||||
// 4 bytes - MessageId
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
// 8 bytes - Date&Time
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 6 bytes - mDeviceID
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 12 bytes
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
];
|
||||
|
||||
packet[12..20].copy_from_slice(&Self::packet_time());
|
||||
packet[20..28].copy_from_slice(&device_id.to_le_bytes()[0..8]);
|
||||
|
||||
Self { command, packet }
|
||||
}
|
||||
|
||||
fn packet_time() -> [u8; 8] {
|
||||
let mut t = Local::now()
|
||||
.format("%Y%m%d%H%M%S%f")
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
t.truncate(16);
|
||||
|
||||
let mut b = [0; 8];
|
||||
|
||||
for (index, chars) in t.chunks(2).enumerate() {
|
||||
b[7 - index] = from_utf8(chars).unwrap().parse().unwrap();
|
||||
}
|
||||
|
||||
b
|
||||
}
|
||||
|
||||
pub fn finalize(self, msg_type: u8) -> Vec<u8> {
|
||||
todo!()
|
||||
let mut packet = self.packet.to_vec();
|
||||
|
||||
if msg_type != 1 {
|
||||
packet[3] = 0x10;
|
||||
packet[6] = 0x7b;
|
||||
} else {
|
||||
let mut data = self.command.serialize();
|
||||
Security::aes_encrypt(&mut data);
|
||||
|
||||
packet.extend(data);
|
||||
}
|
||||
|
||||
// packet length
|
||||
packet[4..6].copy_from_slice(&(self.packet.len() as u16 + 16).to_le_bytes());
|
||||
|
||||
// append a basic checksum data (16 bytes) to the packet
|
||||
packet.extend(Security::encode32_data(&self.packet));
|
||||
|
||||
packet
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::str::from_utf8;
|
||||
|
||||
#[test]
|
||||
fn packet_time() {
|
||||
let t = b"2023092913400641";
|
||||
let b_ref = b")\x06(\r\x1d\t\x17\x14";
|
||||
|
||||
let mut b = [0; 8];
|
||||
|
||||
for (index, chars) in t.chunks(2).enumerate() {
|
||||
b[7 - index] = from_utf8(chars).unwrap().parse().unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(&b, b_ref);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicU16, Ordering::SeqCst};
|
|||
use aes::{
|
||||
cipher::{
|
||||
block_padding::NoPadding, generic_array::GenericArray, BlockDecrypt, BlockDecryptMut,
|
||||
BlockEncryptMut, KeyInit, KeyIvInit,
|
||||
BlockEncrypt, BlockEncryptMut, KeyInit, KeyIvInit,
|
||||
},
|
||||
Aes128,
|
||||
};
|
||||
|
@ -34,6 +34,35 @@ impl Security {
|
|||
const N: u128 = 141661095494369103254425781617665632877;
|
||||
const KEY: [u8; 16] = Self::N.to_be_bytes();
|
||||
const IV: [u8; 16] = [b'\0'; 16];
|
||||
const SALT: &[u8] = b"xhdiwjnchekd4d512chdjx5d8e4c394D2D7S";
|
||||
|
||||
pub fn encode32_data(data: &[u8]) -> Vec<u8> {
|
||||
md5::compute([data, Self::SALT].concat()).to_vec()
|
||||
}
|
||||
|
||||
pub fn aes_encrypt(data: &mut [u8]) -> Vec<u8> {
|
||||
let array = GenericArray::from(Self::KEY);
|
||||
let cipher = Aes128::new(&array);
|
||||
|
||||
let mut result = vec![0; data.len()];
|
||||
let padding = 16 - (result.len() % 16);
|
||||
result.extend(&vec![0; padding]);
|
||||
|
||||
for (inchunk, outchunk) in data.chunks(16).zip(result.chunks_mut(16)) {
|
||||
let in_data = if inchunk.len() != 16 {
|
||||
[inchunk, &vec![0; padding]].concat()
|
||||
} else {
|
||||
inchunk.to_vec()
|
||||
};
|
||||
|
||||
let inblock = GenericArray::from_slice(&in_data);
|
||||
let mut out_block = GenericArray::from_mut_slice(outchunk);
|
||||
|
||||
cipher.encrypt_block_b2b(&inblock, &mut out_block);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn aes_decrypt(data: &mut [u8]) {
|
||||
let array = GenericArray::from(Self::KEY);
|
||||
|
|
Loading…
Reference in a new issue