use std::fmt;
use std::io;
use windows_impersonation_token_sys::{ApplyError, CaptureError, ImpersonationToken};
use crate::request::EnumerationRequest;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Win32Error(u32);
impl Win32Error {
#[must_use]
pub const fn from_code(code: u32) -> Self {
Self(code)
}
#[must_use]
pub fn from_io(error: &io::Error) -> Self {
Self(
error
.raw_os_error()
.and_then(|code| u32::try_from(code).ok())
.unwrap_or(0),
)
}
#[must_use]
pub(crate) fn last() -> Self {
Self::from_io(&io::Error::last_os_error())
}
#[must_use]
pub const fn code(self) -> u32 {
self.0
}
#[must_use]
pub fn to_io_error(self) -> io::Error {
io::Error::from_raw_os_error(
i32::try_from(self.0).expect("a WIN32_ERROR always fits in an i32"),
)
}
}
impl fmt::Display for Win32Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Win32 error {} ({})", self.0, self.to_io_error())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RequestFailure {
EmptyPath,
InteriorNul,
PathTooLong,
NotFullyQualified,
PathResolution,
BufferCapacityUnrepresentable,
}
impl RequestFailure {
const fn describe(self) -> &'static str {
match self {
RequestFailure::EmptyPath => "the path is empty",
RequestFailure::InteriorNul => "the path contains an interior NUL",
RequestFailure::PathTooLong => {
"the path exceeds MAX_PATH; supply a fully qualified \\\\?\\ path"
}
RequestFailure::NotFullyQualified => "the \\\\?\\ path is not fully qualified",
RequestFailure::PathResolution => "the path could not be resolved",
RequestFailure::BufferCapacityUnrepresentable => {
"the native buffer capacity does not fit a Win32 u32"
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RequestError {
failure: RequestFailure,
code: Option<Win32Error>,
}
impl RequestError {
pub(crate) const fn new(failure: RequestFailure) -> Self {
Self {
failure,
code: None,
}
}
pub(crate) const fn with_code(failure: RequestFailure, code: Win32Error) -> Self {
Self {
failure,
code: Some(code),
}
}
#[must_use]
pub const fn failure(&self) -> RequestFailure {
self.failure
}
#[must_use]
pub const fn code(&self) -> Option<Win32Error> {
self.code
}
}
impl fmt::Display for RequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.code {
Some(code) => write!(f, "{}: {code}", self.failure.describe()),
None => f.write_str(self.failure.describe()),
}
}
}
impl std::error::Error for RequestError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BeginFailure {
SubmissionRingFull,
CompletionRingFull,
Abandoned,
TokenCapture,
BufferAllocation,
}
impl BeginFailure {
const fn describe(self) -> &'static str {
match self {
BeginFailure::SubmissionRingFull => "the submission ring is full",
BeginFailure::CompletionRingFull => {
"the completion ring cannot reserve a terminal slot"
}
BeginFailure::Abandoned => "the session has been abandoned by its receiver",
BeginFailure::TokenCapture => "the caller's security context could not be captured",
BeginFailure::BufferAllocation => "the native buffer could not be allocated",
}
}
}
#[derive(Debug)]
pub struct BeginError {
failure: BeginFailure,
request: EnumerationRequest,
token: Option<ImpersonationToken>,
capture: Option<CaptureError>,
}
impl BeginError {
pub(crate) fn rejected(
failure: BeginFailure,
request: EnumerationRequest,
token: Option<ImpersonationToken>,
) -> Self {
Self {
failure,
request,
token,
capture: None,
}
}
pub(crate) fn capture(request: EnumerationRequest, capture: CaptureError) -> Self {
Self {
failure: BeginFailure::TokenCapture,
request,
token: None,
capture: Some(capture),
}
}
#[must_use]
pub const fn failure(&self) -> BeginFailure {
self.failure
}
#[must_use]
pub const fn request(&self) -> &EnumerationRequest {
&self.request
}
#[must_use]
pub fn into_parts(self) -> (EnumerationRequest, Option<ImpersonationToken>) {
(self.request, self.token)
}
#[must_use]
pub const fn capture_error(&self) -> Option<&CaptureError> {
self.capture.as_ref()
}
}
impl fmt::Display for BeginError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.capture {
Some(capture) => write!(f, "{}: {capture}", self.failure.describe()),
None => f.write_str(self.failure.describe()),
}
}
}
impl std::error::Error for BeginError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.capture
.as_ref()
.map(|capture| capture as &(dyn std::error::Error + 'static))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SessionFailure {
SubmissionCapacityTooSmall,
CompletionCapacityTooSmall,
WorkObject,
}
impl SessionFailure {
const fn describe(self) -> &'static str {
match self {
SessionFailure::SubmissionCapacityTooSmall => {
"the submission ring is too small to carry one enumeration"
}
SessionFailure::CompletionCapacityTooSmall => {
"the completion ring is too small to carry one enumeration"
}
SessionFailure::WorkObject => "the servicer's work object could not be created",
}
}
}
#[derive(Debug)]
pub struct SessionError {
failure: SessionFailure,
source: Option<io::Error>,
}
impl SessionError {
pub(crate) const fn new(failure: SessionFailure) -> Self {
Self {
failure,
source: None,
}
}
pub(crate) const fn with_source(failure: SessionFailure, source: io::Error) -> Self {
Self {
failure,
source: Some(source),
}
}
#[must_use]
pub const fn failure(&self) -> SessionFailure {
self.failure
}
#[must_use]
pub const fn os_error(&self) -> Option<&io::Error> {
self.source.as_ref()
}
}
impl fmt::Display for SessionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.source {
Some(source) => write!(f, "{}: {source}", self.failure.describe()),
None => f.write_str(self.failure.describe()),
}
}
}
impl std::error::Error for SessionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|source| source as &(dyn std::error::Error + 'static))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PredicateFailure {
EmptyAttributeMask,
EmptyNameSet,
}
impl PredicateFailure {
const fn describe(self) -> &'static str {
match self {
PredicateFailure::EmptyAttributeMask => {
"an attribute mask clause requires a non-zero mask"
}
PredicateFailure::EmptyNameSet => "a name-set clause requires at least one pattern",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PredicateError {
failure: PredicateFailure,
}
impl PredicateError {
pub(crate) const fn new(failure: PredicateFailure) -> Self {
Self { failure }
}
#[must_use]
pub const fn failure(&self) -> PredicateFailure {
self.failure
}
}
impl fmt::Display for PredicateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.failure.describe())
}
}
impl std::error::Error for PredicateError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MalformedRecord {
Alignment,
TruncatedFixedFields,
NextEntryOffset,
OddNameLength,
NameOutOfBounds,
NegativeSize,
}
impl MalformedRecord {
const fn describe(self) -> &'static str {
match self {
MalformedRecord::Alignment => "the record is misaligned",
MalformedRecord::TruncatedFixedFields => "the record's fixed fields are truncated",
MalformedRecord::NextEntryOffset => "the record's next-entry offset does not advance",
MalformedRecord::OddNameLength => {
"the record's name length is not a whole code-unit count"
}
MalformedRecord::NameOutOfBounds => "the record's name extends past the batch",
MalformedRecord::NegativeSize => "the record reports a negative size",
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum EnumerationError {
Impersonation(ApplyError),
DirectoryOpen(Win32Error),
VolumeIdentity(Win32Error),
UnsupportedExtendedDirectoryInfo(Win32Error),
DirectoryQuery(Win32Error),
RecordTooLarge {
buffer_capacity: usize,
code: Win32Error,
},
MalformedRecord(MalformedRecord),
}
impl EnumerationError {
#[must_use]
pub fn code(&self) -> Option<Win32Error> {
match self {
EnumerationError::Impersonation(error) => error
.raw_os_error()
.and_then(|code| u32::try_from(code).ok())
.map(Win32Error::from_code),
EnumerationError::DirectoryOpen(code)
| EnumerationError::VolumeIdentity(code)
| EnumerationError::UnsupportedExtendedDirectoryInfo(code)
| EnumerationError::DirectoryQuery(code)
| EnumerationError::RecordTooLarge { code, .. } => Some(*code),
EnumerationError::MalformedRecord(_) => None,
}
}
}
impl fmt::Display for EnumerationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EnumerationError::Impersonation(error) => {
write!(f, "the submitted impersonation context failed: {error}")
}
EnumerationError::DirectoryOpen(code) => {
write!(f, "the directory could not be opened: {code}")
}
EnumerationError::VolumeIdentity(code) => {
write!(f, "the required volume identity is unavailable: {code}")
}
EnumerationError::UnsupportedExtendedDirectoryInfo(code) => write!(
f,
"extended directory information is unsupported here: {code}"
),
EnumerationError::DirectoryQuery(code) => {
write!(f, "the directory query failed: {code}")
}
EnumerationError::RecordTooLarge {
buffer_capacity,
code,
} => write!(
f,
"one record exceeds the {buffer_capacity}-byte native buffer: {code}"
),
EnumerationError::MalformedRecord(detail) => {
write!(
f,
"a native record failed validation: {}",
detail.describe()
)
}
}
}
}
impl std::error::Error for EnumerationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
EnumerationError::Impersonation(error) => Some(error),
_ => None,
}
}
}
#[cfg(test)]
mod tests;