vck_common/ioctl.rs
1// SPDX-FileCopyrightText: 2026 JC-Lab <joseph@jc-lab.net>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Windows device I/O control (IOCTL) code construction.
6//!
7//! Single source of truth for the `CTL_CODE` arithmetic shared across the kernel
8//! driver and host tooling. The driver's concrete codes live in
9//! `lib/windrv/src/ioctl/codes.rs` (built via [`ctl_code`] and pinned with
10//! compile-time assertions); the Go SDK (`sdk/ioctl.go`) hardcodes the same hex
11//! values, which are verified here by the host unit tests.
12//!
13//! A control code is laid out exactly like the Win32 `CTL_CODE` macro (`wdm.h`):
14//!
15//! ```text
16//! 31 16 15 14 13 2 1 0
17//! +------------------+-----+-----------------+------+
18//! | DeviceType |Acces| Function |Method|
19//! +------------------+-----+-----------------+------+
20//! ```
21
22/// `METHOD_BUFFERED` transfer type: I/O manager copies the in/out buffers.
23pub const METHOD_BUFFERED: u32 = 0;
24/// `METHOD_IN_DIRECT` transfer type.
25pub const METHOD_IN_DIRECT: u32 = 1;
26/// `METHOD_OUT_DIRECT` transfer type.
27pub const METHOD_OUT_DIRECT: u32 = 2;
28/// `METHOD_NEITHER` transfer type.
29pub const METHOD_NEITHER: u32 = 3;
30
31/// `FILE_ANY_ACCESS`: no specific access is required to issue the IOCTL.
32pub const FILE_ANY_ACCESS: u32 = 0;
33/// `FILE_READ_ACCESS`: the caller's handle must grant read access. Use for
34/// read-only query IOCTLs (status / progress).
35pub const FILE_READ_ACCESS: u32 = 0x0001;
36/// `FILE_WRITE_ACCESS`: the caller's handle must grant write access. Use for
37/// IOCTLs that mutate driver or volume state (attach / detach / encrypt / pause).
38pub const FILE_WRITE_ACCESS: u32 = 0x0002;
39
40/// Device type for all VolumeCryptKit control codes. `0x22` is
41/// `FILE_DEVICE_UNKNOWN`; values below `0x8000` are reserved for Microsoft but
42/// are conventionally reused by sample drivers.
43pub const FILE_DEVICE_VCK: u32 = 0x22;
44
45/// Build a device control code, equivalent to the Win32 `CTL_CODE` macro:
46///
47/// ```text
48/// ((device_type) << 16) | ((access) << 14) | ((function) << 2) | (method)
49/// ```
50///
51/// `function` numbers below `0x800` are reserved by Microsoft; custom codes use
52/// `0x800..=0xFFF`.
53#[inline]
54pub const fn ctl_code(device_type: u32, function: u32, method: u32, access: u32) -> u32 {
55 (device_type << 16) | (access << 14) | (function << 2) | method
56}
57
58/// `macro_rules!` spelling of [`ctl_code`], mirroring the C `CTL_CODE` macro for
59/// call sites that prefer macro syntax. Both forms expand to the same value; the
60/// `const fn` is preferred in `const` definitions because it is type-checked.
61#[macro_export]
62macro_rules! ctl_code {
63 ($device_type:expr, $function:expr, $method:expr, $access:expr) => {
64 $crate::ioctl::ctl_code($device_type, $function, $method, $access)
65 };
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 /// The bit layout must match `wdm.h` exactly. These golden hex values are the
73 /// contract: `lib/windrv/src/ioctl/codes.rs` pins the same values with
74 /// `const` assertions and `sdk/ioctl.go` copies them verbatim.
75 #[test]
76 fn ctl_code_matches_windows_layout() {
77 // DeviceType occupies bits 31..16.
78 assert_eq!(
79 ctl_code(0x22, 0, METHOD_BUFFERED, FILE_ANY_ACCESS),
80 0x0022_0000
81 );
82 // Access occupies bits 15..14.
83 assert_eq!(
84 ctl_code(0, 0, METHOD_BUFFERED, FILE_READ_ACCESS),
85 0x0000_4000
86 );
87 assert_eq!(
88 ctl_code(0, 0, METHOD_BUFFERED, FILE_WRITE_ACCESS),
89 0x0000_8000
90 );
91 // Function occupies bits 13..2.
92 assert_eq!(
93 ctl_code(0, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS),
94 0x0000_2000
95 );
96 // Method occupies bits 1..0.
97 assert_eq!(ctl_code(0, 0, METHOD_NEITHER, FILE_ANY_ACCESS), 0x0000_0003);
98 }
99
100 /// Golden table of every VolumeCryptKit IOCTL value. Keep in lockstep with
101 /// `lib/windrv/src/ioctl/codes.rs` and `sdk/ioctl.go`.
102 #[test]
103 fn vck_ioctl_codes_are_exact() {
104 let dt = FILE_DEVICE_VCK;
105 let m = METHOD_BUFFERED;
106 let r = FILE_READ_ACCESS;
107 let w = FILE_WRITE_ACCESS;
108
109 // Read-only queries → FILE_READ_ACCESS.
110 assert_eq!(ctl_code(dt, 0x800, m, r), 0x0022_6000); // GET_STATUS
111 assert_eq!(ctl_code(dt, 0x803, m, r), 0x0022_600c); // GET_PROGRESS
112
113 // State-mutating commands → FILE_WRITE_ACCESS.
114 assert_eq!(ctl_code(dt, 0x801, m, w), 0x0022_a004); // START_ENCRYPT
115 assert_eq!(ctl_code(dt, 0x802, m, w), 0x0022_a008); // START_DECRYPT
116 assert_eq!(ctl_code(dt, 0x804, m, w), 0x0022_a010); // PAUSE
117 assert_eq!(ctl_code(dt, 0x805, m, w), 0x0022_a014); // JVCK_ATTACH
118 assert_eq!(ctl_code(dt, 0x806, m, w), 0x0022_a018); // VCK_DETACH
119 assert_eq!(ctl_code(dt, 0x807, m, w), 0x0022_a01c); // JVCK_PREPARE
120 assert_eq!(ctl_code(dt, 0x808, m, w), 0x0022_a020); // PAUSE_OS_VOLUME
121 assert_eq!(ctl_code(dt, 0x809, m, w), 0x0022_a024); // DETACH_ALL_VOLUMES
122
123 // Benchmark (FILE_READ_ACCESS — no state mutation).
124 assert_eq!(ctl_code(dt, 0x80a, m, r), 0x0022_6028); // BENCH_AES
125
126 // List attached volumes (read-only).
127 assert_eq!(ctl_code(dt, 0x80b, m, r), 0x0022_602c); // LIST_VOLUMES
128 }
129
130 #[test]
131 fn macro_and_fn_agree() {
132 assert_eq!(
133 ctl_code!(FILE_DEVICE_VCK, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS),
134 ctl_code(FILE_DEVICE_VCK, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS),
135 );
136 }
137}