Skip to main content

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}