Skip to main content

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}