Gitea
2 years ago
13 changed files with 573 additions and 403 deletions
@ -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,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())); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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
|
@ -0,0 +1,3 @@ |
|||||||
|
pub mod packets; |
||||||
|
pub mod constants; |
||||||
|
pub mod specs; |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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())); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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(); |
||||||
|
} |
Loading…
Reference in new issue