win_error_codes/ntstatus.rs
1// Copyright (c) Mike Grier.
2
3use std::fmt;
4
5use crate::HResult;
6
7#[cfg(test)]
8mod tests;
9
10/// Where the severity starts: the top two bits.
11const SEVERITY_SHIFT: u32 = 30;
12/// The severity is two bits.
13const SEVERITY_MASK: u32 = 0b11;
14/// The customer bit: set for a code a third party defined and not Microsoft.
15const CUSTOMER_BIT: u32 = 0x2000_0000;
16/// Where the facility starts.
17const FACILITY_SHIFT: u32 = 16;
18/// The facility is twelve bits, below the customer bit and the bit that `HRESULT_FROM_NT` sets.
19const FACILITY_MASK: u32 = 0x0FFF;
20/// The code field, which is the low 16 bits.
21const CODE_MASK: u32 = 0x0000_FFFF;
22
23/// How bad an `NTSTATUS` is, from its top two bits.
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25#[repr(u8)]
26pub enum NtSeverity {
27 /// `STATUS_SEVERITY_SUCCESS`: the operation succeeded.
28 Success = 0,
29 /// `STATUS_SEVERITY_INFORMATIONAL`: it succeeded, and has something to say.
30 Informational = 1,
31 /// `STATUS_SEVERITY_WARNING`: it did not wholly succeed. A failure to [`NtStatus::is_success`].
32 Warning = 2,
33 /// `STATUS_SEVERITY_ERROR`: it failed.
34 Error = 3,
35}
36
37/// An `NTSTATUS`, the signed status the native API and the drivers beneath it report in.
38///
39/// An `i32`. The layout, from the top: a two-bit severity, the customer bit, a bit that
40/// [`HResult::from_ntstatus`] sets when it makes an `HRESULT` of one, a twelve-bit facility, and a
41/// 16-bit code. It is a number space of its own, not [`Win32Error`](crate::Win32Error)'s or
42/// [`HResult`]'s, and **there is no conversion to a Win32 code here**: that is `RtlNtStatusToDosError`,
43/// an `Rtl*` call, which this repository's rule on documented interfaces excludes. The one
44/// conversion that is a macro, `HRESULT_FROM_NT`, is [`HResult::from_ntstatus`].
45///
46/// `NT_SUCCESS` is `status >= 0`, so a success and an informational status are both successes, and a
47/// warning is not: [`is_success`](Self::is_success) and [`into_result`](Self::into_result) follow
48/// that. It is not an `Error`, because a success is a value of this type.
49///
50/// # Example
51///
52/// ```
53/// use win_error_codes::{HResult, NtSeverity, NtStatus};
54///
55/// // STATUS_ACCESS_VIOLATION.
56/// let status = NtStatus::from_raw(0xC000_0005_u32.cast_signed());
57/// assert_eq!(status.severity(), NtSeverity::Error);
58/// assert!(!status.is_success());
59/// assert_eq!(status.to_string(), "0xC0000005");
60///
61/// // STATUS_PENDING is a success.
62/// assert!(NtStatus::from_raw(0x103).is_success());
63///
64/// // HRESULT_FROM_NT sets one bit, and the status is recovered by clearing it.
65/// let hresult = HResult::from_ntstatus(status);
66/// assert_eq!(hresult.raw().cast_unsigned(), 0xD000_0005);
67/// assert_eq!(hresult.ntstatus(), Some(status));
68/// ```
69#[derive(Clone, Copy, PartialEq, Eq, Hash)]
70pub struct NtStatus(i32);
71
72impl NtStatus {
73 /// Wraps a raw `NTSTATUS`. Any value is accepted.
74 #[must_use]
75 pub const fn from_raw(raw: i32) -> Self {
76 Self(raw)
77 }
78
79 /// The raw `NTSTATUS`, unaltered.
80 #[must_use]
81 pub const fn raw(self) -> i32 {
82 self.0
83 }
84
85 /// The severity, from the top two bits.
86 #[must_use]
87 pub const fn severity(self) -> NtSeverity {
88 match (self.0.cast_unsigned() >> SEVERITY_SHIFT) & SEVERITY_MASK {
89 0 => NtSeverity::Success,
90 1 => NtSeverity::Informational,
91 2 => NtSeverity::Warning,
92 _ => NtSeverity::Error,
93 }
94 }
95
96 /// `NT_SUCCESS`: whether the status is not negative, which is a success or an informational
97 /// status. A warning is not.
98 #[must_use]
99 pub const fn is_success(self) -> bool {
100 self.0 >= 0
101 }
102
103 /// `NT_INFORMATION`: whether the severity is informational.
104 #[must_use]
105 pub const fn is_information(self) -> bool {
106 matches!(self.severity(), NtSeverity::Informational)
107 }
108
109 /// `NT_WARNING`: whether the severity is a warning.
110 #[must_use]
111 pub const fn is_warning(self) -> bool {
112 matches!(self.severity(), NtSeverity::Warning)
113 }
114
115 /// `NT_ERROR`: whether the severity is an error.
116 #[must_use]
117 pub const fn is_error(self) -> bool {
118 matches!(self.severity(), NtSeverity::Error)
119 }
120
121 /// `Ok` for `NT_SUCCESS` and `Err` carrying this status for anything else, a warning included.
122 ///
123 /// # Errors
124 ///
125 /// This status, when it is a warning or an error.
126 pub const fn into_result(self) -> Result<(), Self> {
127 if self.is_success() { Ok(()) } else { Err(self) }
128 }
129
130 /// The low 16 bits.
131 #[must_use]
132 pub const fn code(self) -> u16 {
133 (self.0.cast_unsigned() & CODE_MASK) as u16
134 }
135
136 /// The twelve bits above the code.
137 #[must_use]
138 pub const fn facility(self) -> u16 {
139 ((self.0.cast_unsigned() >> FACILITY_SHIFT) & FACILITY_MASK) as u16
140 }
141
142 /// Whether the customer bit is set: a code a third party defined, not Microsoft.
143 #[must_use]
144 pub const fn is_customer(self) -> bool {
145 self.0.cast_unsigned() & CUSTOMER_BIT != 0
146 }
147}
148
149impl HResult {
150 /// `HRESULT_FROM_NT`: the status with the bit that marks an `HRESULT` made from one set.
151 ///
152 /// Not lossless for a status that already has that bit set: [`ntstatus`](Self::ntstatus) clears
153 /// it, so such a status does not come back as it went in.
154 #[must_use]
155 pub const fn from_ntstatus(status: NtStatus) -> Self {
156 Self::from_raw((status.raw().cast_unsigned() | crate::hresult::NTSTATUS_BIT).cast_signed())
157 }
158
159 /// The status this was made from, or `None` if the bit that `HRESULT_FROM_NT` sets is clear.
160 #[must_use]
161 pub const fn ntstatus(self) -> Option<NtStatus> {
162 if self.is_ntstatus_mapped() {
163 Some(NtStatus::from_raw(
164 (self.raw().cast_unsigned() & !crate::hresult::NTSTATUS_BIT).cast_signed(),
165 ))
166 } else {
167 None
168 }
169 }
170}
171
172/// The value in hex, as `ntstatus.h` writes it: `0xC0000005`.
173impl fmt::Display for NtStatus {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 write!(f, "{:#010X}", self.0.cast_unsigned())
176 }
177}
178
179/// `NtStatus(0xC0000005)`, in hex and not as the negative number the `i32` is.
180impl fmt::Debug for NtStatus {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 write!(f, "NtStatus({self})")
183 }
184}