#![no_std]
#![forbid(unsafe_code)]
#![doc(test(attr(deny(unused))))]
#![warn(missing_docs)]
#![cfg_attr(feature = "allocator-api", feature(allocator_api))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
pub mod common_error;
mod layout;
use layout::status_newtype;
use windows_sys::Win32::Foundation::{NTSTATUS, STATUS_SUCCESS};
pub type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Error(pub(crate) NTSTATUS);
impl core::error::Error for Error {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Severity {
Success = 0,
Information = 1,
Warning = 2,
Error = 3,
}
impl Severity {
#[must_use]
#[inline]
pub const fn from_ntstatus(status: NTSTATUS) -> Self {
match layout::SEVERITY.get(status) {
0 => Self::Success,
1 => Self::Information,
2 => Self::Warning,
_ => Self::Error,
}
}
}
pub trait IntoResult<T, E = Error> {
fn into_result(self) -> Result<T, E>;
}
pub trait IntoError {
#[must_use]
fn into_error(self) -> Error;
}
impl IntoResult<()> for NTSTATUS {
#[inline]
fn into_result(self) -> Result<(), Error> {
match self {
STATUS_SUCCESS => Ok(()),
status => Err(Error::from_ntstatus(status)),
}
}
}
impl<T, E> IntoResult<T> for Result<T, E>
where
E: IntoError,
{
#[inline]
fn into_result(self) -> Result<T, Error> {
self.map_err(IntoError::into_error)
}
}
impl IntoError for NTSTATUS {
#[inline]
fn into_error(self) -> Error {
Error::from_ntstatus(self)
}
}
pub trait NtStatus {
#[must_use]
fn ntstatus(&self) -> NTSTATUS;
}
impl NtStatus for Result<()> {
#[inline]
fn ntstatus(&self) -> NTSTATUS {
match self {
Ok(()) => STATUS_SUCCESS,
Err(err) => err.ntstatus(),
}
}
}
impl NtStatus for StatusResult {
#[inline]
fn ntstatus(&self) -> NTSTATUS {
match self {
Ok(status) => status.ntstatus(),
Err(err) => err.ntstatus(),
}
}
}
pub trait NtStatusOrSuccess {
#[must_use]
fn ntstatus_or_success(&self) -> NTSTATUS;
}
impl<T> NtStatusOrSuccess for Result<T> {
#[inline]
fn ntstatus_or_success(&self) -> NTSTATUS {
match self {
Ok(_) => STATUS_SUCCESS,
Err(err) => err.ntstatus(),
}
}
}
impl NtStatus for Error {
#[inline]
fn ntstatus(&self) -> NTSTATUS {
self.0
}
}
impl From<Error> for NTSTATUS {
#[inline]
fn from(error: Error) -> Self {
error.0
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Status(NTSTATUS);
impl Status {
pub const SUCCESS: Self = Self(STATUS_SUCCESS);
}
impl From<NTSTATUS> for Status {
#[inline]
fn from(status: NTSTATUS) -> Self {
Self(status)
}
}
impl From<Status> for NTSTATUS {
#[inline]
fn from(status: Status) -> Self {
status.0
}
}
impl NtStatus for Status {
#[inline]
fn ntstatus(&self) -> NTSTATUS {
self.0
}
}
status_newtype!(Error);
status_newtype!(Status);
pub type StatusResult = Result<Status>;
#[macro_export]
macro_rules! ntok {
($val:expr) => {
::core::result::Result::Ok($val)
};
}
#[macro_export]
macro_rules! nterr {
($status:expr) => {
::core::result::Result::Err($crate::Error::from_ntstatus($status))
};
}
#[macro_export]
macro_rules! ntok_ret {
($val:expr) => {
return $crate::ntok!($val)
};
}
#[macro_export]
macro_rules! ntres {
($status:expr) => {
$crate::IntoResult::into_result($status)
};
}
#[macro_export]
macro_rules! ntres_ret {
($status:expr) => {
return $crate::ntres!($status)
};
}
#[macro_export]
macro_rules! nterr_ret {
($status:expr) => {
return $crate::nterr!($status)
};
}
#[macro_export]
macro_rules! ntbail {
($status:expr) => {
$crate::nterr_ret!($status)
};
}
#[cfg(test)]
mod tests {
use super::*;
use windows_sys::Win32::Foundation::{
STATUS_ACCESS_DENIED, STATUS_ACPI_INVALID_DATA, STATUS_BUFFER_OVERFLOW,
STATUS_OBJECT_NAME_EXISTS, STATUS_PENDING, STATUS_TIMEOUT,
};
const CUSTOMER_ERROR: NTSTATUS = layout::from_bits(0xE000_0001);
const FACILITY_0X23: NTSTATUS = layout::from_bits(0xC023_0001);
#[test]
fn test_nt_status_result() {
let success: StatusResult = Ok(Status::SUCCESS);
let carried: StatusResult = Ok(Status::from_ntstatus(STATUS_BUFFER_OVERFLOW));
let error: StatusResult = Err(Error::from_ntstatus(STATUS_ACCESS_DENIED));
assert_eq!(success.ntstatus(), STATUS_SUCCESS);
assert_eq!(carried.ntstatus(), STATUS_BUFFER_OVERFLOW);
assert_eq!(error.ntstatus(), STATUS_ACCESS_DENIED);
}
#[test]
fn test_ntstatus() {
let success: Result<()> = Ok(());
let error: Result<()> = Err(Error::from_ntstatus(STATUS_ACCESS_DENIED));
assert_eq!(success.ntstatus(), STATUS_SUCCESS);
assert_eq!(error.ntstatus(), STATUS_ACCESS_DENIED);
}
#[test]
fn test_into_result() {
let success: NTSTATUS = STATUS_SUCCESS;
let error: NTSTATUS = STATUS_ACCESS_DENIED;
assert_eq!(success.into_result(), Ok(()));
assert_eq!(
error.into_result(),
Err(Error::from_ntstatus(STATUS_ACCESS_DENIED))
);
}
#[test]
fn test_into_error() {
let status: NTSTATUS = STATUS_ACCESS_DENIED;
assert_eq!(
status.into_error(),
Error::from_ntstatus(STATUS_ACCESS_DENIED)
);
}
#[test]
fn test_from_ntstatus() {
let status: NTSTATUS = STATUS_ACCESS_DENIED;
let error = Error::from_ntstatus(status);
assert_eq!(error.ntstatus(), STATUS_ACCESS_DENIED);
}
#[test]
fn severity_classifies_every_class() {
assert_eq!(Severity::from_ntstatus(STATUS_SUCCESS), Severity::Success);
assert_eq!(Severity::from_ntstatus(STATUS_PENDING), Severity::Success);
assert_eq!(Severity::from_ntstatus(STATUS_TIMEOUT), Severity::Success);
assert_eq!(
Severity::from_ntstatus(STATUS_OBJECT_NAME_EXISTS),
Severity::Information
);
assert_eq!(
Severity::from_ntstatus(STATUS_BUFFER_OVERFLOW),
Severity::Warning
);
assert_eq!(
Severity::from_ntstatus(STATUS_ACCESS_DENIED),
Severity::Error
);
}
#[test]
fn severity_does_not_sign_extend() {
const { assert!(STATUS_ACCESS_DENIED < 0) };
const { assert!(STATUS_BUFFER_OVERFLOW < 0) };
assert_eq!(
Severity::from_ntstatus(STATUS_ACCESS_DENIED),
Severity::Error
);
assert_eq!(
Severity::from_ntstatus(STATUS_BUFFER_OVERFLOW),
Severity::Warning
);
}
#[test]
fn severity_orders_by_increasing_severity() {
assert!(Severity::Success < Severity::Information);
assert!(Severity::Information < Severity::Warning);
assert!(Severity::Warning < Severity::Error);
}
#[test]
fn severity_is_const_evaluable() {
const SEVERITY: Severity = Severity::from_ntstatus(STATUS_ACCESS_DENIED);
assert_eq!(SEVERITY, Severity::Error);
}
#[test]
fn facility_extracts_bits_16_to_27() {
assert_eq!(Error::from_ntstatus(FACILITY_0X23).facility(), 0x023);
assert_eq!(Error::from_ntstatus(STATUS_ACCESS_DENIED).facility(), 0);
assert_eq!(
Error::from_ntstatus(STATUS_ACPI_INVALID_DATA).facility(),
0x014
);
}
#[test]
fn code_extracts_low_16_bits() {
assert_eq!(Error::from_ntstatus(STATUS_ACCESS_DENIED).code(), 0x0022);
assert_eq!(Error::from_ntstatus(FACILITY_0X23).code(), 0x0001);
assert_eq!(Error::from_ntstatus(STATUS_SUCCESS).code(), 0);
}
#[test]
fn is_customer_reads_bit_29() {
assert!(Error::from_ntstatus(CUSTOMER_ERROR).is_customer());
assert!(!Error::from_ntstatus(STATUS_ACCESS_DENIED).is_customer());
assert_eq!(
Error::from_ntstatus(CUSTOMER_ERROR).severity(),
Severity::Error
);
}
#[test]
fn severity_predicates_are_mutually_exclusive() {
for status in [
STATUS_SUCCESS,
STATUS_OBJECT_NAME_EXISTS,
STATUS_BUFFER_OVERFLOW,
STATUS_ACCESS_DENIED,
] {
let error = Error::from_ntstatus(status);
let flags = [
error.is_success(),
error.is_information(),
error.is_warning(),
error.is_error(),
];
assert_eq!(
flags.iter().filter(|set| **set).count(),
1,
"exactly one predicate must hold for {status:#010x}"
);
}
}
#[test]
fn error_constructors_are_const_evaluable() {
const ERROR: Error = Error::from_ntstatus(STATUS_ACCESS_DENIED);
const STATUS: NTSTATUS = ERROR.ntstatus();
const SEVERITY: Severity = ERROR.severity();
const { assert!(ERROR.is(STATUS_ACCESS_DENIED)) };
assert_eq!(STATUS, STATUS_ACCESS_DENIED);
assert_eq!(SEVERITY, Severity::Error);
}
#[test]
fn from_bits_matches_from_ntstatus() {
assert_eq!(
Error::from_bits(0xC000_0022),
Error::from_ntstatus(STATUS_ACCESS_DENIED)
);
assert_eq!(Error::from_bits(0), Error::from_ntstatus(STATUS_SUCCESS));
assert_eq!(
Error::from_bits(u32::MAX),
Error::from_ntstatus(layout::from_bits(u32::MAX))
);
}
#[test]
fn from_bits_is_const_evaluable() {
const CUSTOM: Error = Error::from_bits(0xE000_0001);
const { assert!(CUSTOM.is_customer()) };
const { assert!(CUSTOM.is_error()) };
assert_eq!(CUSTOM.code(), 0x0001);
}
#[test]
fn error_converts_into_ntstatus() {
let error = Error::from_ntstatus(STATUS_ACCESS_DENIED);
let status: NTSTATUS = error.into();
assert_eq!(status, STATUS_ACCESS_DENIED);
assert_eq!(NTSTATUS::from(error), STATUS_ACCESS_DENIED);
}
#[test]
fn error_implements_the_nt_status_trait() {
fn to_status<T: NtStatus>(value: &T) -> NTSTATUS {
value.ntstatus()
}
let error = Error::from_ntstatus(STATUS_ACCESS_DENIED);
let failure: Result<()> = Err(error);
let success: Result<()> = Ok(());
assert_eq!(to_status(&error), STATUS_ACCESS_DENIED);
assert_eq!(to_status(&failure), STATUS_ACCESS_DENIED);
assert_eq!(to_status(&success), STATUS_SUCCESS);
}
#[test]
fn debug_renders_hex_not_signed_decimal() {
let error = Error::from_ntstatus(STATUS_ACCESS_DENIED);
let status = Status::from_ntstatus(STATUS_ACCESS_DENIED);
assert_eq!(
rendered_str(&formatted(format_args!("{error:?}"))),
"Error(0xC0000022)"
);
assert_eq!(
rendered_str(&formatted(format_args!("{status:?}"))),
"Status(0xC0000022)"
);
}
#[test]
fn debug_zero_pads_and_nests_inside_result() {
let success: Result<()> = Ok(());
let failed: Result<()> = Err(Error::from_ntstatus(STATUS_SUCCESS));
assert_eq!(
rendered_str(&formatted(format_args!("{success:?}"))),
"Ok(())"
);
assert_eq!(
rendered_str(&formatted(format_args!("{failed:?}"))),
"Err(Error(0x00000000))"
);
}
#[test]
fn debug_wraps_the_display_form() {
for status in [
STATUS_SUCCESS,
STATUS_TIMEOUT,
STATUS_OBJECT_NAME_EXISTS,
STATUS_BUFFER_OVERFLOW,
STATUS_ACCESS_DENIED,
CUSTOMER_ERROR,
FACILITY_0X23,
layout::from_bits(u32::MAX),
] {
let error = Error::from_ntstatus(status);
let display = formatted(format_args!("{error}"));
let debug = formatted(format_args!("{error:?}"));
let expected = formatted(format_args!("Error({})", rendered_str(&display)));
assert_eq!(
rendered_str(&debug),
rendered_str(&expected),
"mismatch for {status:#010X}"
);
}
let status = Status::from_ntstatus(STATUS_BUFFER_OVERFLOW);
let display = formatted(format_args!("{status}"));
let debug = formatted(format_args!("{status:?}"));
assert_eq!(rendered_str(&display), "0x80000005");
assert_eq!(
rendered_str(&debug),
rendered_str(&formatted(format_args!(
"Status({})",
rendered_str(&display)
)))
);
}
#[test]
fn status_result_carries_non_success_through_ntstatus() {
let pending: StatusResult = Ok(Status::from_ntstatus(STATUS_PENDING));
assert_eq!(pending.ntstatus(), STATUS_PENDING);
assert_eq!(pending.ntstatus_or_success(), STATUS_SUCCESS);
}
#[test]
fn ntstatus_or_success_handles_arbitrary_payloads() {
let data: Result<[u8; 4]> = Ok([1, 2, 3, 4]);
let failed: Result<[u8; 4]> = Err(Error::from_ntstatus(STATUS_ACCESS_DENIED));
assert_eq!(data.ntstatus_or_success(), STATUS_SUCCESS);
assert_eq!(failed.ntstatus_or_success(), STATUS_ACCESS_DENIED);
}
#[test]
fn status_and_error_accessors_agree() {
for bits in [
0x0000_0000u32,
0x4000_0000,
0x8000_0005,
0xC000_0022,
0xC014_000F,
0xE000_0001,
u32::MAX,
] {
let status = Status::from_bits(bits);
let error = Error::from_bits(bits);
assert_eq!(status.severity(), error.severity(), "{bits:#010X}");
assert_eq!(status.facility(), error.facility(), "{bits:#010X}");
assert_eq!(status.code(), error.code(), "{bits:#010X}");
assert_eq!(status.is_customer(), error.is_customer(), "{bits:#010X}");
assert_eq!(status.is_success(), error.is_success(), "{bits:#010X}");
assert_eq!(
status.is_information(),
error.is_information(),
"{bits:#010X}"
);
assert_eq!(status.is_warning(), error.is_warning(), "{bits:#010X}");
assert_eq!(status.is_error(), error.is_error(), "{bits:#010X}");
assert_eq!(status.ntstatus(), error.ntstatus(), "{bits:#010X}");
assert_eq!(
status.ntstatus(),
Status::from_ntstatus(layout::from_bits(bits)).ntstatus(),
"{bits:#010X}"
);
assert!(status.is(error.ntstatus()), "{bits:#010X}");
assert!(error.is(status.ntstatus()), "{bits:#010X}");
}
}
#[test]
fn is_compares_the_whole_status_on_both_types() {
let error = Error::from_ntstatus(STATUS_ACCESS_DENIED);
let status = Status::from_ntstatus(STATUS_ACCESS_DENIED);
assert!(error.is(STATUS_ACCESS_DENIED));
assert!(status.is(STATUS_ACCESS_DENIED));
assert!(!error.is(STATUS_SUCCESS));
assert!(!status.is(STATUS_SUCCESS));
let sibling = Error::from_bits(0xC000_0023);
assert_eq!(sibling.severity(), error.severity());
assert_eq!(sibling.facility(), error.facility());
assert!(!error.is(sibling.ntstatus()));
}
#[test]
fn is_is_const_evaluable_on_both_types() {
const { assert!(Error::from_ntstatus(STATUS_ACCESS_DENIED).is(STATUS_ACCESS_DENIED)) };
const { assert!(Status::SUCCESS.is(STATUS_SUCCESS)) };
const { assert!(!Status::from_bits(0xE000_0001).is(STATUS_SUCCESS)) };
}
#[test]
fn status_newtype_accessors_are_const_evaluable() {
const CARRIED: Status = Status::from_ntstatus(STATUS_BUFFER_OVERFLOW);
const RAW: NTSTATUS = CARRIED.ntstatus();
const SEVERITY: Severity = CARRIED.severity();
const { assert!(CARRIED.is_warning()) };
const { assert!(Status::from_bits(0xE000_0001).is_customer()) };
assert_eq!(RAW, STATUS_BUFFER_OVERFLOW);
assert_eq!(SEVERITY, Severity::Warning);
assert_eq!(Status::SUCCESS.ntstatus(), STATUS_SUCCESS);
}
#[test]
fn status_converts_both_ways() {
let status: Status = STATUS_PENDING.into();
assert_eq!(status.ntstatus(), STATUS_PENDING);
let raw: NTSTATUS = status.into();
assert_eq!(raw, STATUS_PENDING);
assert_eq!(
Status::from_bits(0xC000_0022),
Status::from_ntstatus(STATUS_ACCESS_DENIED)
);
}
#[test]
fn status_is_transparent_over_ntstatus() {
assert_eq!(
core::mem::size_of::<Status>(),
core::mem::size_of::<NTSTATUS>()
);
assert_eq!(
core::mem::align_of::<Status>(),
core::mem::align_of::<NTSTATUS>()
);
}
#[test]
fn error_is_transparent_over_ntstatus() {
assert_eq!(
core::mem::size_of::<Error>(),
core::mem::size_of::<NTSTATUS>()
);
assert_eq!(
core::mem::align_of::<Error>(),
core::mem::align_of::<NTSTATUS>()
);
}
#[test]
fn severity_discriminants_match_the_wire_encoding() {
assert_eq!(Severity::Success as u32, 0);
assert_eq!(Severity::Information as u32, 1);
assert_eq!(Severity::Warning as u32, 2);
assert_eq!(Severity::Error as u32, 3);
}
#[test]
fn result_into_result_converts_foreign_error_type() {
let ok: core::result::Result<u8, NTSTATUS> = Ok(7);
let err: core::result::Result<u8, NTSTATUS> = Err(STATUS_ACCESS_DENIED);
assert_eq!(ok.into_result(), Ok(7));
assert_eq!(
err.into_result(),
Err(Error::from_ntstatus(STATUS_ACCESS_DENIED))
);
}
#[test]
fn error_is_usable_as_dyn_error() {
let error = Error::from_ntstatus(STATUS_ACCESS_DENIED);
let dynamic: &dyn core::error::Error = &error;
assert!(dynamic.source().is_none());
}
struct FmtBuf {
bytes: [u8; 64],
len: usize,
}
impl core::fmt::Write for FmtBuf {
fn write_str(&mut self, text: &str) -> core::fmt::Result {
let end = self.len + text.len();
if end > self.bytes.len() {
return Err(core::fmt::Error);
}
self.bytes[self.len..end].copy_from_slice(text.as_bytes());
self.len = end;
Ok(())
}
}
fn formatted(args: core::fmt::Arguments<'_>) -> FmtBuf {
use core::fmt::Write;
let mut buf = FmtBuf {
bytes: [0; 64],
len: 0,
};
buf.write_fmt(args).unwrap();
buf
}
fn rendered(status: NTSTATUS) -> FmtBuf {
formatted(format_args!("{}", Error::from_ntstatus(status)))
}
fn rendered_str(buf: &FmtBuf) -> &str {
core::str::from_utf8(&buf.bytes[..buf.len]).unwrap()
}
#[test]
fn display_zero_pads_to_eight_hex_digits() {
assert_eq!(rendered_str(&rendered(STATUS_SUCCESS)), "0x00000000");
assert_eq!(rendered_str(&rendered(STATUS_TIMEOUT)), "0x00000102");
assert_eq!(
rendered_str(&rendered(STATUS_OBJECT_NAME_EXISTS)),
"0x40000000"
);
assert_eq!(
rendered_str(&rendered(STATUS_BUFFER_OVERFLOW)),
"0x80000005"
);
assert_eq!(rendered_str(&rendered(STATUS_ACCESS_DENIED)), "0xC0000022");
}
#[test]
fn display_is_uppercase_and_always_ten_characters() {
for status in [
STATUS_SUCCESS,
STATUS_TIMEOUT,
STATUS_PENDING,
STATUS_OBJECT_NAME_EXISTS,
STATUS_BUFFER_OVERFLOW,
STATUS_ACCESS_DENIED,
CUSTOMER_ERROR,
FACILITY_0X23,
layout::from_bits(u32::MAX),
] {
let buf = rendered(status);
let text = rendered_str(&buf);
assert_eq!(text.len(), 10, "wrong width for {status:#010X}");
assert!(text.starts_with("0x"), "missing prefix for {status:#010X}");
assert!(
!text[2..].chars().any(char::is_lowercase),
"expected uppercase digits, got {text}"
);
}
}
#[test]
fn is_success_reports_severity_not_status_success() {
let pending = Error::from_ntstatus(STATUS_PENDING);
assert!(pending.is_success());
assert!(!pending.is(STATUS_SUCCESS));
assert!(STATUS_PENDING.into_result().is_err());
}
#[test]
fn nterr_ret_skips_the_rest_of_the_function() {
fn run() -> crate::Result<u32> {
nterr_ret!(STATUS_ACCESS_DENIED);
#[allow(unreachable_code)]
Ok(1)
}
assert!(run().unwrap_err().is(STATUS_ACCESS_DENIED));
}
#[test]
fn ntbail_is_an_alias_for_nterr_ret() {
fn via_bail() -> crate::Result<u32> {
ntbail!(STATUS_ACCESS_DENIED);
#[allow(unreachable_code)]
Ok(1)
}
fn via_nterr_ret() -> crate::Result<u32> {
nterr_ret!(STATUS_ACCESS_DENIED);
#[allow(unreachable_code)]
Ok(1)
}
assert_eq!(via_bail().unwrap_err(), via_nterr_ret().unwrap_err());
assert!(via_bail().unwrap_err().is(STATUS_ACCESS_DENIED));
}
#[test]
fn ntres_ret_skips_the_rest_of_the_function() {
fn run(status: NTSTATUS) -> crate::Result<()> {
ntres_ret!(status);
#[allow(unreachable_code)]
Err(Error::from_ntstatus(STATUS_TIMEOUT))
}
assert!(run(STATUS_SUCCESS).is_ok());
assert!(
run(STATUS_ACCESS_DENIED)
.unwrap_err()
.is(STATUS_ACCESS_DENIED)
);
}
#[test]
fn ntok_ret_skips_the_rest_of_the_function() {
fn run() -> crate::Result<u32> {
ntok_ret!(7);
#[allow(unreachable_code)]
Ok(1)
}
assert_eq!(run().unwrap(), 7);
}
#[test]
fn macros_evaluate_their_argument_once() {
let mut calls = 0;
let mut status = || {
calls += 1;
STATUS_SUCCESS
};
assert!(ntres!(status()).is_ok());
assert_eq!(calls, 1);
}
}