refactor: breaking refactor of domo_proto and domo_node

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

View file

@ -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`)

View file

@ -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
View file

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

View file

@ -1,3 +1,3 @@
[node.type.relay]
[node.type.bridge]
bind = "127.0.0.1:4481"
master_address = "127.0.0.1:4480"

View file

@ -2,4 +2,4 @@
bind = "127.0.0.1:4480"
[node]
device_id = "000000ff"
device_id = "ffffffff"

View file

@ -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()
}
}
}

View file

@ -0,0 +1 @@
pub mod server;

View 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
}
}

View file

@ -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
View 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
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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