diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..cf206e9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midea'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=midea" + ], + "filter": { + "name": "midea", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8e3f4a7..5e3b214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,6 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.75", features = ["backtrace"] } -if-addrs = "0.10.1" \ No newline at end of file +if-addrs = "0.10.1" +aes = "0.8.3" +rand = "0.8.5" \ No newline at end of file diff --git a/src/discover.rs b/src/discover.rs index cc56c65..5292eeb 100644 --- a/src/discover.rs +++ b/src/discover.rs @@ -1,13 +1,66 @@ use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}, - num::ParseIntError, + str::from_utf8, time::Duration, }; use anyhow::Result; -pub struct Startup {} +use crate::{ + hex, + security::{MsgType, Security}, +}; + +#[derive(Debug, Clone)] +pub struct DeviceInfo { + id: u64, + model: String, + sn: String, + protocol: u8, + + addr: SocketAddr, +} + +pub struct Device { + info: DeviceInfo, + + socket: UdpSocket, +} + +impl Device { + pub fn connect(info: DeviceInfo) -> Result { + let socket = UdpSocket::bind("0.0.0.0:0")?; + socket.set_write_timeout(Some(Duration::from_secs(10)))?; + socket.set_read_timeout(Some(Duration::from_secs(10)))?; + + socket.connect(info.addr)?; + + let me = Self { info, socket }; + + if me.info.protocol == 3 { + me.authenticate()?; + } + + me.refresh_status()?; + + Ok(me) + } + + fn authenticate(&self) -> Result<()> { + let request = Security::encode_8370(MsgType::HANDSHAKE_REQUEST)?; + + Ok(()) + } + + pub fn refresh_status(&self) -> Result<()> { + // + + Ok(()) + } +} + +struct Startup; impl Startup { const BROADCAST_MSG: &'static [u8] = &[ @@ -18,16 +71,9 @@ impl Startup { 0x42, 0xa5, 0x0f, 0x1f, 0x56, 0x9e, 0xb8, 0xec, 0x91, 0x8e, 0x92, 0xe5, ]; - const DEVICE_INFO_MSG: &'static [u8] = &[ - 0x5a, 0x5a, 0x15, 0x00, 0x00, 0x38, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x33, - 0x05, 0x13, 0x06, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xca, 0x8d, 0x9b, 0xf9, 0xa0, - 0x30, 0x1a, 0xe3, 0xb7, 0xe4, 0x2d, 0x53, 0x49, 0x47, 0x62, 0xbe, - ]; - const NUM_RETRIES: u8 = 5; - pub fn discover() -> Result { + pub fn discover() -> Result> { let socket = UdpSocket::bind("0.0.0.0:0")?; socket.set_broadcast(true)?; socket.set_read_timeout(Some(Duration::from_secs(2)))?; @@ -43,10 +89,10 @@ impl Startup { if let Ok((bytes_read, addr)) = socket.recv_from(&mut buffer) { let mut bytes = buffer[0..bytes_read].to_vec(); - let protocol = if bytes[..2] == Self::hex("5a5a")? { + let protocol = if bytes[..2] == hex("5a5a")? { 2 - } else if bytes[..2] == Self::hex("8370")? { - if bytes[8..10] == Self::hex("5a5a")? { + } else if bytes[..2] == hex("8370")? { + if bytes[8..10] == hex("5a5a")? { bytes = bytes[8..(bytes.len() - 16)].to_vec(); } @@ -61,16 +107,35 @@ impl Startup { let device_id = u64::from_le_bytes(tmp.try_into().unwrap()); - if !devices.contains_key(&device_id) { - devices.insert(device_id, (addr, bytes.to_vec(), protocol)); + if devices.contains_key(&device_id) { + continue; } + + let len = bytes.len(); + let encrypt_data = Security::decrypt(&mut bytes[40..(len - 16)]); + + let model = from_utf8(&encrypt_data[17..25])?; + let sn = from_utf8(&encrypt_data[8..40])?; + + devices.insert( + device_id, + (addr, model.to_string(), sn.to_string(), protocol), + ); } } } - println!("{devices:#?}"); + Ok(devices + .into_iter() + .map(|(id, (addr, model, sn, protocol))| DeviceInfo { + id, + model, + sn, + protocol, - Ok(Self {}) + addr, + }) + .collect()) } } @@ -106,24 +171,28 @@ impl Startup { addresses } - - fn hex(s: &str) -> Result, ParseIntError> { - (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) - .collect() - } } #[cfg(test)] mod test { use anyhow::Result; - use super::Startup; + use super::{Device, Startup}; #[test] fn discover() -> Result<()> { - Startup::discover()?; + let devices = Startup::discover()?; + + println!("{devices:#?}"); + + Ok(()) + } + + #[test] + fn connect() -> Result<()> { + for device_info in Startup::discover()? { + let device = Device::connect(device_info)?; + } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 28ff0e0..9eaef33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,11 @@ +use std::num::ParseIntError; + mod discover; +mod security; + +fn hex(s: &str) -> Result, ParseIntError> { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) + .collect() +} diff --git a/src/security.rs b/src/security.rs new file mode 100644 index 0000000..67eed27 --- /dev/null +++ b/src/security.rs @@ -0,0 +1,60 @@ +use aes::{ + cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit}, + Aes128, +}; +use anyhow::Result; +use rand::{self, RngCore}; + +use crate::hex; + +#[allow(non_camel_case_types)] +#[repr(u8)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum MsgType { + HANDSHAKE_REQUEST = 0x0, + ANDSHAKE_RESPONSE = 0x1, + ENCRYPTED_RESPONSE = 0x3, + ENCRYPTED_REQUEST = 0x6, +} + +pub struct Security; + +impl Security { + pub fn decrypt(data: &mut [u8]) -> &[u8] { + const N: u128 = 141661095494369103254425781617665632877; + const KEY: [u8; 16] = N.to_be_bytes(); + + let array = GenericArray::from(KEY); + let cipher = Aes128::new(&array); + + for chunk in data.chunks_mut(16) { + let mut block = GenericArray::from_mut_slice(chunk); + cipher.decrypt_block(&mut block); + } + + data + } + + pub fn encode_8370(msg_type: MsgType) -> Result { + let mut header = hex("83,70")?; + let mut data: Vec = Vec::new(); + + let mut size = data.len(); + let mut padding = 0; + + if msg_type == MsgType::ENCRYPTED_RESPONSE || msg_type == MsgType::ENCRYPTED_REQUEST { + if (size + 2) % 16 != 0 { + padding = 16 - ((size + 2) & 0xf); + size += padding + 32; + data.extend({ + let mut d = vec![0; padding]; + rand::thread_rng().fill_bytes(&mut d); + + d + }); + } + } + + todo!() + } +}