diff --git a/doc/domo_node/node_types.md b/doc/domo_node/node_types.md index 24c6c3b..d63fcc1 100644 --- a/doc/domo_node/node_types.md +++ b/doc/domo_node/node_types.md @@ -16,8 +16,8 @@ Also, it will make sure no invalid updates can be sent. Most packets go through the master node before getting to the source. Most because the subnet node can have private nodes and handle those themselves. -# The Relay node (`relay`) -The relay node is simple. +# The Bridge node (`relay`) +The bridge node is simple. In config you define where to forward the packets to, and they get delivered to there. # The Subnet node (`subnet`) diff --git a/doc/proto.md b/doc/proto.md index 244f5fa..0839af3 100644 --- a/doc/proto.md +++ b/doc/proto.md @@ -157,6 +157,7 @@ Send a packet to state an error occurred. | `0x01` | `net_broken_packet` | recent packet was broken. | resend packet | | `0x02` | `net_invalid_packet` | the packet sent is not valid | send packet with proper values | | `0x03` | `net_addr_in_use` | the addr requested is in use already | register with another id or `0` | +| `0x04` | `net_dest_unreachable` | the destination could not be reached | no | | `0x1*` | `errc_*` | **Client errors** | | | `0x10` | `errc_not_registered` | client is not registered | register client | | `0x2*` | `errs_*` | **Server errors** | | @@ -173,7 +174,7 @@ Get a properties value | offset | size | name | description | reserved | example | |--------|----------|-----------------|---------------------------------------------|----------|----------| | `0x14` | 32 bytes | `property_name` | The name of the property as a UTF-8 string. | no | "Power" | -| `0x34` | 1 byte | `dynamic_data` | Dynamic data snippet | response | `0x0100` | +| `0x34` | dynamic | `dynamic_data` | Dynamic data snippet | response | `0x0100` | ### `0x11` - Set property @@ -182,7 +183,7 @@ Get a properties value | offset | size | name | description | reserved | example | |--------|----------|-----------------|---------------------------------------------|----------|----------| | `0x14` | 32 bytes | `property_name` | The name of the property as a UTF-8 string. | no | "Power" | -| `0x34` | 1 byte | `dynamic_data` | Dynamic data snippet | no | `0x0100` | +| `0x34` | dynamic | `dynamic_data` | Dynamic data snippet | no | `0x0100` | ### `0x12` - Reset property diff --git a/domo_node/Cargo.lock b/domo_node/Cargo.lock index 80a8835..fde57e0 100644 --- a/domo_node/Cargo.lock +++ b/domo_node/Cargo.lock @@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "domo_proto" -version = "0.1.0" +version = "0.2.0" dependencies = [ "crc32fast", "rand", diff --git a/domo_node/config.relay.toml b/domo_node/config.bridge.toml similarity index 75% rename from domo_node/config.relay.toml rename to domo_node/config.bridge.toml index 53f66e5..2166844 100644 --- a/domo_node/config.relay.toml +++ b/domo_node/config.bridge.toml @@ -1,3 +1,3 @@ -[node.type.relay] +[node.type.bridge] bind = "127.0.0.1:4481" master_address = "127.0.0.1:4480" diff --git a/domo_node/config.toml b/domo_node/config.toml index e104f0b..fed5190 100644 --- a/domo_node/config.toml +++ b/domo_node/config.toml @@ -2,4 +2,4 @@ bind = "127.0.0.1:4480" [node] -device_id = "000000ff" \ No newline at end of file +device_id = "ffffffff" \ No newline at end of file diff --git a/domo_node/src/config/node.rs b/domo_node/src/config/node.rs index c7930f5..d506248 100644 --- a/domo_node/src/config/node.rs +++ b/domo_node/src/config/node.rs @@ -1,19 +1,18 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::net::SocketAddr; -use domo_proto::packet::identifier::Identifier; +use domo_proto::identifier::Identifier; #[derive(Debug, Serialize, Deserialize, Clone)] pub enum NodeType { #[serde(rename = "master")] Master { bind: SocketAddr, - identifier: String }, #[serde(rename = "relay")] - Relay { + Bridge { bind: SocketAddr, - master_address: String + master_address: SocketAddr }, #[serde(rename = "subnet")] Subnet { @@ -25,7 +24,7 @@ impl Display for NodeType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { NodeType::Master { .. } => write!(f, "master"), - NodeType::Relay { .. } => write!(f, "relay"), + NodeType::Bridge { .. } => write!(f, "relay"), NodeType::Subnet { .. } => write!(f, "subnet"), } } @@ -35,14 +34,23 @@ impl Default for NodeType { fn default() -> Self { Self::Master { bind: SocketAddr::from(([0,0,0,0], 4480)), - identifier: hex::encode(Identifier::random().to_string()) } } } -#[derive(Debug, Serialize, Deserialize, Default, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct NodeConfig { #[serde(rename = "type")] pub node_type: NodeType, - pub device_id: Option + #[serde(default)] + pub device_id: String +} + +impl Default for NodeConfig { + fn default() -> Self { + Self { + node_type: NodeType::default(), + device_id: Identifier::random().to_string() + } + } } diff --git a/domo_node/src/connection/mod.rs b/domo_node/src/connection/mod.rs index e69de29..74f47ad 100644 --- a/domo_node/src/connection/mod.rs +++ b/domo_node/src/connection/mod.rs @@ -0,0 +1 @@ +pub mod server; diff --git a/domo_node/src/connection/server.rs b/domo_node/src/connection/server.rs new file mode 100644 index 0000000..7d3c4e7 --- /dev/null +++ b/domo_node/src/connection/server.rs @@ -0,0 +1,57 @@ +use tokio::net::UdpSocket; +use domo_proto::identifier::Identifier; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::io; +use domo_proto::packet::{FULL_PACKET_SIZE, Packet}; +use std::io::ErrorKind; + +pub struct Device { + /// Device identifier + pub device_id: Identifier, + /// Device socket address + pub socket_addr: SocketAddr, +} + +pub struct Server { + sock: UdpSocket, + device_id: Identifier, + devices: HashMap +} + +impl Server { + pub async fn new(socket_addr: SocketAddr, device_id: Option) -> io::Result { + Ok(Self { + sock: UdpSocket::bind(socket_addr).await?, + device_id: device_id.unwrap_or(Identifier::random()), + devices: HashMap::new() + }) + } + + /// Receive 1 packet. + pub async fn recv_packet(&mut self) -> (io::Result, Option) { + let mut buf = vec![0; FULL_PACKET_SIZE]; + let (size, socket_addr) = match self.sock.recv_from(&mut buf).await { + Ok(v) => v, + Err(e) => return (Err(e), None) + }; + + ( + Packet::try_from(buf[..size].to_vec()) + .map_err(|_| io::Error::new(ErrorKind::InvalidData, "Received invalid data")), + Some(socket_addr) + ) + } + + pub fn device_id(&self) -> Identifier { + self.device_id.clone() + } + + pub fn sock(&self) -> &UdpSocket { + &self.sock + } + + pub fn devices(& self) -> &HashMap { + &self.devices + } +} diff --git a/domo_node/src/main.rs b/domo_node/src/main.rs index e1e25d7..9b284a9 100644 --- a/domo_node/src/main.rs +++ b/domo_node/src/main.rs @@ -1,14 +1,16 @@ use crate::config::node::NodeType; -use log::{debug, error, info, trace}; +use log::{debug, error, info, trace, warn}; use std::{env, io}; - - use std::cell::RefCell; -use std::future::Future; -use std::net::SocketAddr; -use std::str::FromStr; +use std::error::Error; use tokio::net::UdpSocket; +use domo_proto::packet::{FULL_PACKET_SIZE, ToPacket}; +use connection::server::Server; +use domo_proto::commands::node_management::NodeManagementCommand; +use domo_proto::identifier::Identifier; +use domo_proto::packet::raw_packet::RawPacket; +pub mod prelude; pub mod config; pub mod connection; @@ -38,25 +40,89 @@ fn main() -> io::Result<()> { match CONFIG.with_borrow(|c| c.node.node_type.clone()) { - NodeType::Master { bind, identifier } => runtime.block_on(async { - let sock = UdpSocket::bind(bind).await.unwrap(); - info!("bound to: {}", sock.local_addr().unwrap()); + NodeType::Master { bind } => runtime.block_on(async { + let mut server = Server::new( + bind, + Some( + Identifier::from( + hex::decode(CONFIG.with_borrow(|c| c.node.device_id.clone())) + .unwrap() + ) + ), + ).await.unwrap(); + + info!("bound server on {} as {}", server.sock().local_addr().unwrap(), server.device_id()); + loop { - let mut buf = vec![0; domo_proto::packet::FULL_PACKET_SIZE]; - match sock.recv_from(&mut buf).await { - Ok((size, addr)) => { - trace!("recv[{addr} -> {size}]: {:?}", &buf[..size]); - }, - Err(e) => { + match server.recv_packet().await { + (Ok(packet), Some(source)) => { + if packet.dest != server.device_id() { + if let Some(d) = server.devices().get(&packet.dest) { + if let Err(e) = server.sock().send_to(packet.build_full_packet().as_slice(), d.socket_addr).await { + error!("Error forwarding to {}: {}", d.device_id, e); + continue + } + trace!("fwd {} -> {}", packet.packet_id, packet.dest); + } else { + if let Err(e) = server.sock().send_to( + prelude::quick_err::net_dest_unreachable( + server.device_id(), + packet.dest, + packet.packet_id + ).build_full_packet().as_slice(), + source + ).await { + error!("Could not send error packet: {e}"); + }; + } + continue; + } + match packet.command >> 4 { + // Node management + 0x0 => { + if let Ok(nm) = NodeManagementCommand::try_from(RawPacket::from(packet)) { + trace!("{nm:?}"); + } + } + // Property control + 0x1 => {} + _ => { + let device_id = server.device_id(); + match server.sock().send_to( + prelude::quick_err::net_invalid_packet( + device_id, + packet.src, + packet.packet_id, + ).build_full_packet().as_slice(), + source, + ).await { + Ok(_) => {} + Err(e) => error!("could not send error packet: {e}") + } + } + } } + (Err(e), Some(source)) => { + error!("{source} sent intelligible data: {e}"); + let device_id = server.device_id(); + if let Err(e) = server.sock().send_to( + prelude::quick_err::net_broken_packet( + device_id, + Identifier::random(), + ).build_full_packet().as_slice(), + source, + ).await { + error!("could not send error packet: {e}"); + } + } + _ => warn!("dropped intelligible packet") } } }), - NodeType::Relay { bind, master_address } => runtime.block_on(async { + NodeType::Bridge { bind, master_address } => runtime.block_on(async { + }), - NodeType::Subnet { master_address: _ } => { - runtime.block_on(async {}) - } + NodeType::Subnet { master_address: _ } => unimplemented!() } Ok(()) diff --git a/domo_node/src/prelude.rs b/domo_node/src/prelude.rs new file mode 100644 index 0000000..1cafefb --- /dev/null +++ b/domo_node/src/prelude.rs @@ -0,0 +1,42 @@ +pub mod quick_err { + use domo_proto::commands::node_management::{NodeManagementCommand, errors::NodeManagementError}; + use domo_proto::identifier::Identifier; + use domo_proto::packet::{Packet, ToPacket}; + + pub fn net_invalid_packet(src: Identifier, dest: Identifier, reply_to: Identifier) -> Packet { + NodeManagementCommand::Error { + error_code: NodeManagementError::net_invalid_packet.into(), + // todo: non rude meta + metadata: "don't send me stupid data, stupid".to_string().into_bytes() + }.to_packet( + src, + dest, + Identifier::random(), + reply_to + ) + } + + pub fn net_broken_packet(src: Identifier, reply_to: Identifier) -> Packet { + NodeManagementCommand::Error { + error_code: NodeManagementError::net_broken_packet.into(), + metadata: "intelligible data, send again.".to_string().into_bytes() + }.to_packet( + src, + Identifier::default(), + Identifier::random(), + reply_to + ) + } + + pub fn net_dest_unreachable(src: Identifier, dest: Identifier, reply_to: Identifier) -> Packet { + NodeManagementCommand::Error { + error_code: NodeManagementError::net_dest_unreachable.into(), + metadata: "packet could not reach destination".to_string().into_bytes() + }.to_packet( + src, + dest, + Identifier::random(), + reply_to + ) + } +} \ No newline at end of file diff --git a/domo_proto/Cargo.lock b/domo_proto/Cargo.lock index a09f7b4..352d6da 100644 --- a/domo_proto/Cargo.lock +++ b/domo_proto/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "domo_proto" -version = "0.1.0" +version = "0.2.0" dependencies = [ "crc32fast", "rand", diff --git a/domo_proto/Cargo.toml b/domo_proto/Cargo.toml index dc53571..46f1840 100644 --- a/domo_proto/Cargo.toml +++ b/domo_proto/Cargo.toml @@ -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 diff --git a/domo_proto/src/commands/mod.rs b/domo_proto/src/commands/mod.rs index 8d3086b..167daaf 100644 --- a/domo_proto/src/commands/mod.rs +++ b/domo_proto/src/commands/mod.rs @@ -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); diff --git a/domo_proto/src/commands/node_management.rs b/domo_proto/src/commands/node_management.rs deleted file mode 100644 index 1187ac7..0000000 --- a/domo_proto/src/commands/node_management.rs +++ /dev/null @@ -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 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 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 - } - } -} diff --git a/domo_proto/src/commands/node_management/errors.rs b/domo_proto/src/commands/node_management/errors.rs new file mode 100644 index 0000000..9372420 --- /dev/null +++ b/domo_proto/src/commands/node_management/errors.rs @@ -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 for NodeManagementError { + fn into(self) -> u8 { + self as u8 + } +} + +impl TryFrom for NodeManagementError { + type Error = io::Error; + fn try_from(value: u8) -> io::Result { + 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")) + } + } +} \ No newline at end of file diff --git a/domo_proto/src/commands/node_management/mod.rs b/domo_proto/src/commands/node_management/mod.rs new file mode 100644 index 0000000..df6fb06 --- /dev/null +++ b/domo_proto/src/commands/node_management/mod.rs @@ -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, + }, +} + +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 for NodeManagementCommand { + type Error = io::Error; + fn try_from(raw_packet: RawPacket) -> io::Result { + 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")) + } + } +} diff --git a/domo_proto/src/commands/node_management/vec.rs b/domo_proto/src/commands/node_management/vec.rs new file mode 100644 index 0000000..7283d80 --- /dev/null +++ b/domo_proto/src/commands/node_management/vec.rs @@ -0,0 +1,26 @@ +use crate::commands::node_management::NodeManagementCommand; + +/// Returns a NodeManagement command. +impl Into> for NodeManagementCommand { + fn into(self) -> Vec { + 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 + } + } + } +} diff --git a/domo_proto/src/commands/property_control.rs b/domo_proto/src/commands/property_control.rs deleted file mode 100644 index b7e4c55..0000000 --- a/domo_proto/src/commands/property_control.rs +++ /dev/null @@ -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, - } -} \ No newline at end of file diff --git a/domo_proto/src/commands/property_control/mod.rs b/domo_proto/src/commands/property_control/mod.rs new file mode 100644 index 0000000..f8e0aae --- /dev/null +++ b/domo_proto/src/commands/property_control/mod.rs @@ -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 for PropertyControlCommand { + type Error = io::Error; + fn try_from(raw_packet: RawPacket) -> io::Result { + 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")) + } + } +} diff --git a/domo_proto/src/commands/property_control/vec.rs b/domo_proto/src/commands/property_control/vec.rs new file mode 100644 index 0000000..b7eee23 --- /dev/null +++ b/domo_proto/src/commands/property_control/vec.rs @@ -0,0 +1,34 @@ +use crate::commands::property_control::PropertyControlCommand; + +/// Returns a PropertyControl command. +impl Into> for PropertyControlCommand { + fn into(self) -> Vec { + match self { + PropertyControlCommand::Get { property_name, data } => { + let mut v = vec![]; + v.extend(property_name.into_bytes()); + v.extend(Into::>::into(data)); + v + }, + PropertyControlCommand::Set { property_name, data } => { + let mut v = vec![]; + v.extend(property_name.into_bytes()); + v.extend(Into::>::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 + } + } + } +} diff --git a/domo_proto/src/data_types/mod.rs b/domo_proto/src/data_types/mod.rs new file mode 100644 index 0000000..4b4d8c9 --- /dev/null +++ b/domo_proto/src/data_types/mod.rs @@ -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), + + // 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) -> Vec { + 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 +} diff --git a/domo_proto/src/data_types/vec.rs b/domo_proto/src/data_types/vec.rs new file mode 100644 index 0000000..e6d8ebd --- /dev/null +++ b/domo_proto/src/data_types/vec.rs @@ -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> for DataType { + fn into(self) -> Vec { + match self { + DataType::Nothing => vec![0x00], + DataType::Array(vec) => { + let mut res = vec![0x01]; + for item in vec { + res.extend(Into::>::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` into a single data type. +/// Returns Datatype::Nothing if it's nothing or can find nothing. +impl TryFrom> for DataType { + type Error = io::Error; + fn try_from(value: Vec) -> io::Result { + 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.")), + } + } +} diff --git a/domo_proto/src/packet/identifier.rs b/domo_proto/src/identifier.rs similarity index 85% rename from domo_proto/src/packet/identifier.rs rename to domo_proto/src/identifier.rs index 8268758..d99b0fb 100644 --- a/domo_proto/src/packet/identifier.rs +++ b/domo_proto/src/identifier.rs @@ -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 for Identifier { } } } + +impl Into for Identifier { + fn into(self) -> u32 { + u32::from_be_bytes(self.bytes) + } +} \ No newline at end of file diff --git a/domo_proto/src/lib.rs b/domo_proto/src/lib.rs index 0d50428..d18517d 100644 --- a/domo_proto/src/lib.rs +++ b/domo_proto/src/lib.rs @@ -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 -> Vec assert_eq!( - DataType::get_data(vec![0x00, 0x10, 0x00]), + get_data_types(vec![0x00, 0x10, 0x00]), vec![ DataType::Nothing, DataType::Switch(false), diff --git a/domo_proto/src/packet/data_types.rs b/domo_proto/src/packet/data_types.rs deleted file mode 100644 index 7d521bd..0000000 --- a/domo_proto/src/packet/data_types.rs +++ /dev/null @@ -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), - - // 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) -> 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() - 1 == $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 => { - 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> for DataType { - fn into(self) -> Vec { - match self { - DataType::Nothing => vec![0x00], - DataType::Array(vec) => { - let mut res = vec![0x01]; - for item in vec { - res.extend(Into::>::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 - }, - } - } -} \ No newline at end of file diff --git a/domo_proto/src/packet/mod.rs b/domo_proto/src/packet/mod.rs index c253a10..651ba68 100644 --- a/domo_proto/src/packet/mod.rs +++ b/domo_proto/src/packet/mod.rs @@ -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 { @@ -69,40 +71,14 @@ impl Packet { } } -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 { - 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> for Packet { - fn into(self) -> Vec { - 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> for Packet { + type Error = io::Error; + fn try_from(data: Vec) -> io::Result { + Ok(RawPacket::try_from(data)?.into()) + } } \ No newline at end of file diff --git a/domo_proto/src/packet/packet_data.rs b/domo_proto/src/packet/packet_data.rs index d7ffd0a..0eef305 100644 --- a/domo_proto/src/packet/packet_data.rs +++ b/domo_proto/src/packet/packet_data.rs @@ -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, +pub enum PacketData { + NodeManagement(NodeManagementCommand), + PropertyControl(PropertyControlCommand), + Raw(Vec) +} + +/// Returns an empty `PacketData::Raw` +impl Default for PacketData { + fn default() -> Self { + PacketData::Raw(vec![]) + } } impl PacketData { - pub fn new(data: Vec) -> PacketData { - PacketData { data } - } - - pub fn len(&self) -> usize { - self.data.len() + pub fn into_bytes(self) -> Vec { + 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 for PacketData { + type Error = io::Error; + fn try_from(raw_packet: RawPacket) -> io::Result { + 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")) + } } -} +} \ No newline at end of file diff --git a/domo_proto/src/packet/raw_packet/mod.rs b/domo_proto/src/packet/raw_packet/mod.rs new file mode 100644 index 0000000..be29862 --- /dev/null +++ b/domo_proto/src/packet/raw_packet/mod.rs @@ -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, + pub checksum: u32 +} + diff --git a/domo_proto/src/packet/raw_packet/packet.rs b/domo_proto/src/packet/raw_packet/packet.rs new file mode 100644 index 0000000..efd380a --- /dev/null +++ b/domo_proto/src/packet/raw_packet/packet.rs @@ -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 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 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)), + } + } +} \ No newline at end of file diff --git a/domo_proto/src/packet/raw_packet/vec.rs b/domo_proto/src/packet/raw_packet/vec.rs new file mode 100644 index 0000000..ca3c6e0 --- /dev/null +++ b/domo_proto/src/packet/raw_packet/vec.rs @@ -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> for RawPacket { + type Error = io::Error; + fn try_from(data: Vec) -> io::Result { + 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> for RawPacket { + fn into(self) -> Vec { + 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 + } +} diff --git a/domo_proto/src/prelude.rs b/domo_proto/src/prelude.rs index cbdca31..928d1b4 100644 --- a/domo_proto/src/prelude.rs +++ b/domo_proto/src/prelude.rs @@ -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) + } + } + }; +} \ No newline at end of file