refactor: breaking refactor of domo_proto and domo_node

This commit is contained in:
Strix 2023-10-15 18:06:33 +02:00
parent be4425aff9
commit 15971aa4fa
No known key found for this signature in database
GPG key ID: 49B2E37B8915B774
31 changed files with 902 additions and 393 deletions

2
domo_proto/Cargo.lock generated
View file

@ -19,7 +19,7 @@ dependencies = [
[[package]]
name = "domo_proto"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"crc32fast",
"rand",

View file

@ -1,6 +1,6 @@
[package]
name = "domo_proto"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -1,2 +1,8 @@
use crate::packet::packet_data::PacketData;
pub mod node_management;
pub mod property_control;
impl_into_enum_variant!(PacketData::NodeManagement, node_management::NodeManagementCommand);
impl_into_enum_variant!(PacketData::PropertyControl, property_control::PropertyControlCommand);
impl_into_enum_variant!(PacketData::Raw, Vec<u8>);

View file

@ -1,126 +0,0 @@
use crate::packet::identifier::Identifier;
use crate::packet::{Packet, ToPacket};
use crate::packet::packet_data::PacketData;
pub enum NodeManagement {
Ping,
RegisterNode { device_id: Identifier },
RemoveNode,
RegisterProperty {
property_name: String,
data_type: u8,
read_only: bool,
},
RemoveProperty {
property_name: String,
},
Error {
error_code: u8,
metadata: [u8; 0xFFFF],
},
}
impl NodeManagement {
fn get_command(&self) -> u8 {
match self {
NodeManagement::Ping => 0x00,
NodeManagement::RegisterNode { .. } => 0x01,
NodeManagement::RemoveNode => 0x02,
NodeManagement::RegisterProperty { .. } => 0x03,
NodeManagement::RemoveProperty { .. } => 0x04,
NodeManagement::Error { .. } => 0x0E
}
}
}
impl Into<NodeManagement> for Packet {
fn into(self) -> NodeManagement {
match self.command {
0x00 => NodeManagement::Ping,
0x01 => NodeManagement::RegisterNode { device_id: self.src },
0x02 => NodeManagement::RemoveNode,
0x03 => NodeManagement::RegisterProperty {
property_name: String::from_utf8(self.data.data[..32].to_vec()).unwrap(),
data_type: self.data.data[33],
read_only: self.data.data[34] != 0,
},
0x04 => NodeManagement::RemoveProperty {
property_name: String::from_utf8(self.data.data[..32].to_vec()).unwrap()
},
0x0E => NodeManagement::Error {
error_code: self.data.data[0],
metadata: {
let mut metadata = [0_u8; 0xFFFF];
let data_slice = self.data.data;
let metadata_length = u16::from_be_bytes([data_slice[1], data_slice[2]]) as usize;
if metadata_length <= 0xFFFF {
metadata[..metadata_length].copy_from_slice(&data_slice[3..(3 + metadata_length)]);
}
metadata
},
},
_ => unreachable!("Invalid command in Packet::V1"),
}
}
}
impl Into<PacketData> for NodeManagement {
fn into(self) -> PacketData {
match self {
NodeManagement::Ping => PacketData::default(),
NodeManagement::RegisterNode { device_id } => PacketData::new(Vec::from(device_id.bytes)),
NodeManagement::RemoveNode => PacketData::default(),
NodeManagement::RegisterProperty { property_name, data_type, read_only } => PacketData::new({
let mut vec = vec![];
vec.extend(property_name.into_bytes());
vec.push(data_type);
vec.push(read_only as u8);
vec
}),
NodeManagement::RemoveProperty { property_name } => PacketData::new(property_name.into_bytes()),
NodeManagement::Error { error_code, metadata } => PacketData::new({
let mut vec = vec![];
vec.push(error_code);
vec.extend_from_slice(metadata.as_ref());
vec
})
}
}
}
impl ToPacket for NodeManagement {
fn to_packet(self, src: Identifier, dest: Identifier, packet_id: Identifier, reply_to: Identifier) -> Packet {
Packet {
src,
dest,
packet_id,
reply_to,
command: self.get_command(),
data: self.into(),
}
}
}
#[allow(non_camel_case_types)]
pub enum NodeManagementError {
net_duplicate_packet,
net_broken_packet,
net_invalid_packet,
errc_not_registered,
errs_not_delivered,
}
impl NodeManagementError {
pub fn error_code(&self) -> u8 {
match self {
NodeManagementError::net_duplicate_packet => 0x00,
NodeManagementError::net_broken_packet => 0x01,
NodeManagementError::net_invalid_packet => 0x02,
NodeManagementError::errc_not_registered => 0x10,
NodeManagementError::errs_not_delivered => 0x20
}
}
}

View file

@ -0,0 +1,37 @@
use std::io;
#[derive(Debug, PartialOrd, PartialEq)]
#[allow(non_camel_case_types)]
pub enum NodeManagementError {
net_duplicate_packet = 0x00,
net_broken_packet = 0x01,
net_invalid_packet = 0x02,
net_addr_in_use = 0x03,
net_dest_unreachable = 0x04,
errc_not_registered = 0x10,
errs_not_delivered = 0x20,
}
impl Into<u8> for NodeManagementError {
fn into(self) -> u8 {
self as u8
}
}
impl TryFrom<u8> for NodeManagementError {
type Error = io::Error;
fn try_from(value: u8) -> io::Result<Self> {
match value {
0x00 => Ok(NodeManagementError::net_duplicate_packet),
0x01 => Ok(NodeManagementError::net_broken_packet),
0x02 => Ok(NodeManagementError::net_invalid_packet),
0x03 => Ok(NodeManagementError::net_addr_in_use),
0x04 => Ok(NodeManagementError::net_dest_unreachable),
0x10 => Ok(NodeManagementError::errc_not_registered),
0x20 => Ok(NodeManagementError::errs_not_delivered),
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, "non supported error"))
}
}
}

View file

@ -0,0 +1,100 @@
use std::io;
use crate::identifier::Identifier;
use crate::packet::raw_packet::RawPacket;
use crate::packet::{Packet, ToPacket};
use crate::packet::packet_data::PacketData;
pub mod vec;
pub mod errors;
#[derive(Debug, Clone)]
pub enum NodeManagementCommand {
Ping,
RegisterNode { device_id: Identifier },
RemoveNode,
RegisterProperty {
property_name: String,
data_type: u8,
read_only: bool,
},
RemoveProperty {
property_name: String,
},
Error {
error_code: u8,
metadata: Vec<u8>,
},
}
impl NodeManagementCommand {
pub fn command(&self) -> u8 {
match self {
NodeManagementCommand::Ping => 0x00,
NodeManagementCommand::RegisterNode { .. } => 0x01,
NodeManagementCommand::RemoveNode => 0x02,
NodeManagementCommand::RegisterProperty { .. } => 0x03,
NodeManagementCommand::RemoveProperty { .. } => 0x04,
NodeManagementCommand::Error { .. } => 0x0E
}
}
}
impl ToPacket for NodeManagementCommand {
fn to_packet(self, src: Identifier, dest: Identifier, packet_id: Identifier, reply_to: Identifier) -> Packet {
Packet {
src, dest, packet_id, reply_to,
command: self.command(),
data: PacketData::NodeManagement(self.clone())
}
}
}
impl TryFrom<RawPacket> for NodeManagementCommand {
type Error = io::Error;
fn try_from(raw_packet: RawPacket) -> io::Result<Self> {
let build_invalid_input_err = |r: &str| {
Err(io::Error::new(io::ErrorKind::InvalidInput, r))
};
match raw_packet.command {
0x00 => Ok(NodeManagementCommand::Ping),
0x01 => if raw_packet.data_length == 4 {
Ok(NodeManagementCommand::RegisterNode {
device_id: Identifier::from(raw_packet.data)
})
} else {
build_invalid_input_err("expected 4 bytes")
},
0x02 => Ok(NodeManagementCommand::RemoveNode),
0x03 => if raw_packet.data_length == 34 {
Ok(NodeManagementCommand::RegisterProperty {
property_name: String::from_utf8(raw_packet.data[..32].to_vec())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "String is not UTF-8"))?,
data_type: raw_packet.data[33],
read_only: raw_packet.data[34] != 0,
})
} else {
build_invalid_input_err("expected 34 bytes")
},
0x04 => if raw_packet.data_length == 32 {
Ok(NodeManagementCommand::RemoveProperty {
property_name: String::from_utf8(raw_packet.data.to_vec())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "String is not UTF-8"))?
})
} else {
build_invalid_input_err("expected 32 bytes")
},
0x0E => if raw_packet.data_length == 3 {
Ok(NodeManagementCommand::Error {
error_code: raw_packet.data[0],
metadata: {
let metadata_length = u16::from_be_bytes([raw_packet.data[1], raw_packet.data[2]]) as usize;
raw_packet.data[3..metadata_length].to_vec()
},
})
} else {
build_invalid_input_err("expected 3 bytes")
},
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, "command group is unsupported"))
}
}
}

View file

@ -0,0 +1,26 @@
use crate::commands::node_management::NodeManagementCommand;
/// Returns a NodeManagement command.
impl Into<Vec<u8>> for NodeManagementCommand {
fn into(self) -> Vec<u8> {
match self {
NodeManagementCommand::Ping => vec![],
NodeManagementCommand::RegisterNode { device_id } => device_id.bytes.to_vec(),
NodeManagementCommand::RemoveNode => vec![],
NodeManagementCommand::RegisterProperty { property_name, data_type, read_only } => {
let mut vec = vec![];
vec.extend(property_name.into_bytes());
vec.push(data_type);
vec.push(read_only as u8);
vec
},
NodeManagementCommand::RemoveProperty { property_name } => property_name.into_bytes(),
NodeManagementCommand::Error { error_code, metadata } => {
let mut vec = vec![];
vec.push(error_code);
vec.extend(metadata);
vec
}
}
}
}

View file

@ -1,15 +0,0 @@
use crate::packet::data_types::DataType;
pub enum PropertyControl {
Get {
property_name: String,
data: DataType
},
Set {
property_name: String,
data: DataType
},
Reset {
property_name: String,
}
}

View file

@ -0,0 +1,118 @@
use std::io;
use crate::data_types::DataType;
use crate::identifier::Identifier;
use crate::packet::raw_packet::RawPacket;
use crate::packet::{Packet, ToPacket};
use crate::packet::packet_data::PacketData;
use crate::prelude::as_u32_be;
pub mod vec;
#[derive(Debug, Clone)]
pub enum PropertyControlCommand {
Get {
property_name: String,
data: DataType,
},
Set {
property_name: String,
data: DataType,
},
Reset {
property_name: String,
},
Subscribe {
device_id: Identifier,
property_name: String,
},
Unsubscribe {
device_id: Identifier,
property_name: String,
},
}
impl PropertyControlCommand {
pub fn command(&self) -> u8 {
match self {
PropertyControlCommand::Get { .. } => 0x10,
PropertyControlCommand::Set { .. } => 0x11,
PropertyControlCommand::Reset { .. } => 0x12,
PropertyControlCommand::Subscribe { .. } => 0x1A,
PropertyControlCommand::Unsubscribe { .. } => 0x1B
}
}
}
impl ToPacket for PropertyControlCommand {
fn to_packet(self, src: Identifier, dest: Identifier, packet_id: Identifier, reply_to: Identifier) -> Packet {
Packet {
src, dest, packet_id, reply_to,
command: self.command(),
data: PacketData::PropertyControl(self.clone())
}
}
}
impl TryFrom<RawPacket> for PropertyControlCommand {
type Error = io::Error;
fn try_from(raw_packet: RawPacket) -> io::Result<Self> {
let build_invalid_input_err = |r: &str| {
Err(io::Error::new(io::ErrorKind::InvalidInput, r))
};
match raw_packet.command {
0x10 => if raw_packet.data_length == 33 {
Ok(PropertyControlCommand::Get {
property_name: String::from_utf8(raw_packet.data[..0x34].to_vec())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "String is not UTF-8"))?,
data: DataType::try_from(raw_packet.data[0x34..].to_vec())?,
})
} else {
build_invalid_input_err("expected 33 bytes")
},
0x11 => {
if raw_packet.data_length == 33 {
Ok(PropertyControlCommand::Set {
property_name: String::from_utf8(raw_packet.data[..0x34].to_vec())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "String is not UTF-8"))?,
data: DataType::try_from(raw_packet.data[0x34..].to_vec())?,
})
} else {
build_invalid_input_err("expected 33 bytes")
}
}
0x12 => {
if raw_packet.data_length == 33 {
Ok(PropertyControlCommand::Reset {
property_name: String::from_utf8(raw_packet.data[..0x34].to_vec())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "String is not UTF-8"))?,
})
} else {
build_invalid_input_err("expected 33 bytes")
}
}
0x1A => {
if raw_packet.data_length == 36 {
Ok(PropertyControlCommand::Subscribe {
device_id: Identifier::from(as_u32_be(raw_packet.data[0x14..0x18].as_ref())),
property_name: String::from_utf8(raw_packet.data[0x18..0x4C].to_vec())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "String is not UTF-8"))?,
})
} else {
build_invalid_input_err("expected 36 bytes")
}
}
0x1B => {
if raw_packet.data_length == 36 {
Ok(PropertyControlCommand::Unsubscribe {
device_id: Identifier::from(as_u32_be(raw_packet.data[0x14..0x18].as_ref())),
property_name: String::from_utf8(raw_packet.data[0x18..0x4C].to_vec())
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "String is not UTF-8"))?,
})
} else {
build_invalid_input_err("expected 36 bytes")
}
}
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, "command group is unsupported"))
}
}
}

View file

@ -0,0 +1,34 @@
use crate::commands::property_control::PropertyControlCommand;
/// Returns a PropertyControl command.
impl Into<Vec<u8>> for PropertyControlCommand {
fn into(self) -> Vec<u8> {
match self {
PropertyControlCommand::Get { property_name, data } => {
let mut v = vec![];
v.extend(property_name.into_bytes());
v.extend(Into::<Vec<u8>>::into(data));
v
},
PropertyControlCommand::Set { property_name, data } => {
let mut v = vec![];
v.extend(property_name.into_bytes());
v.extend(Into::<Vec<u8>>::into(data));
v
},
PropertyControlCommand::Reset { property_name } => property_name.into_bytes(),
PropertyControlCommand::Subscribe { device_id, property_name } => {
let mut v = vec![];
v.extend(device_id.bytes);
v.extend(property_name.into_bytes());
v
},
PropertyControlCommand::Unsubscribe { device_id, property_name } => {
let mut v = vec![];
v.extend(device_id.bytes);
v.extend(property_name.into_bytes());
v
}
}
}
}

View file

@ -0,0 +1,84 @@
use crate::identifier::Identifier;
mod vec;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DataType {
// Meta
/// Meta type: Nothing (`0x00`)
Nothing,
/// Meta type: Array (`0x01`)
Array(Vec<DataType>),
// Basic types
/// Basic Type: Switch (`0x10`]
Switch(bool),
/// Basic Type: Slider (`0x11`)
Slider(u8),
/// Basic Type: Text (`0x12`)
Text(String),
// Cosmetic
/// Cosmetic Type: RGB (`0x20`)
RGB(u8, u8, u8),
// Time & Space
/// Time & Space Type: Seconds (`0x90`)
Seconds(u64),
// Domo Types
/// Domo type: Node Ref (`0xF0`)
NodeRef(Identifier),
}
impl DataType {
pub fn get_data_size(data_type: u8) -> usize {
match data_type {
0x00 => 0,
0x01 => {
2
},
0x10 => 1,
0x11 => 1,
0x12 => 256,
0x20 => 3,
0x90 => 8,
0xF0 => 4,
_ => 0,
}
}
}
/// Get a vector of DataTypes.
/// Do **not** put a raw packet in here, **only** a raw datatype vector.
/// Unparsable data automatically gets turned into DataType::Nothing
pub fn get_data_types(raw: Vec<u8>) -> Vec<DataType> {
let mut data = vec![];
let mut pieces = vec![];
let mut byte = raw.iter().peekable();
while let Some(&c) = byte.next() {
let mut piece = vec![c];
let mut len = DataType::get_data_size(c);
while len > 0 {
if let Some(b) = byte.next() {
piece.push(*b);
}
len -= 1;
}
if piece.len() == DataType::get_data_size(c) + 1 {
pieces.push(piece);
} else {
pieces.push(vec![0x00]);
}
}
for piece in pieces {
data.push(
DataType::try_from(piece)
.unwrap_or(DataType::Nothing)
)
}
data
}

View file

@ -0,0 +1,81 @@
use std::io;
use crate::data_types::{DataType, get_data_types};
use crate::identifier::Identifier;
use crate::prelude::as_u64_be;
impl Into<Vec<u8>> for DataType {
fn into(self) -> Vec<u8> {
match self {
DataType::Nothing => vec![0x00],
DataType::Array(vec) => {
let mut res = vec![0x01];
for item in vec {
res.extend(Into::<Vec<u8>>::into(item).iter());
}
res
},
DataType::Switch(b) => vec![0x10, b as u8],
DataType::Slider(v) => vec![0x11, v],
DataType::Text(s) => {
let mut bytes = vec![0x12];
bytes.extend(s.into_bytes());
bytes
},
DataType::RGB(r, g, b) => vec![0x20, r, g, b],
DataType::Seconds(s) => {
let mut bytes = vec![0x90];
bytes.extend_from_slice(s.to_be_bytes().as_ref());
bytes
},
DataType::NodeRef(id) => {
let mut bytes = vec![0xF0];
bytes.extend_from_slice(id.bytes.as_ref());
bytes
},
}
}
}
macro_rules! impl_data_type {
($v: ident, $len: literal, $($t:tt)*) => {
if $v.len() - 1 == $len {
Ok($($t)*)
} else {
Err(io::Error::new(io::ErrorKind::InvalidData, format!("Expected data length {}, actual {}", $len, $v.len())))
}
};
}
/// Turn a `Vec<u8>` into a single data type.
/// Returns Datatype::Nothing if it's nothing or can find nothing.
impl TryFrom<Vec<u8>> for DataType {
type Error = io::Error;
fn try_from(value: Vec<u8>) -> io::Result<Self> {
if value.len() == 0 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "data length == 0"))
};
match value[0] {
0x01 => {
if value.len() - 1 < 2 { return Err(io::Error::new(io::ErrorKind::InvalidInput, "The array DataType requires a 2 byte header.")) }
let size = ((value[1] as u16) << 8) | value[2] as u16;
if value.len() - 1 < size as usize { return Err(io::Error::new(io::ErrorKind::InvalidData, "Array specified data length != array data length")) }
Ok(DataType::Array(get_data_types(value[3..(size as usize)].to_vec())))
},
0x10 => impl_data_type!(value, 1, DataType::Switch(value[1] != 0)),
0x11 => impl_data_type!(value, 1, DataType::Slider(value[1])),
0x12 => impl_data_type!(
value,
256,
if let Ok(s) = String::from_utf8(value[1..257].to_vec()) {
DataType::Text(s)
} else {
DataType::Nothing
}
),
0x20 => impl_data_type!(value, 3, DataType::RGB(value[1], value[2], value[3])),
0x90 => impl_data_type!(value, 8, DataType::Seconds(as_u64_be(&value[1..9]))),
0xF0 => impl_data_type!(value, 4, DataType::NodeRef(Identifier::from(&value[1..5]))),
_ => Err(io::Error::new(io::ErrorKind::InvalidData, "Could not match data type header to a data type.")),
}
}
}

View file

@ -1,7 +1,8 @@
use std::fmt::{Display, Formatter, LowerHex};
use rand::random;
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
/// Helper struct for the identifiers used in `dest`, `src`, `packet_id` and `reply_to`.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct Identifier {
pub bytes: [u8; 4],
}
@ -66,3 +67,9 @@ impl From<u32> for Identifier {
}
}
}
impl Into<u32> for Identifier {
fn into(self) -> u32 {
u32::from_be_bytes(self.bytes)
}
}

View file

@ -1,19 +1,39 @@
pub mod commands;
pub mod packet;
//! Welcome to the Domo protocol rust implementation.
//!
//! This implementation is up-to-date with DomoProto v1.
//!
//! Most code that should/is actually used (usually) has documentation with it.
#![forbid(clippy::unwrap_used, clippy::expect_used)]
#[macro_use]
#[doc(hidden)]
pub mod prelude;
/// Domo protocol packet abstraction.
pub mod packet;
/// Identifier helper code.
pub mod identifier;
/// Data Types as stated in the Domo protocol spec.
pub mod data_types;
/// Commands are according to Domo protocol spec.
pub mod commands;
#[cfg(test)]
mod tests {
use crate::packet;
use crate::packet::data_types::DataType;
use crate::packet::identifier::Identifier;
use crate::data_types::{DataType, get_data_types};
use crate::identifier::Identifier;
use crate::packet::packet_data::PacketData;
#[test]
pub fn data_types() {
// test Vec<u8> -> Vec<DataType>
assert_eq!(
DataType::get_data(vec![0x00, 0x10, 0x00]),
get_data_types(vec![0x00, 0x10, 0x00]),
vec![
DataType::Nothing,
DataType::Switch(false),

View file

@ -1,153 +0,0 @@
use crate::packet::identifier::Identifier;
use crate::prelude::as_u64_be;
#[derive(Debug, Eq, PartialEq)]
pub enum DataType {
// Meta
/// Meta type: Nothing [0x00]
Nothing,
/// Meta type: Array [0x01]
Array(Vec<DataType>),
// Basic types
/// Basic Type: Switch [0x10]
Switch(bool),
/// Basic Type: Slider [0x11]
Slider(u8),
/// Basic Type: Text [0x12]
Text(String),
// Cosmetic
/// Cosmetic Type: RGB [0x20]
RGB(u8, u8, u8),
// Time & Space
/// Time & Space Type: Seconds [0x90]
Seconds(u64),
// Domo Types
/// Domo type: Node Ref [0xF0]
NodeRef(Identifier),
}
impl DataType {
pub fn get_data_size(data_type: u8) -> usize {
match data_type {
0x00 => 0,
0x01 => {
2
},
0x10 => 1,
0x11 => 1,
0x12 => 256,
0x20 => 3,
0x90 => 8,
0xF0 => 4,
_ => 0,
}
}
pub fn get_data(buf: Vec<u8>) -> Vec<DataType> {
let mut data = vec![];
let mut pieces = vec![];
let mut byte = buf.iter().peekable();
while let Some(&c) = byte.next() {
let mut piece = vec![c];
let mut len = DataType::get_data_size(c);
while len > 0 {
if let Some(_) = byte.peek() {
piece.push(*byte.next().unwrap());
}
len -= 1;
}
if piece.len() == DataType::get_data_size(c) + 1 {
pieces.push(piece);
} else {
pieces.push(vec![0x00]);
}
}
for piece in pieces {
data.push(DataType::from(piece))
}
data
}
}
macro_rules! impl_data_type {
($v: ident, $len: literal, $($t:tt)*) => {
if $v.len() - 1 == $len {
$($t)*
} else {
DataType::Nothing
}
};
}
impl From<Vec<u8>> for DataType {
/// Turn a Vec<u8> into a single data type.
/// Returns Datatype::Nothing if it's nothing or can find nothing.
fn from(value: Vec<u8>) -> Self {
if value.len() == 0 {
return DataType::Nothing;
};
match value[0] {
0x01 => {
if value.len() - 1 < 2 { return DataType::Nothing }
let size = ((value[1] as u16) << 8) | value[2] as u16;
if value.len() - 1 < size as usize { return DataType::Nothing }
DataType::Array(DataType::get_data(value[3..(size as usize)].to_vec()))
},
0x10 => impl_data_type!(value, 1, DataType::Switch(value[1] != 0)),
0x11 => impl_data_type!(value, 1, DataType::Slider(value[1])),
0x12 => impl_data_type!(
value,
256,
if let Ok(s) = String::from_utf8(value[1..257].to_vec()) {
DataType::Text(s)
} else {
DataType::Nothing
}
),
0x20 => impl_data_type!(value, 3, DataType::RGB(value[1], value[2], value[3])),
0x90 => impl_data_type!(value, 8, DataType::Seconds(as_u64_be(&value[1..9]))),
0xF0 => impl_data_type!(value, 4, DataType::NodeRef(Identifier::from(&value[1..5]))),
_ => DataType::Nothing,
}
}
}
impl Into<Vec<u8>> for DataType {
fn into(self) -> Vec<u8> {
match self {
DataType::Nothing => vec![0x00],
DataType::Array(vec) => {
let mut res = vec![0x01];
for item in vec {
res.extend(Into::<Vec<u8>>::into(item).iter());
}
res
},
DataType::Switch(b) => vec![0x10, b as u8],
DataType::Slider(v) => vec![0x11, v],
DataType::Text(s) => {
let mut bytes = vec![0x12];
bytes.extend(s.into_bytes());
bytes
},
DataType::RGB(r, g, b) => vec![0x20, r, g, b],
DataType::Seconds(s) => {
let mut bytes = vec![0x90];
bytes.extend_from_slice(s.to_be_bytes().as_ref());
bytes
},
DataType::NodeRef(id) => {
let mut bytes = vec![0xF0];
bytes.extend_from_slice(id.bytes.as_ref());
bytes
},
}
}
}

View file

@ -1,12 +1,13 @@
pub mod data_types;
pub mod identifier;
pub mod packet_data;
use crate::prelude;
use identifier::Identifier;
use packet_data::PacketData;
use std::convert::TryFrom;
use crate::identifier::Identifier;
use std::fmt::Debug;
use std::io;
use packet_data::PacketData;
use crate::packet::raw_packet::RawPacket;
/// High level abstraction for packet data.
pub mod packet_data;
/// Low level abstraction for packets.
pub mod raw_packet;
/// Size of the packet header.
pub const PACKET_HEADER_SIZE: usize = 20;
@ -15,6 +16,7 @@ pub const FULL_PACKET_SIZE: usize = 65559;
/// Packet size without CRC32.
pub const BASE_PACKET_SIZE: usize = 65555;
/// The abstraction for all DomoProto packets.
#[derive(Debug)]
pub struct Packet {
pub dest: Identifier,
@ -38,6 +40,7 @@ impl Packet {
data,
} => {
let mut buf = Vec::new();
let data = data.clone().into_bytes();
let data_length = data.len();
buf.push(0x01);
buf.extend_from_slice(&dest.bytes);
@ -47,15 +50,14 @@ impl Packet {
buf.push(command.clone());
buf.push((data_length >> 8) as u8);
buf.push((data_length & 0xFF) as u8);
buf.extend(&data.data);
buf.extend(data);
buf
}
}
}
pub fn get_crc32(&self) -> u32 {
let d = self.build_base_packet();
crc32fast::hash(d.as_slice())
crc32fast::hash(self.build_base_packet().as_slice())
}
pub fn build_full_packet(&self) -> Vec<u8> {
@ -69,40 +71,14 @@ impl Packet {
}
}
impl TryFrom<Vec<u8>> for Packet {
type Error = ();
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
match value[0] {
0x01 => {
let header: Vec<u16> = value[..0x15].iter().map(|v| u16::from(*v)).collect();
let data_length = (header[0x12] << 8) | header[0x13];
let crc32 = prelude::as_u32_be(&value[(0x14 + data_length as usize)..]);
let packet = Packet {
dest: Identifier::from(&value[0x01..0x05]),
src: Identifier::from(&value[0x05..0x09]),
packet_id: Identifier::from(&value[0x09..0x0D]),
reply_to: Identifier::from(&value[0x0D..0x11]),
command: value[0x11],
data: PacketData::new(value[0x14..(data_length as usize + 0x14)].to_owned()),
};
if crc32fast::hash(packet.build_base_packet().as_slice()) != crc32 {
Err(())
} else {
Ok(packet)
}
}
_ => Err(()),
}
}
}
impl Into<Vec<u8>> for Packet {
fn into(self) -> Vec<u8> {
self.build_full_packet()
}
}
pub trait ToPacket {
fn to_packet(self, src: Identifier, dest: Identifier, packet_id: Identifier, reply_to: Identifier) -> Packet;
}
/// Secretly calls RawPacket::try_from().into()
impl TryFrom<Vec<u8>> for Packet {
type Error = io::Error;
fn try_from(data: Vec<u8>) -> io::Result<Self> {
Ok(RawPacket::try_from(data)?.into())
}
}

View file

@ -1,20 +1,40 @@
use std::io;
use crate::commands::property_control::PropertyControlCommand;
use crate::commands::node_management::NodeManagementCommand;
use crate::packet::raw_packet::RawPacket;
/// Abstraction used for interpreting the data in a packet.
#[derive(Debug, Clone)]
pub struct PacketData {
pub data: Vec<u8>,
pub enum PacketData {
NodeManagement(NodeManagementCommand),
PropertyControl(PropertyControlCommand),
Raw(Vec<u8>)
}
/// Returns an empty `PacketData::Raw`
impl Default for PacketData {
fn default() -> Self {
PacketData::Raw(vec![])
}
}
impl PacketData {
pub fn new(data: Vec<u8>) -> PacketData {
PacketData { data }
}
pub fn len(&self) -> usize {
self.data.len()
pub fn into_bytes(self) -> Vec<u8> {
match self {
PacketData::NodeManagement(v) => v.into(),
PacketData::PropertyControl(v) => v.into(),
PacketData::Raw(v) => v
}
}
}
impl Default for PacketData {
fn default() -> Self {
PacketData { data: vec![] }
impl TryFrom<RawPacket> for PacketData {
type Error = io::Error;
fn try_from(raw_packet: RawPacket) -> io::Result<Self> {
match raw_packet.command {
0x0 => Ok(PacketData::NodeManagement(NodeManagementCommand::try_from(raw_packet)?)),
0x1 => Ok(PacketData::PropertyControl(PropertyControlCommand::try_from(raw_packet)?)),
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, "command group is unsupported"))
}
}
}
}

View file

@ -0,0 +1,16 @@
mod vec;
mod packet;
#[derive(Debug, Clone)]
pub struct RawPacket {
pub version: u8,
pub dest: u32,
pub src: u32,
pub packet_id: u32,
pub reply_to: u32,
pub command: u8,
pub data_length: usize,
pub data: Vec<u8>,
pub checksum: u32
}

View file

@ -0,0 +1,46 @@
use crate::identifier::Identifier;
use crate::packet::Packet;
use crate::packet::packet_data::PacketData;
use crate::packet::raw_packet::RawPacket;
impl From<Packet> for RawPacket {
fn from(packet: Packet) -> Self {
let data = &packet.data.into_bytes();
let data_length = data.len();
RawPacket {
version: 0x01,
dest: packet.dest.into(),
src: packet.src.into(),
packet_id: packet.packet_id.into(),
reply_to: packet.reply_to.into(),
command: packet.command,
data_length,
data: data.clone(),
checksum: {
let mut v = vec![0x01];
v.extend(packet.dest.bytes);
v.extend(packet.src.bytes);
v.extend(packet.packet_id.bytes);
v.extend(packet.reply_to.bytes);
v.push(packet.command);
v.extend(data_length.to_be_bytes());
v.extend(data);
crc32fast::hash(v.as_ref())
}
}
}
}
impl Into<Packet> for RawPacket {
fn into(self) -> Packet {
Packet {
dest: Identifier::from(self.dest),
src: Identifier::from(self.src),
packet_id: Identifier::from(self.packet_id),
reply_to: Identifier::from(self.reply_to),
command: self.command,
data: PacketData::try_from(self.clone())
.unwrap_or(PacketData::Raw(self.data)),
}
}
}

View file

@ -0,0 +1,47 @@
use std::io;
use crate::packet::PACKET_HEADER_SIZE;
use crate::packet::raw_packet::RawPacket;
use crate::prelude::as_u32_be;
impl TryFrom<Vec<u8>> for RawPacket {
type Error = io::Error;
fn try_from(data: Vec<u8>) -> io::Result<Self> {
if data.len() < PACKET_HEADER_SIZE {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Can't parse data into RawPacket"))
}
let data_length = u16::from_be_bytes([data[0x12], data[0x13]]) as usize;
if data.len() < 0x14 + data_length + 4 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Can't parse data into RawPacket"))
}
let checksum = as_u32_be(data[(data.len() - 5)..].as_ref());
if checksum != crc32fast::hash(data[..(data.len() - 4)].as_ref()) {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Checksum does not match"))
}
Ok(RawPacket {
version: data[0],
dest: as_u32_be(data[0x01..0x05].as_ref()),
src: as_u32_be(data[0x05..0x09].as_ref()),
packet_id: as_u32_be(data[0x09..0x0D].as_ref()),
reply_to: as_u32_be(data[0x0D..0x11].as_ref()),
command: data[0x11],
data_length,
data: data[0x14..data_length].to_vec(),
checksum,
})
}
}
impl Into<Vec<u8>> for RawPacket {
fn into(self) -> Vec<u8> {
let mut v = vec![self.version];
v.extend(self.dest.to_be_bytes());
v.extend(self.src.to_be_bytes());
v.extend(self.packet_id.to_be_bytes());
v.extend(self.reply_to.to_be_bytes());
v.push(self.command);
v.extend(self.data_length.to_be_bytes());
v.extend(self.data);
v.extend(self.checksum.to_be_bytes());
v
}
}

View file

@ -15,3 +15,13 @@ pub fn as_u64_be(array: &[u8]) -> u64 {
+ ((array[6] as u64) << 8)
+ ((array[7] as u64) << 0)
}
macro_rules! impl_into_enum_variant {
($houser: tt::$variant: tt, $impl_type: ty) => {
impl Into<$houser> for $impl_type {
fn into(self) -> $houser {
$houser::$variant(self)
}
}
};
}