1use alloc::boxed::Box;
20use alloc::vec::Vec;
21use core::sync::atomic::{AtomicU16, AtomicU64, Ordering};
22
23use zeroize::Zeroizing;
24
25use crate::{
26 jvck::{
27 codec::{JvckCbcCodec, MetadataCodec, ReplicaCtx, Unsealed},
28 metadata::{self, JvckHeader, JvckSecrets, METADATA_BLOCK_SIZE},
29 options::JvckMetadataOptions,
30 },
31 store::{EncryptedOffsetStore, SectorIo},
32 types::{EncryptedOffset, VolumeState},
33 VckError, VckResult,
34};
35
36#[derive(Debug, Clone, Copy)]
38pub struct Geometry {
39 pub offset_sector: u64,
41 pub data_sectors: u64,
43 pub sector_size: u32,
44}
45
46pub struct JvckMetadataStore<S: SectorIo> {
47 io: S,
48 options: JvckMetadataOptions,
49 vmk: Zeroizing<Vec<u8>>,
50 geometry: Geometry,
51 volume_sectors: u64,
52 header: JvckHeader,
55 secrets: JvckSecrets,
58 offset: AtomicU64,
61 state: AtomicU16,
63 codec: Box<dyn MetadataCodec>,
67}
68
69fn replica_sectors(metadata_size: u32, sector_size: u32) -> u64 {
77 (metadata_size / sector_size) as u64
78}
79
80fn metadata_sector_lbas(
82 volume_sectors: u64,
83 replica_sectors: u64,
84 use_header: u32,
85 use_footer: u32,
86) -> Vec<u64> {
87 let mut lbas = Vec::with_capacity((use_header + use_footer) as usize);
88 for i in 0..use_header as u64 {
90 lbas.push(i * replica_sectors);
91 }
92 let footer_start = volume_sectors - use_footer as u64 * replica_sectors;
94 for j in 0..use_footer as u64 {
95 let region_start = footer_start + j * replica_sectors;
96 lbas.push(region_start + replica_sectors - 1);
97 }
98 lbas
99}
100
101fn vendor_data_base_lba_at(
104 volume_sectors: u64,
105 rs: u64,
106 use_header: u32,
107 use_footer: u32,
108 replica_index: usize,
109) -> Option<u64> {
110 let uh = use_header as usize;
111 let uf = use_footer as usize;
112 if replica_index < uh {
113 Some(replica_index as u64 * rs + 1)
115 } else if replica_index < uh + uf {
116 let j = (replica_index - uh) as u64;
117 let footer_start = volume_sectors - uf as u64 * rs;
118 Some(footer_start + j * rs)
120 } else {
121 None
122 }
123}
124
125fn compute_geometry(
126 sector_size: u32,
127 volume_sectors: u64,
128 options: &JvckMetadataOptions,
129) -> VckResult<Geometry> {
130 if (sector_size as usize) < METADATA_BLOCK_SIZE {
131 return Err(VckError::Unsupported("sector size smaller than 512"));
132 }
133 let rs = replica_sectors(options.metadata_size, sector_size);
138 if rs == 0 {
139 return Err(VckError::ValidationFailed(
140 "metadata_size smaller than one sector",
141 ));
142 }
143 let consumed = (options.use_header + options.use_footer) as u64 * rs;
144 if consumed >= volume_sectors {
145 return Err(VckError::ValidationFailed(
146 "volume too small to hold metadata replicas",
147 ));
148 }
149 Ok(Geometry {
150 offset_sector: options.use_header as u64 * rs,
151 data_sectors: volume_sectors - consumed,
152 sector_size,
153 })
154}
155
156fn read_block<S: SectorIo>(
157 io: &S,
158 sector_size: u32,
159 lba: u64,
160) -> VckResult<[u8; METADATA_BLOCK_SIZE]> {
161 let mut sector = alloc::vec![0u8; sector_size as usize];
162 io.read_sectors(lba, &mut sector)?;
163 let mut block = [0u8; METADATA_BLOCK_SIZE];
164 block.copy_from_slice(§or[..METADATA_BLOCK_SIZE]);
165 Ok(block)
166}
167
168fn write_block<S: SectorIo>(
169 io: &S,
170 sector_size: u32,
171 lba: u64,
172 block: &[u8; METADATA_BLOCK_SIZE],
173) -> VckResult<()> {
174 let mut sector = alloc::vec![0u8; sector_size as usize];
177 sector[..METADATA_BLOCK_SIZE].copy_from_slice(block);
178 io.write_sectors(lba, §or)
179}
180
181impl<S: SectorIo> JvckMetadataStore<S> {
182 pub fn open(io: S, vmk: &[u8]) -> VckResult<Self> {
188 JvckMetadataReader::open(io)?.into_store(vmk, |ctx| {
189 let codec: Box<dyn MetadataCodec> = Box::new(JvckCbcCodec);
190 let unsealed = codec.unseal(ctx, vmk)?;
191 Ok((codec, unsealed))
192 })
193 }
194
195 pub fn create(
202 io: S,
203 vmk: &[u8],
204 options: JvckMetadataOptions,
205 fvek_key1: [u8; 32],
206 fvek_key2: [u8; 32],
207 volume_id: [u8; 16],
208 codec: Box<dyn MetadataCodec>,
209 ) -> VckResult<Self> {
210 options.validate()?;
211 let sector_size = io.sector_size();
212 let volume_sectors = io.total_sectors();
213 let geometry = compute_geometry(sector_size, volume_sectors, &options)?;
214
215 let header = JvckHeader {
216 vendor_id: 0,
217 metadata_version: 1,
218 vendor_version: 0,
219 metadata_size: options.metadata_size,
220 sector_size,
221 header_replica_count: options.use_header as u8,
222 footer_replica_count: options.use_footer as u8,
223 volume_id,
224 vendor_reserved: [0u8; metadata::VENDOR_RESERVED_SIZE],
225 };
226 let secrets = JvckSecrets {
227 fvek_key1,
228 fvek_key2,
229 };
230
231 let store = Self {
232 io,
233 options,
234 vmk: Zeroizing::new(vmk.to_vec()),
235 geometry,
236 volume_sectors,
237 header,
238 secrets,
239 offset: AtomicU64::new(0),
240 state: AtomicU16::new(VolumeState::Encrypt.as_u16()),
241 codec,
242 };
243 store.write_all_replicas()?;
244 Ok(store)
245 }
246
247 pub fn load_offset(&self) -> VckResult<u64> {
254 let sector_size = self.geometry.sector_size;
255 let rs = replica_sectors(self.options.metadata_size, sector_size);
256 let lbas = metadata_sector_lbas(
257 self.volume_sectors,
258 rs,
259 self.options.use_header,
260 self.options.use_footer,
261 );
262
263 let mut best: Option<u64> = None;
264 for (idx, lba) in lbas.iter().enumerate() {
265 let block = match read_block(&self.io, sector_size, *lba) {
266 Ok(block) => block,
267 Err(_) => continue,
268 };
269 if metadata::verify_crc(&block).is_err() {
271 continue;
272 }
273 let vendor_base = vendor_data_base_lba_at(
274 self.volume_sectors,
275 rs,
276 self.options.use_header,
277 self.options.use_footer,
278 idx,
279 )
280 .unwrap_or(0);
281 let ctx = ReplicaCtx::new(
282 &self.header,
283 block,
284 &self.io as &dyn SectorIo,
285 vendor_base,
286 rs.saturating_sub(1),
287 sector_size,
288 idx,
289 );
290 if let Ok(offset) = self.codec.read_offset(&ctx, &self.vmk) {
291 best = Some(best.map_or(offset, |b| b.max(offset)));
292 }
293 }
294 best.ok_or(VckError::NotFound("no valid JVCK metadata replica"))
295 }
296
297 pub fn fvek_keys(&self) -> (&[u8; 32], &[u8; 32]) {
300 (&self.secrets.fvek_key1, &self.secrets.fvek_key2)
301 }
302
303 pub fn volume_id(&self) -> [u8; 16] {
305 self.header.volume_id
306 }
307
308 fn write_all_replicas(&self) -> VckResult<()> {
309 let mut salt = [0u8; metadata::SALT_SIZE];
312 crate::rng::fill_random(&mut salt)?;
313 let encrypted_offset = self.offset.load(Ordering::Relaxed);
314 let state = VolumeState::from_u16(self.state.load(Ordering::Relaxed));
315 let mut block = [0u8; METADATA_BLOCK_SIZE];
316 self.codec.seal(
317 &self.header,
318 &self.secrets,
319 encrypted_offset,
320 state,
321 &salt,
322 &self.vmk,
323 &mut block,
324 )?;
325 let rs = replica_sectors(self.options.metadata_size, self.geometry.sector_size);
326 for lba in metadata_sector_lbas(
327 self.volume_sectors,
328 rs,
329 self.options.use_header,
330 self.options.use_footer,
331 ) {
332 write_block(&self.io, self.geometry.sector_size, lba, &block)?;
333 }
334 Ok(())
335 }
336
337 pub fn offset_sector(&self) -> u64 {
338 self.geometry.offset_sector
339 }
340
341 pub fn data_sector_count(&self) -> u64 {
342 self.geometry.data_sectors
343 }
344
345 pub fn sector_size(&self) -> u32 {
346 self.geometry.sector_size
347 }
348
349 pub fn footer_replica_count(&self) -> u32 {
350 self.options.use_footer
351 }
352
353 pub fn metadata_size(&self) -> u32 {
354 self.options.metadata_size
355 }
356
357 pub fn header(&self) -> &JvckHeader {
361 &self.header
362 }
363
364 pub fn replica_count(&self) -> usize {
373 (self.options.use_header + self.options.use_footer) as usize
374 }
375
376 pub fn vendor_data_sector_count(&self) -> u64 {
379 replica_sectors(self.options.metadata_size, self.geometry.sector_size).saturating_sub(1)
380 }
381
382 fn vendor_data_base_lba(&self, replica_index: usize) -> Option<u64> {
384 let rs = replica_sectors(self.options.metadata_size, self.geometry.sector_size);
385 vendor_data_base_lba_at(
386 self.volume_sectors,
387 rs,
388 self.options.use_header,
389 self.options.use_footer,
390 replica_index,
391 )
392 }
393
394 fn vendor_data_lba_checked(
395 &self,
396 replica_index: usize,
397 rel_sector: u64,
398 len: usize,
399 ) -> VckResult<u64> {
400 let ss = self.geometry.sector_size as usize;
401 if ss == 0 || len == 0 || !len.is_multiple_of(ss) {
402 return Err(VckError::InvalidData(
403 "vendor data buffer must be a non-zero multiple of the sector size",
404 ));
405 }
406 let nsec = (len / ss) as u64;
407 let base = self
408 .vendor_data_base_lba(replica_index)
409 .ok_or(VckError::NotFound("vendor data replica index out of range"))?;
410 let count = self.vendor_data_sector_count();
411 if rel_sector.checked_add(nsec).is_none_or(|end| end > count) {
412 return Err(VckError::ValidationFailed(
413 "vendor data range exceeds the replica region",
414 ));
415 }
416 Ok(base + rel_sector)
417 }
418
419 pub fn read_vendor_data(
422 &self,
423 replica_index: usize,
424 rel_sector: u64,
425 buf: &mut [u8],
426 ) -> VckResult<()> {
427 let lba = self.vendor_data_lba_checked(replica_index, rel_sector, buf.len())?;
428 self.io.read_sectors(lba, buf)
429 }
430
431 pub fn write_vendor_data(
434 &self,
435 replica_index: usize,
436 rel_sector: u64,
437 buf: &[u8],
438 ) -> VckResult<()> {
439 let lba = self.vendor_data_lba_checked(replica_index, rel_sector, buf.len())?;
440 self.io.write_sectors(lba, buf)
441 }
442
443 pub fn write_vendor_data_all(&self, rel_sector: u64, buf: &[u8]) -> VckResult<()> {
452 self.vendor_data_lba_checked(0, rel_sector, buf.len())?;
454 for replica_index in 0..self.replica_count() {
455 self.write_vendor_data(replica_index, rel_sector, buf)?;
456 }
457 Ok(())
458 }
459
460 pub fn vendor_reserved(&self) -> &[u8; metadata::VENDOR_RESERVED_SIZE] {
462 &self.header.vendor_reserved
463 }
464
465 pub fn set_vendor_reserved(
473 &mut self,
474 vendor_reserved: &[u8; metadata::VENDOR_RESERVED_SIZE],
475 ) -> VckResult<()> {
476 self.header.vendor_reserved = *vendor_reserved;
477 self.write_all_replicas()
478 }
479}
480
481impl<S: SectorIo> EncryptedOffsetStore for JvckMetadataStore<S>
482where
483 S: Send + Sync + 'static,
484{
485 fn load(&self) -> VckResult<EncryptedOffset> {
486 Ok(EncryptedOffset {
487 sector: self.load_offset()?,
488 total_sectors: self.geometry.data_sectors,
489 })
490 }
491
492 fn store(&self, offset: &EncryptedOffset) -> VckResult<()> {
493 self.offset.store(offset.sector, Ordering::Relaxed);
494 self.write_all_replicas()
495 }
496
497 fn flush(&self) -> VckResult<()> {
498 Ok(())
500 }
501
502 fn load_state(&self) -> VckResult<VolumeState> {
503 Ok(VolumeState::from_u16(self.state.load(Ordering::Relaxed)))
504 }
505
506 fn store_state(&self, state: VolumeState) -> VckResult<()> {
507 self.state.store(state.as_u16(), Ordering::Relaxed);
510 self.write_all_replicas()
511 }
512}
513
514pub struct JvckMetadataReader<S: SectorIo> {
524 io: S,
525 options: JvckMetadataOptions,
526 geometry: Geometry,
527 volume_sectors: u64,
528 header: JvckHeader,
529}
530
531impl<S: SectorIo> JvckMetadataReader<S> {
532 pub fn open(io: S) -> VckResult<Self> {
537 let sector_size = io.sector_size();
538 if (sector_size as usize) < METADATA_BLOCK_SIZE {
539 return Err(VckError::Unsupported("sector size smaller than 512"));
540 }
541 let volume_sectors = io.total_sectors();
542 if volume_sectors == 0 {
543 return Err(VckError::NotFound("empty volume"));
544 }
545 for lba in [volume_sectors - 1, 0] {
546 let block = read_block(&io, sector_size, lba)?;
547 if metadata::verify_crc(&block).is_err() {
548 continue;
549 }
550 let header = JvckHeader::parse(&block)?;
551 let options = JvckMetadataOptions {
552 use_header: header.header_replica_count as u32,
553 use_footer: header.footer_replica_count as u32,
554 metadata_size: header.metadata_size,
555 };
556 let geometry = compute_geometry(sector_size, volume_sectors, &options)?;
557 return Ok(Self {
558 io,
559 options,
560 geometry,
561 volume_sectors,
562 header,
563 });
564 }
565 Err(VckError::NotFound("no JVCK metadata present"))
566 }
567
568 pub fn header(&self) -> &JvckHeader {
571 &self.header
572 }
573
574 pub fn geometry(&self) -> Geometry {
576 self.geometry
577 }
578
579 pub fn replica_count(&self) -> usize {
581 (self.options.use_header + self.options.use_footer) as usize
582 }
583
584 pub fn replica_ctx(&self, replica_index: usize) -> VckResult<ReplicaCtx<'_>> {
592 let sector_size = self.geometry.sector_size;
593 let rs = replica_sectors(self.options.metadata_size, sector_size);
594 let lbas = metadata_sector_lbas(
595 self.volume_sectors,
596 rs,
597 self.options.use_header,
598 self.options.use_footer,
599 );
600 let lba = *lbas
601 .get(replica_index)
602 .ok_or(VckError::NotFound("replica index out of range"))?;
603 let block = read_block(&self.io, sector_size, lba)?;
604 metadata::verify_crc(&block)?;
605 let vendor_base = vendor_data_base_lba_at(
606 self.volume_sectors,
607 rs,
608 self.options.use_header,
609 self.options.use_footer,
610 replica_index,
611 )
612 .unwrap_or(0);
613 Ok(ReplicaCtx::new(
614 &self.header,
615 block,
616 &self.io as &dyn SectorIo,
617 vendor_base,
618 rs.saturating_sub(1),
619 sector_size,
620 replica_index,
621 ))
622 }
623
624 pub fn read_vendor_data(
628 &self,
629 replica_index: usize,
630 rel_sector: u64,
631 buf: &mut [u8],
632 ) -> VckResult<()> {
633 let sector_size = self.geometry.sector_size;
634 let ss = sector_size as usize;
635 if ss == 0 || buf.is_empty() || !buf.len().is_multiple_of(ss) {
636 return Err(VckError::InvalidData(
637 "vendor data buffer must be a non-zero multiple of the sector size",
638 ));
639 }
640 let rs = replica_sectors(self.options.metadata_size, sector_size);
641 let base = vendor_data_base_lba_at(
642 self.volume_sectors,
643 rs,
644 self.options.use_header,
645 self.options.use_footer,
646 replica_index,
647 )
648 .ok_or(VckError::NotFound("vendor data replica index out of range"))?;
649 let nsec = (buf.len() / ss) as u64;
650 if rel_sector
651 .checked_add(nsec)
652 .is_none_or(|end| end > rs.saturating_sub(1))
653 {
654 return Err(VckError::ValidationFailed(
655 "vendor data range exceeds the replica region",
656 ));
657 }
658 self.io.read_sectors(base + rel_sector, buf)
659 }
660
661 pub fn into_store<F>(self, vmk: &[u8], mut select: F) -> VckResult<JvckMetadataStore<S>>
674 where
675 F: FnMut(&ReplicaCtx<'_>) -> VckResult<(Box<dyn MetadataCodec>, Unsealed)>,
676 {
677 let sector_size = self.geometry.sector_size;
678 let rs = replica_sectors(self.options.metadata_size, sector_size);
679 let lbas = metadata_sector_lbas(
680 self.volume_sectors,
681 rs,
682 self.options.use_header,
683 self.options.use_footer,
684 );
685
686 let mut chosen: Option<(Box<dyn MetadataCodec>, Unsealed)> = None;
687 let mut last_err: Option<VckError> = None;
688 for (idx, lba) in lbas.iter().enumerate() {
689 let block = match read_block(&self.io, sector_size, *lba) {
690 Ok(b) => b,
691 Err(e) => {
692 last_err = Some(e);
693 continue;
694 }
695 };
696 if metadata::verify_crc(&block).is_err() {
699 continue;
700 }
701 let vendor_base = vendor_data_base_lba_at(
702 self.volume_sectors,
703 rs,
704 self.options.use_header,
705 self.options.use_footer,
706 idx,
707 )
708 .unwrap_or(0);
709 let ctx = ReplicaCtx::new(
710 &self.header,
711 block,
712 &self.io as &dyn SectorIo,
713 vendor_base,
714 rs.saturating_sub(1),
715 sector_size,
716 idx,
717 );
718 match select(&ctx) {
719 Ok(pair) => {
720 chosen = Some(pair);
721 break;
722 }
723 Err(e) => {
724 last_err = Some(e);
725 continue;
726 }
727 }
728 }
729 let (codec, unsealed) = chosen.ok_or_else(|| {
730 last_err.unwrap_or(VckError::NotFound(
731 "no JVCK metadata replica could be unsealed",
732 ))
733 })?;
734
735 let store = JvckMetadataStore {
736 io: self.io,
737 options: self.options,
738 vmk: Zeroizing::new(vmk.to_vec()),
739 geometry: self.geometry,
740 volume_sectors: self.volume_sectors,
741 header: self.header,
742 secrets: unsealed.secrets,
743 offset: AtomicU64::new(0),
744 state: AtomicU16::new(unsealed.state.as_u16()),
745 codec,
746 };
747 let recovered = store.load_offset().unwrap_or(unsealed.encrypted_offset);
750 store.offset.store(recovered, Ordering::Relaxed);
751 Ok(store)
752 }
753}
754
755#[cfg(feature = "uefi")]
761pub use uefi_io::{locate_block_io_volume, open_volume_footer_uefi, UefiBlockIoVolume};
762
763#[cfg(feature = "uefi")]
764mod uefi_io {
765 use super::*;
766 use crate::types::{guid_from_windows_bytes, Guid};
767 use alloc::format;
768 use uefi::boot::{self, open_protocol_exclusive, SearchType};
769 use uefi::proto::media::block::BlockIO;
770 use uefi::proto::media::partition::PartitionInfo;
771
772 pub struct UefiBlockIoVolume {
778 block_io: uefi::boot::ScopedProtocol<BlockIO>,
779 media_id: u32,
780 sector_size: u32,
781 total_sectors: u64,
782 }
783
784 unsafe impl Send for UefiBlockIoVolume {}
788 unsafe impl Sync for UefiBlockIoVolume {}
789
790 impl SectorIo for UefiBlockIoVolume {
791 fn sector_size(&self) -> u32 {
792 self.sector_size
793 }
794 fn total_sectors(&self) -> u64 {
795 self.total_sectors
796 }
797 fn read_sectors(&self, lba: u64, buf: &mut [u8]) -> VckResult<()> {
798 self.block_io
799 .read_blocks(self.media_id, lba, buf)
800 .map_err(|e| VckError::Io(format!("BlockIO.ReadBlocks(lba={lba}) failed: {e:?}")))
801 }
802 fn write_sectors(&self, _lba: u64, _buf: &[u8]) -> VckResult<()> {
803 Err(VckError::Unsupported("loader Block IO volume is read-only"))
804 }
805 }
806
807 pub fn locate_block_io_volume(partition_guid: Guid) -> VckResult<UefiBlockIoVolume> {
812 let handles = boot::locate_handle_buffer(SearchType::from_proto::<BlockIO>())
813 .map_err(|e| VckError::Io(format!("locate BlockIO handles failed: {e:?}")))?;
814
815 for &handle in handles.iter() {
816 let matched = match open_protocol_exclusive::<PartitionInfo>(handle) {
819 Ok(pinfo) => match pinfo.gpt_partition_entry() {
820 Some(gpt) => {
821 guid_from_windows_bytes(gpt.unique_partition_guid.to_bytes())
822 == partition_guid
823 }
824 None => false,
825 },
826 Err(_) => false,
827 };
828 if !matched {
829 continue;
830 }
831
832 let block_io = open_protocol_exclusive::<BlockIO>(handle)
833 .map_err(|e| VckError::Io(format!("open BlockIO failed: {e:?}")))?;
834 let media = block_io.media();
835 if !media.is_media_present() {
836 return Err(VckError::Io(
837 "matched partition has no media present".into(),
838 ));
839 }
840 let sector_size = media.block_size();
841 let media_id = media.media_id();
842 let total_sectors = media.last_block().saturating_add(1);
843 return Ok(UefiBlockIoVolume {
844 block_io,
845 media_id,
846 sector_size,
847 total_sectors,
848 });
849 }
850
851 Err(VckError::NotFound(
852 "no Block IO partition matched the target GUID",
853 ))
854 }
855
856 pub fn open_volume_footer_uefi(
862 partition_guid: Guid,
863 vmk: &[u8],
864 ) -> VckResult<JvckMetadataStore<UefiBlockIoVolume>> {
865 let io = locate_block_io_volume(partition_guid)?;
866 JvckMetadataStore::open(io, vmk)
867 }
868}
869
870#[cfg(test)]
871mod tests {
872 use super::*;
873 use crate::jvck::codec::default_codec;
874 use std::sync::Mutex;
875
876 struct TestRng;
878 impl crate::rng::RandomSource for TestRng {
879 fn fill(&self, buf: &mut [u8]) -> VckResult<()> {
880 for (i, b) in buf.iter_mut().enumerate() {
881 *b = (i as u8).wrapping_mul(7).wrapping_add(1);
882 }
883 Ok(())
884 }
885 }
886 static TEST_RNG: TestRng = TestRng;
887 fn ensure_rng() {
889 crate::rng::set_random_source(&TEST_RNG);
890 }
891
892 struct MemVolume {
894 sector_size: u32,
895 data: Mutex<Vec<u8>>,
896 }
897
898 impl MemVolume {
899 fn new(sector_size: u32, sectors: u64) -> Self {
900 Self {
901 sector_size,
902 data: Mutex::new(alloc::vec![0u8; (sectors * sector_size as u64) as usize]),
903 }
904 }
905 }
906
907 impl SectorIo for MemVolume {
908 fn sector_size(&self) -> u32 {
909 self.sector_size
910 }
911 fn total_sectors(&self) -> u64 {
912 self.data.lock().unwrap().len() as u64 / self.sector_size as u64
913 }
914 fn read_sectors(&self, lba: u64, buf: &mut [u8]) -> VckResult<()> {
915 let data = self.data.lock().unwrap();
916 let start = (lba * self.sector_size as u64) as usize;
917 buf.copy_from_slice(&data[start..start + buf.len()]);
918 Ok(())
919 }
920 fn write_sectors(&self, lba: u64, buf: &[u8]) -> VckResult<()> {
921 let mut data = self.data.lock().unwrap();
922 let start = (lba * self.sector_size as u64) as usize;
923 data[start..start + buf.len()].copy_from_slice(buf);
924 Ok(())
925 }
926 }
927
928 const VMK: &[u8] = b"unit-test-volume-master-key";
929 const MD_SIZE: u32 = 128 * 1024; fn footer_only_options() -> JvckMetadataOptions {
932 JvckMetadataOptions {
933 use_header: 0,
934 use_footer: 2,
935 metadata_size: MD_SIZE,
936 }
937 }
938
939 #[test]
940 fn create_then_load_geometry() {
941 ensure_rng();
942 let io = MemVolume::new(512, 1024);
944 let store = JvckMetadataStore::create(
945 io,
946 VMK,
947 footer_only_options(),
948 [1; 32],
949 [2; 32],
950 [9; 16],
951 default_codec(),
952 )
953 .unwrap();
954 assert_eq!(store.offset_sector(), 0);
955 assert_eq!(store.data_sector_count(), 512);
956 assert_eq!(store.footer_replica_count(), 2);
957
958 assert_eq!(store.load_offset().unwrap(), 0);
959 assert_eq!(store.fvek_keys().0, &[1u8; 32]);
960 assert_eq!(store.volume_id(), [9; 16]);
961 }
962
963 #[test]
964 fn header_plus_footer_geometry() {
965 ensure_rng();
966 let io = MemVolume::new(512, 1280);
968 let opts = JvckMetadataOptions {
969 use_header: 1,
970 use_footer: 2,
971 metadata_size: MD_SIZE,
972 };
973 let store =
974 JvckMetadataStore::create(io, VMK, opts, [3; 32], [4; 32], [7; 16], default_codec())
975 .unwrap();
976 assert_eq!(store.offset_sector(), 256);
977 assert_eq!(store.data_sector_count(), 512);
978 }
979
980 #[test]
981 fn store_then_load_offset_roundtrip() {
982 ensure_rng();
983 let io = MemVolume::new(512, 1024);
984 let store = JvckMetadataStore::create(
985 io,
986 VMK,
987 footer_only_options(),
988 [1; 32],
989 [2; 32],
990 [9; 16],
991 default_codec(),
992 )
993 .unwrap();
994
995 store
996 .store(&EncryptedOffset {
997 sector: 1234,
998 total_sectors: 512,
999 })
1000 .unwrap();
1001 let loaded = store.load().unwrap();
1002 assert_eq!(loaded.sector, 1234);
1003 assert_eq!(loaded.total_sectors, 512);
1004 }
1005
1006 #[test]
1007 fn reopen_finds_existing_metadata() {
1008 ensure_rng();
1009 let io = MemVolume::new(512, 1024);
1010 let store = JvckMetadataStore::create(
1011 io,
1012 VMK,
1013 footer_only_options(),
1014 [5; 32],
1015 [6; 32],
1016 [8; 16],
1017 default_codec(),
1018 )
1019 .unwrap();
1020 store
1021 .store(&EncryptedOffset {
1022 sector: 777,
1023 total_sectors: 512,
1024 })
1025 .unwrap();
1026 let io = store.io;
1028 let reopened = JvckMetadataStore::open(io, VMK).unwrap();
1029 assert_eq!(reopened.offset_sector(), 0);
1030 assert_eq!(reopened.data_sector_count(), 512);
1031 assert_eq!(reopened.load_offset().unwrap(), 777);
1032 }
1033
1034 #[test]
1035 fn recovery_picks_largest_offset() {
1036 ensure_rng();
1037 let io = MemVolume::new(512, 1024);
1038 let store = JvckMetadataStore::create(
1039 io,
1040 VMK,
1041 footer_only_options(),
1042 [1; 32],
1043 [2; 32],
1044 [9; 16],
1045 default_codec(),
1046 )
1047 .unwrap();
1048 store
1050 .store(&EncryptedOffset {
1051 sector: 500,
1052 total_sectors: 512,
1053 })
1054 .unwrap();
1055
1056 let mut block = [0u8; METADATA_BLOCK_SIZE];
1058 store
1059 .header
1060 .encode(
1061 &store.secrets,
1062 300,
1063 VolumeState::Encrypt,
1064 &[0u8; metadata::SALT_SIZE],
1065 VMK,
1066 &mut block,
1067 )
1068 .unwrap();
1069 write_block(&store.io, 512, store.volume_sectors - 1, &block).unwrap();
1070
1071 assert_eq!(store.load_offset().unwrap(), 500);
1073 }
1074
1075 #[test]
1076 fn state_persists_across_reopen() {
1077 ensure_rng();
1078 let io = MemVolume::new(512, 1024);
1079 let store = JvckMetadataStore::create(
1080 io,
1081 VMK,
1082 footer_only_options(),
1083 [1; 32],
1084 [2; 32],
1085 [9; 16],
1086 default_codec(),
1087 )
1088 .unwrap();
1089 assert_eq!(store.load_state().unwrap(), VolumeState::Encrypt);
1091
1092 store
1093 .store(&EncryptedOffset {
1094 sector: 100,
1095 total_sectors: 512,
1096 })
1097 .unwrap();
1098 store.store_state(VolumeState::Decrypt).unwrap();
1099
1100 let io = store.io;
1102 let reopened = JvckMetadataStore::open(io, VMK).unwrap();
1103 assert_eq!(reopened.load_state().unwrap(), VolumeState::Decrypt);
1104 assert_eq!(reopened.load_offset().unwrap(), 100);
1105 }
1106
1107 #[test]
1108 fn vendor_data_read_write_roundtrip() {
1109 ensure_rng();
1110 let io = MemVolume::new(512, 1024);
1112 let store = JvckMetadataStore::create(
1113 io,
1114 VMK,
1115 footer_only_options(),
1116 [1; 32],
1117 [2; 32],
1118 [9; 16],
1119 default_codec(),
1120 )
1121 .unwrap();
1122 assert_eq!(store.replica_count(), 2);
1123 assert_eq!(store.vendor_data_sector_count(), 255);
1124
1125 let data = alloc::vec![0xCDu8; 512];
1126 store.write_vendor_data(0, 3, &data).unwrap();
1127 let mut back = alloc::vec![0u8; 512];
1128 store.read_vendor_data(0, 3, &mut back).unwrap();
1129 assert_eq!(back, data);
1130
1131 assert!(store.write_vendor_data(0, 255, &data).is_err());
1133 assert!(store.write_vendor_data(2, 0, &data).is_err());
1134 assert!(store.read_vendor_data(0, 0, &mut [0u8; 100]).is_err());
1136
1137 assert_eq!(store.load_offset().unwrap(), 0);
1139 }
1140
1141 #[test]
1142 fn write_vendor_data_all_mirrors_every_replica() {
1143 ensure_rng();
1144 let io = MemVolume::new(512, 1024);
1145 let store = JvckMetadataStore::create(
1146 io,
1147 VMK,
1148 footer_only_options(),
1149 [1; 32],
1150 [2; 32],
1151 [9; 16],
1152 default_codec(),
1153 )
1154 .unwrap();
1155 assert_eq!(store.replica_count(), 2);
1156
1157 let data = alloc::vec![0x5Au8; 1024]; store.write_vendor_data_all(7, &data).unwrap();
1159
1160 for replica in 0..store.replica_count() {
1162 let mut back = alloc::vec![0u8; 1024];
1163 store.read_vendor_data(replica, 7, &mut back).unwrap();
1164 assert_eq!(back, data, "replica {replica} vendor data mismatch");
1165 }
1166 assert!(store.write_vendor_data_all(255, &data).is_err());
1168 assert_eq!(store.load_offset().unwrap(), 0);
1170 }
1171
1172 #[test]
1173 fn set_vendor_reserved_persists_to_all_replicas() {
1174 ensure_rng();
1175 let io = MemVolume::new(512, 1024);
1176 let mut store = JvckMetadataStore::create(
1177 io,
1178 VMK,
1179 footer_only_options(),
1180 [1; 32],
1181 [2; 32],
1182 [9; 16],
1183 default_codec(),
1184 )
1185 .unwrap();
1186 assert_eq!(
1187 store.vendor_reserved(),
1188 &[0u8; metadata::VENDOR_RESERVED_SIZE]
1189 );
1190
1191 let vr = [0xABu8; metadata::VENDOR_RESERVED_SIZE];
1192 store.set_vendor_reserved(&vr).unwrap();
1193 assert_eq!(store.vendor_reserved(), &vr);
1194
1195 let io = store.io;
1197 let reader = JvckMetadataReader::open(io).unwrap();
1198 assert_eq!(reader.header().vendor_reserved, vr);
1199 for replica in 0..reader.replica_count() {
1200 let ctx = reader.replica_ctx(replica).unwrap();
1201 assert_eq!(ctx.header().vendor_reserved, vr, "replica {replica}");
1202 }
1203 }
1204
1205 #[test]
1206 fn reader_replica_ctx_exposes_block_and_vendor_data() {
1207 ensure_rng();
1208 let io = MemVolume::new(512, 1024);
1209 let store = JvckMetadataStore::create(
1210 io,
1211 VMK,
1212 footer_only_options(),
1213 [1; 32],
1214 [2; 32],
1215 [9; 16],
1216 default_codec(),
1217 )
1218 .unwrap();
1219 let marker = alloc::vec![0xE7u8; 512];
1220 store.write_vendor_data_all(0, &marker).unwrap();
1221
1222 let io = store.io;
1223 let reader = JvckMetadataReader::open(io).unwrap();
1224 assert_eq!(reader.replica_count(), 2);
1225
1226 let ctx = reader.replica_ctx(1).unwrap();
1228 assert_eq!(ctx.replica_index(), 1);
1229 assert_eq!(&ctx.block()[..4], b"JVCK");
1230 assert_eq!(
1231 ctx.encrypted_metadata().len(),
1232 metadata::ENCRYPTED_METADATA_SIZE
1233 );
1234 let mut vd = alloc::vec![0u8; 512];
1235 ctx.read_vendor_data(0, &mut vd).unwrap();
1236 assert_eq!(vd, marker);
1237
1238 assert!(reader.replica_ctx(2).is_err());
1240 }
1241
1242 #[test]
1243 fn open_empty_volume_fails() {
1244 let io = MemVolume::new(512, 1024);
1245 assert!(matches!(
1246 JvckMetadataStore::open(io, VMK),
1247 Err(VckError::NotFound(_))
1248 ));
1249 }
1250
1251 #[test]
1252 fn metadata_size_not_multiple_of_sector_is_floored() {
1253 ensure_rng();
1254 let sector_size = 4096u32;
1258 let md_size = 128 * 1024 + 100;
1259 let opts = JvckMetadataOptions {
1260 use_header: 0,
1261 use_footer: 2,
1262 metadata_size: md_size,
1263 };
1264 let expected_rs = (md_size / sector_size) as u64; assert_eq!(expected_rs, 32);
1266
1267 let io = MemVolume::new(sector_size, 128);
1269 let store =
1270 JvckMetadataStore::create(io, VMK, opts, [1; 32], [2; 32], [9; 16], default_codec())
1271 .unwrap();
1272 assert_eq!(store.data_sector_count(), 128 - 2 * expected_rs);
1273 assert_eq!(store.sector_size(), sector_size);
1274
1275 store
1277 .store(&EncryptedOffset {
1278 sector: 7,
1279 total_sectors: store.data_sector_count(),
1280 })
1281 .unwrap();
1282 let reopened = JvckMetadataStore::open(store.io, VMK).unwrap();
1283 assert_eq!(reopened.metadata_size(), md_size);
1284 assert_eq!(reopened.load_offset().unwrap(), 7);
1285 }
1286}