diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/domo.iml b/.idea/domo.iml new file mode 100644 index 0000000..e8ac0a0 --- /dev/null +++ b/.idea/domo.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..a8e7b91 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2ca2389 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4d64f43 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/doc/domo_node/node_types.md b/doc/domo_node/node_types.md index d63fcc1..dc2818f 100644 --- a/doc/domo_node/node_types.md +++ b/doc/domo_node/node_types.md @@ -8,7 +8,7 @@ The job of a master node is rather complex. ## Forwarding packets The most straight forward job of the node. It works by essentially having a hashmap with a reference to a socket. -When a packet comes in the node gets forwarded to the right socket. +When a packet comes in the packet gets forwarded to the right node and socket. ## Handling network state The master node ensures there are no duplicate identifiers and therefore nodes. @@ -16,7 +16,7 @@ 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 Bridge node (`relay`) +# The Bridge node (`bridge`) The bridge node is simple. In config you define where to forward the packets to, and they get delivered to there. diff --git a/domo_node/config.toml b/domo_node/config.toml index e79669e..940b986 100644 --- a/domo_node/config.toml +++ b/domo_node/config.toml @@ -1,5 +1,5 @@ [node.type.master] -bind = "127.0.0.1:4480" +bind = "0.0.0.0:4480" [node] device_id = "ffffffff" diff --git a/domo_node/ping_packet b/domo_node/ping_packet new file mode 100644 index 0000000..c3d68d7 Binary files /dev/null and b/domo_node/ping_packet differ diff --git a/domo_node/register_packet b/domo_node/register_packet new file mode 100644 index 0000000..a3eb7ec Binary files /dev/null and b/domo_node/register_packet differ diff --git a/domo_node/src/app/master.rs b/domo_node/src/app/master.rs new file mode 100644 index 0000000..944e9c9 --- /dev/null +++ b/domo_node/src/app/master.rs @@ -0,0 +1,72 @@ +use std::collections::HashMap; +use std::io; +use std::net::SocketAddr; +use log::{info, trace}; +use domo_proto::data_types::DataType; +use domo_proto::identifier::Identifier; + +pub struct Device { + /// Device identifier + pub device_id: Identifier, + /// Device socket address + pub socket_addr: SocketAddr, + /// List of properties and last state. + pub properties: HashMap +} + +pub struct Devices { + devices: HashMap +} + +impl Devices { + pub fn new() -> Self { + Self { + devices: HashMap::new() + } + } + + pub fn add_device_random(&mut self, socket_addr: SocketAddr) -> Identifier { + let random_id = loop { + // TODO: random should avoid 0xFFFF---- + let id = Identifier::random(); + trace!("checking if id is taken: {}", id); + if !self.devices.contains_key(&id) { break id; } + }; + self.devices.insert(random_id, Device { + device_id: random_id, + socket_addr, + properties: HashMap::new() + }); + info!("Registered new device: {}", random_id); + random_id + } + + pub fn add_device(&mut self, socket_addr: SocketAddr, identifier: Identifier) -> Identifier { + self.devices.insert(identifier, Device { + device_id: identifier, + socket_addr, + properties: HashMap::new() + }); + identifier + } + + pub fn add_device_auto(&mut self, socket_addr: SocketAddr, identifier: Option) -> Identifier { + let identifier = if identifier == Some(Identifier::default()) { + None + } else { + identifier + }; + if let Some(identifier) = identifier { + match (identifier, self.devices.contains_key(&identifier)) { + (identifier, false) => self.add_device(socket_addr, identifier), + _ => self.add_device_random(socket_addr) + } + } else { + self.add_device_random(socket_addr) + } + } + + pub fn get_device(&self, identifier: Identifier) -> Option<&Device> { + self.devices.get(&identifier) + } +} \ No newline at end of file diff --git a/domo_node/src/app.rs b/domo_node/src/app/mod.rs similarity index 56% rename from domo_node/src/app.rs rename to domo_node/src/app/mod.rs index 0aea84b..45e2eca 100644 --- a/domo_node/src/app.rs +++ b/domo_node/src/app/mod.rs @@ -1,20 +1,21 @@ use std::io; -use log::{error, trace, warn}; -use tokio::sync::mpsc::channel; +use log::{error, info, trace, warn}; use domo_proto::commands::node_management::NodeManagementCommand; use domo_proto::identifier::Identifier; -use domo_proto::packet::{Packet, ToPacket}; +use domo_proto::packet::{ToPacket}; use domo_proto::packet::packet_data::PacketData; -use domo_proto::packet::raw_packet::RawPacket; use crate::{CONFIG, prelude}; +use crate::app::master::Devices; use crate::config::node::NodeType; use crate::connection::server::Server; +pub mod master; + impl NodeType { pub async fn start(self) -> io::Result<()> { match self { NodeType::Master { bind } => { - let server = Server::new( + let mut server = Server::new( bind, Some( Identifier::from( @@ -24,24 +25,50 @@ impl NodeType { ), ).await?; + info!("Started server at {}", server.sock().local_addr()?); + + let mut devices = Devices::new(); + { - let p = NodeManagementCommand::Ping.to_packet( + std::fs::write("./register_packet", Into::>::into(NodeManagementCommand::RegisterNode { + device_id: Identifier::from(0x000000AA) + }.to_packet( Identifier::random(), server.device_id(), Identifier::random(), Identifier::default(), - ); - std::fs::write("./packet", Into::>::into(p))?; + )))?; + std::fs::write("./ping_packet", Into::>::into(NodeManagementCommand::Ping.to_packet( + Identifier::from(0x000000AA), + server.device_id(), + Identifier::random(), + Identifier::default(), + )))?; } loop { match server.recv_packet().await { (Ok(p), Some(s)) => { - if p.dest != server.device_id() { - if let Some(d) = server.devices().get(&p.dest) { - let _ = &server.send(d.socket_addr, p).await; + if devices.get_device(p.src).is_none() { + if let PacketData::NodeManagement(NodeManagementCommand::RegisterNode { device_id }) = p.data { + let device_id = devices.add_device_auto(s, Some(device_id)); + let _ = server.send(s, NodeManagementCommand::RegisterNode { + device_id + }.to_packet( + server.device_id(), + device_id, + Identifier::random(), + p.packet_id + )).await; + } else { + warn!("{s} is unregistered and tried to send a packet {p:#?}."); + let _ = server.send(s, prelude::quick_err::errc_not_registered( + server.device_id(), + p.src, + p.packet_id + )).await; + continue; } - continue; } match p.data { PacketData::NodeManagement(NodeManagementCommand::Ping) => { @@ -53,7 +80,7 @@ impl NodeType { p.packet_id, )).await; } - _ => {} + _ => warn!("dropped packet") } } (Err(e), Some(source)) => { diff --git a/domo_node/src/connection/server.rs b/domo_node/src/connection/server.rs index 19943e4..8e22a2e 100644 --- a/domo_node/src/connection/server.rs +++ b/domo_node/src/connection/server.rs @@ -5,20 +5,15 @@ use std::net::SocketAddr; use std::io; use domo_proto::packet::{FULL_PACKET_SIZE, Packet}; use std::io::ErrorKind; -use log::error; +use log::{error, trace}; +use domo_proto::data_types::DataType; use domo_proto::packet::raw_packet::RawPacket; -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 { @@ -26,7 +21,6 @@ impl Server { Ok(Self { sock: UdpSocket::bind(socket_addr).await?, device_id: device_id.unwrap_or(Identifier::random()), - devices: HashMap::new() }) } @@ -38,6 +32,8 @@ impl Server { Err(e) => return (Err(e), None) }; + trace!("received: {} bytes from {}", size, socket_addr); + ( Packet::try_from(buf[..size].to_vec()), Some(socket_addr) @@ -59,8 +55,4 @@ impl Server { pub fn sock(&self) -> &UdpSocket { &self.sock } - - pub fn devices(&self) -> &HashMap { - &self.devices - } } diff --git a/domo_node/src/prelude.rs b/domo_node/src/prelude.rs index 1cafefb..8f7b283 100644 --- a/domo_node/src/prelude.rs +++ b/domo_node/src/prelude.rs @@ -39,4 +39,16 @@ pub mod quick_err { reply_to ) } + + pub fn errc_not_registered(src: Identifier, dest: Identifier, reply_to: Identifier) -> Packet { + NodeManagementCommand::Error { + error_code: NodeManagementError::errc_not_registered.into(), + metadata: "this node is not registered".to_string().into_bytes() + }.to_packet( + src, + dest, + Identifier::random(), + reply_to + ) + } } \ No newline at end of file diff --git a/domo_node/test.py b/domo_node/test.py new file mode 100644 index 0000000..9e1c148 --- /dev/null +++ b/domo_node/test.py @@ -0,0 +1,36 @@ +import socket +import sys + +# Create a UDP socket +udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + +def send_file_contents(filename): + try: + print(f"reading {filename}...") + with open(filename, 'rb') as file: + file_contents = file.read() + except FileNotFoundError: + print(f"File '{filename}' not found.") + return + + print(f"sending content len: {len(file_contents)}...") + # Send the file contents to the UDP socket + udp_socket.sendto(file_contents, ('0.0.0.0', 4480)) + + print("waiting for response...") + # Receive and display the response + response, server_address = udp_socket.recvfrom(1024) + print(f"Received response from {server_address}:") + print(response.hex()) + +if __name__ == "__main__": + try: + while True: + filename = input("Enter a file name (or 'exit' to quit): ") + if filename == 'exit': + break + send_file_contents(filename) + except KeyboardInterrupt: + print("\nExiting the program.") + finally: + udp_socket.close() diff --git a/domo_proto/src/commands/node_management/vec.rs b/domo_proto/src/commands/node_management/vec.rs index 7283d80..610a0d4 100644 --- a/domo_proto/src/commands/node_management/vec.rs +++ b/domo_proto/src/commands/node_management/vec.rs @@ -5,7 +5,9 @@ impl Into> for NodeManagementCommand { fn into(self) -> Vec { match self { NodeManagementCommand::Ping => vec![], - NodeManagementCommand::RegisterNode { device_id } => device_id.bytes.to_vec(), + NodeManagementCommand::RegisterNode { device_id } => { + vec![device_id.bytes[0], device_id.bytes[1], device_id.bytes[2], device_id.bytes[3]] + }, NodeManagementCommand::RemoveNode => vec![], NodeManagementCommand::RegisterProperty { property_name, data_type, read_only } => { let mut vec = vec![]; diff --git a/domo_proto/src/packet/packet_data.rs b/domo_proto/src/packet/packet_data.rs index 0eef305..0daa807 100644 --- a/domo_proto/src/packet/packet_data.rs +++ b/domo_proto/src/packet/packet_data.rs @@ -31,7 +31,9 @@ impl PacketData { impl TryFrom for PacketData { type Error = io::Error; fn try_from(raw_packet: RawPacket) -> io::Result { - match raw_packet.command { + match { + raw_packet.command >> 4 + } { 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")) diff --git a/domo_proto/src/packet/raw_packet/packet.rs b/domo_proto/src/packet/raw_packet/packet.rs index efd380a..c3fcfeb 100644 --- a/domo_proto/src/packet/raw_packet/packet.rs +++ b/domo_proto/src/packet/raw_packet/packet.rs @@ -40,6 +40,10 @@ impl Into for RawPacket { reply_to: Identifier::from(self.reply_to), command: self.command, data: PacketData::try_from(self.clone()) + .map_err(|e| { + println!("{}", e); + e + }) .unwrap_or(PacketData::Raw(self.data)), } } diff --git a/domo_proto/src/packet/raw_packet/vec.rs b/domo_proto/src/packet/raw_packet/vec.rs index 610aba8..c85125b 100644 --- a/domo_proto/src/packet/raw_packet/vec.rs +++ b/domo_proto/src/packet/raw_packet/vec.rs @@ -10,10 +10,11 @@ impl TryFrom> for RawPacket { return Err(io::Error::new(io::ErrorKind::InvalidData, "Can't parse data into RawPacket")) } let data_length = (((data[0x12] as u16) << 8) | data[0x13] as u16) as usize; + println!("LENGTH {data_length}"); if data.len() < 0x14 + data_length { return Err(io::Error::new(io::ErrorKind::InvalidData, "Can't parse data into RawPacket")) } - let checksum = as_u32_be(data[(data.len() - 4)..].as_ref()); + let checksum = as_u32_be(data[(0x14 + data_length)..].as_ref()); let built_checksum = crc32fast::hash(data[..(data.len() - 4)].as_ref()); if checksum != built_checksum { return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Checksum does not match {checksum:X?} != {built_checksum:X?}"))) diff --git a/recv_server.py b/recv_server.py deleted file mode 100644 index 6a59c55..0000000 --- a/recv_server.py +++ /dev/null @@ -1,45 +0,0 @@ - -import socket, zlib - -HOST = "127.0.0.1" # Standard loopback interface address (localhost) -PORT = 2000 # Port to listen on (non-privileged ports are > 1023) - -def calculate_crc(data): - # Calculate the CRC32 checksum of the data - crc = zlib.crc32(data) - # Convert the CRC32 value to a 4-byte big-endian byte array - crc_bytes = crc.to_bytes(4, byteorder='big') - return crc_bytes - -with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((HOST, PORT)) - s.listen() - while True: - try: - conn, addr = s.accept() - with conn: - print(f"Connected by {addr}") - while True: - data = conn.recv(1024) - if not data: - break - print(f'RECV: {data.hex()} [{len(data)}]') - if data[0x11] == 0x01: - pckt = bytearray([0x01]) - pckt.extend([0x00, 0x00, 0x00, 0x00]) # src - pckt.extend([0x00, 0x00, 0x00, 0xFF]) # dest - pckt.extend([0x00, 0x00, 0x00, 0x00]) # packet_id - pckt.extend(data[0x09:0x0D]) # reply_to - pckt.extend([0x01]) # command - pckt.extend([0x00, 0x04]) # data_length - pckt.extend([0x00, 0x00, 0x00, 0xFF]) - - # Calculate CRC for the received data - crc = calculate_crc(pckt) - # Append the CRC value to the data - data_with_crc = pckt + crc - # Send the modified data (with CRC) back to the client - conn.sendall(data_with_crc) - print(f'SEND: {data_with_crc.hex()} [{len(data_with_crc)}]') - except: - s.close() \ No newline at end of file