use alloc::boxed::Box;
use crate::jvck::metadata::{
self, JvckHeader, JvckSecrets, ENCRYPTED_METADATA_SIZE, METADATA_BLOCK_SIZE,
OFF_ENCRYPTED_METADATA, OFF_SALT, OFF_VOLUME_ID, SALT_SIZE,
};
use crate::store::SectorIo;
use crate::types::VolumeState;
use crate::{VckError, VckResult};
pub struct Unsealed {
pub encrypted_offset: u64,
pub state: VolumeState,
pub secrets: JvckSecrets,
}
pub struct ReplicaCtx<'a> {
header: &'a JvckHeader,
block: [u8; METADATA_BLOCK_SIZE],
io: &'a dyn SectorIo,
vendor_base_lba: u64,
vendor_sector_count: u64,
sector_size: u32,
replica_index: usize,
}
impl<'a> ReplicaCtx<'a> {
pub(crate) fn new(
header: &'a JvckHeader,
block: [u8; METADATA_BLOCK_SIZE],
io: &'a dyn SectorIo,
vendor_base_lba: u64,
vendor_sector_count: u64,
sector_size: u32,
replica_index: usize,
) -> Self {
Self {
header,
block,
io,
vendor_base_lba,
vendor_sector_count,
sector_size,
replica_index,
}
}
pub fn header(&self) -> &JvckHeader {
self.header
}
pub fn block(&self) -> &[u8] {
&self.block
}
pub fn encrypted_metadata(&self) -> &[u8] {
&self.block[OFF_ENCRYPTED_METADATA..OFF_ENCRYPTED_METADATA + ENCRYPTED_METADATA_SIZE]
}
pub fn salt(&self) -> &[u8] {
&self.block[OFF_SALT..OFF_SALT + SALT_SIZE]
}
pub fn volume_id(&self) -> [u8; 16] {
self.block[OFF_VOLUME_ID..OFF_VOLUME_ID + 16]
.try_into()
.unwrap()
}
pub fn replica_index(&self) -> usize {
self.replica_index
}
pub fn vendor_data_sector_count(&self) -> u64 {
self.vendor_sector_count
}
pub fn read_vendor_data(&self, rel_sector: u64, buf: &mut [u8]) -> VckResult<()> {
let ss = self.sector_size as usize;
if ss == 0 || buf.is_empty() || !buf.len().is_multiple_of(ss) {
return Err(VckError::InvalidData(
"vendor data buffer must be a non-zero multiple of the sector size",
));
}
let nsec = (buf.len() / ss) as u64;
if rel_sector
.checked_add(nsec)
.is_none_or(|end| end > self.vendor_sector_count)
{
return Err(VckError::ValidationFailed(
"vendor data range exceeds the replica region",
));
}
self.io.read_sectors(self.vendor_base_lba + rel_sector, buf)
}
}
pub trait MetadataCodec: Send + Sync {
fn unseal(&self, ctx: &ReplicaCtx<'_>, vmk: &[u8]) -> VckResult<Unsealed>;
#[allow(clippy::too_many_arguments)]
fn seal(
&self,
header: &JvckHeader,
secrets: &JvckSecrets,
encrypted_offset: u64,
state: VolumeState,
salt: &[u8; SALT_SIZE],
vmk: &[u8],
out: &mut [u8; METADATA_BLOCK_SIZE],
) -> VckResult<()>;
fn read_offset(&self, ctx: &ReplicaCtx<'_>, vmk: &[u8]) -> VckResult<u64> {
Ok(self.unseal(ctx, vmk)?.encrypted_offset)
}
}
pub struct JvckCbcCodec;
impl MetadataCodec for JvckCbcCodec {
fn unseal(&self, ctx: &ReplicaCtx<'_>, vmk: &[u8]) -> VckResult<Unsealed> {
let (encrypted_offset, state, secrets) = metadata::decrypt_payload(ctx.block(), vmk)?;
Ok(Unsealed {
encrypted_offset,
state,
secrets,
})
}
fn seal(
&self,
header: &JvckHeader,
secrets: &JvckSecrets,
encrypted_offset: u64,
state: VolumeState,
salt: &[u8; SALT_SIZE],
vmk: &[u8],
out: &mut [u8; METADATA_BLOCK_SIZE],
) -> VckResult<()> {
header.encode(secrets, encrypted_offset, state, salt, vmk, out)
}
fn read_offset(&self, ctx: &ReplicaCtx<'_>, vmk: &[u8]) -> VckResult<u64> {
metadata::read_encrypted_offset(ctx.block(), vmk)
}
}
pub fn default_codec() -> Box<dyn MetadataCodec> {
Box::new(JvckCbcCodec)
}