Skip to content

Commit 15c43de

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 c62523a commit 15c43de

File tree

1 file changed

+190
-1
lines changed

1 file changed

+190
-1
lines changed

rust/kernel/str.rs

Lines changed: 190 additions & 1 deletion
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 exceed the bound specified. Only happen when
39+
/// constructing a [`CBoundedStr`].
40+
BoundExceeded,
41+
3742
/// Supplied bytes contain an interior `NUL`.
3843
InteriorNul,
3944

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

0 commit comments

Comments
 (0)