dexios-domain 1.0.1

A library that contains the inner-workings and core logic for Dexios.
Documentation
//! This contains the actual logic for "shredding" a file.
//! 
//! This will not be effective on flash storage, and if you are planning to release a program that uses this function, I'd recommend putting the default number of passes to 1.

use rand::RngCore;
use std::cell::RefCell;
use std::fmt;
use std::io::{Seek, Write};

const BLOCK_SIZE: usize = 512;

#[derive(Debug)]
pub enum Error {
    ResetCursorPosition,
    OverwriteWithRandomBytes,
    OverwriteWithZeros,
    FlushFile,
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::ResetCursorPosition => f.write_str("Unable to reset cursor position"),
            Error::OverwriteWithRandomBytes => f.write_str("Unable to overwrite with random bytes"),
            Error::OverwriteWithZeros => f.write_str("Unable to overwrite with zeros"),
            Error::FlushFile => f.write_str("Unable to flush"),
        }
    }
}

impl std::error::Error for Error {}

pub struct Request<'a, W: Write + Seek> {
    pub writer: &'a RefCell<W>,
    pub buf_capacity: usize,
    pub passes: i32,
}

pub fn execute<W: Write + Seek>(req: Request<'_, W>) -> Result<(), Error> {
    let mut writer = req.writer.borrow_mut();
    for _ in 0..req.passes {
        writer.rewind().map_err(|_| Error::ResetCursorPosition)?;

        let mut blocks = vec![BLOCK_SIZE].repeat(req.buf_capacity / BLOCK_SIZE);
        blocks.push(req.buf_capacity % BLOCK_SIZE);

        for block_size in blocks.into_iter().take_while(|bs| *bs > 0) {
            let mut block_buf = Vec::with_capacity(block_size);
            rand::thread_rng().fill_bytes(&mut block_buf);
            writer
                .write_all(&block_buf)
                .map_err(|_| Error::OverwriteWithRandomBytes)?;
        }

        writer.flush().map_err(|_| Error::FlushFile)?;
    }

    writer.rewind().map_err(|_| Error::ResetCursorPosition)?;
    writer
        .write_all(&[0].repeat(req.buf_capacity))
        .map_err(|_| Error::OverwriteWithZeros)?;
    writer.flush().map_err(|_| Error::FlushFile)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Cursor;

    fn make_test(capacity: usize, passes: i32) {
        let mut buf = Vec::with_capacity(capacity);
        rand::thread_rng().fill_bytes(&mut buf);

        let writer = Cursor::new(&mut buf);

        let req = Request {
            writer: &RefCell::new(writer),
            buf_capacity: capacity,
            passes,
        };

        match execute(req) {
            Ok(_) => {
                assert_eq!(buf.len(), capacity);
                assert_eq!(buf, vec![0].repeat(capacity));
            }
            _ => unreachable!(),
        }
    }

    #[test]
    fn should_overwrite_empty_content() {
        make_test(0, 1);
    }

    #[test]
    fn should_overwrite_small_content() {
        make_test(100, 1);
    }

    #[test]
    fn should_overwrite_perfectly_divisible_content() {
        make_test(BLOCK_SIZE, 1);
    }

    #[test]
    fn should_overwrite_not_perfectly_divisible_content() {
        make_test(515, 1);
    }

    #[test]
    fn should_overwrite_large_content() {
        make_test(BLOCK_SIZE * 100, 1);
    }

    #[test]
    fn should_erase_fill_random_bytes_one_hundred_times() {
        make_test(515, 100);
    }

    #[test]
    fn should_erase_fill_random_bytes_zero_times() {
        make_test(515, 0);
    }
}