diff --git a/midea.py b/midea.py index c54fbaa..818fa79 100644 --- a/midea.py +++ b/midea.py @@ -9,69 +9,67 @@ import packet_builder; import datetime; -async def test(): - cl = cloud.MSmartHomeCloud( - "MSmartHome", - aiohttp.ClientSession(), - "michaelh.95@t-online.de", - "Hoda.semi1" - ) +# async def test(): +# cl = cloud.MSmartHomeCloud( +# "MSmartHome", +# aiohttp.ClientSession(), +# "michaelh.95@t-online.de", +# "Hoda.semi1" +# ) - devices = discover.discover() +# devices = discover.discover() - if len(devices) > 0: - # for device_id in devices: - # token = "702b9dfc3ac6c82979986ee3a053a76f75f9e9c763ce5c25af5c2cc982f797a9409adff3745e23fee3a464d745e005c839efb0b84082acc962e59ab8683e0299" - # key = "52b2feee353841588994e630dcb59819ec71ce1ffacb48628f4f436f5c54f11e" +# if len(devices) > 0: +# if await cl.login(): +# for device_id in devices: +# keys = await cl.get_keys(device_id) - # device_info = devices[device_id] +# for k in keys: +# token = keys[k]['token'] +# key = keys[k]['key'] - # dev = device.MiedaDevice( - # name="", - # device_id=device_id, - # device_type=225, - # ip_address=device_info['ip_address'], - # port=device_info['port'], - # token=token, - # key=key, - # protocol=3, - # model=device_info['model'], - # attributes={} - # ) +# device_info = devices[device_id] - # if dev.connect(False): - # print("success") - # else: - # print("fail") +# dev = devicee1.MideaE1Device( +# name="", +# device_id=device_id, +# ip_address=device_info['ip_address'], +# port=device_info['port'], +# token=token, +# key=key, +# protocol=3, +# model=device_info['model'], +# customize="" +# ) + +# if dev.connect(True): +# return dev + +key = b"V\x103\xba\xa0W\x85\xaa\x0c\x01q\xb7\x94\t\x7f\xd4\xe7=L\x91\x02\x14\x8d`\xf7~\xc6\xfd\x99?\x14\xbc" + +chunk = bytearray([77, 15, 242, 219, 218, 33, 185, 171, 8, 35, 207, 179, 31, 179, 75, 165]) +chunk_ref = bytearray([49, 19, 2, 10, 23, 20, 2, 134, 2, 0, 0, 139, 0, 0, 0, 0]) + +sec = security.LocalSecurity() +chunk_res = sec.aes_cbc_decrypt(chunk, key) + +print(chunk_ref) +print(chunk_res) +dev = devicee1.MideaE1Device( + name="", + device_id=152832116426242, + ip_address="192.168.178.94", + port=6444, + token="dead840607856a4b84c1a9d94ce8f553b50c037b65fa1ca22126de339c367eb765b231b4525d5b8336c48fe5dae38439bbb5e31282ed3790ff98a48049401dca", + key="50e77947dc63426db3883c7616613410044b02567f5240a5baf789391b2e5a79", + protocol=3, + model="760EY015", + customize="" +) - if await cl.login(): - for device_id in devices: - keys = await cl.get_keys(device_id) - - for k in keys: - token = keys[k]['token'] - key = keys[k]['key'] - - device_info = devices[device_id] - - dev = devicee1.MideaE1Device( - name="", - device_id=device_id, - ip_address=device_info['ip_address'], - port=device_info['port'], - token=token, - key=key, - protocol=3, - model=device_info['model'], - customize="" - ) - - if dev.connect(True): - return dev - -dev = asyncio.run(test()) +dev.connect(True) print(dev) diff --git a/security.py b/security.py index 1c62bb2..f398021 100644 --- a/security.py +++ b/security.py @@ -250,6 +250,9 @@ class LocalSecurity: padding = header[5] >> 4 msgtype = header[5] & 0xf data = data[6:] + + # (6) vec![131, 112, 0, 5, 32, 15] + if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): sign = data[-32:] data = data[:-32] diff --git a/src/device.rs b/src/device.rs index 2c204c3..440f260 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, io::{Read, Write}, net::TcpStream, sync::Mutex, @@ -10,10 +11,10 @@ use anyhow::{bail, Context, Error, Result}; use crate::{ command::{ - CommandHeartbeat, CommandQuerySubtype, CommandRequest, CommandSubtypeResponse, MessageType, + CommandHeartbeat, CommandQuerySubtype, CommandSubtypeResponse, MessageType, RequestSerializer, }, - devices::{e1::E1, DeviceBackend}, + devices::{e1::E1, AttributeValue, DeviceBackend}, hex, packet_builder::PacketBuilder, security::{MsgType, Security}, @@ -37,7 +38,7 @@ pub struct Device { sub_type: u16, device_protocol_version: u8, - updates: Vec Result<()>>>, + updates: Vec) -> Result<()>>>, token: [u8; 64], key: [u8; 32], @@ -127,7 +128,10 @@ impl Device { loop { let mut buf = [0; 512]; - let bytes_read = self.socket.lock().unwrap().read(&mut buf)?; + let bytes_read = match self.socket.lock().unwrap().read(&mut buf) { + Ok(b) => b, + Err(_) => break, + }; if bytes_read == 0 { bail!("socket error"); @@ -145,7 +149,7 @@ impl Device { pub fn register_update(&mut self, f: F) where - F: Fn(&[u8]) -> Result<()> + 'static, + F: Fn(&HashMap<&'static str, AttributeValue>) -> Result<()> + 'static, { self.updates.push(Box::new(f)); } @@ -178,12 +182,10 @@ impl Device { Security::aes_decrypt(crypt); if self.pre_process_message(crypt) { - let status = self.device_backend.process_message(crypt)?; + self.device_backend.process_message(crypt); - if status.len() > 0 { - for update in self.updates.iter() { - update(&status)?; - } + for update in self.updates.iter() { + update(self.device_backend.attributes())?; } } } @@ -208,11 +210,6 @@ impl Device { fn send_message(&self, msg: &[u8]) -> Result<()> { let data = self.security.encode_8370(msg, MsgType::ENCRYPTED_REQUEST)?; - // encrypted - let e = b"\x83p\x00~ fq\xa8\xa9b(\x8c\r,\x96X\xbdT\x1d\x06\xa1/\xb5@\xa2\xeb\x96\x0c\x01s\xf0\x8c\x98ELT\x89\x81\xcc\x9d\xaa\xb6[dq\xcf\x98\xd1s\x8c\x08\x0e\xe6u1D\x80\x17I\xe2\x987s\xbe\xb2\xa9\x13\x86\xce\xb3qq\xfe\xa6\x11\x10\xcfi\xc2\xaeXJH\xb8\xa8\x0b5\x08z\x00\xec\xa2t\x13\xeds\xe7:\x0f\x0eP\xfe\x80w7\xbb\xdf\x0f\x14D\xfd9\xceZ\xda\x1a\xda\xfb\x0b\xe0\x92\xc2D\xb4\xdfWE\x89_\xd9\xd0\xb2\xb6\xd9"; - - // assert_eq!(data, e); - self.socket.lock().unwrap().write(&data)?; Ok(()) @@ -226,17 +223,14 @@ impl Device { fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> { let data = PacketBuilder::builder(self.info.id, cmd).finalize(1); - // msg - let d = b"ZZ\x01\x11X\x00 \x00\x00\x00\x00\x00\x0435\x06\x02\n\x17\x14\x02\x86\x02\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17G\x0b\xc3\xe7\r\xaa\xb93\x81r0?\x02s\xf8\x88\xe9G)\xa5\x127\x99\xdd\x8afi\xbd\xf6\xc3\xf5\xf1B\x84\x8a&n\xd9\xdb\xba\xee(\\\'\xb2\x0c\x02"; - - // assert_eq!(data, d); - self.send_message(&data) } } #[cfg(test)] mod test { + use std::net::{Ipv4Addr, SocketAddr}; + use anyhow::{Context, Result}; use futures::future::try_join; use serial_test::serial; @@ -293,4 +287,24 @@ mod test { Ok(()) } + + #[tokio::test] + #[serial] + async fn communication() -> Result<()> { + let token = "dead840607856a4b84c1a9d94ce8f553b50c037b65fa1ca22126de339c367eb765b231b4525d5b8336c48fe5dae38439bbb5e31282ed3790ff98a48049401dca"; + let key = "50e77947dc63426db3883c7616613410044b02567f5240a5baf789391b2e5a79"; + + let device_info = crate::DeviceInfo { + id: 152832116426242, + model: "760EY015".to_string(), + sn: "0000E1541760EY01534091D002581H2R".to_string(), + protocol: 3, + device_type: 225, + addr: SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(192, 168, 178, 94)), 6444), + }; + + Device::connect(device_info, token, key)?; + + Ok(()) + } } diff --git a/src/devices/e1.rs b/src/devices/e1.rs index 2b94271..95a222c 100644 --- a/src/devices/e1.rs +++ b/src/devices/e1.rs @@ -7,166 +7,11 @@ use anyhow::Result; use crate::command::*; -use super::DeviceBackend; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum DeviceAttributes { - Power, - Status, - Mode, - Additional, - Door, - RinseAid, - Salt, - ChildLock, - UV, - Dry, - DryStatus, - Storage, - StorageStatus, - TimeRemaining, - Progress, - StorageRemaining, - Temperature, - Humidity, - Waterswitch, - WaterLack, - ErrorCode, - Softwater, - WrongOperation, - Bright, -} - -impl DeviceAttributes { - fn as_str(&self) -> &str { - match self { - DeviceAttributes::Power => "power", - DeviceAttributes::Status => "status", - DeviceAttributes::Mode => "mode", - DeviceAttributes::Additional => "additional", - DeviceAttributes::Door => "door", - DeviceAttributes::RinseAid => "rinse_aid", - DeviceAttributes::Salt => "salt", - DeviceAttributes::ChildLock => "child_lock", - DeviceAttributes::UV => "uv", - DeviceAttributes::Dry => "dry", - DeviceAttributes::DryStatus => "dry_status", - DeviceAttributes::Storage => "storage", - DeviceAttributes::StorageStatus => "storage_status", - DeviceAttributes::TimeRemaining => "time_remaining", - DeviceAttributes::Progress => "progress", - DeviceAttributes::StorageRemaining => "storage_remaining", - DeviceAttributes::Temperature => "temperature", - DeviceAttributes::Humidity => "humidity", - DeviceAttributes::Waterswitch => "waterswitch", - DeviceAttributes::WaterLack => "water_lack", - DeviceAttributes::ErrorCode => "error_code", - DeviceAttributes::Softwater => "softwater", - DeviceAttributes::WrongOperation => "wrong_operation", - DeviceAttributes::Bright => "bright", - } - } - - fn from_str(s: &str) -> Self { - match s { - "power" => Self::Power, - "status" => Self::Status, - "mode" => Self::Mode, - "additional" => Self::Additional, - "door" => Self::Door, - "rinse_aid" => Self::RinseAid, - "salt" => Self::Salt, - "child_lock" => Self::ChildLock, - "uv" => Self::UV, - "dry" => Self::Dry, - "dry_status" => Self::DryStatus, - "storage" => Self::Storage, - "storage_status" => Self::StorageStatus, - "time_remaining" => Self::TimeRemaining, - "progress" => Self::Progress, - "storage_remaining" => Self::StorageRemaining, - "temperature" => Self::Temperature, - "humidity" => Self::Humidity, - "waterswitch" => Self::Waterswitch, - "water_lack" => Self::WaterLack, - "error_code" => Self::ErrorCode, - "softwater" => Self::Softwater, - "wrong_operation" => Self::WrongOperation, - "bright" => Self::Bright, - - _ => panic!(), - } - } -} - -pub enum AttributeValue { - String(Option), - Bool(bool), - Int(u8), -} - -impl AttributeValue { - pub fn set(&mut self, value: impl Into) { - match (self, value.into()) { - (Self::String(current), Self::String(new)) => *current = new, - (Self::String(current), Self::Bool(new)) => *current = Some(new.to_string()), - (Self::String(current), Self::Int(new)) => *current = Some(new.to_string()), - (Self::Bool(current), Self::Bool(new)) => *current = new, - (Self::Int(current), Self::Int(new)) => *current = new, - - _ => panic!(), - } - } - - pub fn str(&self) -> Option<&str> { - match self { - Self::String(opt) => opt.as_ref().map(|s| s.as_str()), - _ => panic!(), - } - } - - pub fn bool(&self) -> bool { - match self { - Self::Bool(bool) => *bool, - _ => panic!(), - } - } - - pub fn byte(&self) -> u8 { - match self { - Self::Int(b) => *b, - _ => panic!(), - } - } -} - -impl From for AttributeValue { - fn from(value: String) -> Self { - Self::String(Some(value)) - } -} - -impl From> for AttributeValue { - fn from(value: Option) -> Self { - Self::String(value) - } -} - -impl From for AttributeValue { - fn from(value: bool) -> Self { - Self::Bool(value) - } -} - -impl From for AttributeValue { - fn from(value: u8) -> Self { - Self::Int(value) - } -} +use super::{AttributeValue, DeviceBackend}; pub struct E1 { modes: HashMap, - attributes: HashMap, + attributes: HashMap<&'static str, AttributeValue>, status: [&'static str; 5], progress: [&'static str; 6], @@ -205,39 +50,30 @@ impl E1 { .collect(); let attributes = [ - (DeviceAttributes::Power, AttributeValue::Bool(false)), - (DeviceAttributes::Status, AttributeValue::String(None)), - (DeviceAttributes::Mode, AttributeValue::Int(0)), - (DeviceAttributes::Additional, AttributeValue::Int(0)), - (DeviceAttributes::UV, AttributeValue::Bool(false)), - (DeviceAttributes::Dry, AttributeValue::Bool(false)), - (DeviceAttributes::DryStatus, AttributeValue::Bool(false)), - (DeviceAttributes::Door, AttributeValue::Bool(false)), - (DeviceAttributes::RinseAid, AttributeValue::Bool(false)), - (DeviceAttributes::Salt, AttributeValue::Bool(false)), - (DeviceAttributes::ChildLock, AttributeValue::Bool(false)), - (DeviceAttributes::Storage, AttributeValue::Bool(false)), - (DeviceAttributes::StorageStatus, AttributeValue::Bool(false)), - ( - DeviceAttributes::TimeRemaining, - AttributeValue::String(None), - ), - (DeviceAttributes::Progress, AttributeValue::String(None)), - ( - DeviceAttributes::StorageRemaining, - AttributeValue::String(None), - ), - (DeviceAttributes::Temperature, AttributeValue::String(None)), - (DeviceAttributes::Humidity, AttributeValue::String(None)), - (DeviceAttributes::Waterswitch, AttributeValue::Bool(false)), - (DeviceAttributes::WaterLack, AttributeValue::Bool(false)), - (DeviceAttributes::ErrorCode, AttributeValue::String(None)), - (DeviceAttributes::Softwater, AttributeValue::Int(0)), - ( - DeviceAttributes::WrongOperation, - AttributeValue::String(None), - ), - (DeviceAttributes::Bright, AttributeValue::Int(0)), + ("power", AttributeValue::Bool(false)), + ("status", AttributeValue::Int(0)), + ("mode", AttributeValue::Int(0)), + ("additional", AttributeValue::Int(0)), + ("uv", AttributeValue::Bool(false)), + ("dry", AttributeValue::Bool(false)), + ("dry_status", AttributeValue::Bool(false)), + ("door", AttributeValue::Bool(false)), + ("rinse_aid", AttributeValue::Bool(false)), + ("salt", AttributeValue::Bool(false)), + ("child_lock", AttributeValue::Bool(false)), + ("storage", AttributeValue::Bool(false)), + ("storage_progress", AttributeValue::Bool(false)), + ("time_remaining", AttributeValue::String(None)), + ("progress", AttributeValue::String(None)), + ("storage_remaining", AttributeValue::String(None)), + ("temperature", AttributeValue::String(None)), + ("humidity", AttributeValue::String(None)), + ("waterswitch", AttributeValue::Bool(false)), + ("water_lack", AttributeValue::Bool(false)), + ("error_code", AttributeValue::String(None)), + ("softwater", AttributeValue::Int(0)), + ("wrong_operation", AttributeValue::String(None)), + ("bright", AttributeValue::Int(0)), ] .into_iter() .collect(); @@ -261,31 +97,23 @@ impl DeviceBackend for E1 { CommandQuery::new(self.device_protocol_version).request() } - fn process_message(&self, msg: &[u8]) -> Result> { - let cmd = CommandE1Response::new(msg); - - todo!() + fn process_message(&mut self, msg: &[u8]) { + CommandE1Response::new(msg).update_attributes(&mut self.attributes); } fn set_attribute(&self, attribute: &str, value: &str) -> () { - match DeviceAttributes::from_str(attribute) { - DeviceAttributes::Power => (), - DeviceAttributes::ChildLock => (), - DeviceAttributes::Storage => (), + match attribute { + "power" => (), + "child_lock" => (), + "storage" => (), _ => (), } } -} -macro_rules! concat { - ($vec1:expr, $($vec2:expr,)+) => {{ - $( - $vec1.extend(&$vec2); - )+ - - $vec1 - }}; + fn attributes(&self) -> &HashMap<&'static str, AttributeValue> { + &self.attributes + } } pub struct CommandE1Base { @@ -367,7 +195,7 @@ impl CommandLock { command: CommandE1Base::new( device_protocol_version, MessageType::Set, - Body::from((concat!(vec![0x04], vec![0x00; 36],).as_slice(), 0x08)), + Body::from(([vec![0x04], vec![0x00; 36]].concat().as_slice(), 0x08)), #[cfg(debug_assertions)] "Lock", ), @@ -406,7 +234,9 @@ impl CommandStorage { device_protocol_version, MessageType::Set, Body::from(( - concat!(vec![0x00; 4], vec![0xFF; 6], vec![0x00; 27],).as_slice(), + [vec![0x00; 4], vec![0xFF; 6], vec![0x00; 27]] + .concat() + .as_slice(), 0x81, )), #[cfg(debug_assertions)] @@ -473,50 +303,35 @@ impl CommandE1Response { } } - pub fn update_attributes( - &mut self, - attributes: &mut HashMap, - ) { + pub fn update_attributes(&mut self, attributes: &mut HashMap<&'static str, AttributeValue>) { let message_type = self.command.header().message_type(); let body = self.command.body(); let body_type = self.command.body().body_type(); - if (message_type == MessageType::Set && 0 <= body_type && body_type <= 7) + if (message_type == MessageType::Set && body_type <= 7) || ((message_type == MessageType::Query || message_type == MessageType::Notify1) && body_type == 0) { - attributes - .get_mut(&DeviceAttributes::Power) - .unwrap() - .set(body[1] > 0); - attributes - .get_mut(&DeviceAttributes::Status) - .unwrap() - .set(body[1]); - attributes - .get_mut(&DeviceAttributes::Mode) - .unwrap() - .set(body[2]); - attributes - .get_mut(&DeviceAttributes::Additional) - .unwrap() - .set(body[3]); + attributes.get_mut("power").unwrap().set(body[1] > 0); + attributes.get_mut("status").unwrap().set(body[1]); + attributes.get_mut("mode").unwrap().set(body[2]); + attributes.get_mut("additional").unwrap().set(body[3]); // 0 - open, 1 - close attributes - .get_mut(&DeviceAttributes::Door) + .get_mut("door") .unwrap() .set((body[5] & 0x01) == 0); // 0 - enough, 1 - shortage attributes - .get_mut(&DeviceAttributes::RinseAid) + .get_mut("rinse_aid") .unwrap() .set((body[5] & 0x02) > 0); // e - enough, 1 - shortage attributes - .get_mut(&DeviceAttributes::Salt) + .get_mut("salt") .unwrap() .set((body[5] & 0x04) > 0); @@ -524,58 +339,41 @@ impl CommandE1Response { if start_pause { self.start = true; - } else if attributes[&DeviceAttributes::Status].byte() == 2 - || attributes[&DeviceAttributes::Status].byte() == 3 - { + } else if attributes["status"].byte() == 2 || attributes["status"].byte() == 3 { self.start = false; } attributes - .get_mut(&DeviceAttributes::ChildLock) + .get_mut("child_lock") .unwrap() .set((body[5] & 0x10) > 0); + attributes.get_mut("uv").unwrap().set((body[4] & 0x02) > 0); + attributes.get_mut("dry").unwrap().set((body[4] & 0x10) > 0); attributes - .get_mut(&DeviceAttributes::UV) - .unwrap() - .set((body[4] & 0x02) > 0); - attributes - .get_mut(&DeviceAttributes::Dry) - .unwrap() - .set((body[4] & 0x10) > 0); - attributes - .get_mut(&DeviceAttributes::DryStatus) + .get_mut("dry_status") .unwrap() .set((body[4] & 0x20) > 0); attributes - .get_mut(&DeviceAttributes::Status) + .get_mut("storage") .unwrap() .set((body[5] & 0x20) > 0); attributes - .get_mut(&DeviceAttributes::StorageStatus) + .get_mut("storage_progress") .unwrap() .set((body[5] & 0x40) > 0); + attributes.get_mut("time_remaining").unwrap().set(body[6]); + attributes.get_mut("progress").unwrap().set(body[9]); attributes - .get_mut(&DeviceAttributes::TimeRemaining) - .unwrap() - .set(body[6]); - attributes - .get_mut(&DeviceAttributes::Progress) - .unwrap() - .set(body[9]); - attributes - .get_mut(&DeviceAttributes::StorageRemaining) + .get_mut("storage_remaining") .unwrap() .set(if body.len() > 18 { body[11] } else { false.into() }); + attributes.get_mut("temperature").unwrap().set(body[11]); attributes - .get_mut(&DeviceAttributes::Temperature) - .unwrap() - .set(body[11]); - attributes - .get_mut(&DeviceAttributes::Humidity) + .get_mut("humidity") .unwrap() .set(if body.len() > 33 { body[33].into() @@ -583,27 +381,18 @@ impl CommandE1Response { AttributeValue::from(None) }); attributes - .get_mut(&DeviceAttributes::Waterswitch) + .get_mut("waterswitch") .unwrap() .set((body[4] & 0x04) > 0); attributes - .get_mut(&DeviceAttributes::WaterLack) + .get_mut("water_lack") .unwrap() .set((body[5] & 0x80) > 0); + attributes.get_mut("error_code").unwrap().set(body[10]); + attributes.get_mut("softwater").unwrap().set(body[13]); + attributes.get_mut("wrong_operation").unwrap().set(body[16]); attributes - .get_mut(&DeviceAttributes::ErrorCode) - .unwrap() - .set(body[10]); - attributes - .get_mut(&DeviceAttributes::Softwater) - .unwrap() - .set(body[13]); - attributes - .get_mut(&DeviceAttributes::WrongOperation) - .unwrap() - .set(body[16]); - attributes - .get_mut(&DeviceAttributes::Bright) + .get_mut("bright") .unwrap() .set(if body.len() > 24 { body[24].into() diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 6ae1ccd..6e73319 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,11 +1,78 @@ -use anyhow::Result; +use std::collections::HashMap; use crate::command::CommandRequest; pub mod e1; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum AttributeValue { + String(Option), + Bool(bool), + Int(u8), +} + +impl AttributeValue { + pub fn set(&mut self, value: impl Into) { + match (self, value.into()) { + (Self::String(current), Self::String(new)) => *current = new, + (Self::String(current), Self::Bool(new)) => *current = Some(new.to_string()), + (Self::String(current), Self::Int(new)) => *current = Some(new.to_string()), + (Self::Bool(current), Self::Bool(new)) => *current = new, + (Self::Int(current), Self::Int(new)) => *current = new, + + _ => panic!(), + } + } + + pub fn str(&self) -> Option<&str> { + match self { + Self::String(opt) => opt.as_ref().map(|s| s.as_str()), + _ => panic!(), + } + } + + pub fn bool(&self) -> bool { + match self { + Self::Bool(bool) => *bool, + _ => panic!(), + } + } + + pub fn byte(&self) -> u8 { + match self { + Self::Int(b) => *b, + _ => panic!(), + } + } +} + +impl From for AttributeValue { + fn from(value: String) -> Self { + Self::String(Some(value)) + } +} + +impl From> for AttributeValue { + fn from(value: Option) -> Self { + Self::String(value) + } +} + +impl From for AttributeValue { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} + +impl From for AttributeValue { + fn from(value: u8) -> Self { + Self::Int(value) + } +} + pub trait DeviceBackend { fn build_query(&self) -> CommandRequest; - fn process_message(&self, msg: &[u8]) -> Result>; + fn process_message(&mut self, msg: &[u8]); + fn attributes(&self) -> &HashMap<&'static str, AttributeValue>; fn set_attribute(&self, attribute: &str, value: &str) -> (); } diff --git a/src/packet_builder.rs b/src/packet_builder.rs index a61ead1..7a4d287 100644 --- a/src/packet_builder.rs +++ b/src/packet_builder.rs @@ -62,16 +62,7 @@ impl PacketBuilder { packet[3] = 0x10; packet[6] = 0x7b; } else { - let mut data = self.command.serialize(); - - // data - let d = b"\xaa\x1d\xe1\x00\x00\x00\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00b"; - - assert_eq!(data, d); - - Security::aes_encrypt(&mut data); - - packet.extend(data); + packet.extend(Security::aes_encrypt(&self.command.serialize())); } // packet length diff --git a/src/security.rs b/src/security.rs index b236004..d635b20 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, - BlockEncrypt, BlockEncryptMut, KeyInit, KeyIvInit, + BlockEncrypt, BlockEncryptMut, BlockSizeUser, KeyInit, KeyIvInit, }, Aes128, }; @@ -40,7 +40,7 @@ impl Security { md5::compute([data, Self::SALT].concat()).to_vec() } - pub fn aes_encrypt(data: &mut [u8]) -> Vec { + pub fn aes_encrypt(data: &[u8]) -> Vec { let array = GenericArray::from(Self::KEY); let cipher = Aes128::new(&array); @@ -74,27 +74,21 @@ impl Security { } } - pub fn aes_cbc_encrypt(&self, raw: &[u8], key: &[u8; 32]) -> Vec { + pub fn aes_cbc_encrypt(raw: &[u8], key: &[u8; 32]) -> Vec { type Aes256CbcEnc = cbc::Encryptor; - debug_assert_eq!(raw.len() % key.len(), 0); + debug_assert_eq!(raw.len() % Aes256CbcEnc::block_size(), 0); - raw.chunks(key.len()) - .map(|r| { - Aes256CbcEnc::new(key.into(), &Self::IV.into()) - .encrypt_padded_vec_mut::(r) - }) - .flatten() - .collect() + Aes256CbcEnc::new(key.into(), &Self::IV.into()).encrypt_padded_vec_mut::(raw) } - pub fn aes_cbc_decrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] { + pub fn aes_cbc_decrypt(raw: &[u8], key: &[u8; 32]) -> Vec { type Aes256CbcDec = cbc::Decryptor; + debug_assert_eq!(raw.len() % Aes256CbcDec::block_size(), 0); + Aes256CbcDec::new(key.into(), &Self::IV.into()) - .decrypt_padded_vec_mut::(&raw) - .unwrap() - .try_into() + .decrypt_padded_vec_mut::(raw) .unwrap() } @@ -103,15 +97,9 @@ impl Security { bail!("authentication failed! (code ERROR)"); } - let payload: [u8; 32] = response[0..32] - .iter() - .map(|&b| b) - .collect::>() - .try_into() - .unwrap(); - + let payload = &response[0..32]; let sign = &response[32..]; - let result = self.aes_cbc_decrypt(payload, &key); + let result = Self::aes_cbc_decrypt(payload, &key); if Sha256::digest(&result).into_iter().collect::>() != sign { bail!("sign does not match"); @@ -168,7 +156,7 @@ impl Security { .into_iter() .collect(); - data = self.aes_cbc_encrypt(&data, self.tcp_key.as_ref().unwrap()); + data = Self::aes_cbc_encrypt(&data, self.tcp_key.as_ref().unwrap()); data.extend(sign); } @@ -209,14 +197,24 @@ impl Security { let msgtype = header[5] & 0xf; data = data[6..].to_vec(); + let header_ref = b"\x83p\x00\x8e c"; + + assert_eq!(header_ref, header.as_slice()); + if msgtype == MsgType::ENCRYPTED_RESPONSE as u8 || msgtype == MsgType::ENCRYPTED_REQUEST as u8 { let sign = data[(data.len() - 32)..].to_vec(); + // let sign_ref = b"\xf5\x1c\x93\x18\xa3\xb9HR.X\x9aFU\xb1\xc4N\xe8J\x1a\x04\x0f\xd4\x90\xc4\x132\xeby\xa6\x83\x06e"; + + // assert_eq!(sign, sign_ref); + data = data[..(data.len() - 32)].to_vec(); - data = self - .aes_cbc_decrypt(data.try_into().unwrap(), &self.tcp_key.unwrap()) - .to_vec(); + let data_ref = b"\xb2\x174\xbb\xf4\xf1\x8fWg\xc4\x97h%\x06\x9aCIL\xf6tf\x86\x8b\x83\xac\xa9@\xb1r\xb3\xa0>>\xd0_#\x03\x93\xcdZ[\x17\xde\xd8\xaeT\x1a\xd0\x0f\xa5\x8fk\xebJF\x0b\xfd\xf8\\o_\x06\xe9\xdd\xda45\x0b\x90\xf1\x82*\x9f\xa4\x1a\xc3\xb3\xde]\x04\x9e\x9c\xafy\xcfq\x02\xa34\xa4\x15\x0b\xcb\t\x96V\xd3f\x14\xd6\xf5X]\xc3'\xbc\x1b\xc1\x10\x08\x9c\x8d"; + + // assert_eq!(data, data_ref); + + data = Self::aes_cbc_decrypt(&data, &self.tcp_key.unwrap()).to_vec(); let compare_sign: Vec = Sha256::digest(&[header.to_vec(), data.clone()].concat()) .into_iter() @@ -254,21 +252,35 @@ mod test { #[test] fn aes_cbc_decrypt() { - let payload: [u8; 32] = - b",\xcbq_T\x81L\x96\xfa\xe7\xe4\xa7\xc5\xabU \r\xf5x\xd6\x08\x94_\\\xce\x8br\x1b\xa5\xbe\xc6\x1a" - .iter() - .map(|&b| b) - .collect::>() - .try_into() - .unwrap(); - + let payload = + b",\xcbq_T\x81L\x96\xfa\xe7\xe4\xa7\xc5\xabU \r\xf5x\xd6\x08\x94_\\\xce\x8br\x1b\xa5\xbe\xc6\x1a"; let key = b"*[R\x00\xc2\xc0ML\x81\x1d\x05P\xe1\xdc[1CT6\xb9[wM*\x88\xd7\xe4ma\xfd\x96i"; let plain = b"\x9b\xaa\xdf\xff\x07\x1a\xd2\xe4\xb7TY\xe2\xf9\x8c\xdf\xe7!+\xda\xe4\x86GY\xe6j\x94\xdb\xe7\xb9b\xda\xe6"; - let security = Security::default(); - - let result = security.aes_cbc_decrypt(payload, key); + let result = Security::aes_cbc_decrypt(payload, key); assert_eq!(&result, plain); } + + #[test] + fn xor() { + let lhs = b"\xf8\x9c\xd9\xb4{>X\x95Y\xff\xd9\x95\x98nY\x95\x89\x8eY\x95\xe9\x86Y\x95\xe1\xb6Y\x95\xd1\xb2Y\x95"; + let rhs = b"P\xe7yG\xdccBm\xb3\x88\x84z\xfe\xda\xd6Gx\x1d\n\xf6F\xac\xe9\xcd\xc5\xb9\xb8\x98\xae\x1b\n\xab\xc9\xee\x9a\xb6\xd2\xc5=:"; + let res = Security::aes_cbc_decrypt(data, key); + + assert_eq!(res.len(), res_ref.len()); + assert_eq!(res, res_ref); + } }