snapper_box/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(
3    clippy::all,
4    clippy::pedantic,
5    rust_2018_idioms,
6    missing_docs,
7    clippy::missing_docs_in_private_items
8)]
9#![allow(
10    clippy::option_if_let_else,
11    clippy::module_name_repetitions,
12    clippy::shadow_unrelated,
13    clippy::must_use_candidate,
14    clippy::implicit_hasher
15)]
16#![doc(
17    html_logo_url = "https://gitlab.com/rust-community-matrix/snapper/-/raw/trunk/static/snapper.png"
18)]
19
20use std::{
21    collections::HashMap,
22    fs::{create_dir_all, File},
23    io::Write,
24    path::{Path, PathBuf},
25    sync::Arc,
26};
27
28use serde::{de::DeserializeOwned, Serialize};
29use snafu::{ensure, OptionExt, ResultExt};
30use tracing::{info, instrument};
31
32use crate::{
33    crypto::{DerivedKey, EncryptedRootKey, RootKey},
34    entries::{Control, Namespace, Namespaces, Settings},
35    error::{
36        CryptoBoxError, DirectoryAlreadyExists, DirectoryDoesNotExist, FailedCreatingDirectory,
37        Fetch, MissingConfiguration, MissingNamespaceDirectory, NamespaceOpen, RootKeyDecryption,
38        RootKeyEncryption, RootKeyIO, RootKeySerial, RootNamespaceInit, RootNamespaceOpen, Store,
39    },
40    file::LsmFile,
41};
42
43#[cfg(feature = "experimental-async")]
44pub mod async_wrapper;
45pub mod crypto;
46mod entries;
47pub mod error;
48pub mod file;
49
50/// The `CryptoBox` encrypted, namespaced document store
51///
52/// See module level documentation for more information.
53pub struct CryptoBox {
54    /// The path for this CryptoBox
55    path: PathBuf,
56    /// The root key for this `CryptoBox`
57    root_key: Arc<RootKey>,
58    /// The root namespace for this `CryptoBox`
59    root_namespace: LsmFile<File, RootKey>,
60    /// Compression level for namespaces in this `CryptoBox`
61    compression: Option<i32>,
62    /// Maximum cache entries per namespace
63    max_cache_entries: Option<usize>,
64    /// Namespaces in this CryptoBox
65    namespaces: HashMap<String, (Namespace, LsmFile<File, DerivedKey>)>,
66}
67
68impl CryptoBox {
69    /// Initialize a new `CryptoBox`
70    ///
71    /// # Arguments
72    ///
73    ///   * `path`
74    ///
75    ///     The desired path to the `CryptoBox` directory. This must be a directory that the user
76    ///     has write permission to, and _does not_ already exist.
77    ///   * `compression`
78    ///
79    ///     The desired zstd compression level for this `CryptoBox`. Defaults to no compression.
80    ///   * `max_cache_entries`
81    ///
82    ///     The desired maximum number of write-cache entries before a flush automatically occurs.
83    ///     Defaults to 100.
84    ///   * `password`
85    ///
86    ///     The password to encrypt the root string with. This is specified as a byte slice for
87    ///     flexibility.
88    ///
89    ///
90    ///
91    /// # Errors
92    ///   * [`CryptoBoxError::DirectoryAlreadyExists`] if the desired path already exists
93    ///   * [`CryptoBoxError::FailedCreatingDirectory`], [`CryptoBoxError::RootKeyIO`], or
94    ///     [`CryptoBoxError::RootNamespaceInit`] if an IO error creating the `CryptoBox` occurs
95    #[instrument(skip(path, password), err)]
96    pub fn init(
97        path: impl AsRef<Path>,
98        compression: Option<i32>,
99        max_cache_entries: Option<usize>,
100        password: impl AsRef<[u8]>,
101    ) -> Result<Self, CryptoBoxError> {
102        let path = path.as_ref();
103        info!(?path, "Creating CryptoBox");
104        let password = password.as_ref();
105        // Bailout if the directory already exists
106        ensure!(
107            !path.exists(),
108            DirectoryAlreadyExists {
109                directory: format!("{:?}", path)
110            }
111        );
112        // Create the directory
113        create_dir_all(path).context(FailedCreatingDirectory {
114            directory: format!("{:?}", path),
115        })?;
116        // Create the namespaces directory
117        let namespaces_path = path.join("namespaces");
118        create_dir_all(&namespaces_path).context(FailedCreatingDirectory {
119            directory: format!("{:?}", namespaces_path),
120        })?;
121        // Generate the root key
122        let root_key = Arc::new(RootKey::random());
123        // Encrypt and write the key
124        let encrypted_root_key = root_key.encrypt(password).context(RootKeyEncryption)?;
125        let root_key_path = path.join("KEY");
126        let mut key_file = File::create(&root_key_path).context(RootKeyIO {
127            path: format!("{:?}", root_key_path),
128        })?;
129        serde_cbor::to_writer(&mut key_file, &encrypted_root_key).context(RootKeySerial)?;
130        key_file.flush().context(RootKeyIO {
131            path: format!("{:?}", root_key_path),
132        })?;
133        std::mem::drop(key_file);
134        // Create root namespace
135        let root_namespace_path = path.join("root");
136        let mut root_namespace =
137            LsmFile::create(&root_namespace_path, None, root_key.clone(), Some(0)).context(
138                RootNamespaceInit {
139                    path: format!("{:?}", root_namespace_path),
140                },
141            )?;
142        // Write the init
143        root_namespace
144            .insert(
145                &"",
146                &Control::Settings(Settings {
147                    compression,
148                    max_cache_entries,
149                }),
150            )
151            .context(RootNamespaceInit {
152                path: format!("{:?}", root_namespace_path),
153            })?;
154        // Write the namespaces record
155        root_namespace
156            .insert(
157                &"namespaces",
158                &Control::Namespaces(Namespaces { namespaces: vec![] }),
159            )
160            .context(RootNamespaceInit {
161                path: format!("{:?}", root_namespace_path),
162            })?;
163        root_namespace.flush().context(RootNamespaceInit {
164            path: format!("{:?}", root_namespace_path),
165        })?;
166
167        Ok(CryptoBox {
168            path: path.to_path_buf(),
169            root_key,
170            root_namespace,
171            compression,
172            namespaces: HashMap::new(),
173            max_cache_entries,
174        })
175    }
176
177    /// Opens an existing `CryptoBox`
178    ///
179    /// Will read the default compression and cache settings from the root namespace.
180    ///
181    /// # Arguments
182    ///
183    ///   * `path`
184    ///
185    ///     The path to the `CryptoBox` directory.
186    ///   * `password`
187    ///
188    ///     The password to encrypt the root string with. This is specified as a byte slice for
189    ///     flexibility.
190    ///
191    /// # Errors
192    ///
193    ///   * [`CryptoBoxError::DirectoryDoesNotExist`] if the directory does not exist
194    ///   * [`CryptoBoxError::RootKeyDecryption`] if the root key fails to decrypt
195    ///   * [`CryptoBoxError::MissingConfiguration`] if the configuration control structure is missing
196    ///   * [`CryptoBoxError::RootKeyIO`], [`CryptoBoxError::MissingNamespaceDirectory`], or
197    ///     [`CryptoBoxError::RootNamespaceOpen`] if something goes wrong with IO while opening
198    #[instrument(skip(path, password), err)]
199    pub fn open(
200        path: impl AsRef<Path>,
201        password: impl AsRef<[u8]>,
202    ) -> Result<Self, CryptoBoxError> {
203        let path = path.as_ref().to_path_buf();
204        let password = password.as_ref();
205        info!(?path, "Opening CryptoBox");
206        // Make sure the directory exists
207        ensure!(
208            path.exists() && path.is_dir(),
209            DirectoryDoesNotExist {
210                path: format!("{:?}", path)
211            }
212        );
213        // Open up the root key and decrypt it
214        let root_key_path = path.join("KEY");
215        let mut root_key_file = File::open(&root_key_path).context(RootKeyIO {
216            path: format!("{:?}", root_key_path),
217        })?;
218        let enc_root_key: EncryptedRootKey =
219            serde_cbor::from_reader(&mut root_key_file).context(RootKeySerial)?;
220        let root_key = Arc::new(enc_root_key.decrypt(password).context(RootKeyDecryption)?);
221        // Close the file
222        std::mem::drop(root_key_file);
223        // Open the root namespace
224        let root_namespace_path = path.join("root");
225        let mut root_namespace =
226            LsmFile::open(&root_namespace_path, None, root_key.clone(), Some(0)).context(
227                RootNamespaceOpen {
228                    path: format!("{:?}", root_namespace_path),
229                },
230            )?;
231        // ensure the namespaces directory exists
232        ensure!(path.join("namespaces").exists(), MissingNamespaceDirectory);
233        // Get the configuration
234        if let Control::Settings(settings) = root_namespace
235            .get(&"")
236            .ok()
237            .context(MissingConfiguration)?
238            .context(MissingConfiguration)?
239        {
240            // Get the namespaces
241            if let Control::Namespaces(namespaces_raw) = root_namespace
242                .get(&"namespaces")
243                .ok()
244                .context(MissingConfiguration)?
245                .context(MissingConfiguration)?
246            {
247                let mut namespaces = HashMap::new();
248                for namespace in namespaces_raw.namespaces {
249                    let name = namespace.name.clone();
250                    let path = path.join("namespaces").join(namespace.uuid.to_string());
251                    let lsm_file = LsmFile::open(
252                        &path,
253                        settings.compression,
254                        namespace.key.clone(),
255                        settings.max_cache_entries,
256                    )
257                    .context(NamespaceOpen { name: name.clone() })?;
258                    namespaces.insert(name, (namespace, lsm_file));
259                }
260                Ok(CryptoBox {
261                    path,
262                    root_key,
263                    root_namespace,
264                    compression: settings.compression,
265                    namespaces,
266                    max_cache_entries: settings.max_cache_entries,
267                })
268            } else {
269                Err(CryptoBoxError::MissingConfiguration)
270            }
271        } else {
272            Err(CryptoBoxError::MissingConfiguration)
273        }
274    }
275
276    /// Check to see if a namespace exists
277    pub fn namespace_exists(&self, name: &str) -> bool {
278        self.namespaces.contains_key(name)
279    }
280
281    /// Get the list of namespaces
282    pub fn namespaces(&self) -> Vec<String> {
283        self.namespaces.keys().cloned().collect()
284    }
285
286    /// Create a namespace
287    ///
288    /// This method will create the namespace with the given name. In the event that the namespace already
289    /// exists, this method will sort circuit with `Ok()`.
290    ///
291    /// Generates a [`Uuid`](uuid::Uuid) and a derived key for the namespace automatically.
292    ///
293    /// # Errors
294    ///
295    /// Will return [`CryptoBoxError::NamespaceOpen`] if any underlying errors happen while initializing
296    /// the new name space.
297    #[instrument(skip(self), err)]
298    pub fn create_namespace(&mut self, name: String) -> Result<(), CryptoBoxError> {
299        if self.namespace_exists(&name) {
300            Ok(())
301        } else {
302            info!("Creating namespace");
303            let derived_key = Arc::new(self.root_key.derive(&name));
304            let uuid = uuid::Uuid::new_v4();
305            let path = self.path.join("namespaces").join(uuid.to_string());
306            // Init lsm file
307            let lsm_file = LsmFile::create(
308                &path,
309                self.compression,
310                derived_key.clone(),
311                self.max_cache_entries,
312            )
313            .context(NamespaceOpen { name: name.clone() })?;
314            // Build the namespace
315            let namespace = Namespace {
316                name: name.clone(),
317                key: derived_key,
318                uuid,
319            };
320            // Add it to our local store
321            self.namespaces.insert(name.clone(), (namespace, lsm_file));
322            // Update the namespaces entry
323            let namespaces: Vec<_> = self.namespaces.values().map(|(x, _)| x.clone()).collect();
324            let namespaces = Control::Namespaces(Namespaces { namespaces });
325            self.root_namespace
326                .insert(&"namespaces", &namespaces)
327                .context(NamespaceOpen { name: name.clone() })?;
328            self.root_namespace
329                .flush()
330                .context(NamespaceOpen { name })?;
331            Ok(())
332        }
333    }
334
335    /// Gets the specified item from the specified namespace, if it exists
336    ///
337    /// # Arguments
338    ///
339    ///   * `key` - Key for the key/value pair to be retrieved
340    ///   * `namespace` - The namespace the item was from
341    ///
342    /// # Errors
343    ///
344    ///   * [`CryptoBoxError::NoSuchNamespace`] if the namespace does not exist
345    ///   * [`CryptoBoxError::Fetch`] if an underlying error occurs
346    #[instrument(skip(self, key, namespace), err)]
347    pub fn get<K, V>(&mut self, key: &K, namespace: &str) -> Result<Option<V>, CryptoBoxError>
348    where
349        K: Serialize,
350        V: DeserializeOwned,
351    {
352        if let Some((_, lsm)) = self.namespaces.get_mut(namespace) {
353            // Try to get the item from the lsm
354            lsm.get(key).context(Fetch)
355        } else {
356            Err(CryptoBoxError::NoSuchNamespace {
357                name: namespace.to_string(),
358            })
359        }
360    }
361
362    /// Gets the specified item from the root namespace, if it exists
363    ///
364    /// # Arguments
365    ///
366    ///   * `key` - Key for the key/value pair to be retrieved
367    ///
368    /// # Errors
369    ///
370    ///   * [`CryptoBoxError::Fetch`] if an underlying error occurs
371    #[instrument(skip(self, key), err)]
372    pub fn get_root<K, V>(&mut self, key: &K) -> Result<Option<V>, CryptoBoxError>
373    where
374        K: Serialize,
375        V: DeserializeOwned,
376    {
377        self.root_namespace.get(key).context(Fetch)
378    }
379
380    /// Stores the specified item from the specified namespace
381    ///
382    /// # Arguments
383    ///
384    ///   * `key` - Key for the key/value pair to be stored
385    ///   * `value` - Value for the key/value pair to be stored
386    ///   * `namespace` - The namespace the item will be stored in
387    ///
388    /// # Errors
389    ///
390    ///   * [`CryptoBoxError::NoSuchNamespace`] if the namespace does not exist
391    ///   * [`CryptoBoxError::Store`] if an underlying error occurs
392    #[instrument(skip(self, key, value, namespace), err)]
393    pub fn insert<K, V>(
394        &mut self,
395        key: &K,
396        value: &V,
397        namespace: &str,
398    ) -> Result<(), CryptoBoxError>
399    where
400        K: Serialize,
401        V: Serialize,
402    {
403        if let Some((_, lsm)) = self.namespaces.get_mut(namespace) {
404            // Store the key in the lsmfile
405            lsm.insert(key, value).context(Store)
406        } else {
407            Err(CryptoBoxError::NoSuchNamespace {
408                name: namespace.to_string(),
409            })
410        }
411    }
412
413    /// Stores the specified item in the root namespace
414    ///
415    /// # Arguments
416    ///
417    ///   * `key` - Key for the key/value pair to be stored
418    ///   * `value` - Value for the key/value pair to be stored
419    ///
420    /// # Errors
421    ///
422    ///   * [`CryptoBoxError::Store`] if an underlying error occurs
423    #[instrument(skip(self, key, value), err)]
424    pub fn insert_root<K, V>(&mut self, key: &K, value: &V) -> Result<(), CryptoBoxError>
425    where
426        K: Serialize,
427        V: Serialize,
428    {
429        self.root_namespace.insert(key, value).context(Store)
430    }
431
432    /// Returns true if the specified namespace contains the provided key
433    ///
434    /// # Arguments
435    ///
436    ///   * `key` - Key for the key/value pair to be retrieved
437    ///   * `namespace` - The namespace the item was from
438    ///
439    /// # Errors
440    ///
441    ///   * [`CryptoBoxError::NoSuchNamespace`] if the namespace does not exist
442    ///   * [`CryptoBoxError::Fetch`] if an underlying error occurs
443    #[instrument(skip(self, key, namespace), err)]
444    pub fn contains_key<K, V>(&mut self, key: &K, namespace: &str) -> Result<bool, CryptoBoxError>
445    where
446        K: Serialize,
447        V: DeserializeOwned,
448    {
449        let res = self.get::<K, V>(key, namespace)?;
450        Ok(res.is_some())
451    }
452
453    /// Will flush all of the namespaces in the `CryptoBox`, including the root namespace.
454    ///
455    /// # Errors
456    ///
457    /// Will return [`CryptoBoxError::Flush`] if any underlying errors occur.
458    #[instrument(skip(self))]
459    pub fn flush(&mut self) -> Result<(), CryptoBoxError> {
460        let mut errors = vec![];
461        // First try the root namespace
462        let res = self.root_namespace.flush();
463        if let Err(e) = res {
464            errors.push((None, e));
465        }
466        // Now try all the namespaces
467        for (name, (_, lsm)) in &mut self.namespaces {
468            if let Err(e) = lsm.flush() {
469                errors.push((Some(name.clone()), e));
470            }
471        }
472
473        if errors.is_empty() {
474            Ok(())
475        } else {
476            Err(CryptoBoxError::Flush { sources: errors })
477        }
478    }
479
480    /// Gets all the key/value pairs in a namespace as [`HashMap`]
481    ///
482    /// # Errors
483    ///
484    /// Will return [`CryptoBoxError::Fetch`] if there are any underlying errors.
485    pub fn to_hashmap<K, V>(&mut self, namespace: &str) -> Result<HashMap<K, V>, CryptoBoxError>
486    where
487        K: DeserializeOwned + Serialize + std::hash::Hash + Eq,
488        V: DeserializeOwned,
489    {
490        if let Some((_, lsm)) = self.namespaces.get_mut(namespace) {
491            // Store the key in the lsmfile
492            lsm.to_hashmap().context(Fetch)
493        } else {
494            Err(CryptoBoxError::NoSuchNamespace {
495                name: namespace.to_string(),
496            })
497        }
498    }
499
500    /// Gets all the key/value pairs in a namespace as [`Vec`] of pairs
501    ///
502    /// # Errors
503    ///
504    /// Will return [`CryptoBoxError::Fetch`] if there are any underlying errors.
505    pub fn to_pairs<K, V>(&mut self, namespace: &str) -> Result<Vec<(K, V)>, CryptoBoxError>
506    where
507        K: DeserializeOwned + Serialize + Eq + Clone,
508        V: DeserializeOwned + Clone,
509    {
510        if let Some((_, lsm)) = self.namespaces.get_mut(namespace) {
511            // Store the key in the lsmfile
512            lsm.to_pairs().context(Fetch)
513        } else {
514            Err(CryptoBoxError::NoSuchNamespace {
515                name: namespace.to_string(),
516            })
517        }
518    }
519
520    /// Gets all the key/value pairs in the root namespace as a [`Vec`] of pairs
521    ///
522    /// # Errors
523    ///
524    /// Will return [`CryptoBoxError::Fetch`] if there are any underlying errors.
525    pub fn root_to_pairs<K, V>(&mut self) -> Result<Vec<(K, V)>, CryptoBoxError>
526    where
527        K: DeserializeOwned + Serialize + Eq + Clone,
528        V: DeserializeOwned + Clone,
529    {
530        // Store the key in the lsmfile
531        self.root_namespace.to_pairs().context(Fetch)
532    }
533}
534
535/// Unit tests for `CryptoBox`
536#[cfg(test)]
537mod tests {
538    use super::*;
539    use std::collections::HashSet;
540    use tempfile::tempdir;
541    /// initialization
542    mod init {
543        use super::*;
544        /// Make sure it produces the correct files and directories
545        #[test]
546        fn directory_layout() -> Result<(), CryptoBoxError> {
547            let tempdir = tempdir().context(FailedCreatingDirectory {
548                directory: "tempdir".to_string(),
549            })?;
550            let path = tempdir.path().join("box");
551            let _crypto_box = CryptoBox::init(&path, None, None, "testing")?;
552            assert!(path.join("KEY").exists());
553            assert!(path.join("root").exists());
554            assert!(path.join("namespaces").exists());
555            Ok(())
556        }
557        /// Create a box and make sure we can open it back up
558        #[test]
559        fn init_open() -> Result<(), CryptoBoxError> {
560            let tempdir = tempdir().context(FailedCreatingDirectory {
561                directory: "tempdir".to_string(),
562            })?;
563            let path = tempdir.path().join("box");
564            // Initialize the box
565            let crypto_box = CryptoBox::init(&path, None, None, "testing")?;
566            // Drop it
567            std::mem::drop(crypto_box);
568            // Open it back up
569            let _crypto_box = CryptoBox::open(&path, "testing")?;
570            Ok(())
571        }
572        /// Create a box, add some namespaces, close it, open it, and verify the name spaces
573        #[test]
574        fn namespaces() -> Result<(), CryptoBoxError> {
575            let namespace_names = ["one", "two", "three"]
576                .into_iter()
577                .map(std::string::ToString::to_string)
578                .collect::<HashSet<_>>();
579            let tempdir = tempdir().context(FailedCreatingDirectory {
580                directory: "tempdir".to_string(),
581            })?;
582            let path = tempdir.path().join("box");
583            // Initialize the box
584            let mut crypto_box = CryptoBox::init(&path, None, None, "testing")?;
585            // Add the namespaces
586            for namespace in &namespace_names {
587                crypto_box.create_namespace(namespace.to_string())?;
588            }
589            // Drop it
590            std::mem::drop(crypto_box);
591            // Open it back up
592            let crypto_box = CryptoBox::open(&path, "testing")?;
593            // Make sure the namespaces match
594            assert_eq!(
595                namespace_names,
596                crypto_box.namespaces().into_iter().collect::<HashSet<_>>()
597            );
598
599            Ok(())
600        }
601    }
602    /// Basic functionality aka smoke testing
603    mod box_smoke {
604        use super::*;
605        /// Test basic insertions
606        ///
607        /// A handful of pre baked insertions into a single namespace, without flushing or reloading
608        #[test]
609        fn basic_insertions() -> Result<(), CryptoBoxError> {
610            let tempdir = tempdir().context(FailedCreatingDirectory {
611                directory: "tempdir".to_string(),
612            })?;
613            let path = tempdir.path().join("box");
614            let pairs = [(1, 2), (3, 4), (5, 6)];
615            // Initialize the box
616            let mut crypto_box = CryptoBox::init(&path, None, None, "testing")?;
617            // Add the empty string namespace
618            crypto_box.create_namespace("".to_string())?;
619            // do the insertions
620            let namespace = "";
621            for (key, value) in &pairs {
622                crypto_box.insert(key, value, namespace)?;
623            }
624            // Do the comparison
625            for (key, value) in &pairs {
626                let res = crypto_box.get(key, namespace)?;
627                if Some(*value) != res {
628                    panic!("Unable to retrieve pair k: {} v: {}", key, value);
629                }
630            }
631            Ok(())
632        }
633        /// Test basic insertions with flush
634        ///
635        /// A handful of pre baked insertions into a single namespace, with a flush and reload
636        #[test]
637        fn basic_insertions_flush() -> Result<(), CryptoBoxError> {
638            let tempdir = tempdir().context(FailedCreatingDirectory {
639                directory: "tempdir".to_string(),
640            })?;
641            let path = tempdir.path().join("box");
642            let pairs = [(1, 2), (3, 4), (5, 6)];
643            // Initialize the box
644            let mut crypto_box = CryptoBox::init(&path, None, None, "testing")?;
645            // Add the empty string namespace
646            crypto_box.create_namespace("".to_string())?;
647            // do the insertions
648            let namespace = "";
649            for (key, value) in &pairs {
650                crypto_box.insert(key, value, namespace)?;
651            }
652            // Flush and drop the box
653            crypto_box.flush()?;
654            std::mem::drop(crypto_box);
655            // Reopen the box
656            let mut crypto_box = CryptoBox::open(&path, "testing")?;
657            // Do the comparison
658            for (key, value) in &pairs {
659                let res = crypto_box.get(key, namespace)?;
660                if Some(*value) != res {
661                    panic!("Unable to retrieve pair k: {} v: {}", key, value);
662                }
663            }
664            Ok(())
665        }
666        /// Test basic insertions with `HashMap` export
667        #[test]
668        fn basic_insertions_hashmap() -> Result<(), CryptoBoxError> {
669            let tempdir = tempdir().context(FailedCreatingDirectory {
670                directory: "tempdir".to_string(),
671            })?;
672            let path = tempdir.path().join("box");
673            let pairs = [(1, 2), (3, 4), (5, 6)];
674            // Initialize the box
675            let mut crypto_box = CryptoBox::init(&path, None, None, "testing")?;
676            // Add the empty string namespace
677            crypto_box.create_namespace("".to_string())?;
678            // do the insertions
679            let namespace = "";
680            for (key, value) in &pairs {
681                crypto_box.insert(key, value, namespace)?;
682            }
683            assert_eq!(
684                crypto_box.to_hashmap("")?,
685                pairs.into_iter().collect::<HashMap<_, _>>()
686            );
687            Ok(())
688        }
689        /// Test basic insertions with `pairs` export
690        #[test]
691        fn basic_insertions_pairs() -> Result<(), CryptoBoxError> {
692            let tempdir = tempdir().context(FailedCreatingDirectory {
693                directory: "tempdir".to_string(),
694            })?;
695            let path = tempdir.path().join("box");
696            let pairs = [(1, 2), (3, 4), (5, 6)];
697            // Initialize the box
698            let mut crypto_box = CryptoBox::init(&path, None, None, "testing")?;
699            // Add the empty string namespace
700            crypto_box.create_namespace("".to_string())?;
701            // do the insertions
702            let namespace = "";
703            for (key, value) in &pairs {
704                crypto_box.insert(key, value, namespace)?;
705            }
706            let comparison = pairs.into_iter().collect::<HashMap<i32, i32>>();
707            assert_eq!(crypto_box.to_hashmap("")?, comparison,);
708            assert_eq!(
709                crypto_box
710                    .to_pairs::<i32, i32>("")?
711                    .into_iter()
712                    .collect::<HashMap<_, _>>(),
713                comparison
714            );
715            Ok(())
716        }
717    }
718    /// Test failure modes of the box
719    mod failures {
720        use super::*;
721        #[test]
722        fn bad_password() -> Result<(), CryptoBoxError> {
723            let tempdir = tempdir().context(FailedCreatingDirectory {
724                directory: "tempdir".to_string(),
725            })?;
726            let path = tempdir.path().join("box");
727            // Initialize the box
728            let crypto_box = CryptoBox::init(&path, None, None, "testing")?;
729            // Drop it
730            std::mem::drop(crypto_box);
731            // Open it back up with the wrong password
732            let crypto_box = CryptoBox::open(&path, "testing 2");
733            assert!(matches!(
734                crypto_box,
735                Err(CryptoBoxError::RootKeyDecryption { .. })
736            ));
737            Ok(())
738        }
739    }
740}