use aes::Aes128;
use block_modes::block_padding::ZeroPadding;
use block_modes::{BlockMode, Cbc};
use packed_struct::prelude::{PackedStruct, PackedStructSlice};
use rand::Rng;
use crate::{
constants,
network::util::{checksum, reverse_mac},
traits::CommandTrait,
};
pub type AesCbc = Cbc<Aes128, ZeroPadding>;
#[derive(PackedStruct, Clone, Debug)]
#[packed_struct(bit_numbering = "msb0", endian = "lsb", size_bytes = "0x38")]
pub struct CommandMessage {
#[packed_field(bytes = "0x00:0x07")]
magic_header: [u8; 0x08],
#[packed_field(bytes = "0x24:0x25")]
device_type: u16,
#[packed_field(bytes = "0x26:0x27")]
packet_type: u16,
#[packed_field(bytes = "0x28:0x29")]
count: u16,
#[packed_field(bytes = "0x2A:0x2F")]
mac_reversed: [u8; 6],
#[packed_field(bytes = "0x30:0x33")]
id: u32,
#[packed_field(bytes = "0x20:0x21")]
checksum: u16,
#[packed_field(bytes = "0x34:0x35")]
payload_checksum: u16,
}
impl CommandMessage {
pub fn with_count<T>(
count: u16,
device_model_code: u16,
mac: [u8; 6],
id: u32,
) -> CommandMessage
where
T: CommandTrait,
{
return CommandMessage {
magic_header: [0x5A, 0xA5, 0xAA, 0x55, 0x5A, 0xA5, 0xaa, 0x55],
device_type: device_model_code,
packet_type: T::packet_type(),
count: count | 0x8000,
mac_reversed: reverse_mac(mac),
id: id,
checksum: 0, payload_checksum: 0, };
}
pub fn new<T>(device_model_code: u16, mac: [u8; 6], id: u32) -> CommandMessage
where
T: CommandTrait,
{
let mut r = rand::thread_rng();
let random_count = r.gen_range(0x8000..0xFFFF);
return CommandMessage::with_count::<T>(random_count, device_model_code, mac, id);
}
pub fn pack_with_payload(mut self, payload: &[u8], key: &[u8; 16]) -> Result<Vec<u8>, String> {
let cipher = AesCbc::new_from_slices(key, &constants::INITIAL_VECTOR)
.map_err(|e| format!("Could not construct cipher! {}", e))?;
self.payload_checksum = checksum(&payload);
let encrypted = cipher.encrypt_vec(&payload);
let packed = self
.pack()
.map_err(|e| format!("Could not pack command header! {}", e))?;
let mut appended = packed.to_vec();
appended.extend(&encrypted);
self.checksum = checksum(&appended);
let completely_packed = self
.pack()
.map_err(|e| format!("Could not pack completed command! {}", e))?;
let mut complete_command: Vec<u8> = completely_packed.to_vec();
complete_command.extend(&encrypted);
return Ok(complete_command);
}
pub fn unpack_with_payload(mut bytes: Vec<u8>, key: &[u8; 16]) -> Result<Vec<u8>, String> {
if bytes.len() < 0x38 {
return Err(format!(
"Command is too short! Expected 0x38 bytes, got {}",
bytes.len()
));
}
let command_header = CommandMessage::unpack_from_slice(&bytes[0..0x38])
.map_err(|e| format!("Could not unpack command from bytes! {}", e))?;
bytes[0x20] = 0;
bytes[0x21] = 0;
let real_checksum = checksum(&bytes);
if command_header.checksum != real_checksum {
return Err(format!(
"Command checksum does not match actual checksum! Expected {:#06X} got {:#06X}",
real_checksum, command_header.checksum,
));
}
let cipher = AesCbc::new_from_slices(key, &constants::INITIAL_VECTOR)
.map_err(|e| format!("Could not construct cipher! {}", e))?;
let decrypted = cipher
.decrypt_vec(&bytes[0x38..])
.map_err(|e| format!("Could not decrypt command payload! {}", e))?;
let real_checksum = checksum(&decrypted);
if command_header.payload_checksum != real_checksum {
return Err(format!(
"Payload checksum does not match actual checksum! Expected {:#06X} got {:#06X}",
real_checksum, command_header.payload_checksum,
));
}
return Ok(decrypted);
}
}