From 857e2b4f21003d21f254087a7da3fff701b32156 Mon Sep 17 00:00:00 2001 From: Gitea Date: Thu, 21 Jul 2022 20:28:07 +0300 Subject: [PATCH] Handshake packet implementation --- PROTOCOL | 45 ++++++++ TODO | 3 +- src/args/parser.rs | 43 +++---- src/main.rs | 20 ++-- src/nodes/mod.rs | 3 +- src/nodes/node.rs | 9 -- src/nodes/recv_node.rs | 106 +++++++++++++++++ src/nodes/send_node.rs | 21 ++++ src/protocol/packets.rs | 247 +++++++++++++++++++++++++++++++--------- src/protocol/specs.rs | 140 ++--------------------- src/util/buf_read.rs | 14 +-- 11 files changed, 410 insertions(+), 241 deletions(-) create mode 100644 PROTOCOL delete mode 100644 src/nodes/node.rs create mode 100644 src/nodes/recv_node.rs create mode 100644 src/nodes/send_node.rs diff --git a/PROTOCOL b/PROTOCOL new file mode 100644 index 0000000..a9f034c --- /dev/null +++ b/PROTOCOL @@ -0,0 +1,45 @@ +--- DDTU PROTOCOL v1 --- + +- High level overview - + +- Node Modes - + +[Receive] +Is for receiving information. + +[Send] +Is for sending data to the receiving end. + +[Sync] +Is for syncing one directory among 2 ends. (Make contents the same on both sides) + +[Daemon] +Is used to create a longstanding sync daemon which syncs the directory with all incoming +sync connections + + +All infomation|commands are sent via packets with specific structure + +- Packet bytes representation - + +First 16 bytes - the total packet length +The next 1 byte - number that represents this packet's type (ID) where: +1: HANDSHAKE PACKET +2: HANDSHAKE ACCEPT +3: CONNECTION SHUTDOWN PACKET +4: TEXT PACKET +5: FILEINFO PACKET +6: FILEDATA PACKET + +Then the internal structure varies from one packet type to the other, +but the content-types are encoded as follows: +u8 - just 1 BE byte +u128 - 16 BE bytes +String - u128 representing the string length and then UTF-8 encoded character bytes +[u8] - u128 as the length of a byte array, then the array itself +bool - convert it to u8 where 0 == false, 1 == true + +- Packets - +[Handshake] +This is the first packet that must be sent by connecting node to waiting node. +Its existence tells the receiving end that the diff --git a/TODO b/TODO index 8ec32c6..f71714e 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ 1. Write base foundation for sending and receiving information 2. Wire it all together 3. Make it encrypted -4. Daemon \ No newline at end of file +4. Allow receiving node to connect as well +5. Daemon \ No newline at end of file diff --git a/src/args/parser.rs b/src/args/parser.rs index 9b95c85..db66d34 100644 --- a/src/args/parser.rs +++ b/src/args/parser.rs @@ -14,18 +14,11 @@ GNU Affero General Public License for more details. */ use crate::util::error::Error; +use crate::protocol::specs::RunMode; use std::path; #[derive(PartialEq, Eq, Debug)] -pub enum RunMode { - SEND, - RECEIVE, - SYNC, - DAEMON, -} - -#[derive(PartialEq, Eq, Debug)] -pub enum Type { +pub enum DataType { TEXT, FILE, DIR, @@ -36,7 +29,7 @@ pub struct Args { pub help: bool, pub version: bool, pub mode: RunMode, - pub send_type: Type, + pub send_type: DataType, pub address_str: String, pub send_path: path::PathBuf, pub save_directory: path::PathBuf, @@ -50,7 +43,7 @@ impl Args { help: false, version: false, mode: RunMode::DAEMON, - send_type: Type::TEXT, + send_type: DataType::TEXT, address_str: "".to_string(), send_path: path::PathBuf::new(), save_directory: path::PathBuf::new(), @@ -61,6 +54,7 @@ impl Args { } pub fn parse(raw_args: &Vec) -> Result { + let mut parsed_args: Args = Args::default(); // parse arguments, ignoring the first argument @@ -79,37 +73,37 @@ pub fn parse(raw_args: &Vec) -> Result { } "send" => { - if arg_amount < 4 { + if arg_amount < 5 { // not enough arguments ! return Err(Error::new("not enough arguments for send mode")); } parsed_args.mode = RunMode::SEND; - // type + // DataType match raw_args[2].to_lowercase().as_str() { "text" => { - parsed_args.send_type = Type::TEXT; + parsed_args.send_type = DataType::TEXT; parsed_args.text_to_send = String::from(&raw_args[3]); } "file" => { - parsed_args.send_type = Type::FILE; + parsed_args.send_type = DataType::FILE; parsed_args.send_path = std::path::Path::new(&raw_args[3]).to_path_buf(); } "dir" => { - parsed_args.send_type = Type::DIR; + parsed_args.send_type = DataType::DIR; parsed_args.send_path = std::path::Path::new(&raw_args[3]).to_path_buf(); } _ => { - return Err(Error::new(format!("invalid type {}", raw_args[2]).as_str())); + return Err(Error::new(format!("invalid DataType {}", raw_args[2]).as_str())); } } // src - if parsed_args.send_type == Type::FILE || parsed_args.send_type == Type::DIR { + if parsed_args.send_type == DataType::FILE || parsed_args.send_type == DataType::DIR { // check if exists match path::Path::new(raw_args[3].as_str()).exists() { true => { @@ -121,18 +115,15 @@ pub fn parse(raw_args: &Vec) -> Result { } } } + + // addr + parsed_args.address_str = raw_args[4].to_string(); } "recv" => { - if arg_amount < 3 { - // not enough arguments ! - return Err(Error::new("not enough arguments for receiving mode")); - } - parsed_args.mode = RunMode::RECEIVE; - parsed_args.address_str = raw_args[2].to_string(); - if arg_amount > 3 { - parsed_args.save_directory = path::Path::new(raw_args[3].as_str()).to_path_buf(); + if arg_amount > 2 { + parsed_args.save_directory = path::Path::new(raw_args[2].as_str()).to_path_buf(); } } diff --git a/src/main.rs b/src/main.rs index f87d4ec..5a0d734 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,8 +18,10 @@ mod protocol; mod args; mod crypt; mod fsys; +mod nodes; -use args::parser::{Args, RunMode}; +use protocol::specs::RunMode; +use args::parser::Args; use std::net; use crate::protocol::specs::{Packet, PacketType}; @@ -34,10 +36,10 @@ ddtu [FLAG] [MODE] [MODE-specific arguments] [version]: ddtu version -> print version [MODE] -[send]: ddtu send [TYPE] [SRC] -> prepare to send [SRC] of [TYPE] and wait for someone to receive -[recv]: ddtu recv [SRC] [DEST](optional) -> receive anything from another ddtu instance running on [SRC], saving it at [DEST] -[sync]: ddtu sync [DIR] [DEST] -> sync [DIRectory] contents with another ddtu instance on [DEST] -[daemon]: ddtu daemon -> start syncing daemon +[send]: ddtu send [TYPE] [SRC] [ADDR] -> send [SRC] of [TYPE] to [ADDR] +[recv]: ddtu recv [DIR](optional) -> wait for connection and receive anything from another ddtu instance, saving it to [DIR] +[sync]: ddtu sync [DIR] [ADDR] -> sync [DIRectory] contents with another ddtu instance on [ADDR] +[daemon]: ddtu daemon -> start sync daemon [TYPE] [text]: ddtu send text \'text to send\' -> send text @@ -93,11 +95,11 @@ fn main() { println!("connected to {}", args.address_str); - match protocol::specs::read_next_packet(&mut conn) { + match protocol::packets::read_next_packet(&mut conn) { Ok(packet) => { match packet.get_type() { PacketType::TextData => { - let mut text_packet = protocol::packets::TextPacket::empty(); + let mut text_packet = protocol::packets::Text::empty(); text_packet.from_bytes(packet.as_bytes()); println!("{}", text_packet.text); } @@ -144,8 +146,8 @@ fn main() { println!("{} has connected", addr); // send text - let textpacket = protocol::packets::TextPacket::new(&args.text_to_send); - match protocol::specs::send_packet(&mut conn, &textpacket) { + let Text = protocol::packets::Text::new(&args.text_to_send); + match protocol::packets::send_packet(&mut conn, &Text) { Ok(()) => {} Err(error) => { println!("{}", error.text); diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 12e2c60..688271a 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -1 +1,2 @@ -pub mod node; \ No newline at end of file +pub mod recv_node; +pub mod send_node; \ No newline at end of file diff --git a/src/nodes/node.rs b/src/nodes/node.rs deleted file mode 100644 index f82ca6b..0000000 --- a/src/nodes/node.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub struct Node { - -} - -impl Node { - pub fn new() -> Node { - return Node{}; - } -} \ No newline at end of file diff --git a/src/nodes/recv_node.rs b/src/nodes/recv_node.rs new file mode 100644 index 0000000..87cfaff --- /dev/null +++ b/src/nodes/recv_node.rs @@ -0,0 +1,106 @@ +/* +ddtu - digital data transferring utility +Copyright (C) 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. +*/ + +use crate::util::error::Error; +use crate::protocol::specs::PacketType; +use crate::protocol::specs::Packet; +use crate::protocol::packets; +use crate::protocol; +use std::path; +use std::net; + +pub fn start(address_string: String, save_directory: path::PathBuf, verbose_output: bool) -> Option { + let listener: net::TcpListener; + match net::TcpListener::bind(address_string) { + Ok(l) => { + listener = l; + } + Err(error) => { + return Some(Error::new(format!("could not create TCP listener: {}", error).as_str())); + } + } + + + // wait for incoming connection from another ddtu instance, + // reject any connection that doesn't provide handshake information + let mut connection: net::TcpStream; + let mut address: net::SocketAddr; + let mut handshake: packets::Handshake = packets::Handshake::empty(); + loop { + match listener.accept() { + Ok((c, a)) => { + connection = c; + address = a; + } + Err(error) => { + return Some(Error::new(format!("error accepting a new incoming connection: {}", error).as_str())); + } + } + + if verbose_output { + println!("Connection from {}", address); + } + + // read the first packet + let incoming_packet: Box; + match protocol::packets::read_next_packet(&mut connection) { + Ok(packet) => { + incoming_packet = packet; + } + + Err(_) => { + // not a valid packet; it's most probably not another instance + if verbose_output { + println!("{} did not provide valid handshake packet. Dropping connection...", address); + } + connection.shutdown(net::Shutdown::Both); + continue; + } + } + + match incoming_packet.get_type() { + PacketType::Handshake => { + match handshake.from_bytes(incoming_packet.as_bytes()) { + Some(error) => { + return Some( + Error::new( + format!("error constructing handshake from {}: {}", address, error.text).as_str() + )); + } + None => {} + } + + if verbose_output { + println!("Handshake from {}", address); + } + + // break out of loop + break; + } + + _ => { + if verbose_output { + println!("{} didn't send a handshake packet, but {:?} packet instead. Dropping connection...", address, incoming_packet.get_type()); + } + connection.shutdown(net::Shutdown::Both); + continue; + } + } + } + + // now read and process incoming packets; essentialy receiving something ! + + return None; +} diff --git a/src/nodes/send_node.rs b/src/nodes/send_node.rs new file mode 100644 index 0000000..c4e9df0 --- /dev/null +++ b/src/nodes/send_node.rs @@ -0,0 +1,21 @@ +/* +ddtu - digital data transferring utility +Copyright (C) 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. +*/ + +use std::net; +use std::path; + +pub fn start(address: String, source: path::PathBuf) { + +} diff --git a/src/protocol/packets.rs b/src/protocol/packets.rs index e9caf08..8338a62 100644 --- a/src/protocol/packets.rs +++ b/src/protocol/packets.rs @@ -13,45 +13,150 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. */ +use std::net; +use std::io::Write; +use std::io::Read; + use crate::util::error::Error; -use crate::util::buf_read::{read_u128_slice, read_utf8_string_slice}; +use crate::util::buf_read::{read_u128_slice_be, read_utf8_string_slice}; use crate::protocol::specs::*; use crate::fsys::file::ChunkedFile; -pub struct TextPacket { +pub fn send_packet(conn: &mut net::TcpStream, packet: &dyn Packet) -> Result<(), Error> { + let mut payload: Vec = Vec::::new(); + + let packet_bytes: Vec = packet.as_bytes(); + let packet_len: usize = packet_bytes.len(); + + payload.extend_from_slice(&(packet_len as u128).to_be_bytes()); + payload.extend_from_slice(&packet_bytes); + + match conn.write_all(&payload) { + Ok(()) => { + return Ok(()); + } + + Err(error) => { + return Err(Error::new(format!("could not write packet to the connection: {}", error).as_str())) + } + } +} + +pub fn read_next_packet(conn: &mut net::TcpStream) -> Result, Error> { + let mut u128_buf: [u8; 16] = [0; 16]; + + match conn.read_exact(&mut u128_buf) { + Ok(()) => {} + Err(error) => { + return Err(Error::new(format!("error while reading initial length bytes: {}", error).as_str())); + } + } + let packet_len: u128 = u128::from_be_bytes(u128_buf); + + let mut packet_bytes: Vec = vec!(0; packet_len as usize); + match conn.read_exact(&mut packet_bytes) { + Ok(()) => {} + Err(error) => { + return Err(Error::new(format!("error while reading packet contents: {}", error).as_str())); + } + } + + if packet_bytes.len() < packet_len as usize { + return Err( + Error::new( + format!("read {} packet bytes instead of specified {}", packet_bytes.len(), packet_len).as_str()) + ); + } + + let packet_type_byte: u8 = u8::from_be_bytes([packet_bytes[0]]); + match packet_type_byte { + CONNECTION_SHUTDOWN_PACKET_ID => { + return Ok(Box::new(ConnectionShutdown{})); + } + + TEXT_PACKET_ID => { + let mut new_text_packet: Text = Text::empty(); + let result = new_text_packet.from_bytes(packet_bytes); + match result { + Some(error) => { + return Err(Error::new(format!("could not construct new text packet: {}", error.text).as_str())); + } + + None => { + return Ok(Box::new(new_text_packet)); + } + } + } + + FILEINFO_PACKET_ID => { + let mut new_fileinfo_packet: FileInfo = FileInfo::empty(); + let result = new_fileinfo_packet.from_bytes(packet_bytes); + match result { + Some(error) => { + return Err(Error::new(format!("could not construct new fileinfo packet: {}", error.text).as_str())); + } + + None => { + return Ok(Box::new(new_fileinfo_packet)); + } + } + } + + FILEDATA_PACKET_ID => { + let mut new_filedata_packet: FileData = FileData::empty(); + let result = new_filedata_packet.from_bytes(packet_bytes); + match result { + Some(error) => { + return Err(Error::new(format!("could not construct new filedata packet: {}", error.text).as_str())); + } + + None => { + return Ok(Box::new(new_filedata_packet)); + } + } + } + + _ => { + return Err(Error::new(format!("invalid packet type ID \"{}\"", packet_type_byte).as_str())); + } + } +} + + +pub struct Text { pub text: String, } -impl TextPacket { - pub fn empty() -> TextPacket { - return TextPacket{ +impl Text { + pub fn empty() -> Text { + return Text{ text: "".to_string(), }; } - pub fn new(txt: &str) -> TextPacket { - return TextPacket { + pub fn new(txt: &str) -> Text { + return Text { text: txt.to_string(), } } } -impl Packet for TextPacket { +impl Packet for Text { fn get_type(&self) -> PacketType { return PacketType::TextData; } fn as_bytes(&self) -> Vec { - let mut packet_as_bytes: Vec = Vec::::new(); + let mut packet_bytes: Vec = Vec::::new(); - packet_as_bytes.extend_from_slice(&TEXT_PACKET_ID.to_be_bytes()); + packet_bytes.extend_from_slice(&TEXT_PACKET_ID.to_be_bytes()); let text_length: u128 = self.text.len() as u128; - packet_as_bytes.extend_from_slice(&text_length.to_be_bytes()); - packet_as_bytes.extend_from_slice(&self.text.as_bytes()); + packet_bytes.extend_from_slice(&text_length.to_be_bytes()); + packet_bytes.extend_from_slice(&self.text.as_bytes()); - return packet_as_bytes; + return packet_bytes; } fn from_bytes(&mut self, packet_bytes: Vec) -> Option { @@ -70,7 +175,7 @@ impl Packet for TextPacket { } // get text length - let text_length: u128 = read_u128_slice(&packet_bytes[1..17]); + let text_length: u128 = read_u128_slice_be(&packet_bytes[1..17]); if text_length as usize > packet_bytes[17..].len() { return Some( @@ -92,16 +197,16 @@ impl Packet for TextPacket { } -pub struct FileInfoPacket { +pub struct FileInfo { pub file_id: u128, pub filename: String, pub filesize: u128, pub relative_path: String, } -impl FileInfoPacket { - pub fn empty() -> FileInfoPacket { - return FileInfoPacket{ +impl FileInfo { + pub fn empty() -> FileInfo { + return FileInfo{ file_id: 0, filename: String::new(), filesize: 0, @@ -109,8 +214,8 @@ impl FileInfoPacket { }; } - pub fn new(file: &ChunkedFile, file_id: u128, rel_path: String) -> Result { - let mut new_fileinfo_packet: FileInfoPacket = FileInfoPacket::empty(); + pub fn new(file: &ChunkedFile, file_id: u128, rel_path: String) -> Result { + let mut new_fileinfo_packet: FileInfo = FileInfo::empty(); // id new_fileinfo_packet.file_id = file_id; @@ -146,31 +251,33 @@ impl FileInfoPacket { } } -impl Packet for FileInfoPacket { +impl Packet for FileInfo { fn get_type(&self) -> PacketType { return PacketType::FileInfo; } fn as_bytes(&self) -> Vec { - let mut packet_as_bytes: Vec = Vec::::new(); + let mut packet_bytes: Vec = Vec::::new(); + + packet_bytes.extend_from_slice(&FILEINFO_PACKET_ID.to_be_bytes()); // file id - packet_as_bytes.extend_from_slice(&self.file_id.to_be_bytes()); + packet_bytes.extend_from_slice(&self.file_id.to_be_bytes()); // filename let filename_bytes_length: u128 = self.filename.len() as u128; - packet_as_bytes.extend_from_slice(&filename_bytes_length.to_be_bytes()); - packet_as_bytes.extend_from_slice(&self.filename.as_bytes()); + packet_bytes.extend_from_slice(&filename_bytes_length.to_be_bytes()); + packet_bytes.extend_from_slice(&self.filename.as_bytes()); // filesize - packet_as_bytes.extend_from_slice(&self.filesize.to_be_bytes()); + packet_bytes.extend_from_slice(&self.filesize.to_be_bytes()); // relative path let relative_path_bytes_length: u128 = self.relative_path.len() as u128; - packet_as_bytes.extend_from_slice(&relative_path_bytes_length.to_be_bytes()); - packet_as_bytes.extend_from_slice(&self.relative_path.as_bytes()); + packet_bytes.extend_from_slice(&relative_path_bytes_length.to_be_bytes()); + packet_bytes.extend_from_slice(&self.relative_path.as_bytes()); - return packet_as_bytes; + return packet_bytes; } fn from_bytes(&mut self, packet_bytes: Vec) -> Option { @@ -181,17 +288,17 @@ impl Packet for FileInfoPacket { } // file id - self.file_id = read_u128_slice(&packet_bytes[0..16]); + self.file_id = read_u128_slice_be(&packet_bytes[0..16]); // get filename - let filename_length: u128 = read_u128_slice(&packet_bytes[16..32]); + let filename_length: u128 = read_u128_slice_be(&packet_bytes[16..32]); self.filename = read_utf8_string_slice(&packet_bytes[32..(filename_length+32) as usize]); // filesize - self.filesize = read_u128_slice(&packet_bytes[(filename_length+32) as usize..(filename_length+32+16) as usize]); + self.filesize = read_u128_slice_be(&packet_bytes[(filename_length+32) as usize..(filename_length+32+16) as usize]); // relative path - let rel_path_length: u128 = read_u128_slice( + let rel_path_length: u128 = read_u128_slice_be( &packet_bytes[(filename_length+32+16) as usize..(filename_length+32+16+16)as usize] ); self.relative_path = read_utf8_string_slice( @@ -203,15 +310,15 @@ impl Packet for FileInfoPacket { } -pub struct FileDataPacket { +pub struct FileData { pub file_id: u128, pub chunk_no: u128, pub chunk: [u8; CHUNK_SIZE], } -impl FileDataPacket { - pub fn empty() -> FileDataPacket { - return FileDataPacket { +impl FileData { + pub fn empty() -> FileData { + return FileData { file_id: 0, chunk_no: 0, chunk: [0; CHUNK_SIZE], @@ -219,7 +326,7 @@ impl FileDataPacket { } } -impl Packet for FileDataPacket { +impl Packet for FileData { fn get_type(&self) -> PacketType { return PacketType::FileData; } @@ -227,6 +334,8 @@ impl Packet for FileDataPacket { fn as_bytes(&self) -> Vec { let mut packet_bytes: Vec = Vec::::new(); + packet_bytes.extend_from_slice(&FILEDATA_PACKET_ID.to_be_bytes()); + // file id packet_bytes.extend_from_slice(&self.file_id.to_be_bytes()); @@ -248,24 +357,24 @@ impl Packet for FileDataPacket { } // file id - self.file_id = read_u128_slice(&packet_bytes[0..16]); + self.file_id = read_u128_slice_be(&packet_bytes[0..16]); // chunk number - self.chunk_no = read_u128_slice(&packet_bytes[16..32]); + self.chunk_no = read_u128_slice_be(&packet_bytes[16..32]); // chunk bytes - let chunk_length = read_u128_slice(&packet_bytes[32..48]); + let chunk_length = read_u128_slice_be(&packet_bytes[32..48]); for i in 48..(48+chunk_length) as usize { - self.chunk[i-48] = packet_bytes[i]; + self.chunk[i-48] = packet_bytes[i].to_be(); } return None; } } -pub struct ConnectionShutdownPacket {} +pub struct ConnectionShutdown {} -impl Packet for ConnectionShutdownPacket { +impl Packet for ConnectionShutdown { fn get_type(&self) -> PacketType { return PacketType::ConnectionShutdown; } @@ -279,34 +388,64 @@ impl Packet for ConnectionShutdownPacket { } } -pub struct HandshakePaket { +pub struct Handshake { pub protocol_version: u8, - pub use_encryption: bool, pub encryption_type: u8, pub encryption_key: Vec, } -impl HandshakePaket { - fn empty() -> HandshakePaket { - return HandshakePaket{ +impl Handshake { + pub fn empty() -> Handshake { + return Handshake{ protocol_version: PROTOCOL_LATEST_VERSION, - use_encryption: false, - encryption_type: ECRYPTION_XOR, + encryption_type: ENCRYPTION_NO_ENCRYPTION, encryption_key: vec!(0, 0, 0, 0, 0, 0, 0, 0), }; } } -impl Packet for HandshakePaket { +impl Packet for Handshake { fn get_type(&self) -> PacketType { return PacketType::Handshake; } fn as_bytes(&self) -> Vec { - return vec!(HANDSHAKE_PACKET_ID.to_be_bytes()[0]); + let mut packet_bytes: Vec = Vec::::new(); + + packet_bytes.extend_from_slice(&HANDSHAKE_PACKET_ID.to_be_bytes()); + + // protocol version + packet_bytes.extend_from_slice(&PROTOCOL_LATEST_VERSION.to_be_bytes()); + + // encryption type + packet_bytes.extend_from_slice(&self.encryption_type.to_be_bytes()); + + // encryption key + packet_bytes.extend_from_slice(&(self.encryption_key.len() as u128).to_be_bytes()); + packet_bytes.extend_from_slice(&self.encryption_key); + + return packet_bytes; } - fn from_bytes(&mut self, _packet_bytes: Vec) -> Option { + fn from_bytes(&mut self, packet_bytes: Vec) -> Option { + if packet_bytes.len() < 1+1+1+16+1 { + return Some( + Error::new(format!("{} bytes is too small for a handshake packet to be valid", packet_bytes.len()).as_str()) + ); + } + + // protocol version + self.protocol_version = packet_bytes[0].to_be(); + + // encryption type + self.encryption_type = packet_bytes[1].to_be(); + + // key + let key_len: u128 = read_u128_slice_be(&packet_bytes[1..17]); + for i in 17..(17+key_len) as usize { + self.encryption_key[i-17] = packet_bytes[i].to_be(); + } + return None; } } \ No newline at end of file diff --git a/src/protocol/specs.rs b/src/protocol/specs.rs index 5a915b1..21f2673 100644 --- a/src/protocol/specs.rs +++ b/src/protocol/specs.rs @@ -13,36 +13,12 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. */ -/* ----PROTOCOL SPECs--- - -- Packet bytes representation - - -First 16 bytes - the total packet length -The next 1 byte - number that represents this packet's type (ID) where: -1: HANDSHAKE PACKET -2: CONNECTION SHUTDOWN PACKET -3: TEXT PACKET -4: FILEINFO PACKET -5: FILEDATA PACKET - -Then the internal structure varies from one packet type to the other, -but the content-types are encoded as follows: -u8 - just 1 BE byte -u128 - 16 BE bytes -String - u128 representing the string length and then UTF-8 encoded character bytes -[u8] - u128 as the length of a byte array, then the array itself -*/ - use crate::util::error::Error; use crate::protocol::packets::*; -use std::net; -use std::io::Write; -use std::io::Read; - // Protocol versioning -pub const PROTOCOL_LATEST_VERSION: u8 = 1; +pub const PROTOCOL_VERSION_1: u8 = 1; +pub const PROTOCOL_LATEST_VERSION: u8 = PROTOCOL_VERSION_1; // Packets' IDs pub const HANDSHAKE_PACKET_ID: u8 = 1; @@ -55,9 +31,18 @@ pub const FILEDATA_PACKET_ID: u8 = 5; pub const CHUNK_SIZE: usize = 262_144; // 256 KB // Encryption types +pub const ENCRYPTION_NO_ENCRYPTION: u8 = 0; pub const ECRYPTION_XOR: u8 = 1; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Debug)] +pub enum RunMode { + SEND, + RECEIVE, + SYNC, + DAEMON, +} + +#[derive(PartialEq, Eq, Debug)] pub enum PacketType { ConnectionShutdown, TextData, @@ -70,105 +55,4 @@ pub trait Packet { fn get_type(&self) -> PacketType; fn as_bytes(&self) -> Vec; fn from_bytes(&mut self, packet_bytes: Vec) -> Option; -} - - -pub fn send_packet(conn: &mut net::TcpStream, packet: &dyn Packet) -> Result<(), Error> { - let mut payload: Vec = Vec::::new(); - - let packet_bytes: Vec = packet.as_bytes(); - let packet_len: usize = packet_bytes.len(); - - payload.extend_from_slice(&u128::to_be_bytes(packet_len as u128)); - payload.extend_from_slice(&packet_bytes); - - match conn.write_all(&payload) { - Ok(()) => { - return Ok(()); - } - - Err(error) => { - return Err(Error::new(format!("could not write packet to the connection: {}", error).as_str())) - } - } -} - -pub fn read_next_packet(conn: &mut net::TcpStream) -> Result, Error> { - let mut u128_buf: [u8; 16] = [0; 16]; - - match conn.read_exact(&mut u128_buf) { - Ok(()) => {} - Err(error) => { - return Err(Error::new(format!("error while reading initial length bytes: {}", error).as_str())); - } - } - let packet_len: u128 = u128::from_be_bytes(u128_buf); - - let mut packet_bytes: Vec = vec!(0; packet_len as usize); - match conn.read_exact(&mut packet_bytes) { - Ok(()) => {} - Err(error) => { - return Err(Error::new(format!("error while reading packet contents: {}", error).as_str())); - } - } - - if packet_bytes.len() < packet_len as usize { - return Err( - Error::new( - format!("read {} packet bytes instead of specified {}", packet_bytes.len(), packet_len).as_str()) - ); - } - - let packet_type_byte: u8 = u8::from_be_bytes([packet_bytes[0]]); - match packet_type_byte { - CONNECTION_SHUTDOWN_PACKET_ID => { - return Ok(Box::new(ConnectionShutdownPacket{})); - } - - TEXT_PACKET_ID => { - let mut new_text_packet: TextPacket = TextPacket::empty(); - let result = new_text_packet.from_bytes(packet_bytes); - match result { - Some(error) => { - return Err(Error::new(format!("could not construct new text packet: {}", error.text).as_str())); - } - - None => { - return Ok(Box::new(new_text_packet)); - } - } - } - - FILEINFO_PACKET_ID => { - let mut new_fileinfo_packet: FileInfoPacket = FileInfoPacket::empty(); - let result = new_fileinfo_packet.from_bytes(packet_bytes); - match result { - Some(error) => { - return Err(Error::new(format!("could not construct new fileinfo packet: {}", error.text).as_str())); - } - - None => { - return Ok(Box::new(new_fileinfo_packet)); - } - } - } - - FILEDATA_PACKET_ID => { - let mut new_filedata_packet: FileDataPacket = FileDataPacket::empty(); - let result = new_filedata_packet.from_bytes(packet_bytes); - match result { - Some(error) => { - return Err(Error::new(format!("could not construct new filedata packet: {}", error.text).as_str())); - } - - None => { - return Ok(Box::new(new_filedata_packet)); - } - } - } - - _ => { - return Err(Error::new(format!("invalid packet type ID \"{}\"", packet_type_byte).as_str())); - } - } } \ No newline at end of file diff --git a/src/util/buf_read.rs b/src/util/buf_read.rs index cab31f4..3253c4a 100644 --- a/src/util/buf_read.rs +++ b/src/util/buf_read.rs @@ -13,15 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. */ -pub fn read_u128(buf: Vec) -> u128 { - let mut arr: [u8; 16] = [0; 16]; - for i in 0..16 { - arr[i] = buf[i]; - } - return u128::from_be_bytes(arr); -} - -pub fn read_u128_slice(buf_16: &[u8]) -> u128 { +pub fn read_u128_slice_be(buf_16: &[u8]) -> u128 { let mut arr: [u8; 16] = [0; 16]; for i in 0..16 { arr[i] = buf_16[i]; @@ -29,10 +21,6 @@ pub fn read_u128_slice(buf_16: &[u8]) -> u128 { return u128::from_be_bytes(arr); } -pub fn read_utf8_string(buf: Vec) -> String { - return String::from_utf8_lossy(&buf).into_owned(); -} - pub fn read_utf8_string_slice(buf: &[u8]) -> String { return String::from_utf8_lossy(&buf).into_owned(); } \ No newline at end of file