Implement packet builder

This commit is contained in:
hodasemi 2023-09-29 15:10:47 +02:00
parent 3d37654850
commit d1bef25ac9
6 changed files with 192 additions and 43 deletions

View file

@ -5,6 +5,9 @@ import asyncio;
import discover; import discover;
import device; import device;
import devicee1; import devicee1;
import packet_builder;
import datetime;
async def test(): async def test():
cl = cloud.MSmartHomeCloud( cl = cloud.MSmartHomeCloud(
@ -68,6 +71,31 @@ async def test():
if dev.connect(True): if dev.connect(True):
return dev 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()) dev = asyncio.run(test())
print(dev) print(dev)

View file

@ -242,35 +242,15 @@ impl Cloud {
for method in [1, 2] { for method in [1, 2] {
let udp_id = CloudSecurity::udp_id(device_id, method); let udp_id = CloudSecurity::udp_id(device_id, method);
// 1: "a79479839b272432f3bbf971f9faed30"
// 2: "a153e4273b29c04db85763c739d45203"
let mut data = self.make_general_data(); let mut data = self.make_general_data();
data.insert("udpid".to_string(), udp_id.clone()); 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( let response = Response::from_str(
&self &self
.api_request("/v1/iot/secure/getToken", to_string(&data)?) .api_request("/v1/iot/secure/getToken", to_string(&data)?)
.await?, .await?,
)?; )?;
// '76697C9A0685778C6B4F487A70497F4DB3CDD2C0949138B5BA798DADC5AE0712ECF6C3559C7EE68B96B2BB2DC140CF6172D9F1587065D4A536A75D492031E22E'
// '025f9ff7bd3c4aceb1e559ab13d5e73f6fb2358e2bcf4bb883ab62225d6b9d2d'
for token in response.token_list() { for token in response.token_list() {
if token.udpId == udp_id { if token.udpId == udp_id {
return Ok(( return Ok((

View file

@ -1,6 +1,10 @@
use super::{body::Body, header::Header, Command, MessageType}; use super::{body::Body, header::Header, Command, MessageType};
use std::ops::Deref; use std::ops::Deref;
pub trait RequestSerializer {
fn serialize(&self) -> Vec<u8>;
}
pub struct CommandRequest { pub struct CommandRequest {
command: Command, command: Command,
} }
@ -29,16 +33,6 @@ impl CommandRequest {
} }
} }
pub fn serialize(&self) -> Vec<u8> {
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 { pub fn header(&self) -> &Header {
&self.command.header &self.command.header
} }
@ -52,6 +46,18 @@ impl CommandRequest {
} }
} }
impl RequestSerializer for CommandRequest {
fn serialize(&self) -> Vec<u8> {
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 { pub struct CommandQuerySubtype {
command: CommandRequest, command: CommandRequest,
} }
@ -81,6 +87,12 @@ impl Deref for CommandQuerySubtype {
} }
} }
impl RequestSerializer for CommandQuerySubtype {
fn serialize(&self) -> Vec<u8> {
self.command.serialize()
}
}
pub struct CommandQueryCustom { pub struct CommandQueryCustom {
command: CommandRequest, command: CommandRequest,
} }
@ -104,3 +116,17 @@ impl Deref for CommandQueryCustom {
&self.command &self.command
} }
} }
impl RequestSerializer for CommandQueryCustom {
fn serialize(&self) -> Vec<u8> {
self.command.serialize()
}
}
pub struct CommandHeartbeat;
impl RequestSerializer for CommandHeartbeat {
fn serialize(&self) -> Vec<u8> {
vec![0x00]
}
}

View file

@ -7,10 +7,12 @@ use std::{
}; };
use anyhow::{bail, Context, Error, Result}; use anyhow::{bail, Context, Error, Result};
use serde_json::to_string;
use crate::{ use crate::{
command::{CommandQuerySubtype, CommandRequest, CommandSubtypeResponse, MessageType}, command::{
CommandHeartbeat, CommandQuerySubtype, CommandRequest, CommandSubtypeResponse, MessageType,
RequestSerializer,
},
devices::{e1::E1, DeviceBackend}, devices::{e1::E1, DeviceBackend},
hex, hex,
packet_builder::PacketBuilder, packet_builder::PacketBuilder,
@ -211,13 +213,12 @@ impl Device {
} }
fn send_heartbeat(&self) -> Result<()> { 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) self.send_message(&msg)
} }
fn build_send(&self, cmd: CommandRequest) -> Result<()> { fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> {
let data = PacketBuilder::builder(self.info.id, to_string(&cmd.serialize())?.as_bytes()) let data = PacketBuilder::builder(self.info.id, cmd).finalize(1);
.finalize(1);
self.send_message(&data) self.send_message(&data)
} }
} }

View file

@ -1,13 +1,98 @@
pub struct PacketBuilder { use std::str::from_utf8;
//
use chrono::Local;
use crate::{command::RequestSerializer, security::Security};
pub struct PacketBuilder<S: RequestSerializer> {
command: S,
packet: [u8; 40],
} }
impl PacketBuilder { impl<S: RequestSerializer> PacketBuilder<S> {
pub fn builder(device_id: u64, msg: &[u8]) -> Self { pub fn builder(device_id: u64, command: S) -> Self {
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<u8> { pub fn finalize(self, msg_type: u8) -> Vec<u8> {
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);
} }
} }

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,
BlockEncryptMut, KeyInit, KeyIvInit, BlockEncrypt, BlockEncryptMut, KeyInit, KeyIvInit,
}, },
Aes128, Aes128,
}; };
@ -34,6 +34,35 @@ impl Security {
const N: u128 = 141661095494369103254425781617665632877; const N: u128 = 141661095494369103254425781617665632877;
const KEY: [u8; 16] = Self::N.to_be_bytes(); const KEY: [u8; 16] = Self::N.to_be_bytes();
const IV: [u8; 16] = [b'\0'; 16]; const IV: [u8; 16] = [b'\0'; 16];
const SALT: &[u8] = b"xhdiwjnchekd4d512chdjx5d8e4c394D2D7S";
pub fn encode32_data(data: &[u8]) -> Vec<u8> {
md5::compute([data, Self::SALT].concat()).to_vec()
}
pub fn aes_encrypt(data: &mut [u8]) -> Vec<u8> {
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]) { pub fn aes_decrypt(data: &mut [u8]) {
let array = GenericArray::from(Self::KEY); let array = GenericArray::from(Self::KEY);