vck_common/types.rs
1// SPDX-FileCopyrightText: 2026 JC-Lab <joseph@jc-lab.net>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use alloc::string::String;
6
7use serde::{Deserialize, Serialize};
8
9/// Volume / partition GUID. Stored as a 16-byte UUID.
10///
11/// NOTE: GPT partition GUIDs and UEFI `EFI_GUID` use a mixed-endian layout for
12/// the first three fields. Conversions to/from `uefi::Guid` must account for
13/// this; see [`guid_from_windows_bytes`].
14pub type Guid = uuid::Uuid;
15
16/// Build a [`Guid`] from the 16 raw bytes of a Windows `GUID` / GPT
17/// `PARTITION_INFORMATION_GPT.PartitionId` / `EFI_GUID` as they appear in
18/// memory.
19///
20/// Those structures store the first three fields (`Data1: u32`, `Data2: u16`,
21/// `Data3: u16`) little-endian and the trailing 8 bytes (`Data4`) as-is. The
22/// `uuid` crate's canonical (RFC 4122) byte order is big-endian for those
23/// fields, so the raw bytes must be read with `from_bytes_le` for the resulting
24/// `Guid` to match the canonical string the Go app writes into `vck.json`
25/// (which formats `Data1`/`Data2`/`Data3` as integers).
26pub fn guid_from_windows_bytes(bytes: [u8; 16]) -> Guid {
27 Guid::from_bytes_le(bytes)
28}
29
30/// Persisted sweep direction for a volume.
31///
32/// Stored in the EncryptedMetadata `state` field (offset 24, u16) so that after
33/// a reboot the driver resumes the sweep in the correct direction instead of
34/// always re-encrypting. `Encrypt` is `0`, so a zero `state` field (the default
35/// for a freshly-created volume) means "encrypting".
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum VolumeState {
38 Encrypt,
39 Decrypt,
40}
41
42impl VolumeState {
43 pub fn as_u16(self) -> u16 {
44 match self {
45 VolumeState::Encrypt => 0,
46 VolumeState::Decrypt => 1,
47 }
48 }
49
50 /// Parse from the on-disk u16; unknown values fall back to `Encrypt`.
51 pub fn from_u16(v: u16) -> Self {
52 match v {
53 1 => VolumeState::Decrypt,
54 _ => VolumeState::Encrypt,
55 }
56 }
57}
58
59/// Progressive-encryption progress state.
60///
61/// All sector numbers are **relative to the data region** (`offset_sector`):
62/// `0` is the first encryptable sector and header/footer metadata regions are
63/// not counted. The filter maps an absolute LBA to `rel = lba - offset_sector`
64/// before comparing against these values.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct EncryptedOffset {
67 /// Sectors before this (data-region relative) are already encrypted.
68 pub sector: u64,
69 /// Total number of sectors to encrypt (metadata regions excluded).
70 pub total_sectors: u64,
71}
72
73impl EncryptedOffset {
74 /// `sector` is a data-region relative sector number.
75 pub fn is_encrypted(&self, sector: u64) -> bool {
76 sector < self.sector
77 }
78
79 pub fn is_fully_encrypted(&self) -> bool {
80 self.sector >= self.total_sectors
81 }
82}
83
84/// A contiguous run of sectors `[start, start + count)`.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct SectorRange {
87 pub start: u64,
88 pub count: u64,
89}
90
91impl SectorRange {
92 pub fn end(&self) -> u64 {
93 self.start + self.count
94 }
95
96 pub fn contains(&self, sector: u64) -> bool {
97 sector >= self.start && sector < self.end()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn encrypted_offset_boundaries() {
107 let off = EncryptedOffset {
108 sector: 10,
109 total_sectors: 100,
110 };
111 assert!(off.is_encrypted(9));
112 assert!(!off.is_encrypted(10));
113 assert!(!off.is_fully_encrypted());
114
115 let done = EncryptedOffset {
116 sector: 100,
117 total_sectors: 100,
118 };
119 assert!(done.is_fully_encrypted());
120 }
121
122 #[test]
123 fn guid_from_windows_bytes_matches_canonical() {
124 // Windows GUID in-memory bytes for {12345678-9abc-def0-1122-334455667788}:
125 // Data1=0x12345678 LE, Data2=0x9abc LE, Data3=0xdef0 LE, Data4 as-is.
126 let win = [
127 0x78, 0x56, 0x34, 0x12, // Data1 LE
128 0xbc, 0x9a, // Data2 LE
129 0xf0, 0xde, // Data3 LE
130 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // Data4
131 ];
132 let guid = guid_from_windows_bytes(win);
133 let canonical = uuid::Uuid::parse_str("12345678-9abc-def0-1122-334455667788").unwrap();
134 assert_eq!(guid, canonical);
135 }
136
137 #[test]
138 fn sector_range_contains() {
139 let r = SectorRange { start: 5, count: 3 };
140 assert_eq!(r.end(), 8);
141 assert!(r.contains(5));
142 assert!(r.contains(7));
143 assert!(!r.contains(8));
144 assert!(!r.contains(4));
145 }
146}
147
148/// Identifies an attached volume and how to reach its raw sectors.
149#[derive(Debug, Clone)]
150pub struct VolumeId {
151 /// GPT partition unique GUID (matched against the handover `partition_guid`).
152 pub partition_guid: Guid,
153 /// NT device path, e.g. `\Device\HarddiskVolume3`. Used for raw footer
154 /// metadata read/write by the kernel `SectorIo` implementation.
155 pub device_path: String,
156}