Browse Source

Multi-folder; chunked file foundation

master
Gitea 2 years ago
parent
commit
afddb1f32c
  1. 1
      src/args/mod.rs
  2. 2
      src/args/parser.rs
  3. 69
      src/fsys/file.rs
  4. 1
      src/fsys/mod.rs
  5. 21
      src/main.rs
  6. 391
      src/protocol.rs
  7. 22
      src/protocol/constants.rs
  8. 3
      src/protocol/mod.rs
  9. 269
      src/protocol/packets.rs
  10. 157
      src/protocol/specs.rs
  11. 38
      src/util/buf_read.rs
  12. 0
      src/util/error.rs
  13. 2
      src/util/mod.rs

1
src/args/mod.rs

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

2
src/args.rs → 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. GNU Affero General Public License for more details.
*/ */
use super::error::Error; use crate::util::error::Error;
use std::path; use std::path;
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]

69
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<ChunkedFile, Error> {
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<Error> {
return None;
}
pub fn write_chunk(&mut self, chunk: &[u8] ,chunk_no: u128) -> Option<Error> {
return None;
}
}

1
src/fsys/mod.rs

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

21
src/main.rs

@ -13,15 +13,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
*/ */
mod args; mod util;
mod error;
mod protocol; mod protocol;
mod args;
mod fsys;
use args::{Args, RunMode}; use args::parser::{Args, RunMode};
use std::net; use std::net;
use protocol::Packet; use crate::protocol::specs::{Packet, PacketType};
use protocol::TextPacket;
use protocol::PacketType;
const VERSION: &str = "v0.1.0"; const VERSION: &str = "v0.1.0";
const HELP_MESSAGE: &str = const HELP_MESSAGE: &str =
@ -53,7 +52,7 @@ fn main() {
let raw_args: Vec<String> = std::env::args().collect(); let raw_args: Vec<String> = std::env::args().collect();
let args: Args; let args: Args;
match args::parse(&raw_args) { match args::parser::parse(&raw_args) {
Ok(parsed_args) => { Ok(parsed_args) => {
args = parsed_args; args = parsed_args;
} }
@ -93,11 +92,11 @@ fn main() {
println!("connected to {}", args.address_str); println!("connected to {}", args.address_str);
match protocol::read_next_packet(&mut conn) { match protocol::specs::read_next_packet(&mut conn) {
Ok(packet) => { Ok(packet) => {
match packet.get_type() { match packet.get_type() {
PacketType::TextData => { PacketType::TextData => {
let mut text_packet = protocol::TextPacket::empty(); let mut text_packet = protocol::packets::TextPacket::empty();
text_packet.from_bytes(packet.as_bytes()); text_packet.from_bytes(packet.as_bytes());
println!("{}", text_packet.text); println!("{}", text_packet.text);
} }
@ -144,8 +143,8 @@ fn main() {
println!("{} has connected", addr); println!("{} has connected", addr);
// send text // send text
let textpacket = TextPacket::new(&args.text_to_send); let textpacket = protocol::packets::TextPacket::new(&args.text_to_send);
match protocol::send_packet(&mut conn, &textpacket) { match protocol::specs::send_packet(&mut conn, &textpacket) {
Ok(()) => {} Ok(()) => {}
Err(error) => { Err(error) => {
println!("{}", error.text); println!("{}", error.text);

391
src/protocol.rs

@ -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<u8>) -> 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<u8>) -> 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<u8>;
fn from_bytes(&mut self, packet_bytes: Vec<u8>) -> Option<Error>;
}
pub struct ConnectionShutdownPacket {}
impl Packet for ConnectionShutdownPacket {
fn get_type(&self) -> PacketType {
return PacketType::ConnectionShutdown;
}
fn as_bytes(&self) -> Vec<u8> {
return vec!(CONNECTION_SHUTDOWN_PACKET_ID.to_be_bytes()[0]);
}
fn from_bytes(&mut self, _packet_bytes: Vec<u8>) -> Option<Error> {
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<u8> {
let mut packet_as_bytes: Vec<u8> = Vec::<u8>::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<u8>) -> Option<Error> {
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<u8> {
let mut packet_as_bytes: Vec<u8> = Vec::<u8>::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<u8>) -> Option<Error> {
// 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<u8> {
let packet_bytes: Vec<u8> = Vec::<u8>::new();
return packet_bytes;
}
fn from_bytes(&mut self, packet_bytes: Vec<u8>) -> Option<Error> {
return None;
}
}
pub fn send_packet(conn: &mut net::TcpStream, packet: &dyn Packet) -> Result<(), Error> {
let mut payload: Vec<u8> = Vec::<u8>::new();
let packet_bytes: Vec<u8> = 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<Box<dyn Packet>, 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<u8> = 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()));
}
}
}

22
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

3
src/protocol/mod.rs

@ -0,0 +1,3 @@
pub mod packets;
pub mod constants;
pub mod specs;

269
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<u8> {
let mut packet_as_bytes: Vec<u8> = Vec::<u8>::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<u8>) -> Option<Error> {
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<FileInfoPacket, Error> {
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<u8> {
let mut packet_as_bytes: Vec<u8> = Vec::<u8>::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<u8>) -> Option<Error> {
// 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<u8> {
let mut packet_bytes: Vec<u8> = Vec::<u8>::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<u8>) -> Option<Error> {
// 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<u8> {
return vec!(constants::CONNECTION_SHUTDOWN_PACKET_ID.to_be_bytes()[0]);
}
fn from_bytes(&mut self, _packet_bytes: Vec<u8>) -> Option<Error> {
return None;
}
}

157
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<u8>;
fn from_bytes(&mut self, packet_bytes: Vec<u8>) -> Option<Error>;
}
pub fn send_packet(conn: &mut net::TcpStream, packet: &dyn Packet) -> Result<(), Error> {
let mut payload: Vec<u8> = Vec::<u8>::new();
let packet_bytes: Vec<u8> = 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<Box<dyn Packet>, 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<u8> = 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()));
}
}
}

38
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<u8>) -> 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<u8>) -> 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();
}

0
src/error.rs → src/util/error.rs

2
src/util/mod.rs

@ -0,0 +1,2 @@
pub mod error;
pub mod buf_read;
Loading…
Cancel
Save