Fix connection to device

This commit is contained in:
hodasemi 2023-10-03 09:19:19 +02:00
parent 2aec54c7a7
commit 1268d2df99
7 changed files with 276 additions and 402 deletions

View file

@ -9,69 +9,67 @@ import packet_builder;
import datetime; import datetime;
async def test(): # async def test():
cl = cloud.MSmartHomeCloud( # cl = cloud.MSmartHomeCloud(
"MSmartHome", # "MSmartHome",
aiohttp.ClientSession(), # aiohttp.ClientSession(),
"michaelh.95@t-online.de", # "michaelh.95@t-online.de",
"Hoda.semi1" # "Hoda.semi1"
) # )
devices = discover.discover() # devices = discover.discover()
if len(devices) > 0: # if len(devices) > 0:
# if await cl.login():
# for device_id in devices: # for device_id in devices:
# token = "702b9dfc3ac6c82979986ee3a053a76f75f9e9c763ce5c25af5c2cc982f797a9409adff3745e23fee3a464d745e005c839efb0b84082acc962e59ab8683e0299" # keys = await cl.get_keys(device_id)
# key = "52b2feee353841588994e630dcb59819ec71ce1ffacb48628f4f436f5c54f11e"
# for k in keys:
# token = keys[k]['token']
# key = keys[k]['key']
# device_info = devices[device_id] # device_info = devices[device_id]
# dev = device.MiedaDevice( # dev = devicee1.MideaE1Device(
# name="", # name="",
# device_id=device_id, # device_id=device_id,
# device_type=225,
# ip_address=device_info['ip_address'], # ip_address=device_info['ip_address'],
# port=device_info['port'], # port=device_info['port'],
# token=token, # token=token,
# key=key, # key=key,
# protocol=3, # protocol=3,
# model=device_info['model'], # model=device_info['model'],
# attributes={} # customize=""
# ) # )
# if dev.connect(False): # if dev.connect(True):
# print("success") # return dev
# else:
# print("fail")
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])
if await cl.login(): sec = security.LocalSecurity()
for device_id in devices: chunk_res = sec.aes_cbc_decrypt(chunk, key)
keys = await cl.get_keys(device_id)
for k in keys: print(chunk_ref)
token = keys[k]['token'] print(chunk_res)
key = keys[k]['key']
device_info = devices[device_id]
dev = devicee1.MideaE1Device( dev = devicee1.MideaE1Device(
name="", name="",
device_id=device_id, device_id=152832116426242,
ip_address=device_info['ip_address'], ip_address="192.168.178.94",
port=device_info['port'], port=6444,
token=token, token="dead840607856a4b84c1a9d94ce8f553b50c037b65fa1ca22126de339c367eb765b231b4525d5b8336c48fe5dae38439bbb5e31282ed3790ff98a48049401dca",
key=key, key="50e77947dc63426db3883c7616613410044b02567f5240a5baf789391b2e5a79",
protocol=3, protocol=3,
model=device_info['model'], model="760EY015",
customize="" customize=""
) )
if dev.connect(True): dev.connect(True)
return dev
dev = asyncio.run(test())
print(dev) print(dev)

View file

@ -250,6 +250,9 @@ class LocalSecurity:
padding = header[5] >> 4 padding = header[5] >> 4
msgtype = header[5] & 0xf msgtype = header[5] & 0xf
data = data[6:] data = data[6:]
# (6) vec![131, 112, 0, 5, 32, 15]
if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST): if msgtype in (MSGTYPE_ENCRYPTED_RESPONSE, MSGTYPE_ENCRYPTED_REQUEST):
sign = data[-32:] sign = data[-32:]
data = data[:-32] data = data[:-32]

View file

@ -1,4 +1,5 @@
use std::{ use std::{
collections::HashMap,
io::{Read, Write}, io::{Read, Write},
net::TcpStream, net::TcpStream,
sync::Mutex, sync::Mutex,
@ -10,10 +11,10 @@ use anyhow::{bail, Context, Error, Result};
use crate::{ use crate::{
command::{ command::{
CommandHeartbeat, CommandQuerySubtype, CommandRequest, CommandSubtypeResponse, MessageType, CommandHeartbeat, CommandQuerySubtype, CommandSubtypeResponse, MessageType,
RequestSerializer, RequestSerializer,
}, },
devices::{e1::E1, DeviceBackend}, devices::{e1::E1, AttributeValue, DeviceBackend},
hex, hex,
packet_builder::PacketBuilder, packet_builder::PacketBuilder,
security::{MsgType, Security}, security::{MsgType, Security},
@ -37,7 +38,7 @@ pub struct Device {
sub_type: u16, sub_type: u16,
device_protocol_version: u8, device_protocol_version: u8,
updates: Vec<Box<dyn Fn(&[u8]) -> Result<()>>>, updates: Vec<Box<dyn Fn(&HashMap<&'static str, AttributeValue>) -> Result<()>>>,
token: [u8; 64], token: [u8; 64],
key: [u8; 32], key: [u8; 32],
@ -127,7 +128,10 @@ impl Device {
loop { loop {
let mut buf = [0; 512]; 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 { if bytes_read == 0 {
bail!("socket error"); bail!("socket error");
@ -145,7 +149,7 @@ impl Device {
pub fn register_update<F>(&mut self, f: F) pub fn register_update<F>(&mut self, f: F)
where where
F: Fn(&[u8]) -> Result<()> + 'static, F: Fn(&HashMap<&'static str, AttributeValue>) -> Result<()> + 'static,
{ {
self.updates.push(Box::new(f)); self.updates.push(Box::new(f));
} }
@ -178,12 +182,10 @@ impl Device {
Security::aes_decrypt(crypt); Security::aes_decrypt(crypt);
if self.pre_process_message(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() { for update in self.updates.iter() {
update(&status)?; update(self.device_backend.attributes())?;
}
} }
} }
} }
@ -208,11 +210,6 @@ impl Device {
fn send_message(&self, msg: &[u8]) -> Result<()> { fn send_message(&self, msg: &[u8]) -> Result<()> {
let data = self.security.encode_8370(msg, MsgType::ENCRYPTED_REQUEST)?; 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)?; self.socket.lock().unwrap().write(&data)?;
Ok(()) Ok(())
@ -226,17 +223,14 @@ impl Device {
fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> { fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> {
let data = PacketBuilder::builder(self.info.id, cmd).finalize(1); 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) self.send_message(&data)
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::net::{Ipv4Addr, SocketAddr};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use futures::future::try_join; use futures::future::try_join;
use serial_test::serial; use serial_test::serial;
@ -293,4 +287,24 @@ mod test {
Ok(()) 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(())
}
} }

View file

@ -7,166 +7,11 @@ use anyhow::Result;
use crate::command::*; use crate::command::*;
use super::DeviceBackend; use super::{AttributeValue, 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<String>),
Bool(bool),
Int(u8),
}
impl AttributeValue {
pub fn set(&mut self, value: impl Into<Self>) {
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<String> for AttributeValue {
fn from(value: String) -> Self {
Self::String(Some(value))
}
}
impl From<Option<String>> for AttributeValue {
fn from(value: Option<String>) -> Self {
Self::String(value)
}
}
impl From<bool> for AttributeValue {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<u8> for AttributeValue {
fn from(value: u8) -> Self {
Self::Int(value)
}
}
pub struct E1 { pub struct E1 {
modes: HashMap<u32, String>, modes: HashMap<u32, String>,
attributes: HashMap<DeviceAttributes, AttributeValue>, attributes: HashMap<&'static str, AttributeValue>,
status: [&'static str; 5], status: [&'static str; 5],
progress: [&'static str; 6], progress: [&'static str; 6],
@ -205,39 +50,30 @@ impl E1 {
.collect(); .collect();
let attributes = [ let attributes = [
(DeviceAttributes::Power, AttributeValue::Bool(false)), ("power", AttributeValue::Bool(false)),
(DeviceAttributes::Status, AttributeValue::String(None)), ("status", AttributeValue::Int(0)),
(DeviceAttributes::Mode, AttributeValue::Int(0)), ("mode", AttributeValue::Int(0)),
(DeviceAttributes::Additional, AttributeValue::Int(0)), ("additional", AttributeValue::Int(0)),
(DeviceAttributes::UV, AttributeValue::Bool(false)), ("uv", AttributeValue::Bool(false)),
(DeviceAttributes::Dry, AttributeValue::Bool(false)), ("dry", AttributeValue::Bool(false)),
(DeviceAttributes::DryStatus, AttributeValue::Bool(false)), ("dry_status", AttributeValue::Bool(false)),
(DeviceAttributes::Door, AttributeValue::Bool(false)), ("door", AttributeValue::Bool(false)),
(DeviceAttributes::RinseAid, AttributeValue::Bool(false)), ("rinse_aid", AttributeValue::Bool(false)),
(DeviceAttributes::Salt, AttributeValue::Bool(false)), ("salt", AttributeValue::Bool(false)),
(DeviceAttributes::ChildLock, AttributeValue::Bool(false)), ("child_lock", AttributeValue::Bool(false)),
(DeviceAttributes::Storage, AttributeValue::Bool(false)), ("storage", AttributeValue::Bool(false)),
(DeviceAttributes::StorageStatus, AttributeValue::Bool(false)), ("storage_progress", AttributeValue::Bool(false)),
( ("time_remaining", AttributeValue::String(None)),
DeviceAttributes::TimeRemaining, ("progress", AttributeValue::String(None)),
AttributeValue::String(None), ("storage_remaining", AttributeValue::String(None)),
), ("temperature", AttributeValue::String(None)),
(DeviceAttributes::Progress, AttributeValue::String(None)), ("humidity", AttributeValue::String(None)),
( ("waterswitch", AttributeValue::Bool(false)),
DeviceAttributes::StorageRemaining, ("water_lack", AttributeValue::Bool(false)),
AttributeValue::String(None), ("error_code", AttributeValue::String(None)),
), ("softwater", AttributeValue::Int(0)),
(DeviceAttributes::Temperature, AttributeValue::String(None)), ("wrong_operation", AttributeValue::String(None)),
(DeviceAttributes::Humidity, AttributeValue::String(None)), ("bright", AttributeValue::Int(0)),
(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)),
] ]
.into_iter() .into_iter()
.collect(); .collect();
@ -261,31 +97,23 @@ impl DeviceBackend for E1 {
CommandQuery::new(self.device_protocol_version).request() CommandQuery::new(self.device_protocol_version).request()
} }
fn process_message(&self, msg: &[u8]) -> Result<Vec<u8>> { fn process_message(&mut self, msg: &[u8]) {
let cmd = CommandE1Response::new(msg); CommandE1Response::new(msg).update_attributes(&mut self.attributes);
todo!()
} }
fn set_attribute(&self, attribute: &str, value: &str) -> () { fn set_attribute(&self, attribute: &str, value: &str) -> () {
match DeviceAttributes::from_str(attribute) { match attribute {
DeviceAttributes::Power => (), "power" => (),
DeviceAttributes::ChildLock => (), "child_lock" => (),
DeviceAttributes::Storage => (), "storage" => (),
_ => (), _ => (),
} }
} }
fn attributes(&self) -> &HashMap<&'static str, AttributeValue> {
&self.attributes
} }
macro_rules! concat {
($vec1:expr, $($vec2:expr,)+) => {{
$(
$vec1.extend(&$vec2);
)+
$vec1
}};
} }
pub struct CommandE1Base { pub struct CommandE1Base {
@ -367,7 +195,7 @@ impl CommandLock {
command: CommandE1Base::new( command: CommandE1Base::new(
device_protocol_version, device_protocol_version,
MessageType::Set, 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)] #[cfg(debug_assertions)]
"Lock", "Lock",
), ),
@ -406,7 +234,9 @@ impl CommandStorage {
device_protocol_version, device_protocol_version,
MessageType::Set, MessageType::Set,
Body::from(( 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, 0x81,
)), )),
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -473,50 +303,35 @@ impl CommandE1Response {
} }
} }
pub fn update_attributes( pub fn update_attributes(&mut self, attributes: &mut HashMap<&'static str, AttributeValue>) {
&mut self,
attributes: &mut HashMap<DeviceAttributes, AttributeValue>,
) {
let message_type = self.command.header().message_type(); let message_type = self.command.header().message_type();
let body = self.command.body(); let body = self.command.body();
let body_type = self.command.body().body_type(); 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) || ((message_type == MessageType::Query || message_type == MessageType::Notify1)
&& body_type == 0) && body_type == 0)
{ {
attributes attributes.get_mut("power").unwrap().set(body[1] > 0);
.get_mut(&DeviceAttributes::Power) attributes.get_mut("status").unwrap().set(body[1]);
.unwrap() attributes.get_mut("mode").unwrap().set(body[2]);
.set(body[1] > 0); attributes.get_mut("additional").unwrap().set(body[3]);
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]);
// 0 - open, 1 - close // 0 - open, 1 - close
attributes attributes
.get_mut(&DeviceAttributes::Door) .get_mut("door")
.unwrap() .unwrap()
.set((body[5] & 0x01) == 0); .set((body[5] & 0x01) == 0);
// 0 - enough, 1 - shortage // 0 - enough, 1 - shortage
attributes attributes
.get_mut(&DeviceAttributes::RinseAid) .get_mut("rinse_aid")
.unwrap() .unwrap()
.set((body[5] & 0x02) > 0); .set((body[5] & 0x02) > 0);
// e - enough, 1 - shortage // e - enough, 1 - shortage
attributes attributes
.get_mut(&DeviceAttributes::Salt) .get_mut("salt")
.unwrap() .unwrap()
.set((body[5] & 0x04) > 0); .set((body[5] & 0x04) > 0);
@ -524,58 +339,41 @@ impl CommandE1Response {
if start_pause { if start_pause {
self.start = true; self.start = true;
} else if attributes[&DeviceAttributes::Status].byte() == 2 } else if attributes["status"].byte() == 2 || attributes["status"].byte() == 3 {
|| attributes[&DeviceAttributes::Status].byte() == 3
{
self.start = false; self.start = false;
} }
attributes attributes
.get_mut(&DeviceAttributes::ChildLock) .get_mut("child_lock")
.unwrap() .unwrap()
.set((body[5] & 0x10) > 0); .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 attributes
.get_mut(&DeviceAttributes::UV) .get_mut("dry_status")
.unwrap()
.set((body[4] & 0x02) > 0);
attributes
.get_mut(&DeviceAttributes::Dry)
.unwrap()
.set((body[4] & 0x10) > 0);
attributes
.get_mut(&DeviceAttributes::DryStatus)
.unwrap() .unwrap()
.set((body[4] & 0x20) > 0); .set((body[4] & 0x20) > 0);
attributes attributes
.get_mut(&DeviceAttributes::Status) .get_mut("storage")
.unwrap() .unwrap()
.set((body[5] & 0x20) > 0); .set((body[5] & 0x20) > 0);
attributes attributes
.get_mut(&DeviceAttributes::StorageStatus) .get_mut("storage_progress")
.unwrap() .unwrap()
.set((body[5] & 0x40) > 0); .set((body[5] & 0x40) > 0);
attributes.get_mut("time_remaining").unwrap().set(body[6]);
attributes.get_mut("progress").unwrap().set(body[9]);
attributes attributes
.get_mut(&DeviceAttributes::TimeRemaining) .get_mut("storage_remaining")
.unwrap()
.set(body[6]);
attributes
.get_mut(&DeviceAttributes::Progress)
.unwrap()
.set(body[9]);
attributes
.get_mut(&DeviceAttributes::StorageRemaining)
.unwrap() .unwrap()
.set(if body.len() > 18 { .set(if body.len() > 18 {
body[11] body[11]
} else { } else {
false.into() false.into()
}); });
attributes.get_mut("temperature").unwrap().set(body[11]);
attributes attributes
.get_mut(&DeviceAttributes::Temperature) .get_mut("humidity")
.unwrap()
.set(body[11]);
attributes
.get_mut(&DeviceAttributes::Humidity)
.unwrap() .unwrap()
.set(if body.len() > 33 { .set(if body.len() > 33 {
body[33].into() body[33].into()
@ -583,27 +381,18 @@ impl CommandE1Response {
AttributeValue::from(None) AttributeValue::from(None)
}); });
attributes attributes
.get_mut(&DeviceAttributes::Waterswitch) .get_mut("waterswitch")
.unwrap() .unwrap()
.set((body[4] & 0x04) > 0); .set((body[4] & 0x04) > 0);
attributes attributes
.get_mut(&DeviceAttributes::WaterLack) .get_mut("water_lack")
.unwrap() .unwrap()
.set((body[5] & 0x80) > 0); .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 attributes
.get_mut(&DeviceAttributes::ErrorCode) .get_mut("bright")
.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)
.unwrap() .unwrap()
.set(if body.len() > 24 { .set(if body.len() > 24 {
body[24].into() body[24].into()

View file

@ -1,11 +1,78 @@
use anyhow::Result; use std::collections::HashMap;
use crate::command::CommandRequest; use crate::command::CommandRequest;
pub mod e1; pub mod e1;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum AttributeValue {
String(Option<String>),
Bool(bool),
Int(u8),
}
impl AttributeValue {
pub fn set(&mut self, value: impl Into<Self>) {
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<String> for AttributeValue {
fn from(value: String) -> Self {
Self::String(Some(value))
}
}
impl From<Option<String>> for AttributeValue {
fn from(value: Option<String>) -> Self {
Self::String(value)
}
}
impl From<bool> for AttributeValue {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<u8> for AttributeValue {
fn from(value: u8) -> Self {
Self::Int(value)
}
}
pub trait DeviceBackend { pub trait DeviceBackend {
fn build_query(&self) -> CommandRequest; fn build_query(&self) -> CommandRequest;
fn process_message(&self, msg: &[u8]) -> Result<Vec<u8>>; fn process_message(&mut self, msg: &[u8]);
fn attributes(&self) -> &HashMap<&'static str, AttributeValue>;
fn set_attribute(&self, attribute: &str, value: &str) -> (); fn set_attribute(&self, attribute: &str, value: &str) -> ();
} }

View file

@ -62,16 +62,7 @@ impl<S: RequestSerializer> PacketBuilder<S> {
packet[3] = 0x10; packet[3] = 0x10;
packet[6] = 0x7b; packet[6] = 0x7b;
} else { } else {
let mut data = self.command.serialize(); packet.extend(Security::aes_encrypt(&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 length // packet length

View file

@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicU16, Ordering::SeqCst};
use aes::{ use aes::{
cipher::{ cipher::{
block_padding::NoPadding, generic_array::GenericArray, BlockDecrypt, BlockDecryptMut, block_padding::NoPadding, generic_array::GenericArray, BlockDecrypt, BlockDecryptMut,
BlockEncrypt, BlockEncryptMut, KeyInit, KeyIvInit, BlockEncrypt, BlockEncryptMut, BlockSizeUser, KeyInit, KeyIvInit,
}, },
Aes128, Aes128,
}; };
@ -40,7 +40,7 @@ impl Security {
md5::compute([data, Self::SALT].concat()).to_vec() md5::compute([data, Self::SALT].concat()).to_vec()
} }
pub fn aes_encrypt(data: &mut [u8]) -> Vec<u8> { pub fn aes_encrypt(data: &[u8]) -> Vec<u8> {
let array = GenericArray::from(Self::KEY); let array = GenericArray::from(Self::KEY);
let cipher = Aes128::new(&array); let cipher = Aes128::new(&array);
@ -74,27 +74,21 @@ impl Security {
} }
} }
pub fn aes_cbc_encrypt(&self, raw: &[u8], key: &[u8; 32]) -> Vec<u8> { pub fn aes_cbc_encrypt(raw: &[u8], key: &[u8; 32]) -> Vec<u8> {
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>; type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
debug_assert_eq!(raw.len() % key.len(), 0); debug_assert_eq!(raw.len() % Aes256CbcEnc::block_size(), 0);
raw.chunks(key.len()) Aes256CbcEnc::new(key.into(), &Self::IV.into()).encrypt_padded_vec_mut::<NoPadding>(raw)
.map(|r| {
Aes256CbcEnc::new(key.into(), &Self::IV.into())
.encrypt_padded_vec_mut::<NoPadding>(r)
})
.flatten()
.collect()
} }
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<u8> {
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>; type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
debug_assert_eq!(raw.len() % Aes256CbcDec::block_size(), 0);
Aes256CbcDec::new(key.into(), &Self::IV.into()) Aes256CbcDec::new(key.into(), &Self::IV.into())
.decrypt_padded_vec_mut::<NoPadding>(&raw) .decrypt_padded_vec_mut::<NoPadding>(raw)
.unwrap()
.try_into()
.unwrap() .unwrap()
} }
@ -103,15 +97,9 @@ impl Security {
bail!("authentication failed! (code ERROR)"); bail!("authentication failed! (code ERROR)");
} }
let payload: [u8; 32] = response[0..32] let payload = &response[0..32];
.iter()
.map(|&b| b)
.collect::<Vec<u8>>()
.try_into()
.unwrap();
let sign = &response[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::<Vec<u8>>() != sign { if Sha256::digest(&result).into_iter().collect::<Vec<u8>>() != sign {
bail!("sign does not match"); bail!("sign does not match");
@ -168,7 +156,7 @@ impl Security {
.into_iter() .into_iter()
.collect(); .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); data.extend(sign);
} }
@ -209,14 +197,24 @@ impl Security {
let msgtype = header[5] & 0xf; let msgtype = header[5] & 0xf;
data = data[6..].to_vec(); 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 if msgtype == MsgType::ENCRYPTED_RESPONSE as u8
|| msgtype == MsgType::ENCRYPTED_REQUEST as u8 || msgtype == MsgType::ENCRYPTED_REQUEST as u8
{ {
let sign = data[(data.len() - 32)..].to_vec(); 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 = data[..(data.len() - 32)].to_vec();
data = self 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";
.aes_cbc_decrypt(data.try_into().unwrap(), &self.tcp_key.unwrap())
.to_vec(); // assert_eq!(data, data_ref);
data = Self::aes_cbc_decrypt(&data, &self.tcp_key.unwrap()).to_vec();
let compare_sign: Vec<u8> = Sha256::digest(&[header.to_vec(), data.clone()].concat()) let compare_sign: Vec<u8> = Sha256::digest(&[header.to_vec(), data.clone()].concat())
.into_iter() .into_iter()
@ -254,21 +252,35 @@ mod test {
#[test] #[test]
fn aes_cbc_decrypt() { fn aes_cbc_decrypt() {
let payload: [u8; 32] = 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" 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::<Vec<u8>>()
.try_into()
.unwrap();
let key = b"*[R\x00\xc2\xc0ML\x81\x1d\x05P\xe1\xdc[1CT6\xb9[wM*\x88\xd7\xe4ma\xfd\x96i"; 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 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); 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<v\x16a4\x10\x04K\x02V\x7fR@\xa5\xba\xf7\x899\x1b.Zy";
let res = Security::xorstr(lhs, rhs);
assert_eq!(res, b"\xa8{\xa0\xf3\xa7]\x1a\xf8\xeaw\xe5\xe3\x8e\x0fm\x85\x8d\xc5[\xc3\x96\xd4\x190[A\xd0\xac\xca\x9c\x03\xec");
}
#[test]
fn aes_cbc_decrypt_padding() {
let data = b"~\xb3\xb2\xa3x\t\x95\\\xe5U\x08\xba\t\xafe\xd0M\x0f\xf2\xdb\xda!\xb9\xab\x08#\xcf\xb3\x1f\xb3K\xa5\x85\x97\x06\xb4s\xaa\x11\xde\xb7\xf4%<J~\x9d\xdd\x9c8\xa1+\xdd\xa2\xdb\xf0\xc5)\xb3Oa\xb0\xbd\xbf\xf8\xd0Ea~f*\xc7}\xa9\x89\xb8\x9b\x89\xd8\xaci\x11S}\xd4Z\x981z\xed\xbe\xcf\xc8V\xe8\xe9\x19.\x8d\xc9\xf6`\xb7\xc3\xf7\x98O$\x19\x0bUz";
let 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";
let res_ref = b"\x00\x00ZZ\x01\x11h\x00@\x00\xfe\x11\x00\x00\xe9C1\x13\x02\n\x17\x14\x02\x86\x02\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x00\x00\x00\x00\xb9qa\xcbB\xe7\x16\x97 b\xc8\xcd\x03N\x10\xd0\x94\xd9S\x10\xf9\x88\xbdZOX,\n\xe7L'\x1d\xf2p\xa5\x15\xf6\x9d\x1d>\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);
}
} }