diff --git a/midea.py b/midea.py index cd4f313..188cc94 100644 --- a/midea.py +++ b/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) diff --git a/src/cloud.rs b/src/cloud.rs index 2d72eff..e11bc18 100644 --- a/src/cloud.rs +++ b/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(( diff --git a/src/command/request.rs b/src/command/request.rs index 5ae9282..f1c1d7e 100644 --- a/src/command/request.rs +++ b/src/command/request.rs @@ -1,6 +1,10 @@ use super::{body::Body, header::Header, Command, MessageType}; use std::ops::Deref; +pub trait RequestSerializer { + fn serialize(&self) -> Vec; +} + pub struct CommandRequest { command: Command, } @@ -29,16 +33,6 @@ impl CommandRequest { } } - pub fn serialize(&self) -> Vec { - 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 { + 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 { + 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 { + self.command.serialize() + } +} + +pub struct CommandHeartbeat; + +impl RequestSerializer for CommandHeartbeat { + fn serialize(&self) -> Vec { + vec![0x00] + } +} diff --git a/src/device.rs b/src/device.rs index 223449b..a4c784d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -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) } } diff --git a/src/packet_builder.rs b/src/packet_builder.rs index 9a54588..29218d3 100644 --- a/src/packet_builder.rs +++ b/src/packet_builder.rs @@ -1,13 +1,98 @@ -pub struct PacketBuilder { - // +use std::str::from_utf8; + +use chrono::Local; + +use crate::{command::RequestSerializer, security::Security}; + +pub struct PacketBuilder { + command: S, + + packet: [u8; 40], } -impl PacketBuilder { - pub fn builder(device_id: u64, msg: &[u8]) -> Self { - Self {} +impl PacketBuilder { + 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 { - 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); } } diff --git a/src/security.rs b/src/security.rs index 4faed6a..88e13ee 100644 --- a/src/security.rs +++ b/src/security.rs @@ -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 { + md5::compute([data, Self::SALT].concat()).to_vec() + } + + pub fn aes_encrypt(data: &mut [u8]) -> Vec { + 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);