diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53f7426 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/target/ \ No newline at end of file diff --git a/domo_proto/Cargo.lock b/domo_proto/Cargo.lock new file mode 100644 index 0000000..e2a74fd --- /dev/null +++ b/domo_proto/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "domo_proto" +version = "0.1.0" +dependencies = [ + "crc32fast", +] diff --git a/domo_proto/Cargo.toml b/domo_proto/Cargo.toml new file mode 100644 index 0000000..d861428 --- /dev/null +++ b/domo_proto/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "domo_proto" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +crc32fast = "1.3.2" \ No newline at end of file diff --git a/domo_proto/src/commands/mod.rs b/domo_proto/src/commands/mod.rs new file mode 100644 index 0000000..145d886 --- /dev/null +++ b/domo_proto/src/commands/mod.rs @@ -0,0 +1,5 @@ +use crate::packet::packet_data::PacketData; + +pub mod node_management; + +pub trait Command: Into + From {} diff --git a/domo_proto/src/commands/node_management.rs b/domo_proto/src/commands/node_management.rs new file mode 100644 index 0000000..8029d4c --- /dev/null +++ b/domo_proto/src/commands/node_management.rs @@ -0,0 +1,39 @@ +use crate::packet::identifier::Identifier; + +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] + } +} + +#[allow(non_camel_case_types)] +pub enum NodeManagementError { + net_duplicate_packet, + net_broken_packet, + net_invalid_packet, + + errc_not_registered +} + +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 + } + } +} diff --git a/domo_proto/src/lib.rs b/domo_proto/src/lib.rs new file mode 100644 index 0000000..574db87 --- /dev/null +++ b/domo_proto/src/lib.rs @@ -0,0 +1,3 @@ +pub mod commands; +pub mod packet; +pub mod prelude; diff --git a/domo_proto/src/main.rs b/domo_proto/src/main.rs new file mode 100644 index 0000000..a426d16 --- /dev/null +++ b/domo_proto/src/main.rs @@ -0,0 +1,21 @@ +use domo_proto::packet::data_types::DataType; +use domo_proto::packet::identifier::Identifier; +use domo_proto::packet::packet_data::PacketData; + +fn main() { + let _ = domo_proto::packet::Packet::V1 { + src: Identifier::from(0xAABBCCDD), + dest: Identifier::from(0xAABBCCDD), + packet_id: Identifier::from(0x00000001), + reply_to: Identifier::from(0x00000000), + command: 0x00, + data: PacketData::default(), + }; + + let buf = vec![ + 0x00, 0x01, 0x00, 0x10, 0x00, 0xFF, 0x20, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xF0, 0xAA, 0xBB, 0xCC, 0xDD, + ]; + + println!("{:?}", DataType::get_data(buf)); +} diff --git a/domo_proto/src/packet/data_types.rs b/domo_proto/src/packet/data_types.rs new file mode 100644 index 0000000..01ab825 --- /dev/null +++ b/domo_proto/src/packet/data_types.rs @@ -0,0 +1,98 @@ +use crate::prelude::{as_u32_be, as_u64_be}; + +#[derive(Debug)] +pub enum DataType { + // Basic types + Nothing, + Switch(bool), + Slider(u8), + Text(String), + + // Cosmetic + RGB(u8, u8, u8), + + // Time & Space + Seconds(u64), + + // Domo Types + NodeRef(u32), +} + +impl DataType { + pub fn get_data_size(data_type: u8) -> usize { + match data_type { + 0x01 => 1, + 0x02 => 1, + 0x03 => 256, + 0x10 => 3, + 0x90 => 8, + 0xF0 => 4, + _ => 0, + } + } + + pub fn get_data(buf: Vec) -> Vec { + 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() >= $len { + $($t)* + } else { + DataType::Nothing + } + }; +} + +impl From> for DataType { + /// Turn a Vec into a single data type. + /// Returns Datatype::Nothing if it's nothing or can find nothing. + fn from(value: Vec) -> Self { + if value.len() == 0 { + return DataType::Nothing; + }; + match value[0] { + 0x01 => impl_data_type!(value, 1, DataType::Switch(value[1] != 0)), + 0x02 => impl_data_type!(value, 1, DataType::Slider(value[1])), + 0x03 => impl_data_type!( + value, + 256, + if let Ok(s) = String::from_utf8(value[1..257].to_vec()) { + DataType::Text(s) + } else { + DataType::Nothing + } + ), + 0x10 => 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(as_u32_be(&value[1..5]))), + _ => DataType::Nothing, + } + } +} diff --git a/domo_proto/src/packet/identifier.rs b/domo_proto/src/packet/identifier.rs new file mode 100644 index 0000000..da12e61 --- /dev/null +++ b/domo_proto/src/packet/identifier.rs @@ -0,0 +1,45 @@ +use std::fmt::{Formatter, LowerHex}; + +#[derive(Debug, Eq, PartialEq)] +pub struct Identifier { + pub bytes: [u8; 4], +} + +impl LowerHex for Identifier { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{:x}{:x}{:x}{:x}", + self.bytes[0], self.bytes[1], self.bytes[2], self.bytes[3] + ) + } +} + +impl From<&[u8]> for Identifier { + fn from(value: &[u8]) -> Self { + let mut bytes = [0_u8; 4]; + bytes[..value.len()].copy_from_slice(value); + Identifier { + bytes: [bytes[0], bytes[1], bytes[2], bytes[3]], + } + } +} + +impl From> for Identifier { + fn from(value: Vec) -> Self { + Identifier::from(value.as_slice()) + } +} + +impl From for Identifier { + fn from(value: u32) -> Self { + Identifier { + bytes: [ + (value >> 24) as u8, + (value >> 16) as u8, + (value >> 8) as u8, + (value >> 0) as u8, + ], + } + } +} diff --git a/domo_proto/src/packet/mod.rs b/domo_proto/src/packet/mod.rs new file mode 100644 index 0000000..66a6796 --- /dev/null +++ b/domo_proto/src/packet/mod.rs @@ -0,0 +1,94 @@ +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 std::fmt::Debug; + +#[derive(Debug)] +pub enum Packet { + V1 { + src: Identifier, + dest: Identifier, + packet_id: Identifier, + reply_to: Identifier, + command: u8, + data: PacketData, + }, +} + +impl Packet { + /// Build packet **without** CRC32 + pub fn build_base_packet(&self) -> Vec { + match self { + Packet::V1 { + src, + dest, + packet_id, + reply_to, + command, + data, + } => { + let mut buf = Vec::new(); + let data_length = data.len(); + buf.push(0x01); + buf.extend_from_slice(&src.bytes); + buf.extend_from_slice(&dest.bytes); + buf.extend_from_slice(&packet_id.bytes); + buf.extend_from_slice(&reply_to.bytes); + buf.push(command.clone()); + buf.push((data_length >> 8) as u8); + buf.push((data_length & 0xFF) as u8); + buf.extend(data.get_data()); + buf + } + } + } + + pub fn build_full_packet(&self) -> Vec { + match self { + Packet::V1 { .. } => { + let mut buf = self.build_base_packet(); + buf.extend_from_slice(crc32fast::hash(&buf.as_slice()).to_be_bytes().as_ref()); + buf + } + } + } +} + +impl TryFrom> for Packet { + type Error = (); + + fn try_from(value: Vec) -> Result { + match value[0] { + 0x01 => { + let header: Vec = 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::V1 { + src: Identifier::from(&value[0x01..0x05]), + dest: 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> for Packet { + fn into(self) -> Vec { + self.build_full_packet() + } +} diff --git a/domo_proto/src/packet/packet_data.rs b/domo_proto/src/packet/packet_data.rs new file mode 100644 index 0000000..2c83b8e --- /dev/null +++ b/domo_proto/src/packet/packet_data.rs @@ -0,0 +1,24 @@ +#[derive(Debug)] +pub struct PacketData { + data: Vec, +} + +impl PacketData { + pub fn new(data: Vec) -> PacketData { + PacketData { data } + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn get_data(&self) -> Vec { + self.data.clone() + } +} + +impl Default for PacketData { + fn default() -> Self { + PacketData { data: vec![] } + } +} diff --git a/domo_proto/src/prelude.rs b/domo_proto/src/prelude.rs new file mode 100644 index 0000000..cbdca31 --- /dev/null +++ b/domo_proto/src/prelude.rs @@ -0,0 +1,17 @@ +pub fn as_u32_be(array: &[u8]) -> u32 { + ((array[0] as u32) << 24) + + ((array[1] as u32) << 16) + + ((array[2] as u32) << 8) + + ((array[3] as u32) << 0) +} + +pub fn as_u64_be(array: &[u8]) -> u64 { + ((array[0] as u64) << 56) + + ((array[1] as u64) << 48) + + ((array[2] as u64) << 40) + + ((array[3] as u64) << 32) + + ((array[4] as u64) << 24) + + ((array[5] as u64) << 16) + + ((array[6] as u64) << 8) + + ((array[7] as u64) << 0) +}