From afddb1f32c0599eeb98a63a4b5fdc0904966beb8 Mon Sep 17 00:00:00 2001 From: Gitea Date: Sun, 17 Jul 2022 20:27:40 +0300 Subject: [PATCH] Multi-folder; chunked file foundation --- src/args/mod.rs | 1 + src/{args.rs => args/parser.rs} | 2 +- src/fsys/file.rs | 69 ++++++ src/fsys/mod.rs | 1 + src/main.rs | 21 +- src/protocol.rs | 391 -------------------------------- src/protocol/constants.rs | 22 ++ src/protocol/mod.rs | 3 + src/protocol/packets.rs | 269 ++++++++++++++++++++++ src/protocol/specs.rs | 157 +++++++++++++ src/util/buf_read.rs | 38 ++++ src/{ => util}/error.rs | 0 src/util/mod.rs | 2 + 13 files changed, 573 insertions(+), 403 deletions(-) create mode 100644 src/args/mod.rs rename src/{args.rs => args/parser.rs} (99%) create mode 100644 src/fsys/file.rs create mode 100644 src/fsys/mod.rs delete mode 100644 src/protocol.rs create mode 100644 src/protocol/constants.rs create mode 100644 src/protocol/mod.rs create mode 100644 src/protocol/packets.rs create mode 100644 src/protocol/specs.rs create mode 100644 src/util/buf_read.rs rename src/{ => util}/error.rs (100%) create mode 100644 src/util/mod.rs diff --git a/src/args/mod.rs b/src/args/mod.rs new file mode 100644 index 0000000..b2819a7 --- /dev/null +++ b/src/args/mod.rs @@ -0,0 +1 @@ +pub mod parser; \ No newline at end of file diff --git a/src/args.rs b/src/args/parser.rs similarity index 99% rename from src/args.rs rename to src/args/parser.rs index dcb5801..9b95c85 100644 --- a/src/args.rs +++ b/src/args/parser.rs @@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. */ -use super::error::Error; +use crate::util::error::Error; use std::path; #[derive(PartialEq, Eq, Debug)] diff --git a/src/fsys/file.rs b/src/fsys/file.rs new file mode 100644 index 0000000..46cba39 --- /dev/null +++ b/src/fsys/file.rs @@ -0,0 +1,69 @@ +/* +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; + +pub struct ChunkedFile { + pub size: u128, + pub chunk_size: u128, + pub chunks_amount: u128, + pub path: std::path::PathBuf, +} + +impl ChunkedFile { + pub fn new(path: &std::path::Path, chunksize: u128) -> Result { + if !path.is_file() { + return Err( + Error::new(format!("\'{}\' does not exist or not a regular file", path.display()).as_str()) + ); + } + + let mut new_chunked_file: ChunkedFile = ChunkedFile{ + size: 0, + chunk_size: 0, + chunks_amount: 0, + path: std::path::Path::new("").to_path_buf(), + }; + + // path + new_chunked_file.path = path.to_path_buf(); + + // size + match path.metadata() { + Ok(metadata) => { + new_chunked_file.size = metadata.len() as u128; + } + Err(error) => { + return Err( + Error::new(format!("could not retrieve metadata for \'{}\': {}", path.display(), error).as_str()) + ); + } + } + + // chunks + new_chunked_file.chunk_size = chunksize; + new_chunked_file.chunks_amount = (new_chunked_file.size as f32 / chunksize as f32).ceil() as u128; + + return Ok(new_chunked_file); + } + + pub fn read_chunk(&self, buf: &[u8], chunk_no: u128) -> Option { + return None; + } + + pub fn write_chunk(&mut self, chunk: &[u8] ,chunk_no: u128) -> Option { + return None; + } +} \ No newline at end of file diff --git a/src/fsys/mod.rs b/src/fsys/mod.rs new file mode 100644 index 0000000..e0d1abe --- /dev/null +++ b/src/fsys/mod.rs @@ -0,0 +1 @@ +pub mod file; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 42cf1c1..508c51a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,15 +13,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. */ -mod args; -mod error; +mod util; mod protocol; +mod args; +mod fsys; -use args::{Args, RunMode}; +use args::parser::{Args, RunMode}; use std::net; -use protocol::Packet; -use protocol::TextPacket; -use protocol::PacketType; +use crate::protocol::specs::{Packet, PacketType}; const VERSION: &str = "v0.1.0"; const HELP_MESSAGE: &str = @@ -53,7 +52,7 @@ fn main() { let raw_args: Vec = std::env::args().collect(); let args: Args; - match args::parse(&raw_args) { + match args::parser::parse(&raw_args) { Ok(parsed_args) => { args = parsed_args; } @@ -93,11 +92,11 @@ fn main() { println!("connected to {}", args.address_str); - match protocol::read_next_packet(&mut conn) { + match protocol::specs::read_next_packet(&mut conn) { Ok(packet) => { match packet.get_type() { PacketType::TextData => { - let mut text_packet = protocol::TextPacket::empty(); + let mut text_packet = protocol::packets::TextPacket::empty(); text_packet.from_bytes(packet.as_bytes()); println!("{}", text_packet.text); } @@ -144,8 +143,8 @@ fn main() { println!("{} has connected", addr); // send text - let textpacket = TextPacket::new(&args.text_to_send); - match protocol::send_packet(&mut conn, &textpacket) { + let textpacket = protocol::packets::TextPacket::new(&args.text_to_send); + match protocol::specs::send_packet(&mut conn, &textpacket) { Ok(()) => {} Err(error) => { println!("{}", error.text); diff --git a/src/protocol.rs b/src/protocol.rs deleted file mode 100644 index 484a8fd..0000000 --- a/src/protocol.rs +++ /dev/null @@ -1,391 +0,0 @@ -/* -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 super::error::Error; -use std::net; -use std::io::Write; -use std::io::Read; - -/* ----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: CONNECTION SHUTDOWN PACKET -2: TEXT PACKET -3: FILEINFO PACKET -4: 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 -*/ - -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); -} - -fn read_u128_slice(buf_16: &[u8]) -> u128 { - let mut arr: [u8; 16] = [0; 16]; - for i in 0..16 { - arr[i] = buf_16[i]; - } - return u128::from_be_bytes(arr); -} - -fn read_utf8_string(buf: Vec) -> String { - return String::from_utf8_lossy(&buf).into_owned(); -} - -fn read_utf8_string_slice(buf: &[u8]) -> String { - return String::from_utf8_lossy(&buf).into_owned(); -} - - -const CONNECTION_SHUTDOWN_PACKET_ID: u8 = 1; -const TEXT_PACKET_ID: u8 = 2; -const FILEINFO_PACKET_ID: u8 = 3; -const FILEDATA_PACKET_ID: u8 = 4; - -const CHUNK_SIZE: usize = 262_144; // 256 KB - - -#[derive(PartialEq, Eq)] -pub enum PacketType { - ConnectionShutdown, - TextData, - FileInfo, - FileData, -} - -pub trait Packet { - fn get_type(&self) -> PacketType; - fn as_bytes(&self) -> Vec; - fn from_bytes(&mut self, packet_bytes: Vec) -> Option; -} - -pub struct ConnectionShutdownPacket {} - -impl Packet for ConnectionShutdownPacket { - fn get_type(&self) -> PacketType { - return PacketType::ConnectionShutdown; - } - - fn as_bytes(&self) -> Vec { - return vec!(CONNECTION_SHUTDOWN_PACKET_ID.to_be_bytes()[0]); - } - - fn from_bytes(&mut self, _packet_bytes: Vec) -> Option { - return None; - } -} - -pub struct TextPacket { - pub text: String, -} - -impl TextPacket { - pub fn empty() -> TextPacket { - return TextPacket{ - text: "".to_string(), - }; - } - - pub fn new(txt: &str) -> TextPacket { - return TextPacket { - text: txt.to_string(), - } - } -} - -impl Packet for TextPacket { - fn get_type(&self) -> PacketType { - return PacketType::TextData; - } - - fn as_bytes(&self) -> Vec { - let mut packet_as_bytes: Vec = Vec::::new(); - - packet_as_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()); - - return packet_as_bytes; - } - - fn from_bytes(&mut self, packet_bytes: Vec) -> Option { - if packet_bytes.len() < 18 { - return Some( - Error::new(format!("{} bytes is too small for a text packet to be valid", packet_bytes.len()).as_str() - )); - } - - // retrieve and check for packet type byte - match packet_bytes[0].to_be() { - TEXT_PACKET_ID => {} - _ => { - return Some(Error::new("packet type is not of a text packet")); - } - } - - // get text length - let text_length: u128 = read_u128_slice(&packet_bytes[1..17]); - - if text_length as usize > packet_bytes[17..].len() { - return Some( - Error::new( - format!( - "text length ({}) is bigger than provided packet bytes length ({})", - text_length, - packet_bytes[16..].len() - ).as_str() - ) - ); - } - - // extract text - self.text = read_utf8_string_slice(&packet_bytes[17..]); - - return None; - } -} - -pub struct FileinfoPacket { - pub file_id: u128, - pub filename: String, - pub filesize: u128, - pub relative_path: String, -} - -impl FileinfoPacket { - pub fn empty() -> FileinfoPacket { - return FileinfoPacket{ - file_id: 0, - filename: "".to_string(), - filesize: 0, - relative_path: "".to_string(), - }; - } -} - -impl Packet for FileinfoPacket { - fn get_type(&self) -> PacketType { - return PacketType::FileInfo; - } - - fn as_bytes(&self) -> Vec { - let mut packet_as_bytes: Vec = Vec::::new(); - - // file id - packet_as_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()); - - // filesize - packet_as_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()); - - return packet_as_bytes; - } - - fn from_bytes(&mut self, packet_bytes: Vec) -> Option { - // file id - self.file_id = read_u128_slice(&packet_bytes[0..16]); - - // get filename - let filename_length: u128 = read_u128_slice(&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]); - - // relative path - let rel_path_length: u128 = read_u128_slice( - &packet_bytes[(filename_length+32+16) as usize..(filename_length+32+16+16)as usize] - ); - self.relative_path = read_utf8_string_slice( - &packet_bytes[(filename_length+32+16+16) as usize..(filename_length+32+16+16+rel_path_length) as usize] - ); - - return None; - } -} - -pub struct FileDataPacket { - pub file_id: u128, - pub chunk_no: u128, - pub chunk: [u8; CHUNK_SIZE], -} - -impl FileDataPacket { - pub fn empty() -> FileDataPacket { - return FileDataPacket { - file_id: 0, - chunk_no: 0, - chunk: [0; CHUNK_SIZE], - } - } -} - -impl Packet for FileDataPacket { - fn get_type(&self) -> PacketType { - return PacketType::FileData; - } - - fn as_bytes(&self) -> Vec { - let packet_bytes: Vec = Vec::::new(); - return packet_bytes; - } - - fn from_bytes(&mut self, packet_bytes: Vec) -> Option { - return None; - } -} - - -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(()) => {} - // Ok(read_bytes) => { - // if read_bytes != 16 { - // return Err(Error::new(format!("read {} initial length bytes instead of 16", read_bytes).as_str())); - // } - // } - 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!(0; packet_len as usize); - // let mut packet_bytes: Vec = Vec::with_capacity(packet_len as usize); - match conn.read_exact(&mut packet_bytes) { - Ok(()) => {} - // Ok(read_bytes) => { - // if read_bytes != packet_len { - // return Err( - // Error::new( - // format!("read {} bytes of packet contents instead of specified {}", - // read_bytes, - // packet_len).as_str() - // ) - // ); - // } - // } - 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/protocol/constants.rs b/src/protocol/constants.rs new file mode 100644 index 0000000..9ba45ac --- /dev/null +++ b/src/protocol/constants.rs @@ -0,0 +1,22 @@ +/* +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. +*/ + + +pub const CONNECTION_SHUTDOWN_PACKET_ID: u8 = 1; +pub const TEXT_PACKET_ID: u8 = 2; +pub const FILEINFO_PACKET_ID: u8 = 3; +pub const FILEDATA_PACKET_ID: u8 = 4; + +pub const CHUNK_SIZE: usize = 262_144; // 256 KB \ No newline at end of file diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..3659e85 --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,3 @@ +pub mod packets; +pub mod constants; +pub mod specs; \ No newline at end of file diff --git a/src/protocol/packets.rs b/src/protocol/packets.rs new file mode 100644 index 0000000..95e1833 --- /dev/null +++ b/src/protocol/packets.rs @@ -0,0 +1,269 @@ +/* +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::util::buf_read::{read_u128_slice, read_utf8_string_slice}; +use crate::protocol::constants; +use crate::protocol::specs::*; +use crate::fsys::file::ChunkedFile; + + +pub struct TextPacket { + pub text: String, +} + +impl TextPacket { + pub fn empty() -> TextPacket { + return TextPacket{ + text: "".to_string(), + }; + } + + pub fn new(txt: &str) -> TextPacket { + return TextPacket { + text: txt.to_string(), + } + } +} + +impl Packet for TextPacket { + fn get_type(&self) -> PacketType { + return PacketType::TextData; + } + + fn as_bytes(&self) -> Vec { + let mut packet_as_bytes: Vec = Vec::::new(); + + packet_as_bytes.extend_from_slice(&constants::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()); + + return packet_as_bytes; + } + + fn from_bytes(&mut self, packet_bytes: Vec) -> Option { + if packet_bytes.len() < 18 { + return Some( + Error::new(format!("{} bytes is too small for a text packet to be valid", packet_bytes.len()).as_str() + )); + } + + // retrieve and check for packet type byte + match packet_bytes[0].to_be() { + constants::TEXT_PACKET_ID => {} + _ => { + return Some(Error::new("packet type is not of a text packet")); + } + } + + // get text length + let text_length: u128 = read_u128_slice(&packet_bytes[1..17]); + + if text_length as usize > packet_bytes[17..].len() { + return Some( + Error::new( + format!( + "text length ({}) is bigger than provided packet bytes length ({})", + text_length, + packet_bytes[16..].len() + ).as_str() + ) + ); + } + + // extract text + self.text = read_utf8_string_slice(&packet_bytes[17..]); + + return None; + } +} + + +pub struct FileInfoPacket { + pub file_id: u128, + pub filename: String, + pub filesize: u128, + pub relative_path: String, +} + +impl FileInfoPacket { + pub fn empty() -> FileInfoPacket { + return FileInfoPacket{ + file_id: 0, + filename: String::new(), + filesize: 0, + relative_path: String::new(), + }; + } + + pub fn new(file: ChunkedFile, file_id: u128, rel_path: String) -> Result { + let mut new_fileinfo_packet: FileInfoPacket = FileInfoPacket::empty(); + + // id + new_fileinfo_packet.file_id = file_id; + + //filename + match file.path.file_name() { + Some(filename) => { + match filename.to_owned().into_string() { + Ok(filename_string) => { + new_fileinfo_packet.filename = filename_string; + } + Err(filename_os_string) => { + return Err( + Error::new(format!("could not convert \'{:?}\' (OsString) to String", filename_os_string).as_str()) + ); + } + } + } + None => { + return Err( + Error::new(format!("could not get filename from \'{}\'", file.path.display()).as_str()) + ); + } + } + + // size + new_fileinfo_packet.filesize = file.size; + + // rel path + new_fileinfo_packet.relative_path = rel_path; + + return Ok(new_fileinfo_packet); + } +} + +impl Packet for FileInfoPacket { + fn get_type(&self) -> PacketType { + return PacketType::FileInfo; + } + + fn as_bytes(&self) -> Vec { + let mut packet_as_bytes: Vec = Vec::::new(); + + // file id + packet_as_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()); + + // filesize + packet_as_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()); + + return packet_as_bytes; + } + + fn from_bytes(&mut self, packet_bytes: Vec) -> Option { + // file id + self.file_id = read_u128_slice(&packet_bytes[0..16]); + + // get filename + let filename_length: u128 = read_u128_slice(&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]); + + // relative path + let rel_path_length: u128 = read_u128_slice( + &packet_bytes[(filename_length+32+16) as usize..(filename_length+32+16+16)as usize] + ); + self.relative_path = read_utf8_string_slice( + &packet_bytes[(filename_length+32+16+16) as usize..(filename_length+32+16+16+rel_path_length) as usize] + ); + + return None; + } +} + + +pub struct FileDataPacket { + pub file_id: u128, + pub chunk_no: u128, + pub chunk: [u8; constants::CHUNK_SIZE], +} + +impl FileDataPacket { + pub fn empty() -> FileDataPacket { + return FileDataPacket { + file_id: 0, + chunk_no: 0, + chunk: [0; constants::CHUNK_SIZE], + } + } +} + +impl Packet for FileDataPacket { + fn get_type(&self) -> PacketType { + return PacketType::FileData; + } + + fn as_bytes(&self) -> Vec { + let mut packet_bytes: Vec = Vec::::new(); + + // file id + packet_bytes.extend_from_slice(&self.file_id.to_be_bytes()); + + // chunk number + packet_bytes.extend_from_slice(&self.chunk_no.to_be_bytes()); + + // chunk + packet_bytes.extend_from_slice(&self.chunk.len().to_be_bytes()); + packet_bytes.extend_from_slice(&self.chunk); + + return packet_bytes; + } + + fn from_bytes(&mut self, packet_bytes: Vec) -> Option { + // file id + self.file_id = read_u128_slice(&packet_bytes[0..16]); + + // chunk number + self.chunk_no = read_u128_slice(&packet_bytes[16..32]); + + // chunk bytes + let chunk_length = read_u128_slice(&packet_bytes[32..48]); + for i in 48..(48+chunk_length) as usize { + self.chunk[i-48] = packet_bytes[i]; + } + + return None; + } +} + +pub struct ConnectionShutdownPacket {} + +impl Packet for ConnectionShutdownPacket { + fn get_type(&self) -> PacketType { + return PacketType::ConnectionShutdown; + } + + fn as_bytes(&self) -> Vec { + return vec!(constants::CONNECTION_SHUTDOWN_PACKET_ID.to_be_bytes()[0]); + } + + fn from_bytes(&mut self, _packet_bytes: Vec) -> Option { + return None; + } +} \ No newline at end of file diff --git a/src/protocol/specs.rs b/src/protocol/specs.rs new file mode 100644 index 0000000..d1b9dfe --- /dev/null +++ b/src/protocol/specs.rs @@ -0,0 +1,157 @@ +/* +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. +*/ + +/* +---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: CONNECTION SHUTDOWN PACKET +2: TEXT PACKET +3: FILEINFO PACKET +4: 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::constants; +use crate::protocol::packets::*; + +use std::net; +use std::io::Write; +use std::io::Read; + +#[derive(PartialEq, Eq)] +pub enum PacketType { + ConnectionShutdown, + TextData, + FileInfo, + FileData, +} + +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 { + constants::CONNECTION_SHUTDOWN_PACKET_ID => { + return Ok(Box::new(ConnectionShutdownPacket{})); + } + + constants::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)); + } + } + } + + constants::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)); + } + } + } + + constants::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 new file mode 100644 index 0000000..cab31f4 --- /dev/null +++ b/src/util/buf_read.rs @@ -0,0 +1,38 @@ +/* +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. +*/ + +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 { + let mut arr: [u8; 16] = [0; 16]; + for i in 0..16 { + arr[i] = buf_16[i]; + } + 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 diff --git a/src/error.rs b/src/util/error.rs similarity index 100% rename from src/error.rs rename to src/util/error.rs diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..e331771 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod buf_read; \ No newline at end of file