Skip to main content

imping_runtime/
strings.rs

1//! C strings in the target's memory: pointers, fixed-size buffers, and an
2//! owned UTF-16 string to pass to the target.
3//!
4//! Every type here has the layout of the C type it stands for, so generated
5//! structs and function signatures use them directly.
6
7use crate::sys;
8use core::ffi::{CStr, c_char};
9use core::fmt;
10use core::str::Utf8Error;
11use std::borrow::Cow;
12use widestring::{U16CStr, U16CString};
13
14/// A pointer to a NUL-terminated narrow string (`char*`), possibly null.
15///
16/// Reading it is `unsafe`: the pointer comes from the target and can't be
17/// checked. A null pointer reads as an empty string.
18///
19/// # Examples
20///
21/// ```
22/// use imping_runtime::CStrPtr;
23///
24/// let greeting = CStrPtr::from(c"hello");
25/// // SAFETY: `greeting` points to a static, NUL-terminated string.
26/// assert_eq!(unsafe { greeting.to_string_lossy() }, "hello");
27/// assert_eq!(unsafe { CStrPtr::null().to_string_lossy() }, "");
28/// ```
29#[repr(transparent)]
30#[derive(Clone, Copy, PartialEq, Eq, Hash)]
31pub struct CStrPtr(*const c_char);
32
33impl CStrPtr {
34    /// Wraps a raw pointer.
35    pub const fn new(ptr: *const c_char) -> Self {
36        Self(ptr)
37    }
38
39    /// Returns a null pointer.
40    pub const fn null() -> Self {
41        Self(core::ptr::null())
42    }
43
44    /// Returns whether the pointer is null.
45    pub fn is_null(self) -> bool {
46        self.0.is_null()
47    }
48
49    /// Returns the raw pointer.
50    pub const fn as_ptr(self) -> *const c_char {
51        self.0
52    }
53
54    /// Returns the raw pointer as `*mut`, for functions that write through it.
55    pub const fn as_mut_ptr(self) -> *mut c_char {
56        self.0.cast_mut()
57    }
58
59    /// Returns the string's bytes without the terminating NUL, or an empty
60    /// slice if the pointer is null.
61    ///
62    /// As with [`CStr::from_ptr`], the caller chooses the lifetime `'a`.
63    ///
64    /// # Safety
65    ///
66    /// A non-null pointer must point to a NUL-terminated string that stays
67    /// valid and unchanged for `'a`.
68    pub unsafe fn to_bytes<'a>(self) -> &'a [u8] {
69        if self.0.is_null() {
70            return &[];
71        }
72        // SAFETY: Forwarded from the caller.
73        unsafe { CStr::from_ptr(self.0) }.to_bytes()
74    }
75
76    /// Returns the string as UTF-8.
77    ///
78    /// # Errors
79    ///
80    /// If the bytes are not valid UTF-8.
81    ///
82    /// # Safety
83    ///
84    /// A non-null pointer must point to a NUL-terminated string that stays
85    /// valid and unchanged for `'a`.
86    pub unsafe fn to_str<'a>(self) -> Result<&'a str, Utf8Error> {
87        // SAFETY: Forwarded from the caller.
88        core::str::from_utf8(unsafe { self.to_bytes() })
89    }
90
91    /// Returns the string as UTF-8, with invalid sequences replaced by `�`.
92    ///
93    /// # Safety
94    ///
95    /// A non-null pointer must point to a NUL-terminated string that stays
96    /// valid and unchanged for `'a`.
97    pub unsafe fn to_string_lossy<'a>(self) -> Cow<'a, str> {
98        // SAFETY: Forwarded from the caller.
99        String::from_utf8_lossy(unsafe { self.to_bytes() })
100    }
101
102    /// Decodes the string with the system's ANSI code page, as Windows "A"
103    /// functions do.
104    ///
105    /// On other platforms, the same as [`to_string_lossy`](Self::to_string_lossy).
106    ///
107    /// # Safety
108    ///
109    /// A non-null pointer must point to a NUL-terminated string that stays
110    /// valid and unchanged during the call.
111    pub unsafe fn to_string_ansi(self) -> String {
112        // SAFETY: Forwarded from the caller.
113        sys::ansi_to_string(unsafe { self.to_bytes() })
114    }
115}
116
117impl Default for CStrPtr {
118    /// Returns a null pointer.
119    fn default() -> Self {
120        Self::null()
121    }
122}
123
124impl From<&'static CStr> for CStrPtr {
125    /// Points to a static C string, such as a `c"…"` literal.
126    fn from(value: &'static CStr) -> Self {
127        Self(value.as_ptr())
128    }
129}
130
131impl fmt::Debug for CStrPtr {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "CStrPtr({:p})", self.0)
134    }
135}
136
137/// A pointer to a NUL-terminated UTF-16 string (`wchar_t*` on Windows),
138/// possibly null.
139///
140/// Reading it is `unsafe`: the pointer comes from the target and can't be
141/// checked. A null pointer reads as an empty string.
142///
143/// # Examples
144///
145/// ```
146/// use imping_runtime::WString;
147///
148/// let title = WString::from("Héllo");
149/// // SAFETY: `title` is alive and NUL-terminated.
150/// assert_eq!(unsafe { title.as_ptr().to_string_lossy() }, "Héllo");
151/// ```
152#[repr(transparent)]
153#[derive(Clone, Copy, PartialEq, Eq, Hash)]
154pub struct WStrPtr(*const u16);
155
156impl WStrPtr {
157    /// Wraps a raw pointer.
158    pub const fn new(ptr: *const u16) -> Self {
159        Self(ptr)
160    }
161
162    /// Returns a null pointer.
163    pub const fn null() -> Self {
164        Self(core::ptr::null())
165    }
166
167    /// Returns whether the pointer is null.
168    pub fn is_null(self) -> bool {
169        self.0.is_null()
170    }
171
172    /// Returns the raw pointer.
173    pub const fn as_ptr(self) -> *const u16 {
174        self.0
175    }
176
177    /// Returns the raw pointer as `*mut`, for functions that write through it.
178    pub const fn as_mut_ptr(self) -> *mut u16 {
179        self.0.cast_mut()
180    }
181
182    /// Returns the string's UTF-16 code units without the terminating NUL, or
183    /// an empty slice if the pointer is null.
184    ///
185    /// As with [`CStr::from_ptr`], the caller chooses the lifetime `'a`.
186    ///
187    /// # Safety
188    ///
189    /// A non-null pointer must point to a NUL-terminated UTF-16 string that
190    /// stays valid and unchanged for `'a`.
191    pub unsafe fn to_wide<'a>(self) -> &'a [u16] {
192        if self.0.is_null() {
193            return &[];
194        }
195        // SAFETY: Forwarded from the caller.
196        unsafe { U16CStr::from_ptr_str(self.0) }.as_slice()
197    }
198
199    /// Returns the string as UTF-8, with unpaired surrogates replaced by `�`.
200    ///
201    /// # Safety
202    ///
203    /// A non-null pointer must point to a NUL-terminated UTF-16 string that
204    /// stays valid and unchanged during the call.
205    pub unsafe fn to_string_lossy(self) -> String {
206        // SAFETY: Forwarded from the caller.
207        String::from_utf16_lossy(unsafe { self.to_wide() })
208    }
209}
210
211impl Default for WStrPtr {
212    /// Returns a null pointer.
213    fn default() -> Self {
214        Self::null()
215    }
216}
217
218impl fmt::Debug for WStrPtr {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        write!(f, "WStrPtr({:p})", self.0)
221    }
222}
223
224/// An owned, NUL-terminated UTF-16 string, to pass to the target as a
225/// `wchar_t*`.
226///
227/// # Examples
228///
229/// ```
230/// use imping_runtime::WString;
231///
232/// let name = WString::from("Hero");
233/// assert_eq!(name.to_wide(), [72, 101, 114, 111]);
234/// ```
235#[derive(Clone, Default, PartialEq, Eq, Hash)]
236pub struct WString(U16CString);
237
238impl WString {
239    /// Creates an empty string.
240    pub fn new() -> Self {
241        Self::default()
242    }
243
244    /// Returns a pointer to the string, valid while it is alive and unchanged.
245    pub fn as_ptr(&self) -> WStrPtr {
246        WStrPtr(self.0.as_ptr())
247    }
248
249    /// Returns the string's UTF-16 code units, without the terminating NUL.
250    pub fn to_wide(&self) -> &[u16] {
251        self.0.as_slice()
252    }
253
254    /// Returns the string as UTF-8, with unpaired surrogates replaced by `�`.
255    pub fn to_string_lossy(&self) -> String {
256        self.0.to_string_lossy()
257    }
258}
259
260impl From<&str> for WString {
261    /// Encodes `value` as UTF-16, stopping at its first NUL, if any.
262    fn from(value: &str) -> Self {
263        Self(U16CString::from_str_truncate(value))
264    }
265}
266
267impl fmt::Debug for WString {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        f.debug_tuple("WString")
270            .field(&self.to_string_lossy())
271            .finish()
272    }
273}
274
275/// A fixed-size narrow string buffer inside a struct (`char name[N]`),
276/// holding a string up to the first NUL.
277///
278/// # Examples
279///
280/// ```
281/// use imping_runtime::CStrBuf;
282///
283/// let mut tag = CStrBuf::<8>::new();
284/// tag.set("Héllo, world");
285/// assert_eq!(tag.to_string_lossy(), "Héllo,"); // at most 7 bytes, then the NUL
286/// ```
287#[repr(transparent)]
288#[derive(Clone, Copy, PartialEq, Eq, Hash)]
289pub struct CStrBuf<const N: usize>([u8; N]);
290
291impl<const N: usize> CStrBuf<N> {
292    /// Creates an empty, all-zero buffer.
293    pub const fn new() -> Self {
294        Self([0; N])
295    }
296
297    /// Returns the string's bytes: up to the first NUL, or the whole buffer if
298    /// it has none.
299    pub fn to_bytes(&self) -> &[u8] {
300        let end = self.0.iter().position(|&byte| byte == 0).unwrap_or(N);
301        &self.0[..end]
302    }
303
304    /// Returns the string as UTF-8.
305    ///
306    /// # Errors
307    ///
308    /// If the bytes are not valid UTF-8.
309    pub fn to_str(&self) -> Result<&str, Utf8Error> {
310        core::str::from_utf8(self.to_bytes())
311    }
312
313    /// Returns the string as UTF-8, with invalid sequences replaced by `�`.
314    pub fn to_string_lossy(&self) -> Cow<'_, str> {
315        String::from_utf8_lossy(self.to_bytes())
316    }
317
318    /// Decodes the string with the system's ANSI code page.
319    ///
320    /// On other platforms, the same as [`to_string_lossy`](Self::to_string_lossy).
321    pub fn to_string_ansi(&self) -> String {
322        sys::ansi_to_string(self.to_bytes())
323    }
324
325    /// Stores `value` as UTF-8, truncated to fit before the terminating NUL.
326    ///
327    /// Truncation never splits a character. A NUL in `value` ends it, and the
328    /// rest of the buffer is zeroed.
329    pub fn set(&mut self, value: &str) {
330        let value = value.split('\0').next().unwrap_or_default();
331        let mut len = value.len().min(N.saturating_sub(1));
332        while !value.is_char_boundary(len) {
333            len -= 1;
334        }
335        self.0 = [0; N];
336        self.0[..len].copy_from_slice(&value.as_bytes()[..len]);
337    }
338
339    /// Returns the whole buffer.
340    pub fn as_array(&self) -> &[u8; N] {
341        &self.0
342    }
343
344    /// Returns the whole buffer, to write to it directly.
345    pub fn as_array_mut(&mut self) -> &mut [u8; N] {
346        &mut self.0
347    }
348
349    /// Returns a pointer to the buffer, valid while it is alive and unmoved.
350    ///
351    /// Reading through it needs a NUL within the buffer, which
352    /// [`set`](Self::set) guarantees.
353    pub fn as_ptr(&self) -> CStrPtr {
354        CStrPtr(self.0.as_ptr().cast())
355    }
356}
357
358impl<const N: usize> Default for CStrBuf<N> {
359    /// Returns an empty, all-zero buffer.
360    fn default() -> Self {
361        Self::new()
362    }
363}
364
365impl<const N: usize> fmt::Debug for CStrBuf<N> {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        f.debug_tuple("CStrBuf")
368            .field(&self.to_string_lossy())
369            .finish()
370    }
371}
372
373/// A fixed-size UTF-16 string buffer inside a struct (`wchar_t name[N]`),
374/// holding a string up to the first NUL.
375///
376/// # Examples
377///
378/// ```
379/// use imping_runtime::WStrBuf;
380///
381/// let mut name = WStrBuf::<4>::new();
382/// name.set("Héllo");
383/// assert_eq!(name.to_string_lossy(), "Hél"); // at most 3 code units, then the NUL
384/// ```
385#[repr(transparent)]
386#[derive(Clone, Copy, PartialEq, Eq, Hash)]
387pub struct WStrBuf<const N: usize>([u16; N]);
388
389impl<const N: usize> WStrBuf<N> {
390    /// Creates an empty, all-zero buffer.
391    pub const fn new() -> Self {
392        Self([0; N])
393    }
394
395    /// Returns the string's UTF-16 code units: up to the first NUL, or the
396    /// whole buffer if it has none.
397    pub fn to_wide(&self) -> &[u16] {
398        let end = self.0.iter().position(|&unit| unit == 0).unwrap_or(N);
399        &self.0[..end]
400    }
401
402    /// Returns the string as UTF-8, with unpaired surrogates replaced by `�`.
403    pub fn to_string_lossy(&self) -> String {
404        String::from_utf16_lossy(self.to_wide())
405    }
406
407    /// Stores `value` as UTF-16, truncated to fit before the terminating NUL.
408    ///
409    /// Truncation never splits a surrogate pair. A NUL in `value` ends it, and
410    /// the rest of the buffer is zeroed.
411    pub fn set(&mut self, value: &str) {
412        let value = value.split('\0').next().unwrap_or_default();
413        let capacity = N.saturating_sub(1);
414        self.0 = [0; N];
415        let mut len = 0;
416        for c in value.chars() {
417            let mut units = [0; 2];
418            let encoded = c.encode_utf16(&mut units);
419            if len + encoded.len() > capacity {
420                break;
421            }
422            self.0[len..len + encoded.len()].copy_from_slice(encoded);
423            len += encoded.len();
424        }
425    }
426
427    /// Returns the whole buffer.
428    pub fn as_array(&self) -> &[u16; N] {
429        &self.0
430    }
431
432    /// Returns the whole buffer, to write to it directly.
433    pub fn as_array_mut(&mut self) -> &mut [u16; N] {
434        &mut self.0
435    }
436
437    /// Returns a pointer to the buffer, valid while it is alive and unmoved.
438    ///
439    /// Reading through it needs a NUL within the buffer, which
440    /// [`set`](Self::set) guarantees.
441    pub fn as_ptr(&self) -> WStrPtr {
442        WStrPtr(self.0.as_ptr())
443    }
444}
445
446impl<const N: usize> Default for WStrBuf<N> {
447    /// Returns an empty, all-zero buffer.
448    fn default() -> Self {
449        Self::new()
450    }
451}
452
453impl<const N: usize> fmt::Debug for WStrBuf<N> {
454    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455        f.debug_tuple("WStrBuf")
456            .field(&self.to_string_lossy())
457            .finish()
458    }
459}