1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
// Copyright 2023 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: MIT OR Apache-2.0
use core::cmp::Ordering;
use core::iter::Copied;
use core::marker::PhantomData;
use core::slice::Iter;
use core::{fmt, mem, slice};
use widestring::{U16CStr, U16Str};
use crate::error::{NtStringError, Result};
use crate::helpers::{cmp_iter, RawNtString};
use super::iter::{Chars, CharsLossy};
/// An immutable reference to a `UNICODE_STRING` (equivalent of `&str`).
///
/// See the [module-level documentation](super) for more details.
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct NtUnicodeStr<'a> {
raw: RawNtString<*const u16>,
_lifetime: PhantomData<&'a ()>,
}
impl<'a> NtUnicodeStr<'a> {
/// Returns a `*const NtUnicodeStr` pointer
/// (mainly for non-Rust interfaces that expect an immutable `UNICODE_STRING*`).
pub fn as_ptr(&self) -> *const Self {
self as *const Self
}
/// Returns a slice to the raw [`u16`] codepoints of the string.
pub fn as_slice(&self) -> &'a [u16] {
unsafe { slice::from_raw_parts(self.raw.buffer, self.len_in_elements()) }
}
/// Returns a [`U16Str`] reference for this string.
///
/// The [`U16Str`] will only contain the characters and not the NUL terminator.
pub fn as_u16str(&self) -> &'a U16Str {
U16Str::from_slice(self.as_slice())
}
/// Returns the capacity (also known as "maximum length") of this string, in bytes.
pub fn capacity(&self) -> u16 {
self.raw.maximum_length
}
/// Returns the capacity (also known as "maximum length") of this string, in elements.
#[allow(unused)]
pub(crate) fn capacity_in_elements(&self) -> usize {
usize::from(self.raw.maximum_length) / mem::size_of::<u16>()
}
/// Returns an iterator over the [`char`]s of this string.
///
/// As the string may contain invalid UTF-16 characters (unpaired surrogates), the returned iterator is an
/// iterator over `Result<char>`.
/// Unpaired surrogates are returned as an [`NtStringError::UnpairedUtf16Surrogate`] error.
/// If you would like a lossy iterator over [`char`]s directly, use [`chars_lossy`] instead.
///
/// [`chars_lossy`]: Self::chars_lossy
pub fn chars(&self) -> Chars {
Chars::new(self)
}
/// Returns an iterator over the [`char`]s of this string.
///
/// Any invalid UTF-16 characters (unpaired surrogates) are automatically replaced by
/// [`U+FFFD REPLACEMENT CHARACTER`] (�).
/// If you would like to treat them differently, use [`chars`] instead.
///
/// [`chars`]: Self::chars
/// [`U+FFFD REPLACEMENT CHARACTER`]: std::char::REPLACEMENT_CHARACTER
pub fn chars_lossy(&self) -> CharsLossy {
CharsLossy::new(self)
}
/// Creates an [`NtUnicodeStr`] from a [`u16`] string buffer, a byte length of the string,
/// and a buffer capacity in bytes (also known as "maximum length").
///
/// The string is expected to consist of valid UTF-16 characters.
/// The buffer may or may not be NUL-terminated.
/// In any case, `length` does NOT include the terminating NUL character.
///
/// This function is `unsafe` and you are advised to use any of the safe `try_from_*`
/// functions over this one if possible.
///
/// # Safety
///
/// Behavior is undefined if any of the following conditions are violated:
///
/// * `length` must be less than or equal to `maximum_length`.
/// * `buffer` must be valid for at least `maximum_length` bytes.
/// * `buffer` must point to `length` consecutive properly initialized bytes.
/// * `buffer` must be valid for the duration of lifetime `'a`.
///
/// [`try_from_u16`]: Self::try_from_u16
/// [`try_from_u16_until_nul`]: Self::try_from_u16_until_nul
pub const unsafe fn from_raw_parts(
buffer: *const u16,
length: u16,
maximum_length: u16,
) -> Self {
debug_assert!(length <= maximum_length);
Self {
raw: RawNtString {
length,
maximum_length,
buffer,
},
_lifetime: PhantomData,
}
}
/// Returns `true` if this string has a length of zero, and `false` otherwise.
pub fn is_empty(&self) -> bool {
self.raw.length == 0
}
/// Returns the length of this string, in bytes.
///
/// Note that a single character may occupy more than one byte.
/// In other words, the returned value might not be what a human considers the length of the string.
pub fn len(&self) -> u16 {
self.raw.length
}
/// Returns the length of this string, in elements.
///
/// Note that a single character may occupy more than one element.
/// In other words, the returned value might not be what a human considers the length of the string.
pub(crate) fn len_in_elements(&self) -> usize {
usize::from(self.raw.length) / mem::size_of::<u16>()
}
/// Returns the remaining capacity of this string, in bytes.
#[allow(unused)]
pub(crate) fn remaining_capacity(&self) -> u16 {
debug_assert!(self.raw.maximum_length >= self.raw.length);
self.raw.maximum_length - self.raw.length
}
/// Creates an [`NtUnicodeStr`] from an existing [`u16`] string buffer without a terminating NUL character.
///
/// The string is expected to consist of valid UTF-16 characters.
///
/// The given buffer becomes the internal buffer of the [`NtUnicodeStr`] and therefore won't be NUL-terminated.
/// See the [module-level documentation](super) for the implications of that.
///
/// This function has *O*(1) complexity.
///
/// If you have a NUL-terminated buffer, either use [`try_from_u16_until_nul`] or convert from a [`U16CStr`]
/// using the corresponding [`TryFrom`] implementation.
///
/// [`try_from_u16_until_nul`]: Self::try_from_u16_until_nul
pub fn try_from_u16(buffer: &'a [u16]) -> Result<Self> {
let elements = buffer.len();
let length_usize = elements
.checked_mul(mem::size_of::<u16>())
.ok_or(NtStringError::BufferSizeExceedsU16)?;
let length =
u16::try_from(length_usize).map_err(|_| NtStringError::BufferSizeExceedsU16)?;
Ok(Self {
raw: RawNtString {
length,
maximum_length: length,
buffer: buffer.as_ptr(),
},
_lifetime: PhantomData,
})
}
/// Creates an [`NtUnicodeStr`] from an existing [`u16`] string buffer that contains at least one NUL character.
///
/// The string is expected to consist of valid UTF-16 characters.
///
/// The string will be terminated at the NUL character.
/// An [`NtStringError::NulNotFound`] error is returned if no NUL character could be found.
/// As a consequence, this function has *O*(*n*) complexity.
///
/// The resulting internal `buffer` of [`NtUnicodeStr`] will be NUL-terminated.
/// See the [module-level documentation](super) for the implications of that.
///
/// Use [`try_from_u16`] if you have a buffer that is not NUL-terminated.
/// You can also convert from a NUL-terminated [`U16CStr`] in *O*(1) via the corresponding [`TryFrom`] implementation.
///
/// [`try_from_u16`]: Self::try_from_u16
pub fn try_from_u16_until_nul(buffer: &'a [u16]) -> Result<Self> {
let length;
let maximum_length;
match buffer.iter().position(|x| *x == 0) {
Some(nul_pos) => {
// Include the terminating NUL character in `maximum_length` ...
let maximum_elements = nul_pos
.checked_add(1)
.ok_or(NtStringError::BufferSizeExceedsU16)?;
let maximum_length_usize = maximum_elements
.checked_mul(mem::size_of::<u16>())
.ok_or(NtStringError::BufferSizeExceedsU16)?;
maximum_length = u16::try_from(maximum_length_usize)
.map_err(|_| NtStringError::BufferSizeExceedsU16)?;
// ... but not in `length`
length = maximum_length - mem::size_of::<u16>() as u16;
}
None => return Err(NtStringError::NulNotFound),
};
Ok(Self {
raw: RawNtString {
length,
maximum_length,
buffer: buffer.as_ptr(),
},
_lifetime: PhantomData,
})
}
pub(crate) fn u16_iter(&'a self) -> Copied<Iter<'a, u16>> {
self.as_slice().iter().copied()
}
}
impl<'a> fmt::Display for NtUnicodeStr<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for single_char in self.chars_lossy() {
single_char.fmt(f)?;
}
Ok(())
}
}
impl<'a> Eq for NtUnicodeStr<'a> {}
impl<'a> Ord for NtUnicodeStr<'a> {
fn cmp(&self, other: &Self) -> Ordering {
cmp_iter(self.u16_iter(), other.u16_iter())
}
}
impl<'a, 'b> PartialEq<NtUnicodeStr<'a>> for NtUnicodeStr<'b> {
/// Checks that two strings are a (case-sensitive!) match.
fn eq(&self, other: &NtUnicodeStr<'a>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<'a> PartialEq<str> for NtUnicodeStr<'a> {
fn eq(&self, other: &str) -> bool {
cmp_iter(self.u16_iter(), other.encode_utf16()) == Ordering::Equal
}
}
impl<'a> PartialEq<NtUnicodeStr<'a>> for str {
fn eq(&self, other: &NtUnicodeStr<'a>) -> bool {
cmp_iter(self.encode_utf16(), other.u16_iter()) == Ordering::Equal
}
}
impl<'a> PartialEq<&str> for NtUnicodeStr<'a> {
fn eq(&self, other: &&str) -> bool {
cmp_iter(self.u16_iter(), other.encode_utf16()) == Ordering::Equal
}
}
impl<'a> PartialEq<NtUnicodeStr<'a>> for &str {
fn eq(&self, other: &NtUnicodeStr<'a>) -> bool {
cmp_iter(self.encode_utf16(), other.u16_iter()) == Ordering::Equal
}
}
impl<'a> PartialOrd for NtUnicodeStr<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> PartialOrd<str> for NtUnicodeStr<'a> {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
Some(cmp_iter(self.u16_iter(), other.encode_utf16()))
}
}
impl<'a> PartialOrd<NtUnicodeStr<'a>> for str {
fn partial_cmp(&self, other: &NtUnicodeStr<'a>) -> Option<Ordering> {
Some(cmp_iter(self.encode_utf16(), other.u16_iter()))
}
}
impl<'a> PartialOrd<&str> for NtUnicodeStr<'a> {
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
Some(cmp_iter(self.u16_iter(), other.encode_utf16()))
}
}
impl<'a> PartialOrd<NtUnicodeStr<'a>> for &str {
fn partial_cmp(&self, other: &NtUnicodeStr<'a>) -> Option<Ordering> {
Some(cmp_iter(self.encode_utf16(), other.u16_iter()))
}
}
impl<'a> TryFrom<&'a U16CStr> for NtUnicodeStr<'a> {
type Error = NtStringError;
/// Converts a [`U16CStr`] reference into an [`NtUnicodeStr`].
///
/// The internal buffer will be NUL-terminated.
/// See the [module-level documentation](super) for the implications of that.
fn try_from(value: &'a U16CStr) -> Result<Self> {
let buffer = value.as_slice_with_nul();
// Include the terminating NUL character in `maximum_length` ...
let maximum_length_in_elements = buffer.len();
let maximum_length_in_bytes = maximum_length_in_elements
.checked_mul(mem::size_of::<u16>())
.ok_or(NtStringError::BufferSizeExceedsU16)?;
let maximum_length = u16::try_from(maximum_length_in_bytes)
.map_err(|_| NtStringError::BufferSizeExceedsU16)?;
// ... but not in `length`
debug_assert!(maximum_length >= mem::size_of::<u16>() as u16);
let length = maximum_length - mem::size_of::<u16>() as u16;
Ok(Self {
raw: RawNtString {
length,
maximum_length,
buffer: buffer.as_ptr(),
},
_lifetime: PhantomData,
})
}
}
impl<'a> TryFrom<&'a U16Str> for NtUnicodeStr<'a> {
type Error = NtStringError;
/// Converts a [`U16Str`] reference into an [`NtUnicodeStr`].
///
/// The internal buffer will NOT be NUL-terminated.
/// See the [module-level documentation](super) for the implications of that.
fn try_from(value: &'a U16Str) -> Result<Self> {
Self::try_from_u16(value.as_slice())
}
}