Skip to content

Commit c77d581

Browse files
committed
rust: add CBoundedStr
`CBoundedStr<N>` is a `CStr` with length known to be less than `N`. It can be used in cases where a known length limit exists. Signed-off-by: Gary Guo <[email protected]>
1 parent 7451cda commit c77d581

File tree

1 file changed

+191
-1
lines changed

1 file changed

+191
-1
lines changed

rust/kernel/str.rs

+191-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use core::ops::{self, Deref, Index};
66

77
use crate::bindings;
8+
use crate::build_assert;
89
use crate::c_types;
910

1011
/// Byte string without UTF-8 validity guarantee.
@@ -31,9 +32,13 @@ macro_rules! b_str {
3132
}};
3233
}
3334

34-
/// Possible errors when using conversion functions in [`CStr`].
35+
/// Possible errors when using conversion functions in [`CStr`] and [`CBoundedStr`].
3536
#[derive(Debug, Clone, Copy)]
3637
pub enum CStrConvertError {
38+
/// Supplied string length exceeds the specified bound. Only happens when
39+
/// constructing a [`CBoundedStr`].
40+
BoundExceeded,
41+
3742
/// Supplied bytes contain an interior `NUL`.
3843
InteriorNul,
3944

@@ -247,3 +252,188 @@ macro_rules! c_str {
247252
C
248253
}};
249254
}
255+
256+
/// A `NUL`-terminated string that is guaranteed to be shorter than a given
257+
/// length. This type is useful because the C side usually imposes a maximum length
258+
/// on types.
259+
///
260+
/// The size parameter `N` represents the maximum number of bytes including `NUL`.
261+
/// This implies that even though `CBoundedStr<0>` is a well-formed type it cannot
262+
/// be safely created.
263+
#[repr(transparent)]
264+
pub struct CBoundedStr<const N: usize>(CStr);
265+
266+
impl<const N: usize> CBoundedStr<N> {
267+
/// Creates a [`CBoundedStr`] from a [`CStr`].
268+
///
269+
/// The provided [`CStr`] must be shorter than `N`.
270+
#[inline]
271+
pub const fn from_c_str(c_str: &CStr) -> Result<&Self, CStrConvertError> {
272+
if c_str.len_with_nul() > N {
273+
return Err(CStrConvertError::BoundExceeded);
274+
}
275+
276+
// SAFETY: We just checked that all properties hold.
277+
Ok(unsafe { Self::from_c_str_unchecked(c_str) })
278+
}
279+
280+
/// Creates a [`CBoundedStr`] from a [`CStr`] without performing any sanity
281+
/// checks.
282+
///
283+
/// # Safety
284+
///
285+
/// The provided [`CStr`] must be shorter than `N`.
286+
#[inline]
287+
pub const unsafe fn from_c_str_unchecked(c_str: &CStr) -> &Self {
288+
&*(c_str as *const CStr as *const Self)
289+
}
290+
291+
/// Creates a [`CBoundedStr`] from a `[u8]`.
292+
///
293+
/// The provided slice must be `NUL`-terminated, must not contain any
294+
/// interior `NUL` bytes and must be shorter than `N`.
295+
#[inline]
296+
pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, CStrConvertError> {
297+
Self::from_c_str(CStr::from_bytes_with_nul(bytes)?)
298+
}
299+
300+
/// Creates a [`CBoundedStr`] from a `[u8]` without performing any sanity
301+
/// checks.
302+
///
303+
/// # Safety
304+
///
305+
/// The provided slice must be `NUL`-terminated, must not contain any
306+
/// interior `NUL` bytes and must be shorter than `N`.
307+
#[inline]
308+
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &Self {
309+
Self::from_c_str_unchecked(CStr::from_bytes_with_nul_unchecked(bytes))
310+
}
311+
312+
/// Creates a [`CBoundedStr`] from a `[u8; N]` without performing any sanity
313+
/// checks.
314+
///
315+
/// # Safety
316+
///
317+
/// The provided slice must be `NUL`-terminated.
318+
#[inline]
319+
pub const unsafe fn from_exact_bytes_with_nul_unchecked(bytes: &[u8; N]) -> &Self {
320+
Self::from_bytes_with_nul_unchecked(bytes)
321+
}
322+
323+
/// Relaxes the bound from `N` to `M`.
324+
///
325+
/// `M` must be no less than the bound `N`.
326+
#[inline]
327+
pub const fn relax_bound<const M: usize>(&self) -> &CBoundedStr<M> {
328+
build_assert!(N <= M, "relaxed bound should be no less than current bound");
329+
unsafe { CBoundedStr::<M>::from_c_str_unchecked(&self.0) }
330+
}
331+
332+
/// Converts the string to a `c_char` array of the same bound, filling
333+
/// the remaining bytes with zero.
334+
#[inline]
335+
pub const fn to_char_array(&self) -> [c_types::c_char; N] {
336+
let mut ret: [c_types::c_char; N] = [0; N];
337+
let mut i = 0;
338+
while i < self.0 .0.len() {
339+
ret[i] = self.0 .0[i] as _;
340+
i += 1;
341+
}
342+
ret
343+
}
344+
345+
/// Expands the string to a `c_char` array of higher bound, filling
346+
/// the remaining bytes with zero.
347+
///
348+
/// `M` must be no less than the bound `N`.
349+
#[inline]
350+
pub const fn expand_to_char_array<const M: usize>(&self) -> [c_types::c_char; M] {
351+
self.relax_bound().to_char_array()
352+
}
353+
}
354+
355+
impl<const N: usize> AsRef<BStr> for CBoundedStr<N> {
356+
#[inline]
357+
fn as_ref(&self) -> &BStr {
358+
self.as_bytes()
359+
}
360+
}
361+
362+
impl<const N: usize> AsRef<CStr> for CBoundedStr<N> {
363+
#[inline]
364+
fn as_ref(&self) -> &CStr {
365+
&self.0
366+
}
367+
}
368+
369+
impl<const N: usize> Deref for CBoundedStr<N> {
370+
type Target = CStr;
371+
372+
#[inline]
373+
fn deref(&self) -> &Self::Target {
374+
&self.0
375+
}
376+
}
377+
378+
impl<Idx, const N: usize> Index<Idx> for CBoundedStr<N>
379+
where
380+
CStr: Index<Idx>,
381+
{
382+
type Output = <CStr as Index<Idx>>::Output;
383+
384+
#[inline]
385+
fn index(&self, index: Idx) -> &Self::Output {
386+
&self.0[index]
387+
}
388+
}
389+
390+
/// Creates a new [`CBoundedStr`] from a string literal.
391+
///
392+
/// The string literal should not contain any `NUL` bytes, and its length with `NUL` should not
393+
/// exceed the bound supplied.
394+
///
395+
/// # Examples
396+
///
397+
/// ```rust,no_run
398+
/// // If no bound is specified, the tighest bound will be inferred:
399+
/// const MY_CSTR: &'static CBoundedStr<17> = c_bounded_str!("My awesome CStr!");
400+
/// ```
401+
///
402+
/// ```rust,compile_fail
403+
/// // This does not compile as the inferred type is `CBoundedStr<17>`.
404+
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!("My awesome CStr!");
405+
/// ```
406+
///
407+
/// ```rust,no_run
408+
/// // You can relax the bound using the `relax_bound` method.
409+
/// const MY_CSTR: &'static CBoundedStr<100> = c_bounded_str!("My awesome CStr!").relax_bound();
410+
///
411+
/// // Or alternatively specify a bound.
412+
/// // In this case the supplied bound must be a constant expression.
413+
/// const MY_CSTR2: &'static CBoundedStr<100> = c_bounded_str!(100, "My awesome CStr!");
414+
///
415+
/// // Or let the compiler infer the bound for you.
416+
/// const MY_CSTR3: &'static CBoundedStr<100> = c_bounded_str!(_, "My awesome CStr!");
417+
/// ```
418+
///
419+
/// ```rust,compile_fail
420+
/// // These do not compile as the string is longer than the specified bound.
421+
/// const MY_CSTR: &'static CBoundedStr<4> = c_bounded_str!(4, "My awesome CStr!");
422+
/// const MY_CSTR2: &'static CBoundedStr<4> = c_bounded_str!(_, "My awesome CStr!");
423+
/// ```
424+
#[macro_export]
425+
macro_rules! c_bounded_str {
426+
($str:literal) => {{
427+
const S: &$crate::str::CStr = $crate::c_str!($str);
428+
const C: &$crate::str::CBoundedStr<{ S.len_with_nul() }> =
429+
unsafe { $crate::str::CBoundedStr::from_c_str_unchecked(S) };
430+
C
431+
}};
432+
(_, $str:literal) => {{
433+
$crate::c_bounded_str!($str).relax_bound()
434+
}};
435+
($bound:expr, $str:literal) => {{
436+
const C: &$crate::str::CBoundedStr<{ $bound }> = $crate::c_bounded_str!($str).relax_bound();
437+
C
438+
}};
439+
}

0 commit comments

Comments
 (0)