tftpd/
window.rs

1use std::{
2    collections::VecDeque,
3    error::Error,
4    fs::File,
5    io::{Read, Write},
6};
7
8/// Window `struct` is used to store chunks of data from a file. It is
9/// used to help store the data that is being sent or received for the
10/// [RFC 7440](https://www.rfc-editor.org/rfc/rfc7440) Windowsize option.
11///
12/// # Example
13/// ```rust
14/// use std::{fs::{self, OpenOptions, File}, io::Write};
15/// use tftpd::Window;
16///
17/// let mut file = File::create("test.txt").unwrap();
18/// file.write_all(b"Hello, world!").unwrap();
19/// file.flush().unwrap();
20///
21/// let file = File::open("test.txt").unwrap();
22/// let mut window = Window::new(5, 512, file);
23/// window.fill().unwrap();
24/// fs::remove_file("test.txt").unwrap();
25/// ```
26pub struct Window {
27    elements: VecDeque<Vec<u8>>,
28    size: u16,
29    chunk_size: u16,
30    file: File,
31}
32
33impl Window {
34    /// Creates a new `Window` with the supplied size and chunk size.
35    pub fn new(size: u16, chunk_size: u16, file: File) -> Window {
36        Window {
37            elements: VecDeque::new(),
38            size,
39            chunk_size,
40            file,
41        }
42    }
43
44    /// Fills the `Window` with chunks of data from the file.
45    /// Returns `true` if the `Window` is full.
46    pub fn fill(&mut self) -> Result<bool, Box<dyn Error>> {
47        for _ in self.len()..self.size {
48            let mut chunk = vec![0; self.chunk_size as usize];
49            let size = self.file.read(&mut chunk)?;
50
51            if size != self.chunk_size as usize {
52                chunk.truncate(size);
53                self.elements.push_back(chunk);
54                return Ok(false);
55            }
56
57            self.elements.push_back(chunk);
58        }
59
60        Ok(true)
61    }
62
63    /// Empties the `Window` by writing the data to the file.
64    pub fn empty(&mut self) -> Result<(), Box<dyn Error>> {
65        for data in &self.elements {
66            self.file.write_all(data)?;
67        }
68
69        self.elements.clear();
70
71        Ok(())
72    }
73
74    /// Removes the first `amount` of elements from the `Window`.
75    pub fn remove(&mut self, amount: u16) -> Result<(), &'static str> {
76        if amount > self.len() {
77            return Err("amount cannot be larger than length of window");
78        }
79
80        drop(self.elements.drain(0..amount as usize));
81
82        Ok(())
83    }
84
85    /// Adds a data `Vec<u8>` to the `Window`.
86    pub fn add(&mut self, data: Vec<u8>) -> Result<(), &'static str> {
87        if self.len() == self.size {
88            return Err("cannot add to a full window");
89        }
90
91        self.elements.push_back(data);
92
93        Ok(())
94    }
95
96    /// Returns a reference to the `VecDeque` containing the elements.
97    pub fn get_elements(&self) -> &VecDeque<Vec<u8>> {
98        &self.elements
99    }
100
101    /// Returns the length of the `Window`.
102    pub fn len(&self) -> u16 {
103        self.elements.len() as u16
104    }
105
106    /// Returns `true` if the `Window` is empty.
107    pub fn is_empty(&self) -> bool {
108        self.elements.is_empty()
109    }
110
111    /// Returns `true` if the `Window` is full.
112    pub fn is_full(&self) -> bool {
113        self.elements.len() as u16 == self.size
114    }
115
116    /// Returns the length of the file
117    pub fn file_len(&self) -> Result<u64, Box<dyn Error>> {
118        Ok(self.file.metadata()?.len())
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use std::{
126        fs::{self, OpenOptions},
127        io::Write,
128    };
129
130    const DIR_NAME: &str = "target/test";
131
132    #[test]
133    fn fills_and_removes_from_window() {
134        const FILENAME: &str = "fills_and_removes_from_window.txt";
135
136        let mut file = initialize(FILENAME);
137        file.write_all(b"Hello, world!").unwrap();
138        file.flush().unwrap();
139        drop(file);
140
141        file = open(FILENAME);
142
143        let mut window = Window::new(2, 5, file);
144        window.fill().unwrap();
145        assert_eq!(window.elements.len(), 2);
146        assert_eq!(window.elements[0], b"Hello"[..]);
147        assert_eq!(window.elements[1], b", wor"[..]);
148
149        window.remove(1).unwrap();
150        assert_eq!(window.elements.len(), 1);
151        assert_eq!(window.elements[0], b", wor"[..]);
152
153        window.fill().unwrap();
154        assert_eq!(window.elements.len(), 2);
155        assert_eq!(window.elements[0], b", wor"[..]);
156        assert_eq!(window.elements[1], b"ld!"[..]);
157
158        clean(FILENAME);
159    }
160
161    #[test]
162    fn adds_to_and_empties_window() {
163        const FILENAME: &str = "adds_to_and_empties_window.txt";
164
165        let file = initialize(FILENAME);
166
167        let mut window = Window::new(3, 5, file);
168        window.add(b"Hello".to_vec()).unwrap();
169        assert_eq!(window.elements.len(), 1);
170        assert_eq!(window.elements[0], b"Hello"[..]);
171
172        window.add(b", wor".to_vec()).unwrap();
173        assert_eq!(window.elements.len(), 2);
174        assert_eq!(window.elements[0], b"Hello"[..]);
175        assert_eq!(window.elements[1], b", wor"[..]);
176
177        window.add(b"ld!".to_vec()).unwrap();
178        assert_eq!(window.elements.len(), 3);
179        assert_eq!(window.elements[0], b"Hello"[..]);
180        assert_eq!(window.elements[1], b", wor"[..]);
181        assert_eq!(window.elements[2], b"ld!"[..]);
182
183        window.empty().unwrap();
184        assert_eq!(window.elements.len(), 0);
185
186        let mut contents = Default::default();
187        File::read_to_string(
188            &mut File::open(DIR_NAME.to_string() + "/" + FILENAME).unwrap(),
189            &mut contents,
190        )
191        .unwrap();
192        assert_eq!(contents, "Hello, world!");
193
194        clean(FILENAME);
195    }
196
197    fn initialize(filename: &str) -> File {
198        let filename = DIR_NAME.to_string() + "/" + filename;
199
200        let _ = fs::create_dir_all(DIR_NAME);
201
202        if File::open(&filename).is_ok() {
203            fs::remove_file(&filename).unwrap();
204        }
205
206        OpenOptions::new()
207            .read(true)
208            .append(true)
209            .create(true)
210            .open(&filename)
211            .unwrap()
212    }
213
214    fn open(filename: &str) -> File {
215        let filename = DIR_NAME.to_string() + "/" + filename;
216
217        OpenOptions::new()
218            .read(true)
219            .append(true)
220            .create(true)
221            .open(filename)
222            .unwrap()
223    }
224
225    fn clean(filename: &str) {
226        let filename = DIR_NAME.to_string() + "/" + filename;
227        fs::remove_file(filename).unwrap();
228        if fs::remove_dir(DIR_NAME).is_err() {
229            // ignore removing directory, as other tests are
230            // still running
231        }
232    }
233}