use std::borrow::Borrow;
use std::cmp::Ordering;
use std::ffi::c_void;
use std::fmt::{Debug, Display};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::str::FromStr;
use windows_core::{Error, HRESULT, Result};
#[cfg(feature = "windows-full")]
use windows::Win32::Security::PSID;
const SID_REVISION: u8 = 1;
const SID_MAX_SUB_AUTHORITIES: u8 = 15;
const SID_HEADER_WORDS: usize = 2;
pub fn invalid_sid_err() -> Error {
Error::from_hresult(HRESULT::from_win32(0x0539))
}
pub trait AsSidPtr {
fn as_sid_ptr(&self) -> *const c_void;
}
#[cfg(feature = "windows-full")]
impl AsSidPtr for PSID {
fn as_sid_ptr(&self) -> *const c_void {
self.0
}
}
impl AsSidPtr for *const c_void {
fn as_sid_ptr(&self) -> *const c_void {
*self
}
}
impl AsSidPtr for *mut c_void {
fn as_sid_ptr(&self) -> *const c_void {
*self
}
}
#[repr(C)]
pub struct Sid {
revision: u8,
sub_authority_count: u8,
identifier_authority: [u8; 6],
sub_authority: [u32],
}
impl Sid {
fn from_words_unchecked(words: &[u32]) -> &Sid {
debug_assert!(
words.len() >= SID_HEADER_WORDS,
"a SID has at least the two header words"
);
let sub_count = words.len() - SID_HEADER_WORDS;
let ptr = std::ptr::slice_from_raw_parts(words.as_ptr(), sub_count) as *const Sid;
unsafe { &*ptr }
}
fn from_words_unchecked_mut(words: &mut [u32]) -> &mut Sid {
debug_assert!(
words.len() >= SID_HEADER_WORDS,
"a SID has at least the two header words"
);
let sub_count = words.len() - SID_HEADER_WORDS;
let ptr = std::ptr::slice_from_raw_parts_mut(words.as_mut_ptr(), sub_count) as *mut Sid;
unsafe { &mut *ptr }
}
#[inline]
fn logical_sub_count(&self) -> usize {
(self.sub_authority_count as usize).min(self.sub_authority.len())
}
#[inline]
fn words(&self) -> &[u32] {
unsafe {
std::slice::from_raw_parts(
self as *const Sid as *const u32,
SID_HEADER_WORDS + self.logical_sub_count(),
)
}
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let words = self.words();
unsafe { std::slice::from_raw_parts(words.as_ptr() as *const u8, size_of_val(words)) }
}
#[cfg(test)]
fn buffer_bytes(&self) -> &[u8] {
let word_len = SID_HEADER_WORDS + self.sub_authority.len();
unsafe {
std::slice::from_raw_parts(self as *const Sid as *const u8, word_len * size_of::<u32>())
}
}
#[cfg(feature = "windows-full")]
#[inline]
pub unsafe fn as_psid(&self) -> PSID {
PSID(self as *const Sid as *mut _)
}
pub fn as_ptr(&self) -> *const c_void {
self as *const Sid as *const c_void
}
pub unsafe fn from_psid<'a>(psid: impl AsSidPtr) -> Result<&'a Sid> {
let psid = psid.as_sid_ptr();
let p = psid as *const u8;
let (revision, sub_count) = unsafe { (*p, *p.add(1)) };
if revision != SID_REVISION || sub_count > SID_MAX_SUB_AUTHORITIES {
return Err(invalid_sid_err());
}
debug_assert!(
(psid as usize).is_multiple_of(align_of::<u32>()),
"SID pointer is not 4-byte aligned",
);
let word_len = SID_HEADER_WORDS + sub_count as usize;
let words = unsafe { std::slice::from_raw_parts(psid as *const u32, word_len) };
Ok(Sid::from_words_unchecked(words))
}
#[inline]
pub fn revision(&self) -> u8 {
self.revision
}
#[inline]
pub fn sub_authority_count(&self) -> u8 {
self.sub_authority_count
}
#[inline]
pub fn authority(&self) -> u64 {
let [a0, a1, a2, a3, a4, a5] = self.identifier_authority;
u64::from_be_bytes([0, 0, a0, a1, a2, a3, a4, a5])
}
#[inline]
pub fn sub_authority(&self, idx: u8) -> Option<u32> {
self.sub_authorities().get(idx as usize).copied()
}
#[inline]
pub fn sub_authorities(&self) -> &[u32] {
&self.sub_authority[..self.logical_sub_count()]
}
#[inline]
pub fn equal_prefix(&self, other: &Sid) -> bool {
equal_prefix_sid(self, other)
}
#[cfg(feature = "windows-full")]
#[inline]
pub fn equal_domain(&self, other: &Sid) -> Result<bool> {
equal_domain_sid(self, other)
}
#[cfg(feature = "windows-full")]
#[inline]
pub fn account_domain_sid(&self) -> Result<SidBuf> {
get_windows_account_domain_sid(self)
}
#[cfg(feature = "windows-full")]
#[inline]
pub fn is_well_known(
&self,
well_known_type: windows::Win32::Security::WELL_KNOWN_SID_TYPE,
) -> bool {
is_well_known_sid(self, well_known_type)
}
}
#[inline]
pub fn equal_prefix_sid(sid1: &Sid, sid2: &Sid) -> bool {
sid1.revision == sid2.revision
&& sid1.identifier_authority == sid2.identifier_authority
&& sid1.sub_authority_count == sid2.sub_authority_count
&& match (
sid1.sub_authorities().split_last(),
sid2.sub_authorities().split_last(),
) {
(Some((_, prefix1)), Some((_, prefix2))) => prefix1 == prefix2,
(None, None) => true,
_ => false,
}
}
#[cfg(feature = "windows-full")]
pub fn equal_domain_sid(sid1: &Sid, sid2: &Sid) -> Result<bool> {
use windows::Win32::Security::EqualDomainSid;
let mut equal = windows_core::BOOL::default();
unsafe {
EqualDomainSid(sid1.as_psid(), sid2.as_psid(), &mut equal)?;
}
Ok(equal.as_bool())
}
#[cfg(feature = "windows-full")]
pub fn get_windows_account_domain_sid(sid: &Sid) -> Result<SidBuf> {
use windows::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
use windows::Win32::Security::GetWindowsAccountDomainSid;
unsafe {
let mut len = 0u32;
if let Err(error) = GetWindowsAccountDomainSid(sid.as_psid(), None, &mut len)
&& error.code() != HRESULT::from_win32(ERROR_INSUFFICIENT_BUFFER.0)
{
return Err(error);
}
let mut domain_sid = SidBuf::with_capacity(len as usize);
GetWindowsAccountDomainSid(sid.as_psid(), Some(PSID(domain_sid.as_mut_ptr())), &mut len)?;
Ok(domain_sid)
}
}
#[cfg(feature = "windows-full")]
pub fn is_well_known_sid(
sid: &Sid,
well_known_type: windows::Win32::Security::WELL_KNOWN_SID_TYPE,
) -> bool {
use windows::Win32::Security::IsWellKnownSid;
unsafe { IsWellKnownSid(sid.as_psid(), well_known_type).as_bool() }
}
impl ToOwned for Sid {
type Owned = SidBuf;
fn to_owned(&self) -> SidBuf {
SidBuf::from_boxed_words(self.words().to_vec().into_boxed_slice())
}
}
impl Eq for Sid {}
impl PartialEq for Sid {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl Hash for Sid {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(self.as_bytes());
}
}
impl PartialOrd for Sid {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Sid {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.as_bytes().cmp(other.as_bytes())
}
}
impl Display for Sid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("S-1-")?;
let authority = self.authority();
if authority <= 0xFFFFFFFF {
write!(f, "{}", authority)?;
} else {
write!(f, "0x{:012X}", authority)?;
}
for sa in self.sub_authorities() {
write!(f, "-{}", sa)?;
}
Ok(())
}
}
impl Debug for Sid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self)
}
}
pub struct SidBuf {
sid: Box<Sid>,
}
impl Clone for SidBuf {
fn clone(&self) -> Self {
(**self).to_owned()
}
}
#[derive(Debug)]
pub struct ParseError;
impl std::error::Error for ParseError {}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid SID string")
}
}
impl FromStr for SidBuf {
type Err = ParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut parts = s.split('-');
match parts.next() {
Some(p) if p.eq_ignore_ascii_case("S") => {}
_ => return Err(ParseError),
}
if parts.next() != Some("1") {
return Err(ParseError);
}
let authority_str = parts.next().ok_or(ParseError)?;
let authority = match authority_str
.strip_prefix("0x")
.or_else(|| authority_str.strip_prefix("0X"))
{
Some(hex) => u64::from_str_radix(hex, 16).map_err(|_| ParseError)?,
None => authority_str.parse::<u64>().map_err(|_| ParseError)?,
};
if authority > 0xFFFF_FFFF_FFFF {
return Err(ParseError);
}
let identifier_authority: [u8; 6] = authority.to_be_bytes()[2..].try_into().unwrap();
let sub_authorities = parts
.map(|p| p.parse::<u32>())
.collect::<std::result::Result<Vec<u32>, _>>()
.map_err(|_| ParseError)?;
SidBuf::new(identifier_authority, &sub_authorities).map_err(|_| ParseError)
}
}
impl SidBuf {
fn from_boxed_words(words: Box<[u32]>) -> SidBuf {
debug_assert!(
words.len() >= SID_HEADER_WORDS,
"a SID has at least the two header words"
);
let sub_count = words.len() - SID_HEADER_WORDS;
let data = Box::into_raw(words).cast::<u32>();
let ptr = std::ptr::slice_from_raw_parts_mut(data, sub_count) as *mut Sid;
SidBuf {
sid: unsafe { Box::from_raw(ptr) },
}
}
pub fn new(identifier_authority: [u8; 6], sub_authorities: &[u32]) -> Result<Self> {
let count = sub_authorities.len();
if count > SID_MAX_SUB_AUTHORITIES as usize {
return Err(invalid_sid_err());
}
let mut words = vec![0u32; SID_HEADER_WORDS + count].into_boxed_slice();
let sid = Sid::from_words_unchecked_mut(&mut words);
sid.revision = SID_REVISION;
sid.sub_authority_count = count as u8;
sid.identifier_authority = identifier_authority;
sid.sub_authority.copy_from_slice(sub_authorities);
Ok(Self::from_boxed_words(words))
}
pub fn with_capacity(len: usize) -> SidBuf {
let word_len = len.div_ceil(size_of::<u32>()).max(SID_HEADER_WORDS + 1);
let mut words = vec![0u32; word_len].into_boxed_slice();
let sid = Sid::from_words_unchecked_mut(&mut words);
sid.revision = SID_REVISION;
sid.sub_authority_count = 1;
SidBuf::from_boxed_words(words)
}
pub unsafe fn as_mut_ptr(&mut self) -> *mut c_void {
let sid: &mut Sid = &mut self.sid;
sid as *mut Sid as *mut c_void
}
#[cfg(feature = "windows-full")]
pub fn well_known(
well_known_type: windows::Win32::Security::WELL_KNOWN_SID_TYPE,
domain_sid: Option<&Sid>,
) -> Result<SidBuf> {
use windows::Win32::Security::CreateWellKnownSid;
unsafe {
let domain_psid = domain_sid.map(|x| Sid::as_psid(x));
let mut len = 0u32;
let _ = CreateWellKnownSid(well_known_type, domain_psid, None, &mut len);
let word_len = (len as usize).div_ceil(size_of::<u32>());
let mut words: Box<[u32]> = vec![0u32; word_len].into_boxed_slice();
let psid = PSID(words.as_mut_ptr() as *mut _);
CreateWellKnownSid(well_known_type, domain_psid, Some(psid), &mut len)?;
Ok(SidBuf::from_boxed_words(words))
}
}
#[cfg(feature = "windows-full")]
pub fn from_cstr_with_alias(s: &std::ffi::CStr) -> Result<SidBuf> {
use windows::Win32::Foundation::{HLOCAL, LocalFree};
use windows::Win32::Security::Authorization::ConvertStringSidToSidA;
use windows::core::PCSTR;
unsafe {
let mut sid = PSID::default();
ConvertStringSidToSidA(PCSTR(s.as_ptr() as *const u8), &mut sid)?;
let res = SidBuf::from_psid(sid);
LocalFree(Some(HLOCAL(sid.0)));
res
}
}
pub unsafe fn from_psid(psid: impl AsSidPtr) -> Result<Self> {
let src = unsafe { Sid::from_psid(psid) }?;
Ok(src.to_owned())
}
}
impl Deref for SidBuf {
type Target = Sid;
#[inline]
fn deref(&self) -> &Sid {
&self.sid
}
}
impl Borrow<Sid> for SidBuf {
#[inline]
fn borrow(&self) -> &Sid {
self
}
}
impl AsRef<Sid> for SidBuf {
#[inline]
fn as_ref(&self) -> &Sid {
self
}
}
impl Eq for SidBuf {}
impl PartialEq for SidBuf {
#[inline]
fn eq(&self, other: &Self) -> bool {
(**self).eq(&**other)
}
}
impl Hash for SidBuf {
fn hash<H: Hasher>(&self, state: &mut H) {
(**self).hash(state);
}
}
impl PartialOrd for SidBuf {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SidBuf {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
(**self).cmp(&**other)
}
}
impl Display for SidBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&**self, f)
}
}
impl Debug for SidBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&**self, f)
}
}
impl Default for SidBuf {
fn default() -> Self {
SidBuf::new([0, 0, 0, 0, 0, 0], &[0]).unwrap()
}
}
impl PartialEq<SidBuf> for Sid {
#[inline]
fn eq(&self, other: &SidBuf) -> bool {
self == &**other
}
}
impl PartialEq<Sid> for SidBuf {
#[inline]
fn eq(&self, other: &Sid) -> bool {
&**self == other
}
}
#[cfg(test)]
mod tests {
use super::*;
const NT_AUTHORITY: [u8; 6] = [0, 0, 0, 0, 0, 5];
fn nt_sid(sub_authorities: &[u32]) -> SidBuf {
SidBuf::new(NT_AUTHORITY, sub_authorities).unwrap()
}
#[test]
fn from_psid_rejects_invalid_headers() {
for header in [[0, 0], [SID_REVISION, SID_MAX_SUB_AUTHORITIES + 1]] {
let words = [u32::from_ne_bytes([header[0], header[1], 0, 0]), 0];
let psid = words.as_ptr() as *const c_void;
assert!(unsafe { Sid::from_psid(psid) }.is_err());
assert!(unsafe { SidBuf::from_psid(psid) }.is_err());
}
}
#[test]
fn new_enforces_the_sub_authority_limit() {
assert_eq!(
SidBuf::new([0; 6], &[7; 15]).unwrap().sub_authority_count(),
15
);
assert!(SidBuf::new([0; 6], &[0; 16]).is_err());
}
#[cfg(feature = "windows-full")]
#[test]
fn well_known_sids_construct() {
use windows::Win32::Security::{
WinBuiltinAdministratorsSid, WinLocalSystemSid, WinNullSid,
};
for (kind, expected) in [
(WinNullSid, "S-1-0-0"),
(WinLocalSystemSid, "S-1-5-18"),
(WinBuiltinAdministratorsSid, "S-1-5-32-544"),
] {
assert_eq!(
SidBuf::well_known(kind, None).unwrap().to_string(),
expected
);
}
}
#[test]
fn new_builds_the_expected_sid() {
let sid = nt_sid(&[32, 544]);
assert_eq!(
sid.as_bytes(),
&[
0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x20, 0x00, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, ]
);
assert_eq!(sid.revision(), SID_REVISION);
assert_eq!(sid.sub_authority_count(), 2);
assert_eq!(sid.authority(), 5);
assert_eq!(sid.sub_authority(0), Some(32));
assert_eq!(sid.sub_authority(1), Some(544));
assert_eq!(sid.sub_authority(2), None);
assert_eq!(sid.sub_authorities(), [32, 544]);
}
#[test]
fn from_psid_borrows_or_copies_as_requested() {
let copied = {
let src = nt_sid(&[18]);
let borrowed = unsafe { Sid::from_psid(src.as_ptr()) }.expect("valid SID");
let copied = unsafe { SidBuf::from_psid(src.as_ptr()) }.expect("valid SID");
assert_eq!(borrowed, &*src);
assert_eq!(copied, src);
copied
};
assert_eq!(copied.to_string(), "S-1-5-18");
}
#[test]
fn with_capacity_separates_logical_length_from_buffer_length() {
for (requested, allocated) in [(0, 12), (12, 12), (13, 16), (200, 200)] {
let sid = SidBuf::with_capacity(requested);
assert_eq!(sid.buffer_bytes().len(), allocated);
assert_eq!(sid.as_bytes().len(), 12);
assert_eq!(sid, SidBuf::default());
}
}
#[test]
fn as_mut_ptr_lets_a_caller_fill_the_buffer() {
let admins = nt_sid(&[32, 544]);
let mut buf = SidBuf::with_capacity(admins.as_bytes().len());
unsafe {
let dst = buf.as_mut_ptr() as *mut u8;
std::ptr::copy_nonoverlapping(admins.as_bytes().as_ptr(), dst, admins.as_bytes().len());
}
assert_eq!(buf, admins);
}
#[test]
fn value_traits_ignore_spare_buffer_capacity() {
use std::collections::hash_map::DefaultHasher;
let oversized = SidBuf::with_capacity(200);
let null_sid = SidBuf::default();
assert_eq!(oversized, null_sid);
assert_eq!(oversized.as_bytes(), null_sid.as_bytes());
assert_eq!(oversized.cmp(&null_sid), Ordering::Equal);
let mut oversized_hash = DefaultHasher::new();
oversized.hash(&mut oversized_hash);
let mut null_hash = DefaultHasher::new();
null_sid.hash(&mut null_hash);
assert_eq!(oversized_hash.finish(), null_hash.finish());
}
#[test]
fn owned_and_borrowed_views_agree() {
let owned = nt_sid(&[18]);
let via_deref: &Sid = &owned;
let via_borrow: &Sid = owned.borrow();
let via_as_ref: &Sid = owned.as_ref();
assert_eq!(via_deref, via_borrow);
assert_eq!(via_deref, via_as_ref);
assert_eq!(via_deref.to_owned(), owned);
assert_eq!(*via_deref, owned);
assert_eq!(owned, *via_deref);
}
#[test]
fn display_and_debug_render_canonical_strings() {
let cases = [
(SidBuf::default(), "S-1-0-0"),
(nt_sid(&[18]), "S-1-5-18"),
(nt_sid(&[32, 544]), "S-1-5-32-544"),
(
SidBuf::new([0, 0, 0xFF, 0xFF, 0xFF, 0xFF], &[1]).unwrap(),
"S-1-4294967295-1",
),
(
SidBuf::new([0, 1, 0, 0, 0, 0], &[1, 2]).unwrap(),
"S-1-0x000100000000-1-2",
),
];
for (sid, expected) in cases {
assert_eq!(sid.to_string(), expected);
assert_eq!(format!("{sid:?}"), format!("{expected:?}"));
assert_eq!(format!("{:?}", &*sid), format!("{expected:?}"));
}
}
#[test]
fn from_str_parses_and_round_trips_display() {
for s in [
"S-1-0-0",
"S-1-5-18",
"S-1-5-32-544",
"S-1-0x000100000000-1-2",
] {
let sid: SidBuf = s.parse().unwrap();
assert_eq!(sid.to_string(), s);
}
assert_eq!(
"s-1-4294967296-1".parse::<SidBuf>().unwrap(),
"S-1-0x000100000000-1".parse::<SidBuf>().unwrap(),
);
assert_eq!(
"S-1-5".parse::<SidBuf>().unwrap().sub_authorities(),
&[] as &[u32]
);
}
#[test]
fn from_str_rejects_malformed_input() {
for s in [
"", "X-1-5-18", "S-2-5-18", "S-1", "S-1-5-", "S-1-5-4294967296", "S-1-0x1000000000000-1", "S-1-5-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16", ] {
assert!(s.parse::<SidBuf>().is_err(), "parsing should reject {s:?}");
}
}
#[test]
fn equality_and_ordering_follow_sid_bytes() {
let a = nt_sid(&[18]);
let b = nt_sid(&[18]);
let c = nt_sid(&[19]);
assert_eq!(a, b);
assert_ne!(a, c);
assert!(a < nt_sid(&[32, 544]));
}
#[test]
fn equal_prefix_ignores_only_the_last_sub_authority() {
let first = nt_sid(&[21, 1, 2, 3, 1000]);
let same_prefix = nt_sid(&[21, 1, 2, 3, 2000]);
let different_prefix = nt_sid(&[21, 1, 2, 4, 1000]);
let different_count = nt_sid(&[21, 1, 2, 3]);
let different_authority = SidBuf::new([0, 0, 0, 0, 0, 4], &[21, 1, 2, 3, 2000]).unwrap();
assert!(equal_prefix_sid(&first, &same_prefix));
assert!(first.equal_prefix(&same_prefix));
assert!(!first.equal_prefix(&different_prefix));
assert!(!first.equal_prefix(&different_count));
assert!(!first.equal_prefix(&different_authority));
let no_sub_authorities = SidBuf::new(NT_AUTHORITY, &[]).unwrap();
assert!(no_sub_authorities.equal_prefix(&no_sub_authorities));
}
#[cfg(feature = "windows-full")]
#[test]
fn windows_domain_helpers_compare_and_extract_domains() {
let first = nt_sid(&[21, 1, 2, 3, 1000]);
let same_domain = nt_sid(&[21, 1, 2, 3, 2000]);
let different_domain = nt_sid(&[21, 1, 2, 4, 1000]);
let expected_domain = nt_sid(&[21, 1, 2, 3]);
assert!(equal_domain_sid(&first, &same_domain).unwrap());
assert!(first.equal_domain(&same_domain).unwrap());
assert!(!first.equal_domain(&different_domain).unwrap());
assert_eq!(
get_windows_account_domain_sid(&first).unwrap(),
expected_domain
);
assert_eq!(first.account_domain_sid().unwrap(), expected_domain);
}
#[cfg(feature = "windows-full")]
#[test]
fn well_known_sid_helper_classifies_sids() {
use windows::Win32::Security::{WinLocalSystemSid, WinWorldSid};
let local_system = nt_sid(&[18]);
assert!(is_well_known_sid(&local_system, WinLocalSystemSid));
assert!(local_system.is_well_known(WinLocalSystemSid));
assert!(!local_system.is_well_known(WinWorldSid));
}
#[test]
fn borrowed_sid_looks_up_owned_key() {
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert(nt_sid(&[18]), "LocalSystem");
let key = nt_sid(&[18]);
let probe: &Sid = &key;
assert_eq!(map.get(probe), Some(&"LocalSystem"));
}
#[cfg(feature = "windows-full")]
#[test]
fn from_cstr_supports_numeric_and_aliases() {
let ba = nt_sid(&[32, 544]);
assert_eq!(SidBuf::from_cstr_with_alias(c"BA").unwrap(), ba);
assert_eq!(SidBuf::from_cstr_with_alias(c"S-1-5-32-544").unwrap(), ba);
}
#[cfg(feature = "windows-full")]
#[test]
fn from_cstr_fails_on_bad_input() {
assert!(SidBuf::from_cstr_with_alias(c"").is_err());
assert!(SidBuf::from_cstr_with_alias(c"not-a-sid").is_err());
}
}