Skip to main content

zff/file/
file_encoder.rs

1// - STD
2use std::cell::RefCell;
3use std::io::{Cursor, Read};
4use std::path::PathBuf;
5use std::rc::Rc;
6use std::time::SystemTime;
7
8// - internal
9#[cfg(feature = "log")]
10use crate::hashes_to_log;
11use crate::prelude::*;
12use crate::{
13    EncodingState, EncodingThreadPoolManager, PreparedChunk, Signature, chunking,
14    io::{BufferedChunk, buffer_chunk},
15};
16
17// - external
18use ed25519_dalek::SigningKey;
19use time::OffsetDateTime;
20use zeroize::Zeroize;
21
22/// This enum contains the information, which are needed to encode the different file types.
23pub enum FileTypeEncodingInformation {
24    /// A regular file, which contains a Reader to the inner file content.
25    File(Box<dyn Read>),
26    /// A directory with the given children.
27    Directory(Vec<u64>), // directory children,
28    /// A symlink with the given real path.
29    Symlink(PathBuf), // symlink real path
30    /// A hardlink with the given twin filenumber.
31    Hardlink(u64), // hardlink filenumber
32    /// A special file with the given special file information.
33    SpecialFile(SpecialFileEncodingInformation), // special file information (rdev)
34}
35
36/// This enum contains the information, which are needed to encode the different special file types.
37#[derive(Debug, Clone, PartialEq, Eq, Copy)]
38pub enum SpecialFileEncodingInformation {
39    /// A fifo file with the given rdev-id.
40    Fifo(u64), // fifo(rdev),
41    /// A char file with the given rdev-id.
42    Char(u64), // char(rdev),
43    /// A block file with the given rdev-id.
44    Block(u64), // block(rdev),
45    /// A socket file with the given rdev-id.
46    Socket(u64), // socket(rdev),
47}
48
49/// Encodes a file in a zff container.
50///
51/// The FileEncoder handles encoding of file data including compression, encryption,
52/// and hash calculation.
53pub struct FileEncoder {
54    /// The appropriate [FileHeader].
55    file_header: FileHeader,
56    /// The appropriate [ObjectHeader].
57    object_header: ObjectHeader,
58    filetype_encoding_information: FileTypeEncodingInformation,
59    /// The optional signing key, to sign the hashes.
60    signing_key: Option<SigningKey>,
61    /// optional encryption information, to encrypt the data with the given key and algorithm
62    encryption_information: Option<EncryptionInformation>,
63    /// A reference counter to the encoding thread pool manager of the parent logical object encoder.
64    encoding_thread_pool_manager: Rc<RefCell<EncodingThreadPoolManager>>,
65    /// The first chunk number for this file.
66    initial_chunk_number: u64,
67    /// The current chunk number
68    current_chunk_number: u64,
69    /// The number of bytes, which were read from the underlying file.
70    read_bytes_underlying_data: u64,
71    acquisition_start: u64,
72    acquisition_end: u64,
73}
74
75impl Drop for FileEncoder {
76    fn drop(&mut self) {
77        self.encryption_information.zeroize();
78    }
79}
80
81impl FileEncoder {
82    /// creates a new [FileEncoder] with the given values.
83    pub fn new(
84        file_header: FileHeader,
85        object_header: ObjectHeader,
86        filetype_encoding_information: FileTypeEncodingInformation,
87        encoding_thread_pool_manager: Rc<RefCell<EncodingThreadPoolManager>>,
88        signing_key: Option<SigningKey>,
89        encryption_information: Option<EncryptionInformation>,
90        current_chunk_number: u64,
91    ) -> Result<FileEncoder> {
92        Ok(Self {
93            file_header,
94            object_header,
95            encoding_thread_pool_manager,
96            signing_key,
97            encryption_information,
98            initial_chunk_number: current_chunk_number,
99            current_chunk_number,
100            read_bytes_underlying_data: 0,
101            acquisition_start: 0,
102            acquisition_end: 0,
103            filetype_encoding_information,
104        })
105    }
106
107    /// Returns a reference of the appropriate file header
108    pub fn file_header_ref(&self) -> &FileHeader {
109        &self.file_header
110    }
111
112    pub(crate) fn set_initial_chunk_number(&mut self, chunk_number: u64) {
113        self.initial_chunk_number = chunk_number;
114        self.current_chunk_number = chunk_number;
115    }
116
117    /// returns the underlying encoded header
118    /// # Error
119    /// Returns an error if the file header is encrypted and the encryption fails.
120    pub fn get_encoded_header(&mut self) -> Result<Vec<u8>> {
121        if self.acquisition_start == 0 {
122            self.acquisition_start =
123                OffsetDateTime::from(SystemTime::now()).unix_timestamp() as u64;
124        }
125        if let Some(enc_info) = &self.encryption_information {
126            self.file_header.encrypt_directly(enc_info)
127        } else {
128            Ok(self.file_header.encode_directly())
129        }
130    }
131
132    /// returns the encoded chunk - this method will increment the self.current_chunk_number automatically.
133    pub(crate) fn get_next_chunk<D: ReadAt>(
134        &mut self,
135        deduplication_metadata: Option<&mut DeduplicationMetadata<D>>,
136    ) -> Result<EncodingState> {
137        let chunk_size = self.object_header.chunk_size as usize;
138        let mut eof = false;
139
140        let buffered_chunk = match &mut self.filetype_encoding_information {
141            FileTypeEncodingInformation::Directory(directory_children) => {
142                let encoded_directory_children = if directory_children.is_empty() {
143                    Vec::<u64>::new().encode_directly()
144                } else {
145                    directory_children.encode_directly()
146                };
147                let mut cursor = Cursor::new(&encoded_directory_children);
148                cursor.set_position(self.read_bytes_underlying_data);
149                let buffered_chunk = buffer_chunk(&mut cursor, chunk_size)?;
150                self.read_bytes_underlying_data += buffered_chunk.bytes_read;
151                buffered_chunk
152            }
153            FileTypeEncodingInformation::Symlink(symlink_real_path) => {
154                let encoded_symlink_real_path =
155                    PlatformString::from(symlink_real_path.as_os_str()).encode_directly();
156                let mut cursor = Cursor::new(&encoded_symlink_real_path);
157                cursor.set_position(self.read_bytes_underlying_data);
158                let buffered_chunk = buffer_chunk(&mut cursor, chunk_size)?;
159                self.read_bytes_underlying_data += buffered_chunk.bytes_read;
160                if buffered_chunk.bytes_read > 0 {
161                    buffered_chunk
162                } else {
163                    eof = true;
164                    BufferedChunk::default()
165                }
166            }
167            FileTypeEncodingInformation::Hardlink(hardlink_filenumber) => {
168                let encoded_hardlink_filenumber = hardlink_filenumber.encode_directly();
169                let mut cursor = Cursor::new(&encoded_hardlink_filenumber);
170                cursor.set_position(self.read_bytes_underlying_data);
171                let buffered_chunk = buffer_chunk(&mut cursor, chunk_size)?;
172                self.read_bytes_underlying_data += buffered_chunk.bytes_read;
173                if buffered_chunk.bytes_read > 0 {
174                    buffered_chunk
175                } else {
176                    eof = true;
177                    BufferedChunk::default()
178                }
179            }
180            FileTypeEncodingInformation::File(reader) => {
181                let buffered_chunk = buffer_chunk(reader, chunk_size)?;
182                self.read_bytes_underlying_data += buffered_chunk.bytes_read;
183                buffered_chunk
184            }
185            // contains the rdev-id and a flag for the type of the special file
186            // (0 if fifo-, 1 if char-, 2 if block-, and 3 if it is a socket-file).
187            FileTypeEncodingInformation::SpecialFile(specialfile_encoding_information) => {
188                let (rdev_id, type_flag) = match specialfile_encoding_information {
189                    SpecialFileEncodingInformation::Fifo(rdev_id) => (rdev_id, 0_u8),
190                    SpecialFileEncodingInformation::Char(rdev_id) => (rdev_id, 1),
191                    SpecialFileEncodingInformation::Block(rdev_id) => (rdev_id, 2),
192                    SpecialFileEncodingInformation::Socket(rdev_id) => (rdev_id, 3),
193                };
194                let mut encoded_data = rdev_id.encode_directly();
195                encoded_data.append(&mut type_flag.encode_directly());
196                let mut cursor = Cursor::new(&encoded_data);
197                cursor.set_position(self.read_bytes_underlying_data);
198                let buffered_chunk = buffer_chunk(&mut cursor, chunk_size)?;
199                self.read_bytes_underlying_data += buffered_chunk.bytes_read;
200                if buffered_chunk.bytes_read > 0 {
201                    buffered_chunk
202                } else {
203                    eof = true;
204                    BufferedChunk::default()
205                }
206            }
207        };
208
209        if buffered_chunk.buffer.is_empty() && self.read_bytes_underlying_data != 0 || eof {
210            //this case is the normal "file reader reached EOF".
211            return Ok(EncodingState::ReadEOF);
212        } else if buffered_chunk.buffer.is_empty() && self.read_bytes_underlying_data == 0 {
213            //this case is the "file is empty".
214            let chunk_header = ChunkHeader {
215                flags: ChunkFlags {
216                    empty_file: true,
217                    ..Default::default()
218                },
219                ..Default::default()
220            };
221            let prepared_chunk = PreparedChunk::new(Vec::new(), chunk_header, None, None);
222            return Ok(EncodingState::PreparedChunk(prepared_chunk));
223        };
224
225        // Needed for the same byte check
226        let buf_len = buffered_chunk.buffer.len() as u64;
227
228        let mut encoding_thread_pool_manager = self.encoding_thread_pool_manager.borrow_mut();
229
230        encoding_thread_pool_manager.update(buffered_chunk.buffer)?;
231
232        let encryption_algorithm = self
233            .encryption_information
234            .as_ref()
235            .map(|encryption_information| &encryption_information.algorithm);
236        let encryption_key = self
237            .encryption_information
238            .as_ref()
239            .map(|encryption_information| &encryption_information.encryption_key);
240
241        let chunk = chunking(
242            &mut encoding_thread_pool_manager,
243            self.current_chunk_number,
244            buf_len,
245            chunk_size as u64,
246            deduplication_metadata,
247            encryption_key,
248            encryption_algorithm,
249        )?;
250
251        self.current_chunk_number += 1;
252        Ok(EncodingState::PreparedChunk(chunk))
253    }
254
255    /// returns the appropriate encoded [FileFooter].
256    /// A call of this method finalizes the underlying hashers. You should be care.
257    pub fn get_encoded_footer(&mut self) -> Result<Vec<u8>> {
258        let mut encoding_thread_pool_manager = self.encoding_thread_pool_manager.borrow_mut();
259
260        self.acquisition_end = OffsetDateTime::from(SystemTime::now()).unix_timestamp() as u64;
261        let mut hash_values = Vec::new();
262        for (hash_type, hash) in encoding_thread_pool_manager.finalize_all_hashing_threads()? {
263            let mut hash_value = HashValue::new_empty(hash_type.clone());
264            hash_value.set_hash(hash.to_vec());
265            if let Some(signing_key) = &self.signing_key {
266                let signature = Signature::sign(signing_key, &hash);
267                hash_value.set_ed25519_signature(signature);
268            }
269            hash_values.push(hash_value);
270        }
271
272        #[cfg(feature = "log")]
273        hashes_to_log(
274            self.object_header.object_number,
275            Some(self.file_header.file_number),
276            &hash_values,
277        );
278
279        let hash_header = HashHeader::new(hash_values);
280        let footer = FileFooter::new(
281            self.file_header.file_number,
282            self.acquisition_start,
283            self.acquisition_end,
284            hash_header,
285            self.initial_chunk_number,
286            self.current_chunk_number - self.initial_chunk_number,
287            self.read_bytes_underlying_data,
288        );
289        if let Some(enc_info) = &self.encryption_information {
290            footer.encrypt_directly(enc_info)
291        } else {
292            Ok(footer.encode_directly())
293        }
294    }
295}