Skip to main content

mla/
lib.rs

1//! Multi Layer Archive (MLA)
2//!
3//! MLA is an archive file format with the following features:
4//!
5//! * Support for traditional and post-quantum encryption hybridization with asymmetric keys (HPKE with AES256-GCM and a KEM based on an hybridization of X25519 and post-quantum ML-KEM 1024)
6//! * Support for traditional and post-quantum signature hybridization
7//! * Support for compression (based on [`rust-brotli`](https://github.com/dropbox/rust-brotli/))
8//! * Streamable archive creation:
9//!   * An archive can be built even over a data-diode
10//!   * An entry can be added through chunks of data, without initially knowing the final size
11//!   * Entry chunks can be interleaved (one can add the beginning of an entry, start a second one, and then continue adding the first entry's parts)
12//! * Architecture agnostic and portable to some extent (written entirely in Rust)
13//! * Archive reading is seekable, even if compressed or encrypted. An entry can be accessed in the middle of the archive without reading from the beginning
14//! * If truncated, archives can be `clean-truncated` in order to recover its content to some extent. Two modes are available:
15//!   * Authenticated recover (default): only authenticated (as in AEAD, there is no signature verification) encrypted chunks of data are retrieved
16//!   * Unauthenticated recover: authenticated and unauthenticated encrypted chunks of data are retrieved. Use at your own risk.
17//! * Arguably less prone to bugs, especially while parsing an untrusted archive (Rust safety)
18//!
19//! Security
20//! =
21//!
22//! For security-related information, please refer to our [README security section](https://github.com/ANSSI-FR/MLA#security).
23//!
24//! Repository
25//! =
26//!
27//! The MLA repository contains:
28//!
29//! * `mla`: the Rust library implementing MLA reader and writer
30//! * `mlar`: a Rust cli utility wrapping `mla` for common actions (create, list, extract...)
31//! * `doc` : advanced documentation related to MLA (e.g. format specification)
32//! * `bindings` : bindings for other languages
33//! * `samples` : test assets
34//! * `mla-fuzz-afl` : a Rust utility to fuzz `mla`
35//! * `.github`: Continuous Integration needs
36//!
37//! Quick API usage
38//! =
39//!
40//! * Create an archive, with compression, encryption and signature:
41//! ```rust
42//! use mla::ArchiveWriter;
43//! use mla::config::ArchiveWriterConfig;
44//! use mla::crypto::mlakey::{MLAPrivateKey, MLAPublicKey};
45//! use mla::entry::EntryName;
46//! // for encryption
47//! const RECEIVER_PUB_KEY: &[u8] =
48//!     include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapub");
49//! // for signing
50//! const SENDER_PRIV_KEY: &[u8] =
51//!     include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapriv");
52//!
53//! fn main() {
54//!     // For encryption, load the needed receiver public key
55//!     let (pub_enc_key, _pub_sig_verif_key) = MLAPublicKey::deserialize_public_key(RECEIVER_PUB_KEY)
56//!         .unwrap()
57//!         .get_public_keys();
58//!     // For signing, load the needed sender private key
59//!     let (_priv_dec_key, priv_sig_key) = MLAPrivateKey::deserialize_private_key(SENDER_PRIV_KEY)
60//!         .unwrap()
61//!         .get_private_keys();
62//!     // In production, you may want to zeroize the real `SENDER_PRIV_KEY` or
63//!     // associated temporary values of its `Read` implementation here.
64//!     // Create an MLA Archive - Output only needs the Write trait.
65//!     // Here, a Vec is used but it would tipically be a `File` or a network socket.
66//!     let mut buf = Vec::new();
67//!     // The use of multiple keys is supported
68//!     let config =
69//!         ArchiveWriterConfig::with_encryption_with_signature(&[pub_enc_key], &[priv_sig_key])
70//!             .unwrap();
71//!     // Create the Writer
72//!     let mut mla = ArchiveWriter::from_config(&mut buf, config).unwrap();
73//!     // Add a file
74//!     // This creates an entry named "a/filename" (without first "/"), See `EntryName::from_path`
75//!     mla.add_entry(
76//!         EntryName::from_path("/a/filename").unwrap(),
77//!         4,
78//!         &[0, 1, 2, 3][..],
79//!     )
80//!     .unwrap();
81//!     // Complete the archive
82//!     mla.finalize().unwrap();
83//! }
84//! ```
85//! * Add entries part per part, in a "concurrent" fashion:
86//! ```rust
87//! use mla::ArchiveWriter;
88//! use mla::config::ArchiveWriterConfig;
89//! use mla::crypto::mlakey::{MLAPrivateKey, MLAPublicKey};
90//! use mla::entry::EntryName;
91//!
92//! // for encryption
93//! const RECEIVER_PUB_KEY: &[u8] =
94//!     include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapub");
95//! // for signing
96//! const SENDER_PRIV_KEY: &[u8] =
97//!     include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapriv");
98//!
99//! fn main() {
100//!     // For encryption, load the needed receiver public key
101//!     let (pub_enc_key, _pub_sig_verif_key) = MLAPublicKey::deserialize_public_key(RECEIVER_PUB_KEY)
102//!         .unwrap()
103//!         .get_public_keys();
104//!     // For signing, load the needed sender private key
105//!     let (_priv_dec_key, priv_sig_key) = MLAPrivateKey::deserialize_private_key(SENDER_PRIV_KEY)
106//!         .unwrap()
107//!         .get_private_keys();
108//!     // In production, you may want to zeroize the real `SENDER_PRIV_KEY` or
109//!     // associated temporary values of its `Read` implementation here.
110//!
111//!     // Create an MLA Archive - Output only needs the Write trait
112//!     let mut buf = Vec::new();
113//!     let config =
114//!         ArchiveWriterConfig::with_encryption_with_signature(&[pub_enc_key], &[priv_sig_key])
115//!             .unwrap();
116//!
117//!     // Create the Writer
118//!     let mut mla = ArchiveWriter::from_config(&mut buf, config).unwrap();
119//!
120//!     // An entry is tracked by an id, and follows this API's call order:
121//!     // 1. id = start_entry(entry_name);
122//!     // 2. append_entry_content(id, content length, content (impl Read))
123//!     // 2-bis. repeat 2.
124//!     // 3. end_entry(id)
125//!
126//!     // Start an entry and add content
127//!     let id_entry1 = mla
128//!         .start_entry(EntryName::from_path("name1").unwrap())
129//!         .unwrap();
130//!     let entry1_part1 = vec![11, 12, 13, 14];
131//!     mla.append_entry_content(
132//!         id_entry1,
133//!         entry1_part1.len() as u64,
134//!         entry1_part1.as_slice(),
135//!     )
136//!     .unwrap();
137//!
138//!     // Start a second entry and add content
139//!     let id_entry2 = mla
140//!         .start_entry(EntryName::from_path("name2").unwrap())
141//!         .unwrap();
142//!     let entry2_part1 = vec![21, 22, 23, 24];
143//!     mla.append_entry_content(
144//!         id_entry2,
145//!         entry2_part1.len() as u64,
146//!         entry2_part1.as_slice(),
147//!     )
148//!     .unwrap();
149//!
150//!     // Add an entry as a whole
151//!     let entry3 = vec![31, 32, 33, 34];
152//!     mla.add_entry(
153//!         EntryName::from_path("name3").unwrap(),
154//!         entry3.len() as u64,
155//!         entry3.as_slice(),
156//!     )
157//!     .unwrap();
158//!
159//!     // Add new content to the first entry
160//!     let entry1_part2 = vec![15, 16, 17, 18];
161//!     mla.append_entry_content(
162//!         id_entry1,
163//!         entry1_part2.len() as u64,
164//!         entry1_part2.as_slice(),
165//!     )
166//!     .unwrap();
167//!
168//!     // Mark still opened entries as finished
169//!     mla.end_entry(id_entry1).unwrap();
170//!     mla.end_entry(id_entry2).unwrap();
171//!
172//!     // Complete the archive
173//!     mla.finalize().unwrap();
174//! }
175//! ```
176//! * Read entries from an archive
177//! ```rust
178//! use mla::ArchiveReader;
179//! use mla::config::ArchiveReaderConfig;
180//! use mla::crypto::mlakey::{MLAPrivateKey, MLAPublicKey};
181//! use mla::entry::EntryName;
182//! use std::io;
183//!
184//! const DATA: &[u8] = include_bytes!("../../samples/archive_v2.mla");
185//! // for decryption
186//! const RECEIVER_PRIV_KEY: &[u8] =
187//!     include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapriv");
188//! // for signature verification
189//! const SENDER_PUB_KEY: &[u8] = include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapub");
190//!
191//! fn main() {
192//!     // For decryption, load the needed receiver private key
193//!     let (priv_dec_key, _priv_sig_key) = MLAPrivateKey::deserialize_private_key(RECEIVER_PRIV_KEY)
194//!         .unwrap()
195//!         .get_private_keys();
196//!     // For signature verification, load the needed sender public key
197//!     let (_pub_enc_key, pub_sig_verif_key) = MLAPublicKey::deserialize_public_key(SENDER_PUB_KEY)
198//!         .unwrap()
199//!         .get_public_keys();
200//!
201//!     // Specify the key for the Reader
202//!     let config = ArchiveReaderConfig::with_signature_verification(&[pub_sig_verif_key])
203//!         .with_encryption(&[priv_dec_key]);
204//!
205//!     // Read from buf, which needs Read + Seek
206//!     let buf = io::Cursor::new(DATA);
207//!     let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
208//!
209//!     // Get a file
210//!     let mut entry = mla_read
211//!         .get_entry(EntryName::from_path("simple").unwrap()) // or EntryName::from_arbitrary_bytes if name is not representing a file path
212//!         .unwrap() // An error can be raised (I/O, decryption, etc.)
213//!         .unwrap(); // Option(entry), as the entry might not exist in the archive
214//!
215//!     // Get back its name, size, and data
216//!     // display name interpreted as file path and escape to avoid
217//!     // issues with terminal escape sequences for example
218//!     println!(
219//!         "{} ({} bytes)",
220//!         entry.name.to_pathbuf_escaped_string().unwrap(),
221//!         entry.get_size()
222//!     );
223//!     let mut output = Vec::new();
224//!     std::io::copy(&mut entry.data, &mut output).unwrap();
225//!
226//!     // Get back the list of entries names in the archive without
227//!     // interpreting them as file paths, so no need to unwrap as
228//!     // it cannot fail. ASCII slash is encoded too.
229//!     for entry_name in mla_read.list_entries().unwrap() {
230//!         println!("{}", entry_name.raw_content_to_escaped_string());
231//!     }
232//! }
233//! ```
234
235use std::collections::HashMap;
236use std::convert::TryFrom;
237use std::io;
238use std::io::{Read, Seek, SeekFrom, Write};
239#[macro_use]
240extern crate bitflags;
241use crypto::hybrid::MLAEncryptionPublicKey;
242use layers::compress::COMPRESSION_LAYER_MAGIC;
243use layers::encrypt::ENCRYPTION_LAYER_MAGIC;
244use layers::strip_head_tail::StripHeadTailReader;
245use layers::traits::InnerReaderTrait;
246
247pub mod entry;
248use entry::{
249    ArchiveEntry, ArchiveEntryDataReader, ArchiveEntryId, EntryName, deserialize_entry_name,
250    serialize_entry_name,
251};
252
253mod base64;
254
255pub(crate) mod layers;
256use crate::crypto::mlakey::{MLASignatureVerificationPublicKey, MLASigningPrivateKey};
257use crate::layers::compress::{
258    CompressionLayerReader, CompressionLayerTruncatedReader, CompressionLayerWriter,
259};
260use crate::layers::encrypt::{
261    EncryptionLayerReader, EncryptionLayerTruncatedReader, EncryptionLayerWriter,
262};
263use crate::layers::position::PositionLayerWriter;
264use crate::layers::raw::{RawLayerReader, RawLayerTruncatedReader, RawLayerWriter};
265use crate::layers::signature::{
266    SIGNATURE_LAYER_MAGIC, SignatureLayerReader, SignatureLayerTruncatedReader,
267    SignatureLayerWriter,
268};
269use crate::layers::traits::{
270    InnerWriterTrait, InnerWriterType, LayerReader, LayerTruncatedReader, LayerWriter,
271};
272pub mod errors;
273use crate::errors::{Error, TruncatedReadError};
274
275pub mod config;
276use crate::config::{ArchiveReaderConfig, ArchiveWriterConfig, TruncatedReaderConfig};
277
278pub mod crypto;
279use crate::crypto::hash::{HashWrapperReader, HashWrapperWriter, Sha256Hash};
280use sha2::{Digest, Sha256, Sha512};
281
282mod format;
283pub mod helpers;
284use format::ArchiveHeader;
285
286#[cfg(test)]
287#[macro_use]
288extern crate hex_literal;
289
290// -------- Constants --------
291
292const MLA_MAGIC: &[u8; 8] = b"MLAFAAAA";
293const MLA_FORMAT_VERSION: u32 = 2;
294const END_MLA_MAGIC: &[u8; 8] = b"EMLAAAAA";
295/// Maximum number of UTF-8 bytes allowed in an entry name (not characters).
296/// Set to 1024 bytes to stay safely within common `PATH_MAX` limits on Linux,
297/// FreeBSD, OpenBSD, and NetBSD. Windows supports longer paths via special prefixes,
298/// but for maximum portability, we adopt this conservative limit.
299const ENTRY_NAME_MAX_SIZE: u64 = 1024;
300
301const ENTRIES_LAYER_MAGIC: &[u8; 8] = b"MLAENAAA";
302
303const EMPTY_OPTS_SERIALIZATION: &[u8; 1] = &[0];
304const EMPTY_TAIL_OPTS_SERIALIZATION: &[u8; 9] = &[0, 1, 0, 0, 0, 0, 0, 0, 0];
305
306#[derive(Debug)]
307struct Opts;
308
309impl Opts {
310    fn from_reader(mut src: impl Read) -> Result<Self, Error> {
311        let discriminant = u8::deserialize(&mut src)?;
312        match discriminant {
313            0 => (),
314            1 => {
315                let mut n = [0; 8];
316                src.read_exact(&mut n)?;
317                let n = u64::from_le_bytes(n);
318                let mut v = Vec::new();
319                src.take(n).read_to_end(&mut v)?;
320                // no action implemented for the moment, hence no further use
321            }
322            _ => return Err(Error::DeserializationError),
323        }
324        Ok(Opts)
325    }
326
327    #[allow(clippy::unused_self)]
328    fn dump(&mut self, mut src: impl Write) -> Result<u64, Error> {
329        // No option for the moment
330        src.write_all(EMPTY_OPTS_SERIALIZATION)?;
331        Ok(1)
332    }
333}
334
335// -------- MLA Format Footer --------
336
337struct ArchiveFooter {
338    /// `EntryName` -> Corresponding `EntryInfo`
339    entries_info: HashMap<EntryName, EntryInfo>,
340}
341
342impl ArchiveFooter {
343    /// Footer:
344    /// ```ascii-art
345    /// [entries_info][entries_info length]
346    /// ```
347    /// Performs zero-copy serialization of a footer
348    fn serialize_into<W: Write>(
349        mut dest: W,
350        entries_info: &HashMap<EntryName, ArchiveEntryId>,
351        ids_info: &HashMap<ArchiveEntryId, EntryInfo>,
352    ) -> Result<(), Error> {
353        // Combine `entries_info` and `ids_info` to ArchiveFooter.entries_info,
354        // avoiding copies (only references)
355        let mut tmp = Vec::new();
356        for (k, i) in entries_info {
357            let v = ids_info.get(i).ok_or_else(|| {
358                Error::WrongWriterState(
359                    "[ArchiveFooter seriliaze] Unable to find the ID".to_string(),
360                )
361            })?;
362            tmp.push((k, v));
363        }
364        tmp.sort_by_key(|(k, _)| *k);
365
366        tmp.len().serialize(&mut dest)?;
367        let mut footer_serialization_length: u64 = 8;
368        for (k, i) in tmp {
369            footer_serialization_length = footer_serialization_length
370                .checked_add(serialize_entry_name(k, &mut dest)?)
371                .ok_or(Error::SerializationError)?;
372            footer_serialization_length = footer_serialization_length
373                .checked_add(i.serialize(&mut dest)?)
374                .ok_or(Error::SerializationError)?;
375        }
376        footer_serialization_length
377            .checked_add(1)
378            .ok_or(Error::SerializationError)?
379            .serialize(&mut dest)?; // +1 for tag indicating index presence
380        Ok(())
381    }
382
383    /// Parses and instantiates a footer from serialized data
384    pub fn deserialize_from<R: Read + Seek>(mut src: R) -> Result<Option<ArchiveFooter>, Error> {
385        let index_present = u8::deserialize(&mut src)?;
386        if index_present == 1 {
387            // Read entries_info
388            let n = u64::deserialize(&mut src)?;
389            let entries_info = (0..n)
390                .map(|_| {
391                    let name = deserialize_entry_name(&mut src)?;
392                    let info = EntryInfo::deserialize(&mut src)?;
393                    Ok::<_, Error>((name, info))
394                })
395                .collect::<Result<HashMap<_, _>, Error>>()?;
396
397            Ok(Some(ArchiveFooter { entries_info }))
398        } else {
399            Ok(None)
400        }
401    }
402}
403
404// -------- Writer --------
405
406/// Tags used in each `ArchiveEntryBlock` to indicate the type of block that follows
407#[derive(Debug)]
408enum ArchiveEntryBlockType {
409    EntryStart,
410    EntryContent,
411
412    EndOfArchiveData,
413    EndOfEntry,
414}
415
416impl<W: Write> MLASerialize<W> for ArchiveEntryBlockType {
417    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
418        let byte: u8 = match self {
419            ArchiveEntryBlockType::EntryStart => 0,
420            ArchiveEntryBlockType::EntryContent => 1,
421            ArchiveEntryBlockType::EndOfArchiveData => 0xFE,
422            ArchiveEntryBlockType::EndOfEntry => 0xFF,
423        };
424        byte.serialize(dest)
425    }
426}
427
428impl<R: Read> MLADeserialize<R> for ArchiveEntryBlockType {
429    fn deserialize(src: &mut R) -> Result<Self, Error> {
430        let serialized_block_type = u8::deserialize(src)?;
431        match serialized_block_type {
432            0 => Ok(ArchiveEntryBlockType::EntryStart),
433            1 => Ok(ArchiveEntryBlockType::EntryContent),
434            0xFE => Ok(ArchiveEntryBlockType::EndOfArchiveData),
435            0xFF => Ok(ArchiveEntryBlockType::EndOfEntry),
436            _ => Err(Error::WrongBlockSubFileType),
437        }
438    }
439}
440
441use format::ArchiveEntryBlock;
442
443#[derive(Debug, Clone)]
444enum ArchiveWriterState {
445    /// Initialized, with files opened
446    OpenedFiles {
447        ids: Vec<ArchiveEntryId>,
448        hashes: HashMap<ArchiveEntryId, Sha256>,
449    },
450    /// File finalized, no more change allowed
451    Finalized,
452}
453
454impl ArchiveWriterState {
455    /// Wrap a `impl Read` with hash updating, corresponding to the file identified by `id`
456    fn wrap_with_hash<R: Read>(
457        &mut self,
458        id: ArchiveEntryId,
459        src: R,
460    ) -> Result<HashWrapperReader<'_, R>, Error> {
461        let hash = match self {
462            ArchiveWriterState::OpenedFiles { hashes, .. } => match hashes.get_mut(&id) {
463                Some(hash) => hash,
464                None => {
465                    return Err(Error::WrongWriterState(
466                        "[wrap_with_hash] Unable to find the ID".to_string(),
467                    ));
468                }
469            },
470            ArchiveWriterState::Finalized => {
471                return Err(Error::WrongWriterState(
472                    "[wrap_with_hash] Wrong state".to_string(),
473                ));
474            }
475        };
476
477        Ok(HashWrapperReader::new(src, hash))
478    }
479}
480
481/// Used to check whether the current state is the one expected
482/// ```text
483/// check_state!(self.state, ArchiveWriterState::XXX)
484/// ```
485macro_rules! check_state {
486    ( $x:expr, $y:ident ) => {{
487        match $x {
488            ArchiveWriterState::$y { .. } => (),
489            _ => {
490                return Err(Error::WrongArchiveWriterState {
491                    current_state: format!("{:?}", $x).to_string(),
492                    expected_state: format! {"{}", "ArchiveWriterState::$y"}.to_string(),
493                });
494            }
495        }
496    }};
497}
498
499/// Used to check whether the current state is `OpenedFiles`, with the expected file opened
500/// ```text
501/// check_state_file_opened!(self.state, file_id)
502/// ```
503macro_rules! check_state_file_opened {
504    ( $x:expr, $y:expr ) => {{
505        match $x {
506            ArchiveWriterState::OpenedFiles { ids, hashes } => {
507                if !ids.contains($y) || !hashes.contains_key($y) {
508                    return Err(Error::WrongArchiveWriterState {
509                        current_state: format!("{:?}", $x).to_string(),
510                        expected_state: "ArchiveWriterState with id $y".to_string(),
511                    });
512                }
513            }
514            _ => {
515                return Err(Error::WrongArchiveWriterState {
516                    current_state: format!("{:?}", $x).to_string(),
517                    expected_state: "ArchiveWriterState with id $y".to_string(),
518                });
519            }
520        }
521    }};
522}
523
524/// Use this to write an archive
525///
526/// See crate root documentation for example usage.
527///
528/// Don't forget to call `ArchiveWriter::finalize`
529pub struct ArchiveWriter<'a, W: 'a + InnerWriterTrait> {
530    /// MLA Archive format writer
531    ///
532    ///
533    /// Internals part:
534    ///
535    /// Destination: use a Box to be able to dynamically changes layers
536    dest: Box<PositionLayerWriter<'a, W>>,
537    /// Internal state
538    state: ArchiveWriterState,
539    /// Entry name -> Corresponding `ArchiveEntryID`
540    ///
541    /// This is done to keep a quick check for entry name existence
542    entries_info: HashMap<EntryName, ArchiveEntryId>,
543    /// ID -> Corresponding `EntryInfo`
544    ///
545    /// File chunks identify their relative file using the `ArchiveEntryID`.
546    /// `entries_info` and `ids_info` could have been merged into a single `HashMap`
547    /// String -> `EntryInfo`, at the cost of an additional `HashMap` `ArchiveEntryID` ->
548    /// String, thus increasing memory footprint.
549    /// These hashmaps are actually merged at the last moment, on footer
550    /// serialization
551    ids_info: HashMap<ArchiveEntryId, EntryInfo>,
552    /// Next file id to use
553    next_id: ArchiveEntryId,
554    /// Current file being written (for continuous block detection)
555    current_id: ArchiveEntryId,
556}
557
558// This is an unstable feature for now (`Vec.remove_item`), use a function
559// instead to keep stable compatibility
560fn vec_remove_item<T: std::cmp::PartialEq>(vec: &mut Vec<T>, item: &T) -> Option<T> {
561    let pos = vec.iter().position(|x| *x == *item)?;
562    Some(vec.remove(pos))
563}
564
565impl<W: InnerWriterTrait> ArchiveWriter<'_, W> {
566    /// Create an `ArchiveWriter` from config.
567    pub fn from_config(dest: W, config: ArchiveWriterConfig) -> Result<Self, Error> {
568        let dest: InnerWriterType<W> = Box::new(RawLayerWriter::new(dest));
569
570        let archive_header = ArchiveHeader {
571            format_version_number: MLA_FORMAT_VERSION,
572        };
573        let mut archive_header_hash = Sha512::new();
574        let mut dest = HashWrapperWriter::new(dest, &mut archive_header_hash);
575        archive_header.serialize(&mut dest)?;
576        let mut dest = dest.into_inner();
577
578        // Enable layers depending on user option
579        dest = match config.signature {
580            Some(signature_config) => Box::new(SignatureLayerWriter::new(
581                dest,
582                signature_config,
583                archive_header_hash,
584            )?),
585            None => dest,
586        };
587        dest = match config.encryption {
588            Some(encryption_config) => {
589                Box::new(EncryptionLayerWriter::new(dest, &encryption_config)?)
590            }
591            None => dest,
592        };
593        dest = match config.compression {
594            Some(cfg) => Box::new(CompressionLayerWriter::new(dest, &cfg)?),
595            None => dest,
596        };
597
598        // Upper layer must be a PositionLayer
599        let mut final_dest = Box::new(PositionLayerWriter::new(dest));
600        final_dest.reset_position();
601
602        // Write the magic
603        final_dest.write_all(ENTRIES_LAYER_MAGIC)?;
604        let _ = Opts.dump(&mut final_dest)?;
605
606        // Build initial archive
607        Ok(ArchiveWriter {
608            dest: final_dest,
609            state: ArchiveWriterState::OpenedFiles {
610                ids: Vec::new(),
611                hashes: HashMap::new(),
612            },
613            entries_info: HashMap::new(),
614            ids_info: HashMap::new(),
615            next_id: ArchiveEntryId(0),
616            current_id: ArchiveEntryId(0),
617        })
618    }
619
620    /// Create an `ArchiveWriter` with a default config (encryption, signature and compression with default level).
621    ///
622    /// Do not mix up keys. If `A` sends an archive to `B`,
623    /// `encryption_public_keys` must contain
624    /// `B`'s encryption public key and
625    /// `signing_private_keys` must contain `A`'s signature private key.
626    ///
627    /// Returns `ConfigError::EncryptionKeyIsMissing` if `encryption_public_keys` is empty.
628    /// Returns `ConfigError::PrivateKeyNotSet` if `signing_private_keys` is empty.
629    pub fn new(
630        dest: W,
631        encryption_public_keys: &[MLAEncryptionPublicKey],
632        signing_private_keys: &[MLASigningPrivateKey],
633    ) -> Result<Self, Error> {
634        let config = ArchiveWriterConfig::with_encryption_with_signature(
635            encryption_public_keys,
636            signing_private_keys,
637        )?;
638        Self::from_config(dest, config)
639    }
640
641    /// Finalize an archive (appends footer, finalize compression, truncation protection, etc.).
642    ///
643    /// Must be done to use `ArchiveReader` then.
644    pub fn finalize(mut self) -> Result<W, Error> {
645        // Check final state (empty ids, empty hashes)
646        check_state!(self.state, OpenedFiles);
647        match &mut self.state {
648            ArchiveWriterState::OpenedFiles { ids, hashes } => {
649                if !ids.is_empty() || !hashes.is_empty() {
650                    return Err(Error::WrongWriterState(
651                        "[Finalize] At least one file is still open".to_string(),
652                    ));
653                }
654            }
655            ArchiveWriterState::Finalized => {
656                // Never happens, due to `check_state!`
657                return Err(Error::WrongWriterState(
658                    "[Finalize] State have changes inside finalize".to_string(),
659                ));
660            }
661        }
662        self.state = ArchiveWriterState::Finalized;
663
664        // Mark the end of the data
665
666        // Use std::io::Empty as a readable placeholder type
667        ArchiveEntryBlock::EndOfArchiveData::<std::io::Empty> {}.dump(&mut self.dest)?;
668
669        self.dest.write_all(&[1])?; // We always keep an index for the moment
670        ArchiveFooter::serialize_into(&mut self.dest, &self.entries_info, &self.ids_info)?;
671
672        self.dest.write_all(EMPTY_TAIL_OPTS_SERIALIZATION)?; // No option for the moment
673
674        // Recursive call
675        let mut final_dest = self.dest.finalize()?;
676        final_dest.write_all(EMPTY_TAIL_OPTS_SERIALIZATION)?; // No option for the moment
677        final_dest.write_all(END_MLA_MAGIC)?;
678        Ok(final_dest)
679    }
680
681    /// Add the current offset to the corresponding list if the file id is not
682    /// the current one, ie. if blocks are not continuous
683    fn record_offset_and_size_in_index(
684        &mut self,
685        id: ArchiveEntryId,
686        size: u64,
687    ) -> Result<(), Error> {
688        let offset = self.dest.position();
689        match self.ids_info.get_mut(&id) {
690            Some(file_info) => file_info.offsets_and_sizes.push((offset, size)),
691            None => {
692                return Err(Error::WrongWriterState(
693                    "[mark_continuous_block] Unable to find the ID".to_string(),
694                ));
695            }
696        }
697        self.current_id = id;
698        Ok(())
699    }
700
701    /// Start a new entry in archive without giving content for the moment.
702    ///
703    /// Returns an Id that must be kept to be able to append data to this entry.
704    ///
705    /// See `ArchiveWriter::append_entry_content` and `ArchiveWriter::end_entry`.
706    pub fn start_entry(&mut self, name: EntryName) -> Result<ArchiveEntryId, Error> {
707        check_state!(self.state, OpenedFiles);
708
709        if self.entries_info.contains_key(&name) {
710            return Err(Error::DuplicateEntryName);
711        }
712
713        // Create ID for this file
714        let id = self.next_id;
715        self.next_id = ArchiveEntryId(
716            self.next_id
717                .0
718                .checked_add(1)
719                .ok_or(Error::SerializationError)?,
720        );
721        self.current_id = id;
722        self.entries_info.insert(name.clone(), id);
723
724        // Save the current position
725        self.ids_info.insert(
726            id,
727            EntryInfo {
728                offsets_and_sizes: vec![(self.dest.position(), 0)],
729            },
730        );
731        // Use std::io::Empty as a readable placeholder type
732        ArchiveEntryBlock::EntryStart::<std::io::Empty> {
733            name,
734            id,
735            opts: Opts,
736        }
737        .dump(&mut self.dest)?;
738
739        match &mut self.state {
740            ArchiveWriterState::OpenedFiles { ids, hashes } => {
741                ids.push(id);
742                hashes.insert(id, Sha256::default());
743            }
744            ArchiveWriterState::Finalized => {
745                // Never happens, due to `check_state!`
746                return Err(Error::WrongWriterState(
747                    "[StartFile] State have changes inside start_file".to_string(),
748                ));
749            }
750        }
751        Ok(id)
752    }
753
754    /// Appends data to an entry started with `ArchiveWriter::start_entry`.
755    ///
756    /// Can be called multiple times to append data to the same entry.
757    /// Can be interleaved with other calls writing data for other entries.
758    pub fn append_entry_content<U: Read>(
759        &mut self,
760        id: ArchiveEntryId,
761        size: u64,
762        src: U,
763    ) -> Result<(), Error> {
764        check_state_file_opened!(&self.state, &id);
765
766        if size == 0 {
767            // Avoid creating 0-sized block
768            return Ok(());
769        }
770
771        self.record_offset_and_size_in_index(id, size)?;
772        let src = self.state.wrap_with_hash(id, src)?;
773
774        ArchiveEntryBlock::EntryContent {
775            id,
776            length: size,
777            data: Some(src),
778            opts: Opts,
779        }
780        .dump(&mut self.dest)
781    }
782
783    /// Mark an entry as terminated and record its Sha256 hash.
784    pub fn end_entry(&mut self, id: ArchiveEntryId) -> Result<(), Error> {
785        check_state_file_opened!(&self.state, &id);
786
787        let hash = match &mut self.state {
788            ArchiveWriterState::OpenedFiles { ids, hashes } => {
789                let hash = hashes.remove(&id).ok_or_else(|| {
790                    Error::WrongWriterState("[EndFile] Unable to retrieve the hash".to_string())
791                })?;
792                vec_remove_item(ids, &id);
793                hash.finalize().into()
794            }
795            ArchiveWriterState::Finalized => {
796                // Never happens, due to `check_state_file_opened!`
797                return Err(Error::WrongWriterState(
798                    "[EndFile] State have changes inside end_file".to_string(),
799                ));
800            }
801        };
802
803        self.record_offset_and_size_in_index(id, 0)?;
804        // Use std::io::Empty as a readable placeholder type
805        ArchiveEntryBlock::EndOfEntry::<std::io::Empty> {
806            id,
807            hash,
808            opts: Opts,
809        }
810        .dump(&mut self.dest)?;
811
812        Ok(())
813    }
814
815    /// Helper calling `start_entry`, `append_entry_content` and `end_entry` one after the other.
816    pub fn add_entry<U: Read>(&mut self, name: EntryName, size: u64, src: U) -> Result<(), Error> {
817        let id = self.start_entry(name)?;
818        self.append_entry_content(id, size, src)?;
819        self.end_entry(id)
820    }
821
822    /// Flushes data to the destination `Writer` `W`.
823    /// Calls flush on the destination too.
824    pub fn flush(&mut self) -> io::Result<()> {
825        self.dest.flush()
826    }
827}
828
829trait MLASerialize<W: Write> {
830    fn serialize(&self, dest: &mut W) -> Result<u64, Error>;
831}
832
833trait MLADeserialize<R: Read> {
834    fn deserialize(src: &mut R) -> Result<Self, Error>
835    where
836        Self: std::marker::Sized;
837}
838
839impl<W: Write> MLASerialize<W> for u8 {
840    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
841        dest.write_all(&[*self])?;
842        Ok(1)
843    }
844}
845
846impl<W: Write> MLASerialize<W> for u64 {
847    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
848        dest.write_all(&self.to_le_bytes())?;
849        Ok(8)
850    }
851}
852
853impl<R: Read> MLADeserialize<R> for u64 {
854    fn deserialize(src: &mut R) -> Result<Self, Error> {
855        let mut n = [0; 8];
856        src.read_exact(&mut n)
857            .map_err(|_| Error::DeserializationError)?;
858        Ok(u64::from_le_bytes(n))
859    }
860}
861
862impl<W: Write> MLASerialize<W> for usize {
863    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
864        let u64self = u64::try_from(*self).map_err(|_| Error::SerializationError)?;
865        dest.write_all(&u64self.to_le_bytes())?;
866        Ok(8)
867    }
868}
869
870impl<W: Write> MLASerialize<W> for u32 {
871    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
872        dest.write_all(&self.to_le_bytes())?;
873        Ok(4)
874    }
875}
876
877impl<R: Read> MLADeserialize<R> for u32 {
878    fn deserialize(src: &mut R) -> Result<Self, Error> {
879        let mut n = [0; 4];
880        src.read_exact(&mut n)
881            .map_err(|_| Error::DeserializationError)?;
882        Ok(u32::from_le_bytes(n))
883    }
884}
885
886impl<W: Write> MLASerialize<W> for u16 {
887    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
888        dest.write_all(&self.to_le_bytes())?;
889        Ok(2)
890    }
891}
892
893impl<R: Read> MLADeserialize<R> for u16 {
894    fn deserialize(src: &mut R) -> Result<Self, Error> {
895        let mut n = [0; 2];
896        src.read_exact(&mut n)
897            .map_err(|_| Error::DeserializationError)?;
898        Ok(u16::from_le_bytes(n))
899    }
900}
901
902impl<R: Read> MLADeserialize<R> for u8 {
903    fn deserialize(src: &mut R) -> Result<Self, Error> {
904        let mut n = [0; 1];
905        src.read_exact(&mut n)
906            .map_err(|_| Error::DeserializationError)?;
907        Ok(u8::from_le_bytes(n))
908    }
909}
910
911impl<W: Write, A: MLASerialize<W>, B: MLASerialize<W>> MLASerialize<W> for (A, B) {
912    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
913        let mut serialization_length = self.0.serialize(dest)?;
914        serialization_length = serialization_length
915            .checked_add(self.1.serialize(dest)?)
916            .ok_or(Error::SerializationError)?;
917        Ok(serialization_length)
918    }
919}
920
921impl<W: Write, T: MLASerialize<W>> MLASerialize<W> for Vec<T> {
922    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
923        let u64len = u64::try_from(self.len()).map_err(|_| Error::SerializationError)?;
924        let mut serialization_length = u64len.serialize(dest)?;
925        serialization_length = serialization_length
926            .checked_add(self.as_slice().serialize(dest)?)
927            .ok_or(Error::SerializationError)?;
928        Ok(serialization_length)
929    }
930}
931
932impl<W: Write, T: MLASerialize<W>> MLASerialize<W> for &[T] {
933    fn serialize(&self, dest: &mut W) -> Result<u64, Error> {
934        let mut serialization_length: u64 = 0;
935        for e in *self {
936            serialization_length = serialization_length
937                .checked_add(e.serialize(dest)?)
938                .ok_or(Error::SerializationError)?;
939        }
940        Ok(serialization_length)
941    }
942}
943
944impl<R: Read, T: MLADeserialize<R>> MLADeserialize<R> for Vec<T> {
945    fn deserialize(src: &mut R) -> Result<Self, Error> {
946        let n = u64::deserialize(src)?;
947        let v: Result<Vec<T>, Error> = (0..n).map(|_| T::deserialize(src)).collect();
948        v
949    }
950}
951
952impl<R: Read, const N: usize> MLADeserialize<R> for [u8; N] {
953    fn deserialize(src: &mut R) -> Result<Self, Error> {
954        let mut a = [0; N];
955        for e in &mut a {
956            *e = u8::deserialize(src)?;
957        }
958        Ok(a)
959    }
960}
961
962impl<R: Read, T1: MLADeserialize<R>, T2: MLADeserialize<R>> MLADeserialize<R> for (T1, T2) {
963    fn deserialize(src: &mut R) -> Result<Self, Error> {
964        Ok((T1::deserialize(src)?, T2::deserialize(src)?))
965    }
966}
967
968// -------- Reader --------
969
970#[cfg_attr(test, derive(PartialEq, Eq, Debug, Clone))]
971pub(crate) struct EntryInfo {
972    /// Entry information to save in the footer
973    ///
974    /// Offsets of chunks of `ArchiveEntryBlock`
975    offsets_and_sizes: Vec<(u64, u64)>,
976}
977
978impl<W: Write> MLASerialize<W> for EntryInfo {
979    fn serialize(&self, mut dest: &mut W) -> Result<u64, Error> {
980        self.offsets_and_sizes.serialize(&mut dest)
981    }
982}
983
984impl<R: Read> MLADeserialize<R> for EntryInfo {
985    fn deserialize(src: &mut R) -> Result<Self, Error> {
986        let offsets_and_sizes = MLADeserialize::deserialize(src)?;
987
988        Ok(Self { offsets_and_sizes })
989    }
990}
991
992fn read_layer_magic<R: Read>(src: &mut R) -> Result<[u8; 8], Error> {
993    let mut buf = [0; 8];
994    src.read_exact(&mut buf)?;
995    Ok(buf)
996}
997
998/// Use this to read an archive
999pub struct ArchiveReader<'a, R: 'a + InnerReaderTrait> {
1000    /// MLA Archive format Reader
1001    //
1002    /// Source
1003    src: Box<dyn 'a + LayerReader<'a, R>>,
1004    /// Metadata (from footer if any)
1005    metadata: Option<ArchiveFooter>,
1006}
1007
1008fn read_mla_entries_header(mut src: impl Read) -> Result<(), Error> {
1009    // Read the magic
1010    let mut magic = [0u8; 8];
1011    src.read_exact(&mut magic)?;
1012    if magic != *ENTRIES_LAYER_MAGIC {
1013        return Err(Error::WrongMagic);
1014    }
1015    read_mla_entries_header_skip_magic(src)
1016}
1017
1018fn read_mla_entries_header_skip_magic(mut src: impl Read) -> Result<(), Error> {
1019    let _ = Opts::from_reader(&mut src)?; // No option handled at the moment
1020    Ok(())
1021}
1022
1023impl<'b, R: 'b + InnerReaderTrait> ArchiveReader<'b, R> {
1024    /// Create an `ArchiveReader`.
1025    pub fn from_config(
1026        mut src: R,
1027        config: ArchiveReaderConfig,
1028    ) -> Result<(Self, Vec<MLASignatureVerificationPublicKey>), Error> {
1029        // Make sure we read the archive header from the start
1030        src.rewind()?;
1031
1032        // Read Archive Header while hashing it
1033        let mut archive_header_hash = Sha512::new();
1034        let mut src = HashWrapperReader::new(src, &mut archive_header_hash);
1035        ArchiveHeader::deserialize(&mut src)?;
1036
1037        // Pin the current position (after header) as the new 0
1038        let mut raw_src = Box::new(RawLayerReader::new(src.into_inner()));
1039        raw_src.reset_position()?;
1040        let mut src: Box<dyn 'b + LayerReader<'b, R>> = raw_src;
1041
1042        // Read and strip tail (end magic, tail options)
1043        let end_magic_position = src.seek(SeekFrom::End(-8))?;
1044        let end_magic = read_layer_magic(&mut src)?;
1045        if &end_magic != END_MLA_MAGIC {
1046            return Err(Error::WrongEndMagic);
1047        }
1048        src.seek(SeekFrom::End(-16))?;
1049        let mla_footer_options_length = u64::deserialize(&mut src)?;
1050        let mla_tail_len = mla_footer_options_length
1051            .checked_add(16)
1052            .ok_or(Error::DeserializationError)?;
1053        let inner_len = end_magic_position
1054            .checked_add(8)
1055            .ok_or(Error::DeserializationError)?;
1056        src.seek(SeekFrom::Start(0))?;
1057        src = Box::new(StripHeadTailReader::new(
1058            src,
1059            0,
1060            mla_tail_len,
1061            inner_len,
1062            0,
1063        )?);
1064
1065        // Enable layers depending on user option. Order is relevant
1066
1067        // Read first layer magic while updating hash
1068        let mut src = HashWrapperReader::new(src, &mut archive_header_hash);
1069        let mut magic = read_layer_magic(&mut src)?;
1070        let mut src = src.into_inner();
1071        let accept_unencrypted = config.accept_unencrypted;
1072
1073        // check signature first if any
1074
1075        // The following var lets us ensure the encryption persistent config we use is the one read during signature verification
1076        // This is cool because it contains the key commitment.
1077        // Thus the authentication of the AEAD can protect us if the source we read from is manipulated after signature verification.
1078        let mut signed_persistent_encryption_config = None;
1079        let mut keys_with_valid_signatures = Vec::new();
1080
1081        if magic == SIGNATURE_LAYER_MAGIC {
1082            let (sig_layer, new_keys_with_valid_signatures, read_persistent_encryption_config) =
1083                SignatureLayerReader::new_skip_magic(
1084                    src,
1085                    &config.signature_reader_config,
1086                    archive_header_hash,
1087                )?;
1088            signed_persistent_encryption_config = read_persistent_encryption_config;
1089            keys_with_valid_signatures = new_keys_with_valid_signatures;
1090            src = Box::new(sig_layer);
1091            src.initialize()?;
1092            magic = read_layer_magic(&mut src)?;
1093        } else if config.signature_reader_config.signature_check {
1094            return Err(Error::SignatureVerificationAskedButNoSignatureLayerFound);
1095        }
1096
1097        if &magic == ENCRYPTION_LAYER_MAGIC {
1098            src = Box::new(EncryptionLayerReader::new_skip_magic(
1099                src,
1100                config.encrypt,
1101                signed_persistent_encryption_config,
1102            )?);
1103            src.initialize()?;
1104            magic = read_layer_magic(&mut src)?;
1105        } else if !accept_unencrypted {
1106            return Err(Error::EncryptionAskedButNotMarkedPresent);
1107        }
1108
1109        if &magic == COMPRESSION_LAYER_MAGIC {
1110            src = Box::new(CompressionLayerReader::new_skip_magic(src)?);
1111            src.initialize()?;
1112        }
1113
1114        // read `entries_footer_options`
1115        src.seek(SeekFrom::End(-8))?;
1116        let entries_footer_options_length = u64::deserialize(&mut src)?;
1117        // skip reading them as there are none for the moment
1118
1119        // Read the eventual index in footer
1120        let entries_footer_length_offset_from_end =
1121            (-16i64) // -8 for Tail<Opts>'s length, -8 for `Tail<EntriesIndex>`'s length field
1122                .checked_sub_unsigned(entries_footer_options_length)
1123                .ok_or(Error::DeserializationError)?;
1124        // Read the index length
1125        src.seek(SeekFrom::End(entries_footer_length_offset_from_end))?;
1126        let entries_footer_length = u64::deserialize(&mut src)?;
1127        // Prepare for deserialization
1128        let start_of_entries_footer_from_current = (-8i64)
1129            .checked_sub_unsigned(entries_footer_length)
1130            .ok_or(Error::DeserializationError)?;
1131        src.seek(SeekFrom::Current(start_of_entries_footer_from_current))?;
1132        let metadata = ArchiveFooter::deserialize_from(&mut src)?;
1133
1134        // Reset the position for further uses
1135        src.rewind()?;
1136
1137        read_mla_entries_header(&mut src)?;
1138
1139        Ok((ArchiveReader { src, metadata }, keys_with_valid_signatures))
1140    }
1141
1142    /// Return an iterator on the name of each entry in the archive.
1143    ///
1144    /// Order is not relevant, and may change.
1145    pub fn list_entries(&self) -> Result<impl Iterator<Item = &EntryName>, Error> {
1146        if let Some(ArchiveFooter { entries_info, .. }) = &self.metadata {
1147            Ok(entries_info.keys())
1148        } else {
1149            Err(Error::MissingMetadata)
1150        }
1151    }
1152
1153    /// Get the hash recorded in the archive footer for an entry content.
1154    pub fn get_hash(&mut self, name: &EntryName) -> Result<Option<Sha256Hash>, Error> {
1155        if let Some(ArchiveFooter { entries_info }) = &self.metadata {
1156            // Get file relative information
1157            let Some(file_info) = entries_info.get(name) else {
1158                return Ok(None);
1159            };
1160
1161            // Set the inner layer at the start of the EoE tag
1162            let eoe_offset = file_info
1163                .offsets_and_sizes
1164                .last()
1165                .ok_or(Error::DeserializationError)?
1166                .0;
1167            self.src.seek(SeekFrom::Start(eoe_offset))?;
1168
1169            // Return the file hash
1170            match ArchiveEntryBlock::from(&mut self.src)? {
1171                ArchiveEntryBlock::EndOfEntry { hash, .. } => Ok(Some(hash)),
1172                _ => Err(Error::WrongReaderState(
1173                    "[ArchiveReader] last offset must point to a EndOfEntry".to_string(),
1174                )),
1175            }
1176        } else {
1177            Err(Error::MissingMetadata)
1178        }
1179    }
1180
1181    /// Get an archive entry.
1182    ///
1183    /// If no entry is found with given `name`, returns `Ok(None)`.
1184    /// If found, return `Ok(Some(e))` where e is an `ArchiveEntry`, letting you read its content and size.
1185    /// Returns an `Err` on error...
1186    pub fn get_entry(
1187        &mut self,
1188        name: EntryName,
1189    ) -> Result<Option<ArchiveEntry<'_, impl InnerReaderTrait>>, Error> {
1190        if let Some(ArchiveFooter { entries_info }) = &self.metadata {
1191            // Get file relative information
1192            let Some(file_info) = entries_info.get(&name) else {
1193                return Ok(None);
1194            };
1195            if file_info.offsets_and_sizes.is_empty() {
1196                return Err(Error::WrongReaderState(
1197                    "[ArchiveReader] A file must have at least one offset".to_string(),
1198                ));
1199            }
1200
1201            // Instantiate the file representation
1202            let reader = ArchiveEntryDataReader::new(&mut self.src, &file_info.offsets_and_sizes)?;
1203            Ok(Some(ArchiveEntry { name, data: reader }))
1204        } else {
1205            Err(Error::MissingMetadata)
1206        }
1207    }
1208}
1209
1210// This code is very similar with MLAArchiveReader
1211
1212/// Use this to convert a truncated archive to one that can be opened with `ArchiveReader`, eventually loosing some content and security or performance properties.
1213pub struct TruncatedArchiveReader<'a, R: 'a + Read> {
1214    /// MLA Archive format Reader for eventually truncated archives
1215    //
1216    /// Source
1217    src: Box<dyn 'a + LayerTruncatedReader<'a, R>>,
1218}
1219
1220// Size of the `clean-truncated` file blocks
1221const CACHE_SIZE: usize = 8 * 1024 * 1024; // 8MB
1222
1223/// Used to update the error state only if it was `NoError`
1224/// ```text
1225/// update_error!(error_var, TruncatedReadError::...)
1226/// ```
1227macro_rules! update_error {
1228    ( $x:ident = $y:expr ) => {
1229        #[allow(clippy::single_match)]
1230        match $x {
1231            TruncatedReadError::NoError => {
1232                $x = $y;
1233            }
1234            _ => {}
1235        }
1236    };
1237}
1238
1239impl<'b, R: 'b + Read> TruncatedArchiveReader<'b, R> {
1240    /// Create a `TruncatedArchiveReader` with given config.
1241    pub fn from_config(mut src: R, config: TruncatedReaderConfig) -> Result<Self, Error> {
1242        ArchiveHeader::deserialize(&mut src)?;
1243
1244        // Enable layers depending on user option. Order is relevant
1245        let mut src: Box<dyn 'b + LayerTruncatedReader<'b, R>> =
1246            Box::new(RawLayerTruncatedReader::new(src));
1247        let accept_unencrypted = config.accept_unencrypted;
1248        let truncated_decryption_mode = config.truncated_decryption_mode;
1249        let mut magic = read_layer_magic(&mut src)?;
1250        if magic == SIGNATURE_LAYER_MAGIC {
1251            src = Box::new(SignatureLayerTruncatedReader::new_skip_magic(src)?);
1252            magic = read_layer_magic(&mut src)?;
1253        }
1254        if &magic == ENCRYPTION_LAYER_MAGIC {
1255            src = Box::new(EncryptionLayerTruncatedReader::new_skip_magic(
1256                src,
1257                config.encrypt,
1258                None,
1259                truncated_decryption_mode,
1260            )?);
1261            magic = read_layer_magic(&mut src)?;
1262        } else if !accept_unencrypted {
1263            return Err(Error::EncryptionAskedButNotMarkedPresent);
1264        }
1265        if &magic == COMPRESSION_LAYER_MAGIC {
1266            src = Box::new(CompressionLayerTruncatedReader::new_skip_magic(src)?);
1267            magic = read_layer_magic(&mut src)?;
1268        }
1269
1270        if &magic != ENTRIES_LAYER_MAGIC {
1271            return Err(Error::DeserializationError);
1272        }
1273
1274        // Read the magic
1275        read_mla_entries_header_skip_magic(&mut src)?;
1276
1277        Ok(Self { src })
1278    }
1279
1280    /// Best-effort conversion of the current archive to a correct
1281    /// one. On success, returns the reason conversion terminates (ideally,
1282    /// `EndOfOriginalArchiveData`)
1283    #[allow(clippy::cognitive_complexity)]
1284    pub fn convert_to_archive<W: InnerWriterTrait>(
1285        &mut self,
1286        mut output: ArchiveWriter<W>,
1287    ) -> Result<TruncatedReadError, Error> {
1288        let mut error = TruncatedReadError::NoError;
1289
1290        // Associate an id retrieved from the archive to recover, to the
1291        // corresponding output file id
1292        let mut id_truncated2id_output: HashMap<ArchiveEntryId, ArchiveEntryId> = HashMap::new();
1293        // Associate an id retrieved from the archive to corresponding entry name
1294        let mut id_truncated2entryname: HashMap<ArchiveEntryId, EntryName> = HashMap::new();
1295        // List of IDs from the archive already fully added
1296        let mut id_truncated_done = Vec::new();
1297        // Associate an id retrieved from the archive with its ongoing Hash
1298        let mut id_truncated2hash: HashMap<ArchiveEntryId, Sha256> = HashMap::new();
1299
1300        'read_block: loop {
1301            match ArchiveEntryBlock::from(&mut self.src) {
1302                Err(Error::IOError(err)) => {
1303                    if let std::io::ErrorKind::UnexpectedEof = err.kind() {
1304                        update_error!(error = TruncatedReadError::UnexpectedEOFOnNextBlock);
1305                        break;
1306                    }
1307                    update_error!(error = TruncatedReadError::IOErrorOnNextBlock(err));
1308                    break;
1309                }
1310                Err(err) => {
1311                    update_error!(error = TruncatedReadError::ErrorOnNextBlock(err));
1312                    break;
1313                }
1314                Ok(block) => {
1315                    match block {
1316                        ArchiveEntryBlock::EntryStart { name, id, opts: _ } => {
1317                            if let Some(_id_output) = id_truncated2id_output.get(&id) {
1318                                update_error!(error = TruncatedReadError::ArchiveEntryIDReuse(id));
1319                                break 'read_block;
1320                            }
1321                            if id_truncated_done.contains(&id) {
1322                                update_error!(
1323                                    error = TruncatedReadError::ArchiveEntryIDAlreadyClosed(id)
1324                                );
1325                                break 'read_block;
1326                            }
1327
1328                            id_truncated2entryname.insert(id, name.clone());
1329                            let id_output = match output.start_entry(name.clone()) {
1330                                Err(Error::DuplicateEntryName) => {
1331                                    update_error!(
1332                                        error = TruncatedReadError::EntryNameReuse(
1333                                            name.raw_content_to_escaped_string()
1334                                        )
1335                                    );
1336                                    break 'read_block;
1337                                }
1338                                Err(err) => {
1339                                    return Err(err);
1340                                }
1341                                Ok(id) => id,
1342                            };
1343                            id_truncated2id_output.insert(id, id_output);
1344                            id_truncated2hash.insert(id, Sha256::default());
1345                        }
1346                        ArchiveEntryBlock::EntryContent { length, id, .. } => {
1347                            let id_output = if let Some(id_output) = id_truncated2id_output.get(&id)
1348                            {
1349                                *id_output
1350                            } else {
1351                                update_error!(
1352                                    error = TruncatedReadError::ContentForUnknownFile(id)
1353                                );
1354                                break 'read_block;
1355                            };
1356
1357                            if id_truncated_done.contains(&id) {
1358                                update_error!(
1359                                    error = TruncatedReadError::ArchiveEntryIDAlreadyClosed(id)
1360                                );
1361                                break 'read_block;
1362                            }
1363                            let entry = id_truncated2entryname.get(&id).expect(
1364                                "`id_truncated2entryname` not more sync with `id_truncated2id_output`",
1365                            );
1366                            let hash = id_truncated2hash.get_mut(&id).expect(
1367                                "`id_truncated2hash` not more sync with `id_truncated2id_output`",
1368                            );
1369
1370                            // Limit the reader to at most the file's content
1371                            let src = &mut (&mut self.src).take(length);
1372
1373                            // `Read` trait normally guarantees that if an error is returned by `.read()`, no data
1374                            // has been read
1375                            //
1376                            // It must then be equivalent to
1377                            // - call `n` times the API for 1 byte looking for the first fail
1378                            // - call the API for `n` bytes, possibly returning a first bunch of bytes then a failure
1379                            //
1380                            // The second method is used to reduced the calls' count when recovering large files.
1381                            // Being equivalent to the first method, it should extracts as many bytes as possible
1382                            // from the potentially broken stream.
1383                            //
1384                            // Note: some `Read` implementation does not respect this contract, as it might be
1385                            // subject to different interpretation
1386
1387                            // This buffer is used to reduced the resulting file fragmentation by aggregating `read` results
1388                            let mut buf = vec![0; CACHE_SIZE];
1389                            'content: loop {
1390                                let mut next_write_pos = 0;
1391                                'buf_fill: loop {
1392                                    match src.read(&mut buf[next_write_pos..]) {
1393                                        Ok(read) => {
1394                                            if read == 0 {
1395                                                // EOF
1396                                                break 'buf_fill;
1397                                            }
1398                                            next_write_pos = next_write_pos
1399                                                .checked_add(read)
1400                                                .ok_or(Error::DeserializationError)?;
1401                                        }
1402                                        Err(err) => {
1403                                            // Stop reconstruction
1404                                            output.append_entry_content(
1405                                                id_output,
1406                                                next_write_pos as u64,
1407                                                &buf[..next_write_pos],
1408                                            )?;
1409                                            update_error!(
1410                                                error = TruncatedReadError::ErrorInFile(
1411                                                    err,
1412                                                    entry.raw_content_to_escaped_string()
1413                                                )
1414                                            );
1415                                            break 'read_block;
1416                                        }
1417                                    }
1418                                    // Cache full
1419                                    if next_write_pos >= CACHE_SIZE {
1420                                        break 'buf_fill;
1421                                    }
1422                                }
1423                                output.append_entry_content(
1424                                    id_output,
1425                                    next_write_pos as u64,
1426                                    &buf[..next_write_pos],
1427                                )?;
1428                                hash.update(&buf[..next_write_pos]);
1429                                if next_write_pos < CACHE_SIZE {
1430                                    // EOF
1431                                    break 'content;
1432                                }
1433                            }
1434                        }
1435                        ArchiveEntryBlock::EndOfEntry {
1436                            id,
1437                            hash,
1438                            opts: Opts,
1439                        } => {
1440                            let id_output = if let Some(id_output) = id_truncated2id_output.get(&id)
1441                            {
1442                                *id_output
1443                            } else {
1444                                update_error!(error = TruncatedReadError::EOFForUnknownFile(id));
1445                                break 'read_block;
1446                            };
1447
1448                            if id_truncated_done.contains(&id) {
1449                                update_error!(
1450                                    error = TruncatedReadError::ArchiveEntryIDAlreadyClosed(id)
1451                                );
1452                                break 'read_block;
1453                            }
1454                            if let Some(hash_archive) = id_truncated2hash.remove(&id) {
1455                                let computed_hash = hash_archive.finalize();
1456                                if computed_hash.as_slice() != hash {
1457                                    update_error!(
1458                                        error = TruncatedReadError::HashDiffers {
1459                                            expected: Vec::from(computed_hash.as_slice()),
1460                                            obtained: Vec::from(&hash[..]),
1461                                        }
1462                                    );
1463                                    break 'read_block;
1464                                }
1465                            } else {
1466                                // Synchronisation error
1467                                update_error!(
1468                                    error = TruncatedReadError::TruncatedReadInternalError
1469                                );
1470                                break 'read_block;
1471                            }
1472
1473                            output.end_entry(id_output)?;
1474                            id_truncated_done.push(id);
1475                        }
1476                        ArchiveEntryBlock::EndOfArchiveData => {
1477                            // Expected end
1478                            update_error!(error = TruncatedReadError::EndOfOriginalArchiveData);
1479                            break 'read_block;
1480                        }
1481                    }
1482                }
1483            }
1484        }
1485
1486        let mut unfinished_files = Vec::new();
1487
1488        // Clean-up files still opened
1489        for (id_truncated, id_output) in id_truncated2id_output {
1490            if id_truncated_done.contains(&id_truncated) {
1491                // File is OK
1492                continue;
1493            }
1494
1495            let entry = id_truncated2entryname
1496                .get(&id_truncated)
1497                .expect("`id_truncated2entryname` not more sync with `id_truncated2id_output`");
1498            output.end_entry(id_output)?;
1499
1500            unfinished_files.push(entry.clone());
1501        }
1502
1503        // Report which files are not completed, if any
1504        if !unfinished_files.is_empty() {
1505            error = TruncatedReadError::UnfinishedEntries {
1506                names: unfinished_files,
1507                stopping_error: Box::new(error),
1508            };
1509        }
1510
1511        output.finalize()?;
1512        Ok(error)
1513    }
1514}
1515
1516/// Extract information from MLA Header
1517pub mod info;
1518
1519#[cfg(test)]
1520pub(crate) mod tests {
1521    use crate::config::TruncatedReaderDecryptionMode;
1522    use crate::crypto::mlakey::{MLAPrivateKey, MLAPublicKey, generate_mla_keypair_from_seed};
1523
1524    use super::*;
1525    use crypto::hybrid::generate_keypair_from_seed;
1526    use rand::distributions::{Distribution, Standard};
1527    use rand::{RngCore, SeedableRng};
1528    use rand_chacha::ChaChaRng;
1529    #[cfg(feature = "send")]
1530    use static_assertions;
1531    #[cfg(feature = "send")]
1532    use std::fs::File;
1533    use std::io::{Cursor, Empty, Read};
1534
1535    #[test]
1536    fn read_dump_header() {
1537        let header = ArchiveHeader {
1538            format_version_number: MLA_FORMAT_VERSION,
1539        };
1540        let mut buf = Vec::new();
1541        header.serialize(&mut buf).unwrap();
1542        println!("{buf:?}");
1543
1544        let header_rebuild = ArchiveHeader::deserialize(&mut buf.as_slice()).unwrap();
1545        assert_eq!(header_rebuild.format_version_number, MLA_FORMAT_VERSION);
1546    }
1547
1548    #[test]
1549    fn dump_block() {
1550        let mut buf = Vec::new();
1551        let id = ArchiveEntryId(0);
1552        let hash = Sha256Hash::default();
1553
1554        // std::io::Empty is used because a type with Read is needed
1555        ArchiveEntryBlock::EntryStart::<Empty> {
1556            id,
1557            name: EntryName::from_path("foobaré.exe").unwrap(),
1558            opts: Opts,
1559        }
1560        .dump(&mut buf)
1561        .unwrap();
1562
1563        let fake_content = vec![1, 2, 3, 4];
1564        let mut block = ArchiveEntryBlock::EntryContent {
1565            id,
1566            length: fake_content.len() as u64,
1567            data: Some(fake_content.as_slice()),
1568            opts: Opts,
1569        };
1570        block.dump(&mut buf).unwrap();
1571
1572        // std::io::Empty is used because a type with Read is needed
1573        ArchiveEntryBlock::EndOfEntry::<Empty> {
1574            id,
1575            hash,
1576            opts: Opts,
1577        }
1578        .dump(&mut buf)
1579        .unwrap();
1580
1581        println!("{buf:?}");
1582    }
1583
1584    #[test]
1585    fn new_mla() {
1586        let file = Vec::new();
1587        // Use a deterministic RNG in tests, for reproducibility. DO NOT DO THIS IS IN ANY RELEASED BINARY!
1588        let (private_key, public_key) = generate_keypair_from_seed([0; 32]);
1589        let config = ArchiveWriterConfig::with_encryption_without_signature(&[public_key]).unwrap();
1590        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
1591
1592        let fake_file = vec![1, 2, 3, 4];
1593        mla.add_entry(
1594            EntryName::from_path("my_file").unwrap(),
1595            fake_file.len() as u64,
1596            fake_file.as_slice(),
1597        )
1598        .unwrap();
1599        let fake_file = vec![5, 6, 7, 8];
1600        let fake_entry2 = vec![9, 10, 11, 12];
1601        let id = mla
1602            .start_entry(EntryName::from_path("my_entry2").unwrap())
1603            .unwrap();
1604        mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
1605            .unwrap();
1606        mla.append_entry_content(id, fake_entry2.len() as u64, fake_entry2.as_slice())
1607            .unwrap();
1608        mla.end_entry(id).unwrap();
1609
1610        let dest = mla.finalize().unwrap();
1611        let buf = Cursor::new(dest.as_slice());
1612        let config =
1613            ArchiveReaderConfig::without_signature_verification().with_encryption(&[private_key]);
1614        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
1615
1616        let mut file = mla_read
1617            .get_entry(EntryName::from_path("my_file").unwrap())
1618            .unwrap()
1619            .unwrap();
1620        let mut rez = Vec::new();
1621        file.data.read_to_end(&mut rez).unwrap();
1622        assert_eq!(rez, vec![1, 2, 3, 4]);
1623        // Explicit drop here, because otherwise mla_read.get_entry() cannot be
1624        // recall. It is not detected by the NLL analysis
1625        drop(file);
1626        let mut entry2 = mla_read
1627            .get_entry(EntryName::from_path("my_entry2").unwrap())
1628            .unwrap()
1629            .unwrap();
1630        let mut rez2 = Vec::new();
1631        entry2.data.read_to_end(&mut rez2).unwrap();
1632        assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
1633    }
1634
1635    #[test]
1636    fn new_encryption_only_mla() {
1637        let file = Vec::new();
1638        // Use a deterministic RNG in tests, for reproducibility. DO NOT DO THIS IS IN ANY RELEASED BINARY!
1639        let (private_key, public_key) = generate_keypair_from_seed([0; 32]);
1640        let config = ArchiveWriterConfig::with_encryption_without_signature(&[public_key])
1641            .unwrap()
1642            .without_compression();
1643        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
1644
1645        let fake_file = vec![1, 2, 3, 4];
1646        mla.add_entry(
1647            EntryName::from_path("my_file").unwrap(),
1648            fake_file.len() as u64,
1649            fake_file.as_slice(),
1650        )
1651        .unwrap();
1652        let fake_file = vec![5, 6, 7, 8];
1653        let fake_entry2 = vec![9, 10, 11, 12];
1654        let id = mla
1655            .start_entry(EntryName::from_path("my_entry2").unwrap())
1656            .unwrap();
1657        mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
1658            .unwrap();
1659        mla.append_entry_content(id, fake_entry2.len() as u64, fake_entry2.as_slice())
1660            .unwrap();
1661        mla.end_entry(id).unwrap();
1662
1663        let dest = mla.finalize().unwrap();
1664        let buf = Cursor::new(dest.as_slice());
1665        let config =
1666            ArchiveReaderConfig::without_signature_verification().with_encryption(&[private_key]);
1667        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
1668
1669        let mut file = mla_read
1670            .get_entry(EntryName::from_path("my_file").unwrap())
1671            .unwrap()
1672            .unwrap();
1673        let mut rez = Vec::new();
1674        file.data.read_to_end(&mut rez).unwrap();
1675        assert_eq!(rez, vec![1, 2, 3, 4]);
1676        // Explicit drop here, because otherwise mla_read.get_entry() cannot be
1677        // recall. It is not detected by the NLL analysis
1678        drop(file);
1679        let mut entry2 = mla_read
1680            .get_entry(EntryName::from_path("my_entry2").unwrap())
1681            .unwrap()
1682            .unwrap();
1683        let mut rez2 = Vec::new();
1684        entry2.data.read_to_end(&mut rez2).unwrap();
1685        assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
1686    }
1687
1688    #[test]
1689    fn new_naked_mla() {
1690        let file = Vec::new();
1691        // Use a deterministic RNG in tests, for reproducibility. DO NOT DO THIS IS IN ANY RELEASED BINARY!
1692        let config = ArchiveWriterConfig::without_encryption_without_signature()
1693            .unwrap()
1694            .without_compression();
1695        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
1696
1697        let fake_file = vec![1, 2, 3, 4];
1698        mla.add_entry(
1699            EntryName::from_path("my_file").unwrap(),
1700            fake_file.len() as u64,
1701            fake_file.as_slice(),
1702        )
1703        .unwrap();
1704        let fake_file = vec![5, 6, 7, 8];
1705        let fake_entry2 = vec![9, 10, 11, 12];
1706        let id = mla
1707            .start_entry(EntryName::from_path("my_entry2").unwrap())
1708            .unwrap();
1709        mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
1710            .unwrap();
1711        mla.append_entry_content(id, fake_entry2.len() as u64, fake_entry2.as_slice())
1712            .unwrap();
1713        mla.end_entry(id).unwrap();
1714
1715        let dest = mla.finalize().unwrap();
1716        let buf = Cursor::new(dest.as_slice());
1717        let config = ArchiveReaderConfig::without_signature_verification().without_encryption();
1718        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
1719
1720        let mut file = mla_read
1721            .get_entry(EntryName::from_path("my_file").unwrap())
1722            .unwrap()
1723            .unwrap();
1724        let mut rez = Vec::new();
1725        file.data.read_to_end(&mut rez).unwrap();
1726        assert_eq!(rez, vec![1, 2, 3, 4]);
1727        // Explicit drop here, because otherwise mla_read.get_entry() cannot be
1728        // recall. It is not detected by the NLL analysis
1729        drop(file);
1730        let mut entry2 = mla_read
1731            .get_entry(EntryName::from_path("my_entry2").unwrap())
1732            .unwrap()
1733            .unwrap();
1734        let mut rez2 = Vec::new();
1735        entry2.data.read_to_end(&mut rez2).unwrap();
1736        assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
1737    }
1738
1739    #[test]
1740    fn new_compression_only_mla() {
1741        let file = Vec::new();
1742        // Use a deterministic RNG in tests, for reproducibility. DO NOT DO THIS IS IN ANY RELEASED BINARY!
1743        let config = ArchiveWriterConfig::without_encryption_without_signature().unwrap();
1744        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
1745
1746        let fake_file = vec![1, 2, 3, 4];
1747        mla.add_entry(
1748            EntryName::from_path("my_file").unwrap(),
1749            fake_file.len() as u64,
1750            fake_file.as_slice(),
1751        )
1752        .unwrap();
1753        let fake_file = vec![5, 6, 7, 8];
1754        let fake_entry2 = vec![9, 10, 11, 12];
1755        let id = mla
1756            .start_entry(EntryName::from_path("my_entry2").unwrap())
1757            .unwrap();
1758        mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
1759            .unwrap();
1760        mla.append_entry_content(id, fake_entry2.len() as u64, fake_entry2.as_slice())
1761            .unwrap();
1762        mla.end_entry(id).unwrap();
1763
1764        let dest = mla.finalize().unwrap();
1765        let buf = Cursor::new(dest.as_slice());
1766        let config = ArchiveReaderConfig::without_signature_verification().without_encryption();
1767        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
1768
1769        let mut file = mla_read
1770            .get_entry(EntryName::from_path("my_file").unwrap())
1771            .unwrap()
1772            .unwrap();
1773        let mut rez = Vec::new();
1774        file.data.read_to_end(&mut rez).unwrap();
1775        assert_eq!(rez, vec![1, 2, 3, 4]);
1776        // Explicit drop here, because otherwise mla_read.get_entry() cannot be
1777        // recall. It is not detected by the NLL analysis
1778        drop(file);
1779        let mut entry2 = mla_read
1780            .get_entry(EntryName::from_path("my_entry2").unwrap())
1781            .unwrap()
1782            .unwrap();
1783        let mut rez2 = Vec::new();
1784        entry2.data.read_to_end(&mut rez2).unwrap();
1785        assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
1786    }
1787
1788    #[test]
1789    fn new_sig_mla() {
1790        let file = Vec::new();
1791        // Use a deterministic RNG in tests, for reproducibility. DO NOT DO THIS IS IN ANY RELEASED BINARY!
1792        let (private_key, public_key) = generate_mla_keypair_from_seed([0; 32]);
1793        let config = ArchiveWriterConfig::without_encryption_with_signature(&[private_key
1794            .get_signing_private_key()
1795            .clone()])
1796        .unwrap();
1797        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
1798
1799        let fake_file = vec![1, 2, 3, 4];
1800        mla.add_entry(
1801            EntryName::from_path("my_file").unwrap(),
1802            fake_file.len() as u64,
1803            fake_file.as_slice(),
1804        )
1805        .unwrap();
1806        let fake_file = vec![5, 6, 7, 8];
1807        let fake_entry2 = vec![9, 10, 11, 12];
1808        let id = mla
1809            .start_entry(EntryName::from_path("my_entry2").unwrap())
1810            .unwrap();
1811        mla.append_entry_content(id, fake_file.len() as u64, fake_file.as_slice())
1812            .unwrap();
1813        mla.append_entry_content(id, fake_entry2.len() as u64, fake_entry2.as_slice())
1814            .unwrap();
1815        mla.end_entry(id).unwrap();
1816
1817        let dest = mla.finalize().unwrap();
1818        let buf = Cursor::new(dest.as_slice());
1819        let config = ArchiveReaderConfig::with_signature_verification(&[public_key
1820            .get_signature_verification_public_key()
1821            .clone()])
1822        .without_encryption();
1823        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
1824
1825        let mut file = mla_read
1826            .get_entry(EntryName::from_path("my_file").unwrap())
1827            .unwrap()
1828            .unwrap();
1829        let mut rez = Vec::new();
1830        file.data.read_to_end(&mut rez).unwrap();
1831        assert_eq!(rez, vec![1, 2, 3, 4]);
1832        // Explicit drop here, because otherwise mla_read.get_entry() cannot be
1833        // recall. It is not detected by the NLL analysis
1834        drop(file);
1835        let mut entry2 = mla_read
1836            .get_entry(EntryName::from_path("my_entry2").unwrap())
1837            .unwrap()
1838            .unwrap();
1839        let mut rez2 = Vec::new();
1840        entry2.data.read_to_end(&mut rez2).unwrap();
1841        assert_eq!(rez2, vec![5, 6, 7, 8, 9, 10, 11, 12]);
1842    }
1843
1844    #[allow(clippy::type_complexity)]
1845    pub(crate) fn build_archive(
1846        compression: bool,
1847        encryption: bool,
1848        signature: bool,
1849        interleaved: bool,
1850    ) -> (
1851        Vec<u8>,
1852        (MLAPrivateKey, MLAPublicKey),
1853        (MLAPrivateKey, MLAPublicKey),
1854        Vec<(EntryName, Vec<u8>)>,
1855    ) {
1856        let (written_archive, sender_keys, receiver_keys, entries_content, _, _) =
1857            build_archive2(compression, encryption, signature, interleaved);
1858        (written_archive, sender_keys, receiver_keys, entries_content)
1859    }
1860
1861    #[allow(clippy::type_complexity)]
1862    pub(crate) fn build_archive2(
1863        compression: bool,
1864        encryption: bool,
1865        signature: bool,
1866        interleaved: bool,
1867    ) -> (
1868        Vec<u8>,
1869        (MLAPrivateKey, MLAPublicKey),
1870        (MLAPrivateKey, MLAPublicKey),
1871        Vec<(EntryName, Vec<u8>)>,
1872        HashMap<EntryName, ArchiveEntryId>,
1873        HashMap<ArchiveEntryId, EntryInfo>,
1874    ) {
1875        // Build an archive with 3 files
1876        let file = Vec::new();
1877        // Use a deterministic RNG in tests, for reproducibility. DO NOT DO THIS IS IN ANY RELEASED BINARY!
1878        let (sender_private_key, sender_public_key) = generate_mla_keypair_from_seed([0; 32]);
1879        let (receiver_private_key, receiver_public_key) = generate_mla_keypair_from_seed([1; 32]);
1880        let config = if encryption {
1881            if signature {
1882                ArchiveWriterConfig::with_encryption_with_signature(
1883                    &[receiver_public_key.get_encryption_public_key().clone()],
1884                    &[sender_private_key.get_signing_private_key().clone()],
1885                )
1886            } else {
1887                ArchiveWriterConfig::with_encryption_without_signature(&[receiver_public_key
1888                    .get_encryption_public_key()
1889                    .clone()])
1890            }
1891        } else if signature {
1892            ArchiveWriterConfig::without_encryption_with_signature(&[sender_private_key
1893                .get_signing_private_key()
1894                .clone()])
1895        } else {
1896            ArchiveWriterConfig::without_encryption_without_signature()
1897        }
1898        .unwrap();
1899        let config = if compression {
1900            config
1901        } else {
1902            config.without_compression()
1903        };
1904        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
1905
1906        let entry1 = EntryName::from_arbitrary_bytes(b"my_entry1").unwrap();
1907        let entry2 = EntryName::from_arbitrary_bytes(b"my_entry2").unwrap();
1908        let entry3 = EntryName::from_arbitrary_bytes(b"my_entry3").unwrap();
1909        let fake_file_part1 = vec![1, 2, 3];
1910        let fake_file_part2 = vec![4, 5, 6, 7, 8];
1911        let mut fake_entry1 = Vec::new();
1912        fake_entry1.extend_from_slice(fake_file_part1.as_slice());
1913        fake_entry1.extend_from_slice(fake_file_part2.as_slice());
1914        let fake_entry2 = vec![9, 10, 11, 12];
1915        let fake_entry3 = vec![13, 14, 15];
1916
1917        if interleaved {
1918            // Interleaved writes, expected result is:
1919            // [File1 start]
1920            // [File1 content 1 2 3]
1921            // [File2 start]
1922            // [File2 content 9 10 11 12]
1923            // [File3 start]
1924            // [File3 content 13 14 15]
1925            // [File3 end]
1926            // [File1 content 4 5 6 7 8]
1927            // [File1 end]
1928            // [File2 end]
1929            let id_entry1 = mla.start_entry(entry1.clone()).unwrap();
1930            mla.append_entry_content(
1931                id_entry1,
1932                fake_file_part1.len() as u64,
1933                fake_file_part1.as_slice(),
1934            )
1935            .unwrap();
1936            let id_entry2 = mla.start_entry(entry2.clone()).unwrap();
1937            mla.append_entry_content(id_entry2, fake_entry2.len() as u64, fake_entry2.as_slice())
1938                .unwrap();
1939            mla.add_entry(
1940                entry3.clone(),
1941                fake_entry3.len() as u64,
1942                fake_entry3.as_slice(),
1943            )
1944            .unwrap();
1945            mla.append_entry_content(
1946                id_entry1,
1947                fake_file_part2.len() as u64,
1948                fake_file_part2.as_slice(),
1949            )
1950            .unwrap();
1951            mla.end_entry(id_entry1).unwrap();
1952            mla.end_entry(id_entry2).unwrap();
1953        } else {
1954            mla.add_entry(
1955                entry1.clone(),
1956                fake_entry1.len() as u64,
1957                fake_entry1.as_slice(),
1958            )
1959            .unwrap();
1960            mla.add_entry(
1961                entry2.clone(),
1962                fake_entry2.len() as u64,
1963                fake_entry2.as_slice(),
1964            )
1965            .unwrap();
1966            mla.add_entry(
1967                entry3.clone(),
1968                fake_entry3.len() as u64,
1969                fake_entry3.as_slice(),
1970            )
1971            .unwrap();
1972        }
1973        let entries_info = mla.entries_info.clone();
1974        let ids_info = mla.ids_info.clone();
1975        let written_archive = mla.finalize().unwrap();
1976
1977        (
1978            written_archive,
1979            (sender_private_key, sender_public_key),
1980            (receiver_private_key, receiver_public_key),
1981            vec![
1982                (entry1, fake_entry1),
1983                (entry2, fake_entry2),
1984                (entry3, fake_entry3),
1985            ],
1986            entries_info,
1987            ids_info,
1988        )
1989    }
1990
1991    #[test]
1992    fn interleaved_files() {
1993        // Build an archive with 3 interleaved files
1994        let (mla, _sender_key, receiver_key, files) = build_archive(true, true, false, true);
1995
1996        let buf = Cursor::new(mla.as_slice());
1997        let config = ArchiveReaderConfig::without_signature_verification()
1998            .with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
1999        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
2000
2001        for (entry, content) in files {
2002            let mut file = mla_read.get_entry(entry).unwrap().unwrap();
2003            let mut rez = Vec::new();
2004            file.data.read_to_end(&mut rez).unwrap();
2005            assert_eq!(rez, content);
2006        }
2007    }
2008
2009    #[test]
2010    fn mla_all_layer_combinations() {
2011        // Deterministic keys for reproducibility
2012        let (privkey, pubkey) = generate_mla_keypair_from_seed([42; 32]);
2013
2014        // All combinations: (compress, encrypt, sign)
2015        let combinations = [
2016            (false, false, false), // naked
2017            (true, false, false),  // compress only
2018            (false, true, false),  // encrypt only
2019            (false, false, true),  // signature only
2020            (true, true, false),   // compress + encrypt
2021            (true, false, true),   // compress + signature
2022            (false, true, true),   // encrypt + signature
2023            (true, true, true),    // compress + encrypt + signature
2024        ];
2025
2026        for (compress, encrypt, sign) in combinations {
2027            let mut config = match (encrypt, sign) {
2028                (true, true) => ArchiveWriterConfig::with_encryption_with_signature(
2029                    &[pubkey.get_encryption_public_key().clone()],
2030                    &[privkey.get_signing_private_key().clone()],
2031                )
2032                .unwrap(),
2033                (true, false) => ArchiveWriterConfig::with_encryption_without_signature(&[pubkey
2034                    .get_encryption_public_key()
2035                    .clone()])
2036                .unwrap(),
2037                (false, true) => ArchiveWriterConfig::without_encryption_with_signature(&[privkey
2038                    .get_signing_private_key()
2039                    .clone()])
2040                .unwrap(),
2041                (false, false) => {
2042                    ArchiveWriterConfig::without_encryption_without_signature().unwrap()
2043                }
2044            };
2045            if !compress {
2046                config = config.without_compression();
2047            }
2048
2049            // Write archive
2050            let file = Vec::new();
2051            let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
2052            let data = vec![1, 2, 3, 4, 5];
2053            mla.add_entry(
2054                EntryName::from_path("testfile").unwrap(),
2055                data.len() as u64,
2056                data.as_slice(),
2057            )
2058            .unwrap();
2059            let archive = mla.finalize().unwrap();
2060
2061            // Read archive
2062            let buf = Cursor::new(archive);
2063            let reader_config = match (encrypt, sign) {
2064                (true, true) => ArchiveReaderConfig::with_signature_verification(&[pubkey
2065                    .get_signature_verification_public_key()
2066                    .clone()])
2067                .with_encryption(&[privkey.get_decryption_private_key().clone()]),
2068                (true, false) => ArchiveReaderConfig::without_signature_verification()
2069                    .with_encryption(&[privkey.get_decryption_private_key().clone()]),
2070                (false, true) => ArchiveReaderConfig::with_signature_verification(&[pubkey
2071                    .get_signature_verification_public_key()
2072                    .clone()])
2073                .without_encryption(),
2074                (false, false) => {
2075                    ArchiveReaderConfig::without_signature_verification().without_encryption()
2076                }
2077            };
2078            let mut mla_read = ArchiveReader::from_config(buf, reader_config).unwrap().0;
2079            let mut file = mla_read
2080                .get_entry(EntryName::from_path("testfile").unwrap())
2081                .unwrap()
2082                .unwrap();
2083            let mut out = Vec::new();
2084            file.data.read_to_end(&mut out).unwrap();
2085            assert_eq!(
2086                out,
2087                vec![1, 2, 3, 4, 5],
2088                "Failed for combination compress={compress}, encrypt={encrypt}, sign={sign}"
2089            );
2090        }
2091    }
2092
2093    #[test]
2094    fn list_and_read_files() {
2095        // Build an archive with 3 files
2096        let (mla, _sender_key, receiver_key, files) = build_archive(true, true, false, false);
2097
2098        let buf = Cursor::new(mla.as_slice());
2099        let config = ArchiveReaderConfig::without_signature_verification()
2100            .with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
2101        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
2102
2103        // Check the list of files is correct
2104        let mut sorted_list: Vec<EntryName> = mla_read.list_entries().unwrap().cloned().collect();
2105        sorted_list.sort();
2106        assert_eq!(
2107            sorted_list,
2108            files.iter().map(|(x, _y)| x.clone()).collect::<Vec<_>>(),
2109        );
2110
2111        // Get and check file per file, not in the writing order
2112        for (entry, content) in files.iter().rev() {
2113            let mut mla_file = mla_read.get_entry(entry.clone()).unwrap().unwrap();
2114            assert_eq!(mla_file.name, entry.clone());
2115            let mut buf = Vec::new();
2116            mla_file.data.read_to_end(&mut buf).unwrap();
2117            assert_eq!(&buf, content);
2118        }
2119    }
2120
2121    #[test]
2122    fn convert_truncated() {
2123        // Build an archive with 3 files
2124        let (dest, _sender_key, receiver_key, files) = build_archive(true, true, false, false);
2125
2126        // Prepare the truncated reader
2127        let config = TruncatedReaderConfig::without_signature_verification_with_encryption(
2128            &[receiver_key.0.get_decryption_private_key().clone()],
2129            TruncatedReaderDecryptionMode::OnlyAuthenticatedData,
2130        );
2131        let mut mla_fsread = TruncatedArchiveReader::from_config(dest.as_slice(), config).unwrap();
2132
2133        // Prepare the writer
2134        let mut dest_w = Vec::new();
2135        let config = ArchiveWriterConfig::with_encryption_without_signature(&[receiver_key
2136            .1
2137            .get_encryption_public_key()
2138            .clone()])
2139        .unwrap();
2140        let mla_w = ArchiveWriter::from_config(&mut dest_w, config).expect("Writer init failed");
2141
2142        // Conversion
2143        match mla_fsread.convert_to_archive(mla_w).unwrap() {
2144            TruncatedReadError::EndOfOriginalArchiveData => {
2145                // We expect to end with the final tag - all files have been
2146                // read and we stop on the tag before the footer
2147            }
2148            status => {
2149                panic!("Unexpected status: {status}");
2150            }
2151        }
2152
2153        // New archive can now be checked
2154        let buf2 = Cursor::new(dest_w.as_slice());
2155        let config = ArchiveReaderConfig::without_signature_verification()
2156            .with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
2157        let mut mla_read = ArchiveReader::from_config(buf2, config).unwrap().0;
2158
2159        // Check the list of files is correct
2160        let mut sorted_list: Vec<EntryName> = mla_read.list_entries().unwrap().cloned().collect();
2161        sorted_list.sort();
2162        assert_eq!(
2163            sorted_list,
2164            files.iter().map(|(x, _y)| x.clone()).collect::<Vec<_>>(),
2165        );
2166
2167        // Get and check file per file, not in the writing order
2168        for (entry, content) in files.iter().rev() {
2169            let mut mla_file = mla_read.get_entry(entry.clone()).unwrap().unwrap();
2170            assert_eq!(mla_file.name, entry.clone());
2171            let mut buf = Vec::new();
2172            mla_file.data.read_to_end(&mut buf).unwrap();
2173            assert_eq!(&buf, content);
2174        }
2175    }
2176
2177    #[test]
2178    fn convert_trunc_truncated() {
2179        for interleaved in &[false, true] {
2180            // Build an archive with 3 files, without compressing to truncate at the correct place
2181            let (dest, _sender_key, receiver_key, files, entries_info, ids_info) =
2182                build_archive2(false, true, false, *interleaved);
2183            // Truncate the resulting file (before the footer, hopefully after the header), and prepare the truncated reader
2184            let footer_size = {
2185                let mut cursor = Cursor::new(Vec::new());
2186                ArchiveFooter::serialize_into(&mut cursor, &entries_info, &ids_info).unwrap();
2187                usize::try_from(cursor.position())
2188                    .expect("Failed to convert cursor position to usize")
2189            };
2190
2191            for remove in &[1, 10, 30, 50, 70, 95, 100] {
2192                let config = TruncatedReaderConfig::without_signature_verification_with_encryption(
2193                    &[receiver_key.0.get_decryption_private_key().clone()],
2194                    TruncatedReaderDecryptionMode::DataEvenUnauthenticated,
2195                );
2196                let mut mla_fsread = TruncatedArchiveReader::from_config(
2197                    &dest[..dest.len() - footer_size - remove],
2198                    config,
2199                )
2200                .expect("Unable to create");
2201
2202                // Prepare the writer
2203                let mut dest_w = Vec::new();
2204                let config_w =
2205                    ArchiveWriterConfig::with_encryption_without_signature(&[receiver_key
2206                        .1
2207                        .get_encryption_public_key()
2208                        .clone()])
2209                    .expect("Writer init failed");
2210                let mla_w = ArchiveWriter::from_config(&mut dest_w, config_w).unwrap();
2211
2212                // Conversion
2213                let _status = mla_fsread.convert_to_archive(mla_w).unwrap();
2214
2215                // New archive can now be checked
2216                let buf2 = Cursor::new(dest_w.as_slice());
2217                let config = ArchiveReaderConfig::without_signature_verification()
2218                    .with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
2219                let mut mla_read = ArchiveReader::from_config(buf2, config).unwrap().0;
2220
2221                // Check *the start of* the files list is correct
2222                let expected = files.iter().map(|(x, _y)| x.clone()).collect::<Vec<_>>();
2223                let mut file_list = mla_read
2224                    .list_entries()
2225                    .unwrap()
2226                    .cloned()
2227                    .collect::<Vec<_>>();
2228                file_list.sort();
2229                assert_eq!(
2230                    file_list[..],
2231                    expected[..file_list.len()],
2232                    "File lists not equal {} interleaving and {} bytes removed",
2233                    if *interleaved { "with" } else { "without" },
2234                    remove
2235                );
2236
2237                // Get and check file per file, not in the writing order
2238                for (entry, content) in files.iter().rev() {
2239                    // The file may be missing
2240                    let Some(mut mla_file) = mla_read.get_entry(entry.clone()).unwrap() else {
2241                        continue;
2242                    };
2243                    // If the file is present, ensure there are bytes and the first
2244                    // bytes are the same
2245                    assert_eq!(mla_file.name, entry.clone());
2246                    let mut buf = Vec::new();
2247                    mla_file.data.read_to_end(&mut buf).unwrap();
2248                    assert_ne!(
2249                        buf.len(),
2250                        0,
2251                        "Read 0 bytes from entry {} {} interleaving and {} bytes removed",
2252                        mla_file.name.raw_content_to_escaped_string(),
2253                        if *interleaved { "with" } else { "without" },
2254                        remove
2255                    );
2256                    assert_eq!(&buf[..], &content[..buf.len()]);
2257                }
2258            }
2259            // /!\ This test doesn't ensure the code is doing the best effort; it only check the result is correct.
2260        }
2261    }
2262
2263    #[test]
2264    fn avoid_duplicate_entryname() {
2265        let buf = Vec::new();
2266        let config = ArchiveWriterConfig::without_encryption_without_signature()
2267            .unwrap()
2268            .without_compression();
2269        let mut mla = ArchiveWriter::from_config(buf, config).unwrap();
2270        mla.add_entry(
2271            EntryName::from_path("Test").unwrap(),
2272            4,
2273            vec![1, 2, 3, 4].as_slice(),
2274        )
2275        .unwrap();
2276        assert!(
2277            mla.add_entry(
2278                EntryName::from_path("Test").unwrap(),
2279                4,
2280                vec![1, 2, 3, 4].as_slice()
2281            )
2282            .is_err()
2283        );
2284        assert!(
2285            mla.start_entry(EntryName::from_path("Test").unwrap())
2286                .is_err()
2287        );
2288    }
2289
2290    #[test]
2291    fn check_file_size() {
2292        // Build an archive with 3 non-interleaved files and another with
2293        // interleaved files
2294        for interleaved in &[false, true] {
2295            let (dest, _sender_key, receiver_key, files, entries_info, ids_info) =
2296                build_archive2(true, true, false, *interleaved);
2297
2298            for (entry, data) in &files {
2299                let id = entries_info.get(entry).unwrap();
2300                let size: u64 = ids_info
2301                    .get(id)
2302                    .unwrap()
2303                    .offsets_and_sizes
2304                    .iter()
2305                    .map(|p| p.1)
2306                    .sum();
2307                assert_eq!(size, data.len() as u64);
2308            }
2309
2310            let buf = Cursor::new(dest.as_slice());
2311            let config = ArchiveReaderConfig::without_signature_verification()
2312                .with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
2313            let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
2314
2315            for (entry, data) in &files {
2316                let mla_file = mla_read.get_entry(entry.clone()).unwrap().unwrap();
2317                assert_eq!(mla_file.get_size(), data.len() as u64);
2318            }
2319        }
2320    }
2321
2322    #[test]
2323    fn truncated_detect_integrity() {
2324        // Build an archive with 3 files
2325        let (mut dest, _sender_key, _receiver_key, files) =
2326            build_archive(false, false, false, false);
2327
2328        // Swap the first 2 bytes of entry1
2329        let expect = files[0].1.as_slice();
2330        let pos: Vec<usize> = dest
2331            .iter()
2332            .enumerate()
2333            .filter_map(|e| {
2334                if e.0 + expect.len() < dest.len() && &dest[e.0..e.0 + expect.len()] == expect {
2335                    Some(e.0)
2336                } else {
2337                    None
2338                }
2339            })
2340            .collect();
2341        dest.swap(pos[0], pos[0] + 1);
2342
2343        // Prepare the truncated reader
2344        let mut mla_fsread = TruncatedArchiveReader::from_config(
2345            dest.as_slice(),
2346            TruncatedReaderConfig::without_signature_verification_without_encryption(),
2347        )
2348        .unwrap();
2349
2350        // Prepare the writer
2351        let dest_w = Vec::new();
2352        let config = ArchiveWriterConfig::without_encryption_without_signature()
2353            .unwrap()
2354            .without_compression();
2355        let mla_w = ArchiveWriter::from_config(dest_w, config).expect("Writer init failed");
2356
2357        // Conversion
2358        match mla_fsread.convert_to_archive(mla_w).unwrap() {
2359            TruncatedReadError::UnfinishedEntries {
2360                names,
2361                stopping_error,
2362            } => {
2363                // We expect to ends with a HashDiffers on first file
2364                assert_eq!(names, vec![files[0].0.clone()]);
2365                match *stopping_error {
2366                    TruncatedReadError::HashDiffers { .. } => {}
2367                    _ => {
2368                        panic!("Unexpected stopping_error: {stopping_error}");
2369                    }
2370                }
2371            }
2372            status => {
2373                panic!("Unexpected status: {status}");
2374            }
2375        }
2376    }
2377
2378    #[test]
2379    fn get_hash() {
2380        // Build an archive with 3 files
2381        let (dest, _sender_key, receiver_key, files) = build_archive(true, true, false, false);
2382
2383        // Prepare the reader
2384        let buf = Cursor::new(dest.as_slice());
2385        let config = ArchiveReaderConfig::without_signature_verification()
2386            .with_encryption(&[receiver_key.0.get_decryption_private_key().clone()]);
2387        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
2388
2389        // Get hashes and compare
2390        for (name, content) in files {
2391            let hash = mla_read.get_hash(&name).unwrap().unwrap();
2392
2393            let mut hasher = Sha256::new();
2394            hasher.update(content);
2395            let result = hasher.finalize();
2396            assert_eq!(result.as_slice(), hash);
2397        }
2398    }
2399
2400    fn make_format_regression_files() -> HashMap<EntryName, Vec<u8>> {
2401        // Build files easily scriptables and checkable
2402        let mut files: HashMap<EntryName, Vec<u8>> = HashMap::new();
2403
2404        // One simple file
2405        let mut simple: Vec<u8> = Vec::new();
2406        for i in 0..=255 {
2407            simple.push(i);
2408        }
2409        let pattern = simple.clone();
2410        files.insert(EntryName::from_path("simple").unwrap(), simple);
2411
2412        // One big file (10 MB)
2413        let big: Vec<u8> = pattern
2414            .iter()
2415            .cycle()
2416            .take(10 * 1024 * 1024)
2417            .copied()
2418            .collect();
2419        files.insert(EntryName::from_path("big").unwrap(), big);
2420
2421        // Some constant files
2422        for i in 0..=255 {
2423            files.insert(
2424                EntryName::from_path(format!("file_{i}")).unwrap(),
2425                std::iter::repeat_n(i, 0x1000).collect::<Vec<u8>>(),
2426            );
2427        }
2428
2429        // sha256 sum of them
2430        let mut sha256sum: Vec<u8> = Vec::new();
2431        let mut info: Vec<(&EntryName, &Vec<_>)> = files.iter().collect();
2432        info.sort_by(|i1, i2| Ord::cmp(&i1.0, &i2.0));
2433        for (entry, content) in &info {
2434            let h = Sha256::digest(content);
2435            sha256sum.extend_from_slice(hex::encode(h).as_bytes());
2436            sha256sum.push(0x20);
2437            sha256sum.push(0x20);
2438            sha256sum.extend(entry.as_arbitrary_bytes());
2439            sha256sum.push(0x0a);
2440        }
2441        files.insert(EntryName::from_path("sha256sum").unwrap(), sha256sum);
2442        files
2443    }
2444
2445    #[test]
2446    fn create_archive_format_version() {
2447        // Build an archive to be committed, for format regression
2448        let file = Vec::new();
2449
2450        // Use committed keys
2451        let priv_bytes: &'static [u8] =
2452            include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapriv");
2453        let pub_bytes: &'static [u8] =
2454            include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapub");
2455        let (_, priv_key) = MLAPrivateKey::deserialize_private_key(priv_bytes)
2456            .unwrap()
2457            .get_private_keys();
2458        let (pub_key, _) = MLAPublicKey::deserialize_public_key(pub_bytes)
2459            .unwrap()
2460            .get_public_keys();
2461
2462        let mut config =
2463            ArchiveWriterConfig::with_encryption_with_signature(&[pub_key], &[priv_key]).unwrap();
2464        if let Some(cfg) = config.encryption.as_mut() {
2465            cfg.rng = crate::crypto::MaybeSeededRNG::Seed([0; 32]);
2466        }
2467        if let Some(cfg) = config.signature.as_mut() {
2468            cfg.rng = crate::crypto::MaybeSeededRNG::Seed([0; 32]);
2469        }
2470        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
2471
2472        let files = make_format_regression_files();
2473        // First, add a simple file
2474        let entry_simple = EntryName::from_path("simple").unwrap();
2475        mla.add_entry(
2476            entry_simple.clone(),
2477            files.get(&entry_simple).unwrap().len() as u64,
2478            files.get(&entry_simple).unwrap().as_slice(),
2479        )
2480        .unwrap();
2481
2482        // Second, add interleaved files
2483        let entries: Vec<EntryName> = (0..=255)
2484            .map(|i| format!("file_{i}"))
2485            .map(|s| EntryName::from_path(&s).unwrap())
2486            .collect();
2487        let mut name2id: HashMap<_, _> = HashMap::new();
2488
2489        // Start files in normal order
2490        (0..=255)
2491            .map(|i| {
2492                let id = mla.start_entry(entries[i].clone()).unwrap();
2493                name2id.insert(&entries[i], id);
2494            })
2495            .for_each(drop);
2496
2497        // Add some parts in reverse order
2498        (0..=255)
2499            .rev()
2500            .map(|i| {
2501                let id = name2id.get(&entries[i]).unwrap();
2502                mla.append_entry_content(*id, 32, &files.get(&entries[i]).unwrap()[..32])
2503                    .unwrap();
2504            })
2505            .for_each(drop);
2506
2507        // Add the rest of files in normal order
2508        (0..=255)
2509            .map(|i| {
2510                let id = name2id.get(&entries[i]).unwrap();
2511                let data = &files.get(&entries[i]).unwrap()[32..];
2512                mla.append_entry_content(*id, data.len() as u64, data)
2513                    .unwrap();
2514            })
2515            .for_each(drop);
2516
2517        // Finish files in reverse order
2518        (0..=255)
2519            .rev()
2520            .map(|i| {
2521                let id = name2id.get(&entries[i]).unwrap();
2522                mla.end_entry(*id).unwrap();
2523            })
2524            .for_each(drop);
2525
2526        // Add a big file
2527        let entry_big = EntryName::from_path("big").unwrap();
2528        mla.add_entry(
2529            entry_big.clone(),
2530            files.get(&entry_big).unwrap().len() as u64,
2531            files.get(&entry_big).unwrap().as_slice(),
2532        )
2533        .unwrap();
2534
2535        // Add sha256sum file
2536        let entry_sha256sum = EntryName::from_path("sha256sum").unwrap();
2537        mla.add_entry(
2538            entry_sha256sum.clone(),
2539            files.get(&entry_sha256sum).unwrap().len() as u64,
2540            files.get(&entry_sha256sum).unwrap().as_slice(),
2541        )
2542        .unwrap();
2543        let raw_mla = mla.finalize().unwrap();
2544
2545        std::fs::File::create(std::path::Path::new(&format!(
2546            "../samples/archive_v{MLA_FORMAT_VERSION}.mla"
2547        )))
2548        .unwrap()
2549        .write_all(&raw_mla)
2550        .unwrap();
2551
2552        // check archive_v2 hash
2553        assert_eq!(
2554            Sha256::digest(&raw_mla).as_slice(),
2555            [
2556                252, 199, 175, 3, 17, 155, 237, 195, 81, 200, 149, 68, 209, 57, 5, 58, 245, 242,
2557                100, 13, 170, 168, 241, 27, 169, 112, 81, 182, 99, 141, 93, 248
2558            ]
2559        );
2560    }
2561
2562    #[test]
2563    fn check_archive_format_v2_content() {
2564        let privbytes: &'static [u8] =
2565            include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapriv");
2566        let pubbytes: &'static [u8] =
2567            include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapub");
2568
2569        let mla_data: &'static [u8] = include_bytes!("../../samples/archive_v2.mla");
2570        let files = make_format_regression_files();
2571
2572        // Build Reader
2573        let buf = Cursor::new(mla_data);
2574        let (privkey, _) = MLAPrivateKey::deserialize_private_key(privbytes)
2575            .unwrap()
2576            .get_private_keys();
2577        let (_, pubkey) = MLAPublicKey::deserialize_public_key(pubbytes)
2578            .unwrap()
2579            .get_public_keys();
2580        let config = ArchiveReaderConfig::with_signature_verification(&[pubkey])
2581            .with_encryption(std::slice::from_ref(&privkey));
2582        let mut mla_read = ArchiveReader::from_config(buf, config).unwrap().0;
2583
2584        // Build TruncatedReader
2585        let config = TruncatedReaderConfig::without_signature_verification_with_encryption(
2586            &[privkey],
2587            TruncatedReaderDecryptionMode::OnlyAuthenticatedData,
2588        );
2589        let mut mla_fsread = TruncatedArchiveReader::from_config(mla_data, config).unwrap();
2590
2591        // Repair the archive (without any damage, but trigger the corresponding code)
2592        let mut dest_w = Vec::new();
2593        let config = ArchiveWriterConfig::without_encryption_without_signature()
2594            .unwrap()
2595            .without_compression();
2596        let mla_w = ArchiveWriter::from_config(&mut dest_w, config).expect("Writer init failed");
2597        if let TruncatedReadError::EndOfOriginalArchiveData =
2598            mla_fsread.convert_to_archive(mla_w).unwrap()
2599        {
2600            // Everything runs as expected
2601        } else {
2602            panic!();
2603        }
2604        // Get a reader on the `clean-truncated` archive
2605        let buf2 = Cursor::new(dest_w);
2606        let repread_config =
2607            ArchiveReaderConfig::without_signature_verification().without_encryption();
2608        let mut mla_repread = ArchiveReader::from_config(buf2, repread_config).unwrap().0;
2609
2610        assert_eq!(files.len(), mla_read.list_entries().unwrap().count());
2611        assert_eq!(files.len(), mla_repread.list_entries().unwrap().count());
2612
2613        // Get and check file per file
2614        for (entry, content) in &files {
2615            let mut mla_file = mla_read.get_entry(entry.clone()).unwrap().unwrap();
2616            let mut mla_rep_file = mla_repread.get_entry(entry.clone()).unwrap().unwrap();
2617            assert_eq!(mla_file.name, entry.clone());
2618            assert_eq!(mla_rep_file.name, entry.clone());
2619            let mut buf = Vec::new();
2620            mla_file.data.read_to_end(&mut buf).unwrap();
2621            assert_eq!(buf.as_slice(), content.as_slice());
2622            let mut buf = Vec::new();
2623            mla_rep_file.data.read_to_end(&mut buf).unwrap();
2624            assert_eq!(buf.as_slice(), content.as_slice());
2625        }
2626    }
2627
2628    #[test]
2629    fn not_path_entry_name() {
2630        let mut file: Vec<u8> = Vec::new();
2631        let priv_bytes: &'static [u8] =
2632            include_bytes!("../../samples/test_mlakey_archive_v2_sender.mlapriv");
2633        let pub_bytes: &'static [u8] =
2634            include_bytes!("../../samples/test_mlakey_archive_v2_receiver.mlapub");
2635        let (_, priv_key) = MLAPrivateKey::deserialize_private_key(priv_bytes)
2636            .unwrap()
2637            .get_private_keys();
2638        let (pub_key, _) = MLAPublicKey::deserialize_public_key(pub_bytes)
2639            .unwrap()
2640            .get_public_keys();
2641
2642        let mut config =
2643            ArchiveWriterConfig::with_encryption_with_signature(&[pub_key], &[priv_key])
2644                .unwrap()
2645                .without_compression();
2646        if let Some(cfg) = config.encryption.as_mut() {
2647            cfg.rng = crate::crypto::MaybeSeededRNG::Seed([0; 32]);
2648        }
2649        if let Some(cfg) = config.signature.as_mut() {
2650            cfg.rng = crate::crypto::MaybeSeededRNG::Seed([0; 32]);
2651        }
2652        let mut mla = ArchiveWriter::from_config(&mut file, config).expect("Writer init failed");
2653
2654        let name = EntryName::from_arbitrary_bytes(
2655            b"c:/\0;\xe2\x80\xae\nc\rd\x1b[1;31ma<script>evil\\../\xd8\x01\xc2\x85\xe2\x88\x95",
2656        )
2657        .unwrap();
2658
2659        mla.add_entry(name.clone(), 8, b"' OR 1=1".as_slice())
2660            .expect("start_file");
2661        mla.finalize().unwrap();
2662
2663        std::fs::File::create(std::path::Path::new("../samples/archive_weird.mla"))
2664            .unwrap()
2665            .write_all(&file)
2666            .unwrap();
2667
2668        assert_eq!(
2669            Sha256::digest(&file).as_slice(),
2670            [
2671                95, 79, 59, 224, 200, 239, 240, 229, 137, 227, 3, 80, 95, 185, 106, 204, 219, 224,
2672                5, 102, 120, 246, 158, 151, 64, 151, 97, 52, 69, 134, 26, 25
2673            ]
2674        );
2675    }
2676
2677    #[test]
2678    fn empty_blocks() {
2679        // Add a file with containing an empty block - it should work
2680        let file = Vec::new();
2681        let config = ArchiveWriterConfig::without_encryption_without_signature()
2682            .unwrap()
2683            .without_compression();
2684        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
2685
2686        let entry = EntryName::from_path("my_file").unwrap();
2687        let fake_file = vec![1, 2, 3, 4, 5, 6, 7, 8];
2688
2689        let id = mla.start_entry(entry.clone()).expect("start_file");
2690        mla.append_entry_content(id, 4, &fake_file[..4])
2691            .expect("add content");
2692        mla.append_entry_content(id, 0, &fake_file[..1])
2693            .expect("add content empty");
2694        mla.append_entry_content(id, fake_file.len() as u64 - 4, &fake_file[4..])
2695            .expect("add rest");
2696        mla.end_entry(id).unwrap();
2697
2698        let mla_data = mla.finalize().unwrap();
2699
2700        let buf = Cursor::new(mla_data);
2701        let mut mla_read = ArchiveReader::from_config(
2702            buf,
2703            ArchiveReaderConfig::without_signature_verification().without_encryption(),
2704        )
2705        .expect("archive reader")
2706        .0;
2707        let mut out = Vec::new();
2708        mla_read
2709            .get_entry(entry)
2710            .unwrap()
2711            .unwrap()
2712            .data
2713            .read_to_end(&mut out)
2714            .unwrap();
2715        assert_eq!(out.as_slice(), fake_file.as_slice());
2716    }
2717
2718    #[test]
2719    #[ignore = "As it is costly, only enabled by default in the CI."]
2720    fn more_than_u32_entry() {
2721        const MORE_THAN_U32: u64 = 0x0001_0001_0000; // U32_max + 0x10000
2722        const MAX_SIZE: u64 = 5 * 1024 * 1024 * 1024; // 5 GB
2723        const CHUNK_SIZE: usize = 10 * 1024 * 1024; // 10 MB
2724
2725        // Use a deterministic RNG in tests, for reproducibility. DO NOT DO THIS IS IN ANY RELEASED BINARY!
2726        let mut rng = ChaChaRng::seed_from_u64(0);
2727        let mut rng_data = ChaChaRng::seed_from_u64(0);
2728
2729        let (private_key, public_key) = generate_keypair_from_seed([0; 32]);
2730        let config = ArchiveWriterConfig::with_encryption_without_signature(&[public_key]).unwrap();
2731        let file = Vec::new();
2732        let mut mla = ArchiveWriter::from_config(file, config).expect("Writer init failed");
2733
2734        // At least one file will be bigger than 32bits
2735        let id1 = mla
2736            .start_entry(EntryName::from_path("file_0").unwrap())
2737            .unwrap();
2738        let mut cur_size = 0;
2739        while cur_size < MORE_THAN_U32 {
2740            let size = std::cmp::min(u64::from(rng.next_u32()), MORE_THAN_U32 - cur_size);
2741            let data: Vec<u8> = Standard
2742                .sample_iter(&mut rng_data)
2743                .take(usize::try_from(size).expect("Failed to convert size to usize"))
2744                .collect();
2745            mla.append_entry_content(id1, size, data.as_slice())
2746                .unwrap();
2747            cur_size += size;
2748        }
2749        mla.end_entry(id1).unwrap();
2750
2751        let mut nb_file = 1;
2752
2753        // Complete up to MAX_SIZE
2754        while cur_size < MAX_SIZE {
2755            let id = mla
2756                .start_entry(EntryName::from_path(format!("file_{nb_file:}")).unwrap())
2757                .unwrap();
2758            let size = std::cmp::min(u64::from(rng.next_u32()), MAX_SIZE - cur_size);
2759            let data: Vec<u8> = Standard
2760                .sample_iter(&mut rng_data)
2761                .take(usize::try_from(size).expect("Failed to convert size to usize"))
2762                .collect();
2763            mla.append_entry_content(id, size, data.as_slice()).unwrap();
2764            cur_size += size;
2765            mla.end_entry(id).unwrap();
2766            nb_file += 1;
2767        }
2768        let mla_data = mla.finalize().unwrap();
2769
2770        // List files and check the list
2771
2772        let buf = Cursor::new(mla_data);
2773        let config =
2774            ArchiveReaderConfig::without_signature_verification().with_encryption(&[private_key]);
2775        let mut mla_read = ArchiveReader::from_config(buf, config)
2776            .expect("archive reader")
2777            .0;
2778
2779        let file_names: Vec<EntryName> = (0..nb_file)
2780            .map(|nb| EntryName::from_path(format!("file_{nb:}")).unwrap())
2781            .collect();
2782        let mut file_list = mla_read
2783            .list_entries()
2784            .unwrap()
2785            .cloned()
2786            .collect::<Vec<_>>();
2787        file_list.sort();
2788        assert_eq!(file_list, file_names);
2789
2790        // Check files content
2791
2792        // Using the same seed than the one used for data creation, we can compare expected content
2793        let mut rng_data = ChaChaRng::seed_from_u64(0);
2794
2795        let mut chunk = vec![0u8; CHUNK_SIZE];
2796        for file_name in file_names {
2797            let mut file_stream = mla_read.get_entry(file_name).unwrap().unwrap().data;
2798            loop {
2799                let read = file_stream.read(&mut chunk).unwrap();
2800                let expect: Vec<u8> = Standard.sample_iter(&mut rng_data).take(read).collect();
2801                assert_eq!(&chunk[..read], expect.as_slice());
2802                if read == 0 {
2803                    break;
2804                }
2805            }
2806        }
2807    }
2808
2809    #[test]
2810    fn signature_verification_fails_with_wrong_public_key() {
2811        let file = Vec::new();
2812
2813        // Correct keypair for signing
2814        let (privkey_correct, _pubkey_correct) = generate_mla_keypair_from_seed([1u8; 32]);
2815        // Wrong public key for verification
2816        let (_privkey_wrong, pubkey_wrong) = generate_mla_keypair_from_seed([2u8; 32]);
2817
2818        let config = ArchiveWriterConfig::without_encryption_with_signature(&[privkey_correct
2819            .get_signing_private_key()
2820            .clone()])
2821        .unwrap();
2822
2823        let mut writer = ArchiveWriter::from_config(file, config).expect("Writer init failed");
2824
2825        let data = vec![10, 20, 30, 40];
2826        writer
2827            .add_entry(
2828                EntryName::from_path("entry1").unwrap(),
2829                data.len() as u64,
2830                data.as_slice(),
2831            )
2832            .unwrap();
2833
2834        let archive = writer.finalize().unwrap();
2835        let buf = Cursor::new(archive.as_slice());
2836
2837        let config = ArchiveReaderConfig::with_signature_verification(&[pubkey_wrong
2838            .get_signature_verification_public_key()
2839            .clone()])
2840        .without_encryption();
2841
2842        // Reading with wrong public key should fail on signature verification
2843        let reader_result = ArchiveReader::from_config(buf, config);
2844
2845        assert!(
2846            reader_result.is_err(),
2847            "Verification should fail with wrong public key"
2848        );
2849    }
2850
2851    #[test]
2852    fn signature_verification_fails_on_tampered_archive() {
2853        let file = Vec::new();
2854
2855        let (privkey, pubkey) = generate_mla_keypair_from_seed([5u8; 32]);
2856
2857        let config = ArchiveWriterConfig::without_encryption_with_signature(&[privkey
2858            .get_signing_private_key()
2859            .clone()])
2860        .unwrap();
2861
2862        let mut writer = ArchiveWriter::from_config(file, config).expect("Writer init failed");
2863
2864        let data = vec![50, 60, 70, 80];
2865        writer
2866            .add_entry(
2867                EntryName::from_path("my_file").unwrap(),
2868                data.len() as u64,
2869                data.as_slice(),
2870            )
2871            .unwrap();
2872
2873        let mut archive = writer.finalize().unwrap();
2874
2875        // Tamper with archive bytes (flip one byte)
2876        archive[10] ^= 0xFF;
2877
2878        let buf = Cursor::new(archive.as_slice());
2879
2880        let config = ArchiveReaderConfig::with_signature_verification(&[pubkey
2881            .get_signature_verification_public_key()
2882            .clone()])
2883        .without_encryption();
2884
2885        // Signature verification should fail on tampered archive
2886        let result = ArchiveReader::from_config(buf, config);
2887
2888        assert!(
2889            result.is_err(),
2890            "Signature verification should fail on tampered archive"
2891        );
2892    }
2893
2894    #[test]
2895    fn signature_verification_fails_if_ed25519_signature_corrupted() {
2896        let file = Vec::new();
2897
2898        let (privkey, pubkey) = generate_mla_keypair_from_seed([7u8; 32]);
2899
2900        let config = ArchiveWriterConfig::without_encryption_with_signature(&[privkey
2901            .get_signing_private_key()
2902            .clone()])
2903        .unwrap();
2904
2905        let mut writer = ArchiveWriter::from_config(file, config).expect("Writer init failed");
2906
2907        let data = vec![10, 20, 30, 40];
2908        writer
2909            .add_entry(
2910                EntryName::from_path("entry1").unwrap(),
2911                data.len() as u64,
2912                data.as_slice(),
2913            )
2914            .unwrap();
2915
2916        let mut archive = writer.finalize().unwrap();
2917
2918        // Flip a byte inside Ed25519 signature payload,
2919        // should land in the middle of the Ed25519 signature
2920        let flip_pos = 50;
2921        archive[flip_pos] ^= 0xFF;
2922
2923        let buf = std::io::Cursor::new(archive);
2924
2925        let config = ArchiveReaderConfig::with_signature_verification(&[pubkey
2926            .get_signature_verification_public_key()
2927            .clone()])
2928        .without_encryption();
2929
2930        let reader_result = ArchiveReader::from_config(buf, config);
2931
2932        assert!(
2933            matches!(reader_result, Err(Error::NoValidSignatureFound)),
2934            "Verification should fail if Ed25519 signature is corrupted"
2935        );
2936    }
2937
2938    #[test]
2939    fn signature_verification_fails_if_mldsa87_signature_corrupted() {
2940        let file = Vec::new();
2941
2942        let (privkey, pubkey) = generate_mla_keypair_from_seed([8u8; 32]);
2943
2944        let config = ArchiveWriterConfig::without_encryption_with_signature(&[privkey
2945            .get_signing_private_key()
2946            .clone()])
2947        .unwrap();
2948
2949        let mut writer = ArchiveWriter::from_config(file, config).expect("Writer init failed");
2950
2951        let data = vec![50, 60, 70, 80];
2952        writer
2953            .add_entry(
2954                EntryName::from_path("my_file").unwrap(),
2955                data.len() as u64,
2956                data.as_slice(),
2957            )
2958            .unwrap();
2959
2960        let mut archive = writer.finalize().unwrap();
2961
2962        // Flip a byte inside MlDsa87 signature payload,
2963        // should land in the middle of the MlDsa87 signature
2964        let flip_pos = 4000;
2965        archive[flip_pos] ^= 0xFF;
2966
2967        let buf = std::io::Cursor::new(archive);
2968
2969        let config = ArchiveReaderConfig::with_signature_verification(&[pubkey
2970            .get_signature_verification_public_key()
2971            .clone()])
2972        .without_encryption();
2973
2974        let reader_result = ArchiveReader::from_config(buf, config);
2975
2976        assert!(
2977            matches!(reader_result, Err(Error::NoValidSignatureFound)),
2978            "Verification should fail if MlDsa87 signature is corrupted"
2979        );
2980    }
2981
2982    #[test]
2983    fn test_footer_deserialization_none() {
2984        let data = vec![0u8]; // 0x00 tag = no index
2985        let res = ArchiveFooter::deserialize_from(Cursor::new(&data)).unwrap();
2986        assert!(res.is_none());
2987    }
2988
2989    #[test]
2990    fn test_footer_deserialization_invalid_index() {
2991        let data = vec![0x42]; // Not 0x00 or 0x01
2992        let res = ArchiveFooter::deserialize_from(Cursor::new(&data)).unwrap();
2993        assert!(res.is_none());
2994    }
2995
2996    #[test]
2997    fn test_footer_deserialization_valid_index() {
2998        let (_dest, _sender_key, _receiver_key, _files, entries_info, ids_info) =
2999            build_archive2(false, false, false, false);
3000
3001        // Manually write the index tag before the actual footer content
3002        let mut buf = Cursor::new(Vec::new());
3003        buf.write_all(&[1]).unwrap();
3004        ArchiveFooter::serialize_into(&mut buf, &entries_info, &ids_info).unwrap();
3005        buf.rewind().unwrap();
3006
3007        let result = ArchiveFooter::deserialize_from(buf).unwrap();
3008        assert!(result.is_some());
3009    }
3010
3011    #[test]
3012    #[cfg(feature = "send")]
3013    fn test_send() {
3014        static_assertions::assert_cfg!(feature = "send");
3015        static_assertions::assert_impl_all!(File: Send);
3016        static_assertions::assert_impl_all!(ArchiveWriter<File>: Send);
3017        static_assertions::assert_impl_all!(ArchiveReader<File>: Send);
3018    }
3019}