#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use super::crypto::*;
use super::types::*;
use super::volume::VolumeStorage;
pub trait VaultBlockDeviceTrait {
fn read(&self, offset: u64, buf: &mut [u8]) -> Result<usize, VaultError>;
fn write(&mut self, offset: u64, buf: &[u8]) -> Result<usize, VaultError>;
fn size(&self) -> u64;
fn sync(&mut self) -> Result<(), VaultError>;
fn is_read_only(&self) -> bool;
}
pub struct VaultBlockDevice {
storage: VolumeStorage,
master_key: [u8; 64],
secondary_key: [u8; 64],
data_offset: u64,
data_size: u64,
algorithm: EncryptionAlgorithm,
sector_size: u32,
read_only: bool,
protected_start: Option<u64>,
protected_end: Option<u64>,
bytes_read: AtomicU64,
bytes_written: AtomicU64,
sectors_encrypted: AtomicU64,
sectors_decrypted: AtomicU64,
is_open: AtomicBool,
}
impl VaultBlockDevice {
pub fn new(
storage: VolumeStorage,
header: &VaultHeader,
algorithm: EncryptionAlgorithm,
read_only: bool,
) -> Self {
Self {
storage,
master_key: header.master_key,
secondary_key: header.secondary_key,
data_offset: header.data_offset,
data_size: header.data_size,
algorithm,
sector_size: header.sector_size,
read_only,
protected_start: None,
protected_end: None,
bytes_read: AtomicU64::new(0),
bytes_written: AtomicU64::new(0),
sectors_encrypted: AtomicU64::new(0),
sectors_decrypted: AtomicU64::new(0),
is_open: AtomicBool::new(true),
}
}
pub fn set_protected_range(&mut self, start: u64, end: u64) {
self.protected_start = Some(start);
self.protected_end = Some(end);
}
pub fn clear_protected_range(&mut self) {
self.protected_start = None;
self.protected_end = None;
}
fn is_protected(&self, offset: u64, len: u64) -> bool {
if let (Some(start), Some(end)) = (self.protected_start, self.protected_end) {
let range_end = offset + len;
offset < end && range_end > start
} else {
false
}
}
pub fn stats(&self) -> VaultBlockDeviceStats {
VaultBlockDeviceStats {
bytes_read: self.bytes_read.load(Ordering::Relaxed),
bytes_written: self.bytes_written.load(Ordering::Relaxed),
sectors_encrypted: self.sectors_encrypted.load(Ordering::Relaxed),
sectors_decrypted: self.sectors_decrypted.load(Ordering::Relaxed),
}
}
pub fn close(&mut self) {
self.is_open.store(false, Ordering::SeqCst);
self.master_key = [0u8; 64];
self.secondary_key = [0u8; 64];
}
pub fn is_open(&self) -> bool {
self.is_open.load(Ordering::SeqCst)
}
pub fn into_storage(mut self) -> VolumeStorage {
self.close();
self.storage
}
}
impl VaultBlockDeviceTrait for VaultBlockDevice {
fn read(&self, offset: u64, buf: &mut [u8]) -> Result<usize, VaultError> {
if !self.is_open() {
return Err(VaultError::NotMounted);
}
if offset + buf.len() as u64 > self.data_size {
return Err(VaultError::IoError("Read past end of volume".into()));
}
let sector_size = self.sector_size as usize;
let file_offset = self.data_offset + offset;
let encrypted = self.storage.read(file_offset, buf.len())?;
let start_sector = offset / sector_size as u64;
let mut decrypted = encrypted;
for (i, chunk) in decrypted.chunks_mut(sector_size).enumerate() {
let sector_num = start_sector + i as u64;
decrypt_sector(
chunk,
sector_num,
&self.master_key,
&self.secondary_key,
self.algorithm,
);
self.sectors_decrypted.fetch_add(1, Ordering::Relaxed);
}
buf.copy_from_slice(&decrypted);
self.bytes_read
.fetch_add(buf.len() as u64, Ordering::Relaxed);
Ok(buf.len())
}
fn write(&mut self, offset: u64, buf: &[u8]) -> Result<usize, VaultError> {
if !self.is_open() {
return Err(VaultError::NotMounted);
}
if self.read_only {
return Err(VaultError::IoError("Volume is read-only".into()));
}
if offset + buf.len() as u64 > self.data_size {
return Err(VaultError::IoError("Write past end of volume".into()));
}
if self.is_protected(offset, buf.len() as u64) {
return Err(VaultError::ProtectedAreaViolation);
}
let sector_size = self.sector_size as usize;
let file_offset = self.data_offset + offset;
let start_sector = offset / sector_size as u64;
let mut encrypted = buf.to_vec();
for (i, chunk) in encrypted.chunks_mut(sector_size).enumerate() {
let sector_num = start_sector + i as u64;
encrypt_sector(
chunk,
sector_num,
&self.master_key,
&self.secondary_key,
self.algorithm,
);
self.sectors_encrypted.fetch_add(1, Ordering::Relaxed);
}
self.storage.write(file_offset, &encrypted)?;
self.bytes_written
.fetch_add(buf.len() as u64, Ordering::Relaxed);
Ok(buf.len())
}
fn size(&self) -> u64 {
self.data_size
}
fn sync(&mut self) -> Result<(), VaultError> {
Ok(())
}
fn is_read_only(&self) -> bool {
self.read_only
}
}
#[derive(Debug, Clone, Copy)]
pub struct VaultBlockDeviceStats {
pub bytes_read: u64,
pub bytes_written: u64,
pub sectors_decrypted: u64,
pub sectors_encrypted: u64,
}
pub struct ProtectedVaultDevice {
inner: VaultBlockDevice,
hidden_header: Option<VaultHeader>,
}
impl ProtectedVaultDevice {
pub fn new_with_protection(
storage: VolumeStorage,
outer_header: &VaultHeader,
hidden_header: VaultHeader,
algorithm: EncryptionAlgorithm,
) -> Self {
let mut device = VaultBlockDevice::new(storage, outer_header, algorithm, false);
let protected_start = hidden_header.data_offset - outer_header.data_offset;
let protected_end = protected_start + hidden_header.data_size;
device.set_protected_range(protected_start, protected_end);
Self {
inner: device,
hidden_header: Some(hidden_header),
}
}
pub fn new_unprotected(
storage: VolumeStorage,
header: &VaultHeader,
algorithm: EncryptionAlgorithm,
read_only: bool,
) -> Self {
Self {
inner: VaultBlockDevice::new(storage, header, algorithm, read_only),
hidden_header: None,
}
}
pub fn has_protection(&self) -> bool {
self.hidden_header.is_some()
}
pub fn inner(&self) -> &VaultBlockDevice {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut VaultBlockDevice {
&mut self.inner
}
}
impl VaultBlockDeviceTrait for ProtectedVaultDevice {
fn read(&self, offset: u64, buf: &mut [u8]) -> Result<usize, VaultError> {
self.inner.read(offset, buf)
}
fn write(&mut self, offset: u64, buf: &[u8]) -> Result<usize, VaultError> {
self.inner.write(offset, buf)
}
fn size(&self) -> u64 {
self.inner.size()
}
fn sync(&mut self) -> Result<(), VaultError> {
self.inner.sync()
}
fn is_read_only(&self) -> bool {
self.inner.is_read_only()
}
}
pub struct LcpfsVaultAdapter {
device: VaultBlockDevice,
}
impl LcpfsVaultAdapter {
pub fn new(device: VaultBlockDevice) -> Self {
Self { device }
}
pub fn into_inner(self) -> VaultBlockDevice {
self.device
}
pub fn read(&self, offset: u64, buf: &mut [u8]) -> Result<usize, VaultError> {
VaultBlockDeviceTrait::read(&self.device, offset, buf)
}
pub fn write(&mut self, offset: u64, buf: &[u8]) -> Result<usize, VaultError> {
VaultBlockDeviceTrait::write(&mut self.device, offset, buf)
}
pub fn size(&self) -> u64 {
VaultBlockDeviceTrait::size(&self.device)
}
pub fn block_size(&self) -> u32 {
self.device.sector_size
}
pub fn is_read_only(&self) -> bool {
VaultBlockDeviceTrait::is_read_only(&self.device)
}
pub fn device_path(&self) -> &str {
self.device.storage.path()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vault::volume::create_volume;
fn create_test_device() -> VaultBlockDevice {
let options = VaultCreateOptions {
size: 2 * 1024 * 1024,
encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256,
wipe_mode: WipeMode::Zero,
..Default::default()
};
let storage = create_volume("test_password", &options).unwrap();
let mount_options = VaultMountOptions {
hash_hint: Some(HashAlgorithm::Sha256),
encryption_hint: Some(EncryptionAlgorithm::Aes256),
..Default::default()
};
let (header, enc) =
crate::vault::volume::open_volume(&storage, "test_password", &mount_options).unwrap();
VaultBlockDevice::new(storage, &header, enc, false)
}
#[test]
fn test_read_write() {
let mut device = create_test_device();
let write_data = vec![0xABu8; 512];
device.write(0, &write_data).unwrap();
let mut read_data = vec![0u8; 512];
device.read(0, &mut read_data).unwrap();
assert_eq!(write_data, read_data);
}
#[test]
fn test_multi_sector_io() {
let mut device = create_test_device();
let write_data: Vec<u8> = (0..2048).map(|i| (i % 256) as u8).collect();
device.write(0, &write_data).unwrap();
let mut read_data = vec![0u8; 2048];
device.read(0, &mut read_data).unwrap();
assert_eq!(write_data, read_data);
}
#[test]
fn test_statistics() {
let mut device = create_test_device();
let data = vec![0u8; 1024];
device.write(0, &data).unwrap();
let mut buf = vec![0u8; 1024];
device.read(0, &mut buf).unwrap();
let stats = device.stats();
assert_eq!(stats.bytes_written, 1024);
assert_eq!(stats.bytes_read, 1024);
assert!(stats.sectors_encrypted > 0);
assert!(stats.sectors_decrypted > 0);
}
#[test]
fn test_protected_range() {
let mut device = create_test_device();
device.set_protected_range(1024, 2048);
let data = vec![0u8; 512];
assert!(device.write(0, &data).is_ok());
assert!(matches!(
device.write(1024, &data),
Err(VaultError::ProtectedAreaViolation)
));
assert!(matches!(
device.write(768, &data),
Err(VaultError::ProtectedAreaViolation)
));
assert!(device.write(2048, &data).is_ok());
let mut buf = vec![0u8; 512];
assert!(device.read(1024, &mut buf).is_ok());
}
#[test]
fn test_read_only() {
let options = VaultCreateOptions {
size: 2 * 1024 * 1024,
encryption: EncryptionAlgorithm::Aes256,
hash: HashAlgorithm::Sha256,
wipe_mode: WipeMode::Zero,
..Default::default()
};
let storage = create_volume("test_password", &options).unwrap();
let mount_options = VaultMountOptions {
hash_hint: Some(HashAlgorithm::Sha256),
encryption_hint: Some(EncryptionAlgorithm::Aes256),
..Default::default()
};
let (header, enc) =
crate::vault::volume::open_volume(&storage, "test_password", &mount_options).unwrap();
let mut device = VaultBlockDevice::new(storage, &header, enc, true);
assert!(device.is_read_only());
let data = vec![0u8; 512];
assert!(device.write(0, &data).is_err());
}
#[test]
fn test_close() {
let mut device = create_test_device();
assert!(device.is_open());
device.close();
assert!(!device.is_open());
let mut buf = vec![0u8; 512];
assert!(matches!(
device.read(0, &mut buf),
Err(VaultError::NotMounted)
));
}
}