windows_thread_ambient_sys/capture_set.rs
1// Copyright (c) Mike Grier.
2
3//! Which capturable aspects to collect.
4//!
5//! A capture set names the aspects that can be **read** off the calling thread.
6//! Declared aspects are not in this vocabulary at all: there is nothing to
7//! collect for them, so they are stated by the caller instead -- see
8//! [`crate::declared`].
9//!
10//! # There is deliberately no `Default` implementation
11//!
12//! The default set is a **named constant**, [`CaptureSet::DEFAULT`], which a
13//! caller must name to get.
14//!
15//! That is not ceremony. The workspace decision that this composite is
16//! exhaustively enumerated rests on its field list being contract surface: a
17//! silently added field is a silent semantic change. An implicit default has the
18//! same property in a worse form, because growing it changes behaviour for
19//! callers who never named it and have no diff to review. Naming it makes
20//! growth a visible change to a named thing, lets a caller who wants stability
21//! list aspects explicitly, and gives a caller who takes the default somewhere
22//! to go and read what it contains.
23//!
24//! # Example
25//!
26//! ```
27//! use windows_thread_ambient_sys::capture_set::{CaptureSet, CapturableAspect};
28//!
29//! // The default is opted into by name, never inherited by accident.
30//! let set = CaptureSet::DEFAULT;
31//! assert!(set.contains(CaptureSet::IMPERSONATION));
32//!
33//! // TxF is excluded from the default; ask for it deliberately.
34//! assert!(!set.contains(CaptureSet::TRANSACTION));
35//! let with_txf = set.union(CaptureSet::TRANSACTION);
36//! assert!(with_txf.contains(CaptureSet::TRANSACTION));
37//!
38//! // A caller that wants stability names its aspects rather than taking a set
39//! // whose membership may grow.
40//! let pinned = CaptureSet::IMPERSONATION.union(CaptureSet::ERROR_MODE);
41//! assert_eq!(pinned.aspects().count(), 2);
42//! assert!(pinned.aspects().any(|a| a == CapturableAspect::ErrorMode));
43//! ```
44
45use std::fmt;
46
47/// The bit each aspect occupies in a [`CaptureSet`].
48///
49/// Internal to this crate and not a wire format, so the values carry no
50/// compatibility obligation; they exist so no bare literal appears in the logic.
51mod bit {
52 pub(super) const IMPERSONATION: u8 = 1 << 0;
53 pub(super) const ERROR_MODE: u8 = 1 << 1;
54 pub(super) const TRANSACTION: u8 = 1 << 2;
55}
56
57/// One aspect that can be read off the calling thread.
58///
59/// Declared aspects are absent by construction: there is nothing to capture for
60/// them.
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62#[non_exhaustive]
63pub enum CapturableAspect {
64 /// The thread's impersonation context.
65 Impersonation,
66 /// The thread error mode.
67 ErrorMode,
68 /// The thread's current TxF transaction.
69 Transaction,
70}
71
72impl CapturableAspect {
73 /// Every capturable aspect.
74 ///
75 /// [`CaptureSet::ALL`] is derived from this list rather than restating it,
76 /// so an aspect added here joins that set automatically instead of leaving
77 /// it quietly stale.
78 pub const EVERY: &'static [Self] = &[Self::Impersonation, Self::ErrorMode, Self::Transaction];
79
80 const fn bit(self) -> u8 {
81 match self {
82 Self::Impersonation => bit::IMPERSONATION,
83 Self::ErrorMode => bit::ERROR_MODE,
84 Self::Transaction => bit::TRANSACTION,
85 }
86 }
87
88 /// The singleton set containing just this aspect.
89 #[must_use]
90 pub const fn as_set(self) -> CaptureSet {
91 CaptureSet { bits: self.bit() }
92 }
93}
94
95impl fmt::Display for CapturableAspect {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.write_str(match self {
98 Self::Impersonation => "impersonation",
99 Self::ErrorMode => "error mode",
100 Self::Transaction => "transaction",
101 })
102 }
103}
104
105/// Which capturable aspects a capture should collect.
106///
107/// There is no `Default` implementation; see the module documentation.
108#[derive(Clone, Copy, PartialEq, Eq, Hash)]
109pub struct CaptureSet {
110 bits: u8,
111}
112
113const fn derive_all() -> CaptureSet {
114 let mut bits = 0u8;
115 let mut index = 0;
116 while index < CapturableAspect::EVERY.len() {
117 bits |= CapturableAspect::EVERY[index].bit();
118 index += 1;
119 }
120 CaptureSet { bits }
121}
122
123impl CaptureSet {
124 /// Collect nothing.
125 ///
126 /// Every aspect is then [`Captured::NotCaptured`](crate::Captured::NotCaptured),
127 /// which leaves the target thread's own values alone.
128 pub const NONE: Self = Self { bits: 0 };
129
130 /// Just the impersonation context.
131 pub const IMPERSONATION: Self = CapturableAspect::Impersonation.as_set();
132
133 /// Just the thread error mode.
134 pub const ERROR_MODE: Self = CapturableAspect::ErrorMode.as_set();
135
136 /// Just the current TxF transaction.
137 pub const TRANSACTION: Self = CapturableAspect::Transaction.as_set();
138
139 /// The recommended starting point: impersonation and the thread error mode.
140 ///
141 /// **Adding an aspect to this set is a breaking change**, and that is the
142 /// reason it exists as a name rather than as a `Default` implementation.
143 ///
144 /// TxF is deliberately excluded. It is deprecated by Microsoft, capturing it
145 /// costs a lazy `ntdll` binding a caller may never need, and -- the reason
146 /// that actually decides it -- a captured transaction enlists remoted work
147 /// in a transaction the caller may commit or roll back while that work is
148 /// still running. That is a hazard to opt into deliberately, not one to
149 /// acquire by taking a default. Add [`TRANSACTION`](Self::TRANSACTION) when
150 /// you mean it.
151 pub const DEFAULT: Self = Self {
152 bits: bit::IMPERSONATION | bit::ERROR_MODE,
153 };
154
155 /// Every capturable aspect this version knows.
156 ///
157 /// **This set grows.** Membership is its meaning, so a later version adding
158 /// an aspect will capture it here without further notice. A caller that
159 /// needs a fixed set should name its aspects instead.
160 pub const ALL: Self = derive_all();
161
162 /// Both sets' aspects.
163 #[must_use]
164 pub const fn union(self, other: Self) -> Self {
165 Self {
166 bits: self.bits | other.bits,
167 }
168 }
169
170 /// This set without `other`'s aspects.
171 #[must_use]
172 pub const fn without(self, other: Self) -> Self {
173 Self {
174 bits: self.bits & !other.bits,
175 }
176 }
177
178 /// Whether every aspect of `other` is present.
179 #[must_use]
180 pub const fn contains(self, other: Self) -> bool {
181 self.bits & other.bits == other.bits
182 }
183
184 /// Whether nothing would be collected.
185 #[must_use]
186 pub const fn is_empty(self) -> bool {
187 self.bits == 0
188 }
189
190 /// The aspects in this set, in a stable order.
191 pub fn aspects(self) -> impl Iterator<Item = CapturableAspect> {
192 CapturableAspect::EVERY
193 .iter()
194 .copied()
195 .filter(move |aspect| self.bits & aspect.bit() != 0)
196 }
197}
198
199impl fmt::Debug for CaptureSet {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 if self.is_empty() {
202 return f.write_str("CaptureSet(none)");
203 }
204 f.write_str("CaptureSet(")?;
205 for (index, aspect) in self.aspects().enumerate() {
206 if index > 0 {
207 f.write_str(", ")?;
208 }
209 write!(f, "{aspect}")?;
210 }
211 f.write_str(")")
212 }
213}
214
215impl From<CapturableAspect> for CaptureSet {
216 fn from(aspect: CapturableAspect) -> Self {
217 aspect.as_set()
218 }
219}
220
221#[cfg(test)]
222mod tests;