Implement discover
This commit is contained in:
parent
50476b3acc
commit
7f62fd65e1
5 changed files with 195 additions and 27 deletions
27
.vscode/launch.json
vendored
Normal file
27
.vscode/launch.json
vendored
Normal file
|
@ -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}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -8,3 +8,5 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
||||
if-addrs = "0.10.1"
|
||||
aes = "0.8.3"
|
||||
rand = "0.8.5"
|
121
src/discover.rs
121
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<Self> {
|
||||
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<Self> {
|
||||
pub fn discover() -> Result<Vec<DeviceInfo>> {
|
||||
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<Vec<u8>, 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(())
|
||||
}
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -1 +1,11 @@
|
|||
use std::num::ParseIntError;
|
||||
|
||||
mod discover;
|
||||
mod security;
|
||||
|
||||
fn hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
|
||||
(0..s.len())
|
||||
.step_by(2)
|
||||
.map(|i| u8::from_str_radix(&s[i..i + 2], 16))
|
||||
.collect()
|
||||
}
|
||||
|
|
60
src/security.rs
Normal file
60
src/security.rs
Normal file
|
@ -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<String> {
|
||||
let mut header = hex("83,70")?;
|
||||
let mut data: Vec<u8> = 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!()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue