use crate::util::msg_id;
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Operation {
SendVariable, RequireVariable, Invoke, GetVersion, Start, EndTransaction, Acknowledge, Query, Return, Empty, Data, Await, Error, }
impl Operation {
pub fn to_name(&self) -> &'static str {
match self {
Operation::SendVariable => "SENDV",
Operation::RequireVariable => "REQUV",
Operation::Invoke => "INVOK",
Operation::GetVersion => "PKVER",
Operation::Start => "START",
Operation::EndTransaction => "ENDTR",
Operation::Acknowledge => "ACKNO",
Operation::Query => "QUERY",
Operation::Return => "RTURN",
Operation::Empty => "EMPTY",
Operation::Data => "SDATA",
Operation::Await => "AWAIT",
Operation::Error => "ERROR",
}
}
pub fn from_name(name: &str) -> Option<Operation> {
match name {
"SENDV" => Some(Operation::SendVariable),
"REQUV" => Some(Operation::RequireVariable),
"INVOK" => Some(Operation::Invoke),
"PKVER" => Some(Operation::GetVersion),
"START" => Some(Operation::Start),
"ENDTR" => Some(Operation::EndTransaction),
"ACKNO" => Some(Operation::Acknowledge),
"QUERY" => Some(Operation::Query),
"RTURN" => Some(Operation::Return),
"EMPTY" => Some(Operation::Empty),
"SDATA" => Some(Operation::Data),
"AWAIT" => Some(Operation::Await),
"ERROR" => Some(Operation::Error),
_ => None,
}
}
pub fn is_root(&self) -> bool {
match self {
Operation::SendVariable
| Operation::RequireVariable
| Operation::Invoke
| Operation::GetVersion => true,
_ => false,
}
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Role {
Host, Device, Idle, }
#[derive(PartialEq, Clone, Debug)]
pub struct Command {
pub msg_id: u16,
pub operation: Operation,
pub object: Option<String>,
pub data: Option<Vec<u8>>,
}
impl Command {
pub fn parse(msg_bytes: &[u8]) -> Result<Command, &'static str> {
if msg_bytes.len() < 7 {
return Err("Invalid length: message is too short.");
}
let msg_id_slice = msg_bytes.get(0..2).ok_or("Failed to slice MSG ID")?;
if msg_id_slice == b" " {
let op_name_slice = msg_bytes.get(2..7);
let space1_slice = msg_bytes.get(7..8);
let object_slice = msg_bytes.get(8..13);
let is_ackno_error = op_name_slice == Some(b"ACKNO")
&& space1_slice == Some(b" ")
&& object_slice == Some(b"ERROR");
let is_error_error = op_name_slice == Some(b"ERROR")
&& space1_slice == Some(b" ")
&& object_slice == Some(b"ERROR");
if !(is_ackno_error || is_error_error) {
return Err("Invalid ERROR command format.");
}
let data = if msg_bytes.len() > 14 {
if msg_bytes.get(13..14) != Some(b" ") {
return Err("Missing space before data in ERROR command.");
}
Some(msg_bytes.get(14..).unwrap().to_vec())
} else if msg_bytes.len() == 13 {
None
} else {
return Err("Invalid length for ERROR command.");
};
return Ok(Command {
msg_id: 0,
operation: if op_name_slice == Some(b"ACKNO") {
Operation::Acknowledge
} else {
Operation::Error
},
object: Some("ERROR".to_string()),
data,
});
}
let msg_id_str =
std::str::from_utf8(msg_id_slice).map_err(|_| "MSG ID is not valid UTF-8")?;
let msg_id = msg_id::to_u16(msg_id_str).map_err(|_| "Invalid MSG ID format.")?;
let op_name_slice = msg_bytes
.get(2..7)
.ok_or("Failed to slice operation name.")?;
let op_name_str =
std::str::from_utf8(op_name_slice).map_err(|_| "Operation name is not valid UTF-8")?;
let operation = Operation::from_name(op_name_str).ok_or("Unrecognized operation name.")?;
let (object, data) = match msg_bytes.len() {
7 => (None, None),
13 => {
if msg_bytes.get(7..8) != Some(b" ") {
return Err("Missing space after operation name.");
}
let obj_slice = msg_bytes.get(8..13).ok_or("Failed to slice object.")?;
let obj_str =
std::str::from_utf8(obj_slice).map_err(|_| "Object is not valid UTF-8")?;
(Some(obj_str.to_string()), None)
}
len if len > 14 => {
if msg_bytes.get(7..8) != Some(b" ") || msg_bytes.get(13..14) != Some(b" ") {
return Err("Missing space separator for object or data.");
}
let obj_slice = msg_bytes.get(8..13).ok_or("Failed to slice object.")?;
let obj_str =
std::str::from_utf8(obj_slice).map_err(|_| "Object is not valid UTF-8")?;
let data_slice = msg_bytes.get(14..).unwrap();
(Some(obj_str.to_string()), Some(data_slice.to_vec()))
}
_ => return Err("Invalid message length."),
};
Ok(Command {
msg_id,
operation,
object,
data,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let id = match self.operation {
Operation::Error => String::from(" "),
Operation::Acknowledge => {
if self.object == Some(String::from("ERROR")) {
String::from(" ")
} else {
msg_id::from_u16(self.msg_id)
.map_err(|_| panic!("Invalid MSG ID"))
.unwrap()
}
}
_ => msg_id::from_u16(self.msg_id)
.map_err(|_| panic!("Invalid MSG ID"))
.unwrap(),
};
if self.data.is_none() {
if self.object.is_none() {
format!("{}{}", id, self.operation.to_name())
.as_bytes()
.to_vec()
} else {
format!(
"{}{} {}",
id,
self.operation.to_name(),
self.object.clone().unwrap()
)
.as_bytes()
.to_vec()
}
} else {
let mut vec = format!(
"{}{} {}",
id,
self.operation.to_name(),
self.object.clone().unwrap()
)
.as_bytes()
.to_vec();
vec.push(b' ');
vec.append(&mut self.data.clone().unwrap());
vec
}
}
}
impl std::fmt::Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let id = match self.operation {
Operation::Error => String::from(" "),
Operation::Acknowledge => {
if self.object == Some(String::from("ERROR")) {
String::from(" ")
} else {
msg_id::from_u16(self.msg_id).map_err(|_| std::fmt::Error)?
}
}
_ => msg_id::from_u16(self.msg_id).map_err(|_| std::fmt::Error)?, };
let op = self.operation.to_name();
write!(f, "{}{}", id, op)?;
if let Some(obj) = &self.object {
write!(f, " {}", obj)?;
if let Some(data_vec) = &self.data {
let data_to_display = if self.operation == Operation::Error
|| !data_vec.iter().any(|&b| b == 0 || b > 127)
{
String::from_utf8_lossy(data_vec)
} else {
String::from_utf8_lossy(data_vec) };
write!(f, " {}", data_to_display)?;
}
}
Ok(())
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Status {
Other, AwaitingAck, AwaitingErrAck, }
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Stage {
Idle,
Started, RootOperationAssigned, SendingParameter, ParameterSent, SendingResponse, }
#[cfg(test)]
mod tests {
use super::*;
use crate::util::msg_id;
#[test]
fn test_command_parse_valid_simple() {
let bytes = b"!!START";
let cmd = Command::parse(bytes).unwrap();
assert_eq!(cmd.msg_id, 0);
assert_eq!(cmd.operation, Operation::Start);
assert!(cmd.object.is_none());
assert!(cmd.data.is_none());
}
#[test]
fn test_command_parse_valid_with_object() {
let bytes = b"!\"SENDV VARIA";
let cmd = Command::parse(bytes).unwrap();
assert_eq!(cmd.msg_id, msg_id::to_u16("!\"").unwrap());
assert_eq!(cmd.operation, Operation::SendVariable);
assert_eq!(cmd.object, Some("VARIA".to_string()));
assert!(cmd.data.is_none());
}
#[test]
fn test_command_parse_valid_with_object_and_data() {
let bytes = b"!#SENDV VARIA data_payload";
let cmd = Command::parse(bytes).unwrap();
assert_eq!(cmd.msg_id, msg_id::to_u16("!#").unwrap());
assert_eq!(cmd.operation, Operation::SendVariable);
assert_eq!(cmd.object, Some("VARIA".to_string()));
assert_eq!(cmd.data, Some(b"data_payload".to_vec()));
}
#[test]
fn test_command_parse_error_command() {
let bytes = b" ERROR ERROR Some error description";
let cmd = Command::parse(bytes).unwrap();
assert_eq!(cmd.msg_id, 0);
assert_eq!(cmd.operation, Operation::Error);
assert_eq!(cmd.object, Some("ERROR".to_string()));
assert_eq!(cmd.data, Some(b"Some error description".to_vec()));
}
#[test]
fn test_command_parse_ackno_error_command() {
let bytes = b" ACKNO ERROR";
let cmd = Command::parse(bytes).unwrap();
assert_eq!(cmd.msg_id, 0);
assert_eq!(cmd.operation, Operation::Acknowledge);
assert_eq!(cmd.object, Some("ERROR".to_string()));
assert!(cmd.data.is_none());
}
#[test]
fn test_command_parse_invalid_error_msg_id() {
let byetes = b" START";
let result = Command::parse(byetes);
assert!(result.is_err());
}
#[test]
fn test_command_parse_invalid_too_short() {
let result = Command::parse(b"!!STA");
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Invalid length: message is too short.");
}
#[test]
fn test_command_parse_invalid_msg_id() {
let result = Command::parse(b"\n\rSTART");
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Invalid MSG ID format.");
}
#[test]
fn test_command_to_bytes_simple() {
let cmd = Command {
msg_id: 0,
operation: Operation::Start,
object: None,
data: None,
};
assert_eq!(cmd.to_bytes(), b"!!START".to_vec());
}
#[test]
fn test_command_to_bytes_with_object_and_data() {
let cmd = Command {
msg_id: msg_id::to_u16("!#").unwrap(),
operation: Operation::SendVariable,
object: Some("VARIA".to_string()),
data: Some(b"payload".to_vec()),
};
let mut expected = b"!#SENDV VARIA".to_vec();
expected.push(b' ');
expected.extend_from_slice(b"payload");
assert_eq!(cmd.to_bytes(), expected);
}
#[test]
fn test_command_to_bytes_error() {
let cmd = Command {
msg_id: 0, operation: Operation::Error,
object: Some("ERROR".to_string()),
data: Some(b"Test error".to_vec()),
};
let mut expected = b" ERROR ERROR".to_vec();
expected.push(b' ');
expected.extend_from_slice(b"Test error");
assert_eq!(cmd.to_bytes(), expected);
}
}