rs-tftpd/src/window.rs
2023-05-12 07:59:53 +00:00

215 lines
5.9 KiB
Rust

use std::{
collections::VecDeque,
error::Error,
fs::File,
io::{Read, Write},
};
/// Window `struct` is used to store chunks of data from a file. It is
/// used to help store the data that is being sent or received for the
/// [RFC 7440](https://www.rfc-editor.org/rfc/rfc7440) Windowsize option.
///
/// # Example
/// ```rust
/// use std::{fs::{self, OpenOptions, File}, io::Write};
/// use tftpd::Window;
///
/// let mut file = File::create("test.txt").unwrap();
/// file.write_all(b"Hello, world!").unwrap();
/// file.flush().unwrap();
///
/// let file = File::open("test.txt").unwrap();
/// let mut window = Window::new(5, 512, file);
/// window.fill().unwrap();
/// fs::remove_file("test.txt").unwrap();
/// ```
pub struct Window {
elements: VecDeque<Vec<u8>>,
size: u16,
chunk_size: usize,
file: File,
}
impl Window {
/// Creates a new `Window` with the supplied size and chunk size.
pub fn new(size: u16, chunk_size: usize, file: File) -> Window {
Window {
elements: VecDeque::new(),
size,
chunk_size,
file,
}
}
/// Fills the `Window` with chunks of data from the file.
/// Returns `true` if the `Window` is full.
pub fn fill(&mut self) -> Result<bool, Box<dyn Error>> {
for _ in self.len()..self.size {
let mut chunk = vec![0; self.chunk_size];
let size = self.file.read(&mut chunk)?;
if size != self.chunk_size {
chunk.truncate(size);
self.elements.push_back(chunk);
return Ok(false);
}
self.elements.push_back(chunk);
}
Ok(true)
}
/// Empties the `Window` by writing the data to the file.
pub fn empty(&mut self) -> Result<(), Box<dyn Error>> {
for data in &self.elements {
self.file.write_all(data)?;
}
self.elements.clear();
Ok(())
}
/// Removes the first `amount` of elements from the `Window`.
pub fn remove(&mut self, amount: u16) -> Result<(), &'static str> {
if amount > self.len() {
return Err("amount cannot be larger than length of window");
}
drop(self.elements.drain(0..amount as usize));
Ok(())
}
/// Adds a data `Vec<u8>` to the `Window`.
pub fn add(&mut self, data: Vec<u8>) -> Result<(), &'static str> {
if self.len() == self.size {
return Err("cannot add to a full window");
}
self.elements.push_back(data);
Ok(())
}
/// Returns a reference to the `VecDeque` containing the elements.
pub fn get_elements(&self) -> &VecDeque<Vec<u8>> {
&self.elements
}
/// Returns the length of the `Window`.
pub fn len(&self) -> u16 {
self.elements.len() as u16
}
/// Returns `true` if the `Window` is empty.
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
/// Returns `true` if the `Window` is full.
pub fn is_full(&self) -> bool {
self.elements.len() as u16 == self.size
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
fs::{self, OpenOptions},
io::{Seek, Write},
path::Path,
};
const DIR_NAME: &str = "tmp";
#[test]
fn fills_and_removes_from_window() {
const FILE_NAME: &str = "fills_and_removes_from_window.txt";
let mut file = initialize(FILE_NAME);
file.write_all(b"Hello, world!").unwrap();
file.flush().unwrap();
file.rewind().unwrap();
let mut window = Window::new(2, 5, file);
window.fill().unwrap();
assert_eq!(window.elements.len(), 2);
assert_eq!(window.elements[0], b"Hello"[..]);
assert_eq!(window.elements[1], b", wor"[..]);
window.remove(1).unwrap();
assert_eq!(window.elements.len(), 1);
assert_eq!(window.elements[0], b", wor"[..]);
window.fill().unwrap();
assert_eq!(window.elements.len(), 2);
assert_eq!(window.elements[0], b", wor"[..]);
assert_eq!(window.elements[1], b"ld!"[..]);
clean(FILE_NAME);
}
#[test]
fn adds_to_and_empties_window() {
const FILE_NAME: &str = "adds_to_and_empties_window.txt";
let file = initialize(FILE_NAME);
let mut window = Window::new(3, 5, file);
window.add(b"Hello".to_vec()).unwrap();
assert_eq!(window.elements.len(), 1);
assert_eq!(window.elements[0], b"Hello"[..]);
window.add(b", wor".to_vec()).unwrap();
assert_eq!(window.elements.len(), 2);
assert_eq!(window.elements[0], b"Hello"[..]);
assert_eq!(window.elements[1], b", wor"[..]);
window.add(b"ld!".to_vec()).unwrap();
assert_eq!(window.elements.len(), 3);
assert_eq!(window.elements[0], b"Hello"[..]);
assert_eq!(window.elements[1], b", wor"[..]);
assert_eq!(window.elements[2], b"ld!"[..]);
window.empty().unwrap();
assert_eq!(window.elements.len(), 0);
let mut contents = Default::default();
File::read_to_string(
&mut File::open(DIR_NAME.to_string() + "/" + FILE_NAME).unwrap(),
&mut contents,
)
.unwrap();
assert_eq!(contents, "Hello, world!");
clean(FILE_NAME);
}
fn initialize(file_name: &str) -> File {
let file_name = DIR_NAME.to_string() + "/" + file_name;
if !Path::new(DIR_NAME).is_dir() {
fs::create_dir(DIR_NAME).unwrap();
}
if File::open(&file_name).is_ok() {
fs::remove_file(&file_name).unwrap();
}
OpenOptions::new()
.read(true)
.append(true)
.create(true)
.open(&file_name)
.unwrap()
}
#[allow(unused_must_use)]
fn clean(file_name: &str) {
let file_name = DIR_NAME.to_string() + "/" + file_name;
fs::remove_file(file_name).unwrap();
fs::remove_dir(DIR_NAME);
}
}