#[cfg(test)]
use hex_literal::hex;
pub mod action;
pub mod dash7;
pub mod operand;
pub use crate::codec::{Codec, WithOffset, WithSize};
pub use action::Action;
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Command {
pub actions: Vec<Action>,
}
impl std::fmt::Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "[")?;
let end = self.actions.len() - 1;
for (i, action) in self.actions.iter().enumerate() {
write!(f, "{}", action)?;
if i != end {
write!(f, "; ")?;
}
}
write!(f, "]")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CommandParseFail {
pub actions: Vec<Action>,
pub error: action::ActionDecodingError,
}
impl Command {
pub fn encoded_size(&self) -> usize {
self.actions.iter().map(|act| act.encoded_size()).sum()
}
pub unsafe fn encode_in(&self, out: &mut [u8]) -> usize {
let mut offset = 0;
for action in self.actions.iter() {
offset += action.encode_in(&mut out[offset..]);
}
offset
}
pub fn encode(&self) -> Box<[u8]> {
let mut data = vec![0; self.encoded_size()].into_boxed_slice();
unsafe { self.encode_in(&mut data) };
data
}
pub fn decode(out: &[u8]) -> Result<Self, WithOffset<CommandParseFail>> {
let mut actions = vec![];
let mut offset = 0;
loop {
if offset == out.len() {
break;
}
match Action::decode(&out[offset..]) {
Ok(WithSize { value, size }) => {
actions.push(value);
offset += size;
}
Err(error) => {
let WithOffset { offset: off, value } = error;
return Err(WithOffset {
offset: offset + off,
value: CommandParseFail {
actions,
error: value,
},
});
}
}
}
Ok(Self { actions })
}
pub fn request_id(&self) -> Option<u8> {
for action in self.actions.iter() {
if let Action::RequestTag(action::RequestTag { id, .. }) = action {
return Some(*id);
}
}
None
}
pub fn response_id(&self) -> Option<u8> {
for action in self.actions.iter() {
if let Action::ResponseTag(action::ResponseTag { id, .. }) = action {
return Some(*id);
}
}
None
}
pub fn is_last_response(&self) -> bool {
for action in self.actions.iter() {
if let Action::ResponseTag(action::ResponseTag { eop, .. }) = action {
return *eop;
}
}
false
}
}
#[test]
fn test_command() {
let cmd = Command {
actions: vec![
Action::RequestTag(action::RequestTag { id: 66, eop: true }),
Action::ReadFileData(action::ReadFileData {
resp: true,
group: false,
file_id: 0,
offset: 0,
size: 8,
}),
Action::ReadFileData(action::ReadFileData {
resp: false,
group: true,
file_id: 4,
offset: 2,
size: 3,
}),
Action::Nop(action::Nop {
resp: true,
group: true,
}),
],
};
let data = &hex!("B4 42 41 00 00 08 81 04 02 03 C0") as &[u8];
assert_eq!(&cmd.encode()[..], data);
assert_eq!(
Command::decode(data).expect("should be parsed without error"),
cmd,
);
}
#[test]
fn test_command_display() {
assert_eq!(
Command {
actions: vec![
Action::RequestTag(action::RequestTag { id: 66, eop: true }),
Action::Nop(action::Nop {
resp: true,
group: true,
}),
]
}
.to_string(),
"[RTAG[E](66); NOP[GR]]"
);
}
#[test]
fn test_command_request_id() {
assert_eq!(
Command {
actions: vec![Action::request_tag(true, 66), Action::nop(true, true)]
}
.request_id(),
Some(66)
);
assert_eq!(
Command {
actions: vec![Action::nop(true, false), Action::request_tag(true, 44)]
}
.request_id(),
Some(44)
);
assert_eq!(
Command {
actions: vec![Action::nop(true, false), Action::nop(true, false)]
}
.request_id(),
None
);
}
#[test]
fn test_comman_response_id() {
assert_eq!(
Command {
actions: vec![
Action::response_tag(true, true, 66),
Action::nop(true, true)
]
}
.response_id(),
Some(66)
);
assert_eq!(
Command {
actions: vec![
Action::nop(true, false),
Action::response_tag(true, true, 44)
]
}
.response_id(),
Some(44)
);
assert_eq!(
Command {
actions: vec![Action::nop(true, false), Action::nop(true, false)]
}
.response_id(),
None
);
}
#[test]
fn test_command_is_last_response() {
assert!(Command {
actions: vec![
Action::response_tag(true, true, 66),
Action::nop(true, true)
]
}
.is_last_response());
assert!(!Command {
actions: vec![
Action::response_tag(false, false, 66),
Action::nop(true, true)
]
}
.is_last_response());
assert!(!Command {
actions: vec![
Action::response_tag(false, true, 44),
Action::response_tag(true, true, 44)
]
}
.is_last_response());
assert!(!Command {
actions: vec![Action::nop(true, false), Action::nop(true, false)]
}
.is_last_response());
}