434 lines
12 KiB
Rust
434 lines
12 KiB
Rust
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,
|
|
})
|
|
}
|
|
}
|