refactor: breaking refactor of domo_proto and domo_node
This commit is contained in:
parent
be4425aff9
commit
15971aa4fa
31 changed files with 902 additions and 393 deletions
|
@ -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`)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
2
domo_node/Cargo.lock
generated
2
domo_node/Cargo.lock
generated
|
@ -91,7 +91,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "domo_proto"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"rand",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[node.type.relay]
|
||||
[node.type.bridge]
|
||||
bind = "127.0.0.1:4481"
|
||||
master_address = "127.0.0.1:4480"
|
|
@ -2,4 +2,4 @@
|
|||
bind = "127.0.0.1:4480"
|
||||
|
||||
[node]
|
||||
device_id = "000000ff"
|
||||
device_id = "ffffffff"
|
|
@ -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<String>
|
||||
#[serde(default)]
|
||||
pub device_id: String
|
||||
}
|
||||
|
||||
impl Default for NodeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
node_type: NodeType::default(),
|
||||
device_id: Identifier::random().to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pub mod server;
|
57
domo_node/src/connection/server.rs
Normal file
57
domo_node/src/connection/server.rs
Normal file
|
@ -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<Identifier, Device>
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub async fn new(socket_addr: SocketAddr, device_id: Option<Identifier>) -> io::Result<Self> {
|
||||
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<Packet>, Option<SocketAddr>) {
|
||||
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<Identifier, Device> {
|
||||
&self.devices
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
|
|
42
domo_node/src/prelude.rs
Normal file
42
domo_node/src/prelude.rs
Normal file
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
2
domo_proto/Cargo.lock
generated
2
domo_proto/Cargo.lock
generated
|
@ -19,7 +19,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "domo_proto"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"rand",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
37
domo_proto/src/commands/node_management/errors.rs
Normal file
37
domo_proto/src/commands/node_management/errors.rs
Normal 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"))
|
||||
}
|
||||
}
|
||||
}
|
100
domo_proto/src/commands/node_management/mod.rs
Normal file
100
domo_proto/src/commands/node_management/mod.rs
Normal 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"))
|
||||
}
|
||||
}
|
||||
}
|
26
domo_proto/src/commands/node_management/vec.rs
Normal file
26
domo_proto/src/commands/node_management/vec.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
118
domo_proto/src/commands/property_control/mod.rs
Normal file
118
domo_proto/src/commands/property_control/mod.rs
Normal 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"))
|
||||
}
|
||||
}
|
||||
}
|
34
domo_proto/src/commands/property_control/vec.rs
Normal file
34
domo_proto/src/commands/property_control/vec.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
84
domo_proto/src/data_types/mod.rs
Normal file
84
domo_proto/src/data_types/mod.rs
Normal 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
|
||||
}
|
81
domo_proto/src/data_types/vec.rs
Normal file
81
domo_proto/src/data_types/vec.rs
Normal 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.")),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
domo_proto/src/packet/raw_packet/mod.rs
Normal file
16
domo_proto/src/packet/raw_packet/mod.rs
Normal 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
|
||||
}
|
||||
|
46
domo_proto/src/packet/raw_packet/packet.rs
Normal file
46
domo_proto/src/packet/raw_packet/packet.rs
Normal 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)),
|
||||
}
|
||||
}
|
||||
}
|
47
domo_proto/src/packet/raw_packet/vec.rs
Normal file
47
domo_proto/src/packet/raw_packet/vec.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue