fofc_rs/
lib.rs

1use std::error::Error;
2use std::io::{Cursor, Read, Write};
3use std::time::{SystemTime, UNIX_EPOCH};
4use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
5
6#[derive(Debug)]
7pub struct Container {
8    pub comment: String,
9    pub x: u64,
10    pub y: u64,
11    pub z: u64,
12    pub files: Vec<File>
13}
14
15#[derive(Clone, Debug)]
16pub struct File {
17    pub name: String,
18    pub content: Vec<u8>
19}
20
21pub const Y_DIFFERENCE: u64 = 43;
22pub const Z_DIFFERENCE: u64 = 34;
23pub const MAGIC_NUMBER: u8 = 0x46;
24
25fn read_string_until_0x00(cursor: &mut Cursor<&[u8]>) -> Result<String, Box<dyn Error>> {
26    let mut buffer: Vec<u8> = Vec::new();
27
28    loop {
29        let mut byte = [0; 1];
30        cursor.read_exact(&mut byte)?;
31        if byte[0] == 0x00 {
32            break;
33        }
34
35        buffer.push(byte[0])
36    }
37
38    let string = String::from_utf8_lossy(&buffer).into_owned();
39    Ok(string)
40}
41
42impl Container {
43    pub fn new(comment: &str) -> Result<Container, Box<dyn Error>> {
44        let x = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
45
46        return Ok(Container {
47            comment: comment.to_string(),
48            x,
49            y: x + Y_DIFFERENCE,
50            z: x + Z_DIFFERENCE,
51            files: vec![]
52        })
53    }
54
55    pub fn from_bytes(bytes: &[u8]) -> Result<Container, Box<dyn Error>> {
56        let mut cursor = Cursor::new(bytes);
57
58        if cursor.read_u8()? != MAGIC_NUMBER {
59            return Err(Box::from("invalid or incorrect magic number"));
60        }
61
62        let comment = read_string_until_0x00(&mut cursor)?;
63        let x = cursor.read_u64::<LittleEndian>()?;
64        let y = x + Y_DIFFERENCE;
65        let z  = x + Z_DIFFERENCE;
66        let file_count = cursor.read_u16::<LittleEndian>()?;
67
68        let mut files: Vec<File> = Vec::new();
69
70        for _ in 1..=file_count {
71            let name = read_string_until_0x00(&mut cursor)?;
72            let length = cursor.read_u64::<LittleEndian>()?;
73            let mut content = vec![0; length as usize];
74            cursor.read_exact(&mut content)?;
75            files.push(File {
76                name,
77                content
78            })
79        }
80
81
82        Ok(Container {x, y, z, comment, files})
83    }
84
85    pub fn add_file(&mut self, file: File) {
86        self.files.push(file)
87    }
88
89    pub fn remove_file(&mut self, name: String) {
90        self.files = self.files.iter().cloned().filter(|f| f.name != name).collect()
91    }
92
93    pub fn get_file(&self, name: String) -> Option<&File> {
94        self.files.iter().find(|f| f.name == name)
95    }
96
97    pub fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn Error>> {
98        let mut bytes: Vec<u8> = Vec::new();
99        bytes.push(MAGIC_NUMBER);
100        bytes.write(self.comment.as_bytes())?;
101        bytes.push(0x00);
102        bytes.write_u64::<LittleEndian>(self.x)?;
103        bytes.write_u16::<LittleEndian>(self.files.len() as u16)?;
104
105        for f in self.files.iter() {
106            bytes.write(f.name.as_bytes())?;
107            bytes.push(0x00);
108            bytes.write_u64::<LittleEndian>(f.content.len() as u64)?;
109            bytes.write_all(f.content.as_slice())?;
110        }
111
112        Ok(bytes)
113    }
114}
115
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn create_container_has_correct_values() {
123        let mut container = Container::new("Example").unwrap();
124        let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
125        assert_eq!(container.x, current_time);
126        assert_eq!(container.y, current_time + Y_DIFFERENCE);
127        assert_eq!(container.z, current_time + Z_DIFFERENCE);
128        assert_eq!(container.files.len(), 0); // files are empty
129
130        // ensure you can add files
131        let file_name = "C:\\farting.png".to_string();
132        let file = File {
133            name: file_name.clone(),
134            content: vec![0x00, 0xF2]
135        };
136        container.add_file(file);
137        assert_eq!(container.files.len(), 1);
138
139        container.remove_file(file_name);
140        assert_eq!(container.files.len(), 0);
141    }
142
143    #[test]
144    fn read_write() {
145        let mut container = Container::new("The Best In The World").unwrap();
146
147        let file_name = "C:\\hello.png".to_string();
148        let file_content: [u8; 4] = [0x66, 0x66, 0x66, 0x66];
149        let file = File {name: file_name, content: file_content.to_vec()};
150        container.add_file(file);
151        let file2 = File {name: "better file name!!!!".to_string(), content: [0x23, 0x54, 0xFF].to_vec()};
152        container.add_file(file2);
153
154        let as_bytes = container.to_bytes().unwrap();
155
156        let new_container = Container::from_bytes(as_bytes.as_slice()).unwrap();
157
158        println!("{:?}", new_container.files);
159    }
160}