feat: new stuff

This commit is contained in:
Strix 2023-10-15 18:06:34 +02:00
parent 3eece7ecd9
commit f88706bac8
No known key found for this signature in database
GPG key ID: 49B2E37B8915B774
20 changed files with 232 additions and 77 deletions

8
.idea/.gitignore vendored Normal file
View file

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

14
.idea/domo.iml Normal file
View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/domo_node/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/domo_proto/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/domo_node/target" />
<excludeFolder url="file://$MODULE_DIR$/domo_proto/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/encodings.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/domo_node/packet" charset="ISO-8859-1" />
</component>
</project>

10
.idea/misc.xml Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/domo.iml" filepath="$PROJECT_DIR$/.idea/domo.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -8,7 +8,7 @@ The job of a master node is rather complex.
## Forwarding packets ## Forwarding packets
The most straight forward job of the node. The most straight forward job of the node.
It works by essentially having a hashmap with a reference to a socket. 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 ## Handling network state
The master node ensures there are no duplicate identifiers and therefore nodes. 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 packets go through the master node before getting to the source.
Most because the subnet node can have private nodes and handle those themselves. 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. The bridge node is simple.
In config you define where to forward the packets to, and they get delivered to there. In config you define where to forward the packets to, and they get delivered to there.

View file

@ -1,5 +1,5 @@
[node.type.master] [node.type.master]
bind = "127.0.0.1:4480" bind = "0.0.0.0:4480"
[node] [node]
device_id = "ffffffff" device_id = "ffffffff"

BIN
domo_node/ping_packet Normal file

Binary file not shown.

BIN
domo_node/register_packet Normal file

Binary file not shown.

View file

@ -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<String, DataType>
}
pub struct Devices {
devices: HashMap<Identifier, Device>
}
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>) -> 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)
}
}

View file

@ -1,20 +1,21 @@
use std::io; use std::io;
use log::{error, trace, warn}; use log::{error, info, trace, warn};
use tokio::sync::mpsc::channel;
use domo_proto::commands::node_management::NodeManagementCommand; use domo_proto::commands::node_management::NodeManagementCommand;
use domo_proto::identifier::Identifier; 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::packet_data::PacketData;
use domo_proto::packet::raw_packet::RawPacket;
use crate::{CONFIG, prelude}; use crate::{CONFIG, prelude};
use crate::app::master::Devices;
use crate::config::node::NodeType; use crate::config::node::NodeType;
use crate::connection::server::Server; use crate::connection::server::Server;
pub mod master;
impl NodeType { impl NodeType {
pub async fn start(self) -> io::Result<()> { pub async fn start(self) -> io::Result<()> {
match self { match self {
NodeType::Master { bind } => { NodeType::Master { bind } => {
let server = Server::new( let mut server = Server::new(
bind, bind,
Some( Some(
Identifier::from( Identifier::from(
@ -24,25 +25,51 @@ impl NodeType {
), ),
).await?; ).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::<Vec<u8>>::into(NodeManagementCommand::RegisterNode {
device_id: Identifier::from(0x000000AA)
}.to_packet(
Identifier::random(), Identifier::random(),
server.device_id(), server.device_id(),
Identifier::random(), Identifier::random(),
Identifier::default(), Identifier::default(),
); )))?;
std::fs::write("./packet", Into::<Vec<u8>>::into(p))?; std::fs::write("./ping_packet", Into::<Vec<u8>>::into(NodeManagementCommand::Ping.to_packet(
Identifier::from(0x000000AA),
server.device_id(),
Identifier::random(),
Identifier::default(),
)))?;
} }
loop { loop {
match server.recv_packet().await { match server.recv_packet().await {
(Ok(p), Some(s)) => { (Ok(p), Some(s)) => {
if p.dest != server.device_id() { if devices.get_device(p.src).is_none() {
if let Some(d) = server.devices().get(&p.dest) { if let PacketData::NodeManagement(NodeManagementCommand::RegisterNode { device_id }) = p.data {
let _ = &server.send(d.socket_addr, p).await; 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 { match p.data {
PacketData::NodeManagement(NodeManagementCommand::Ping) => { PacketData::NodeManagement(NodeManagementCommand::Ping) => {
trace!("ping from: [{s}->{}]", p.src); trace!("ping from: [{s}->{}]", p.src);
@ -53,7 +80,7 @@ impl NodeType {
p.packet_id, p.packet_id,
)).await; )).await;
} }
_ => {} _ => warn!("dropped packet")
} }
} }
(Err(e), Some(source)) => { (Err(e), Some(source)) => {

View file

@ -5,20 +5,15 @@ use std::net::SocketAddr;
use std::io; use std::io;
use domo_proto::packet::{FULL_PACKET_SIZE, Packet}; use domo_proto::packet::{FULL_PACKET_SIZE, Packet};
use std::io::ErrorKind; use std::io::ErrorKind;
use log::error; use log::{error, trace};
use domo_proto::data_types::DataType;
use domo_proto::packet::raw_packet::RawPacket; 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 { pub struct Server {
sock: UdpSocket, sock: UdpSocket,
device_id: Identifier, device_id: Identifier,
devices: HashMap<Identifier, Device>
} }
impl Server { impl Server {
@ -26,7 +21,6 @@ impl Server {
Ok(Self { Ok(Self {
sock: UdpSocket::bind(socket_addr).await?, sock: UdpSocket::bind(socket_addr).await?,
device_id: device_id.unwrap_or(Identifier::random()), device_id: device_id.unwrap_or(Identifier::random()),
devices: HashMap::new()
}) })
} }
@ -38,6 +32,8 @@ impl Server {
Err(e) => return (Err(e), None) Err(e) => return (Err(e), None)
}; };
trace!("received: {} bytes from {}", size, socket_addr);
( (
Packet::try_from(buf[..size].to_vec()), Packet::try_from(buf[..size].to_vec()),
Some(socket_addr) Some(socket_addr)
@ -59,8 +55,4 @@ impl Server {
pub fn sock(&self) -> &UdpSocket { pub fn sock(&self) -> &UdpSocket {
&self.sock &self.sock
} }
pub fn devices(&self) -> &HashMap<Identifier, Device> {
&self.devices
}
} }

View file

@ -39,4 +39,16 @@ pub mod quick_err {
reply_to 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
)
}
} }

36
domo_node/test.py Normal file
View file

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

View file

@ -5,7 +5,9 @@ impl Into<Vec<u8>> for NodeManagementCommand {
fn into(self) -> Vec<u8> { fn into(self) -> Vec<u8> {
match self { match self {
NodeManagementCommand::Ping => vec![], 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::RemoveNode => vec![],
NodeManagementCommand::RegisterProperty { property_name, data_type, read_only } => { NodeManagementCommand::RegisterProperty { property_name, data_type, read_only } => {
let mut vec = vec![]; let mut vec = vec![];

View file

@ -31,7 +31,9 @@ impl PacketData {
impl TryFrom<RawPacket> for PacketData { impl TryFrom<RawPacket> for PacketData {
type Error = io::Error; type Error = io::Error;
fn try_from(raw_packet: RawPacket) -> io::Result<Self> { fn try_from(raw_packet: RawPacket) -> io::Result<Self> {
match raw_packet.command { match {
raw_packet.command >> 4
} {
0x0 => Ok(PacketData::NodeManagement(NodeManagementCommand::try_from(raw_packet)?)), 0x0 => Ok(PacketData::NodeManagement(NodeManagementCommand::try_from(raw_packet)?)),
0x1 => Ok(PacketData::PropertyControl(PropertyControlCommand::try_from(raw_packet)?)), 0x1 => Ok(PacketData::PropertyControl(PropertyControlCommand::try_from(raw_packet)?)),
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, "command group is unsupported")) _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "command group is unsupported"))

View file

@ -40,6 +40,10 @@ impl Into<Packet> for RawPacket {
reply_to: Identifier::from(self.reply_to), reply_to: Identifier::from(self.reply_to),
command: self.command, command: self.command,
data: PacketData::try_from(self.clone()) data: PacketData::try_from(self.clone())
.map_err(|e| {
println!("{}", e);
e
})
.unwrap_or(PacketData::Raw(self.data)), .unwrap_or(PacketData::Raw(self.data)),
} }
} }

View file

@ -10,10 +10,11 @@ impl TryFrom<Vec<u8>> for RawPacket {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Can't parse data into 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; let data_length = (((data[0x12] as u16) << 8) | data[0x13] as u16) as usize;
println!("LENGTH {data_length}");
if data.len() < 0x14 + data_length { if data.len() < 0x14 + data_length {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Can't parse data into RawPacket")) 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()); let built_checksum = crc32fast::hash(data[..(data.len() - 4)].as_ref());
if checksum != built_checksum { if checksum != built_checksum {
return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Checksum does not match {checksum:X?} != {built_checksum:X?}"))) return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Checksum does not match {checksum:X?} != {built_checksum:X?}")))

View file

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