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}