use anyhow::{ anyhow, bail, Context, Error, Result, }; pub const IMPROV_HEADER: [u8; 6] = [ 'I' as u8, 'M' as u8, 'P' as u8, 'R' as u8, 'O' as u8, 'V' as u8, ]; pub const IMPROV_VERSION: u8 = 0x01; #[derive(Clone, PartialEq)] #[repr(u8)] pub enum PacketType { CurrentState = 0x01, ErrorState = 0x02, RPCCommand = 0x03, RPCResult = 0x04, } impl TryFrom<&u8> for PacketType { type Error = Error; fn try_from(b: &u8) -> Result<Self, Self::Error> { match b { 0x01 => Ok(Self::CurrentState), 0x02 => Ok(Self::ErrorState), 0x03 => Ok(Self::RPCCommand), 0x04 => Ok(Self::RPCResult), _ => Err(anyhow!("Cannot convert to packet type")), } } } impl std::fmt::Display for PacketType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::CurrentState => write!(f, "Current State"), Self::ErrorState => write!(f, "Error State"), Self::RPCCommand => write!(f, "RPC Command"), Self::RPCResult => write!(f, "RPC Result"), } } } #[derive(Clone)] #[repr(u8)] pub enum CurrentState { Ready = 0x02, Provisioning = 0x03, Provisioned = 0x04, } impl TryFrom<&u8> for CurrentState { type Error = Error; fn try_from(b: &u8) -> Result<Self, Self::Error> { match b { 0x02 => Ok(Self::Ready), 0x03 => Ok(Self::Provisioning), 0x04 => Ok(Self::Provisioned), _ => Err(anyhow!("Cannot convert to current state")), } } } impl std::fmt::Display for CurrentState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Ready => write!(f, "Ready"), Self::Provisioning => write!(f, "Provisioning"), Self::Provisioned => write!(f, "Provisioned"), } } } #[derive(Clone)] #[repr(u8)] pub enum ErrorState { NoError = 0x00, InvalidRPCPacket = 0x01, UnknownRPCCommand = 0x02, UnableToConnect = 0x03, UnknownError = 0xFF, } impl TryFrom<&u8> for ErrorState { type Error = Error; fn try_from(b: &u8) -> Result<Self, Self::Error> { match b { 0x00 => Ok(Self::NoError), 0x01 => Ok(Self::InvalidRPCPacket), 0x02 => Ok(Self::UnknownRPCCommand), 0x03 => Ok(Self::UnableToConnect), 0x04 => Ok(Self::UnknownError), _ => Err(anyhow!("Cannot convert to error type")), } } } impl std::fmt::Display for ErrorState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::NoError => write!(f, "No Error"), Self::InvalidRPCPacket => write!(f, "Invalid RPC Packet"), Self::UnknownRPCCommand => write!(f, "Unknown RPC Command"), Self::UnableToConnect => write!(f, "Unable To Connect"), Self::UnknownError => write!(f, "Unknown Error"), } } } #[derive(Clone)] #[repr(u8)] pub enum RPCCommand { SendWiFiSettings = 0x01, RequestCurrentState = 0x02, RequestDeviceInformation = 0x03, RequestScannedWiFiNetworks = 0x04, } impl TryFrom<&u8> for RPCCommand { type Error = Error; fn try_from(b: &u8) -> Result<Self, Self::Error> { match b { 0x01 => Ok(Self::SendWiFiSettings), 0x02 => Ok(Self::RequestCurrentState), 0x03 => Ok(Self::RequestDeviceInformation), 0x04 => Ok(Self::RequestScannedWiFiNetworks), _ => Err(anyhow!("Cannot convert to RPC command")), } } } pub fn calculate_checksum(data: &[u8]) -> u8 { // Pass data as full packet, with header, but without checksum byte let mut checksum: u8 = 0x00; for e in data { checksum = checksum.wrapping_add(e.clone()); } return checksum; } pub trait ImprovDataPacketType { const packet_type: PacketType; } pub trait ImprovDataToPacket: ImprovDataPacketType { fn to_bytes(&self) -> Vec<u8>; fn to_raw_packet(&self) -> RawPacket { return RawPacket { version: IMPROV_VERSION, r#type: Self::packet_type, data: self.to_bytes().to_vec(), } } } pub trait ImprovDataFromPacket: ImprovDataPacketType + Sized { type Error; fn try_from_raw_packet(raw_packet: &RawPacket) -> Result<Self, Self::Error>; } pub struct RawPacket { pub version: u8, pub r#type: PacketType, pub data: Vec<u8>, } impl RawPacket { pub fn try_from_bytes(bytes: &Vec<u8>) -> Result<Self> { if bytes.len() < 11 { bail!("Packet too small"); } for i in 0..5 { if bytes[i] != IMPROV_HEADER[i] { bail!("Improv header not found"); } } let length: usize = bytes[8].into(); if bytes.len() != length + 10 { bail!("Packet with invalid length"); } return Ok(Self { version: bytes[6], r#type: PacketType::try_from(&bytes[7])?, data: bytes[9..(bytes.len()-1)].to_vec(), }); } pub fn to_bytes(self: &Self) -> Vec<u8> { let mut out = Vec::new(); for b in IMPROV_HEADER.clone() { out.push(b); } out.push(self.version.clone()); out.push(self.r#type.clone() as u8); let length: u8 = self.data.len().try_into().unwrap(); out.push(length.clone()); for b in self.data.clone() { out.push(b); } let checksum: u8 = calculate_checksum(&out); out.push(checksum.clone()); return out; } } pub enum ImprovPacket { CurrentState(CurrentStatePacket), ErrorState(ErrorStatePacket), RequestCurrentState(RequestCurrentStatePacket), RequestDeviceInformation(RequestDeviceInformationPacket), RequestScannedWiFiNetworks(RequestScannedWiFiNetworksPacket), RPCResult(RPCResultPacket), } impl ImprovPacket { pub fn try_from_raw_packet(raw_packet: &RawPacket) -> Result<Self> { match raw_packet.r#type { PacketType::CurrentState => Ok(ImprovPacket::CurrentState(CurrentStatePacket::try_from_raw_packet(raw_packet)?)), PacketType::ErrorState => Ok(ImprovPacket::ErrorState(ErrorStatePacket::try_from_raw_packet(raw_packet)?)), //PacketType::RPCCommand => _, PacketType::RPCResult => Ok(ImprovPacket::RPCResult(RPCResultPacket::try_from_raw_packet(raw_packet)?)), _ => Err(anyhow!("Conversion into packet type not implemented")), } } } pub struct CurrentStatePacket { pub current_state: CurrentState, } impl ImprovDataPacketType for CurrentStatePacket { const packet_type: PacketType = PacketType::CurrentState; } impl ImprovDataToPacket for CurrentStatePacket { fn to_bytes(self: &Self) -> Vec<u8> { let mut out = Vec::new(); out.push(self.current_state.clone() as u8); return out; } } impl ImprovDataFromPacket for CurrentStatePacket { type Error = Error; fn try_from_raw_packet(raw_packet: &RawPacket) -> Result<Self, Self::Error>{ if raw_packet.r#type != Self::packet_type { return Err(anyhow!("Packet is not CurrentStatePacket")); } return Ok(Self { current_state: CurrentState::try_from(&raw_packet.data[0])?, }) } } pub struct ErrorStatePacket { pub error_state: ErrorState, } impl ImprovDataPacketType for ErrorStatePacket { const packet_type: PacketType = PacketType::ErrorState; } impl ImprovDataFromPacket for ErrorStatePacket { type Error = Error; fn try_from_raw_packet(raw_packet: &RawPacket) -> Result<Self, Self::Error>{ if raw_packet.r#type != Self::packet_type { return Err(anyhow!("Packet is not ErrorState")); } return Ok(Self { error_state: ErrorState::try_from(&raw_packet.data[0])?, }) } } pub struct SendWiFiSettingsPacket { pub ssid: String, pub password: String, } impl ImprovDataPacketType for SendWiFiSettingsPacket { const packet_type: PacketType = PacketType::RPCCommand; } impl ImprovDataToPacket for SendWiFiSettingsPacket { fn to_bytes(self: &Self) -> Vec<u8> { let ssid_bytes = self.ssid.as_bytes(); let ssid_len: u8 = ssid_bytes.len().try_into().unwrap(); let password_bytes = self.password.as_bytes(); let password_len: u8 = password_bytes.len().try_into().unwrap(); let mut out = Vec::new(); out.push(RPCCommand::SendWiFiSettings as u8); out.push(1 + ssid_len + 1 + password_len); // data len out.push(ssid_len); out.extend_from_slice(ssid_bytes); out.push(password_len); out.extend_from_slice(password_bytes); return out; } } pub struct RequestCurrentStatePacket { } impl ImprovDataPacketType for RequestCurrentStatePacket { const packet_type: PacketType = PacketType::RPCCommand; } impl ImprovDataToPacket for RequestCurrentStatePacket { fn to_bytes(self: &Self) -> Vec<u8> { let mut out = Vec::new(); out.push(RPCCommand::RequestCurrentState as u8); out.push(0x00); // rpc command payload length return out; } } pub struct RequestDeviceInformationPacket { } impl ImprovDataPacketType for RequestDeviceInformationPacket { const packet_type: PacketType = PacketType::RPCCommand; } impl ImprovDataToPacket for RequestDeviceInformationPacket { fn to_bytes(self: &Self) -> Vec<u8> { let mut out = Vec::new(); out.push(RPCCommand::RequestDeviceInformation as u8); out.push(0x00); // rpc command payload length return out; } } pub struct RequestScannedWiFiNetworksPacket { } impl ImprovDataPacketType for RequestScannedWiFiNetworksPacket { const packet_type: PacketType = PacketType::RPCCommand; } impl ImprovDataToPacket for RequestScannedWiFiNetworksPacket { fn to_bytes(self: &Self) -> Vec<u8> { let mut out = Vec::new(); out.push(RPCCommand::RequestScannedWiFiNetworks as u8); out.push(0x00); // rpc command payload length return out; } } pub struct RPCResultPacket { pub command_responded_to: RPCCommand, pub results: Vec<String>, } impl ImprovDataPacketType for RPCResultPacket { const packet_type: PacketType = PacketType::RPCResult; } impl ImprovDataFromPacket for RPCResultPacket { type Error = Error; fn try_from_raw_packet(raw_packet: &RawPacket) -> Result<Self, Self::Error>{ if raw_packet.r#type != Self::packet_type { return Err(anyhow!("Packet is not RPCResult")); } let mut results: Vec<String> = Vec::new(); let mut data_position: usize = 2; loop { if data_position >= raw_packet.data.len() { break; } // find string bounds let current_string_len: usize = usize::from(raw_packet.data[data_position]); let current_string_begin = data_position + 1; let current_string_end = data_position + current_string_len; // load string and append to results let string_bytes = &raw_packet.data[current_string_begin..current_string_end+1]; let string = std::str::from_utf8(string_bytes).context("Failed to convert response to UTF-8 string")?.to_string(); results.push(string); // next data position data_position = data_position + current_string_len + 1; } return Ok(Self { command_responded_to: RPCCommand::try_from(&raw_packet.data[0]).context("Failed to determine RPC command this packet responds to")?, results: results, }) } }