|
4 | 4 |
|
5 | 5 | use crate::{TagTrait, TagType, TagTypeId}; |
6 | 6 | use core::fmt; |
7 | | -use core::fmt::{Debug, Formatter}; |
| 7 | +use core::fmt::{Debug, Display, Formatter}; |
8 | 8 | use core::marker::PhantomData; |
9 | 9 | use core::str::Utf8Error; |
10 | 10 |
|
| 11 | +/// Error type describing failures when parsing the string from a tag. |
| 12 | +#[derive(Debug, PartialEq, Eq, Clone)] |
| 13 | +pub enum StringError { |
| 14 | + /// There is no terminating null-byte, although the spec requires one. |
| 15 | + MissingNull(core::ffi::FromBytesUntilNulError), |
| 16 | + /// The sequence until the first NULL byte is not valid UTF-8. |
| 17 | + Utf8(Utf8Error), |
| 18 | +} |
| 19 | + |
| 20 | +impl Display for StringError { |
| 21 | + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
| 22 | + write!(f, "{:?}", self) |
| 23 | + } |
| 24 | +} |
| 25 | + |
| 26 | +#[cfg(feature = "unstable")] |
| 27 | +impl core::error::Error for StringError { |
| 28 | + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { |
| 29 | + match self { |
| 30 | + StringError::MissingNull(e) => Some(e), |
| 31 | + StringError::Utf8(e) => Some(e), |
| 32 | + } |
| 33 | + } |
| 34 | +} |
| 35 | + |
11 | 36 | /// Common base structure for all tags that can be passed via the Multiboot2 |
12 | 37 | /// Information Structure (MBI) to a Multiboot2 payload/program/kernel. |
13 | 38 | /// |
@@ -38,22 +63,13 @@ impl Tag { |
38 | 63 | unsafe { TagTrait::from_base_tag(self) } |
39 | 64 | } |
40 | 65 |
|
41 | | - /// Some multiboot2 tags are a DST as they end with a dynamically sized byte |
42 | | - /// slice. This function parses this slice as [`str`] so that either a valid |
43 | | - /// UTF-8 Rust string slice without a terminating null byte or an error is |
44 | | - /// returned. |
45 | | - pub fn get_dst_str_slice(bytes: &[u8]) -> Result<&str, Utf8Error> { |
46 | | - // Determine length of string before first NUL character |
47 | | - |
48 | | - let length = match bytes.iter().position(|ch| (*ch) == 0x00) { |
49 | | - // Unterminated string case: |
50 | | - None => bytes.len(), |
51 | | - |
52 | | - // Terminated case: |
53 | | - Some(idx) => idx, |
54 | | - }; |
55 | | - // Convert to Rust string: |
56 | | - core::str::from_utf8(&bytes[..length]) |
| 66 | + /// Parses the provided byte sequence as Multiboot string, which maps to a |
| 67 | + /// [`str`]. |
| 68 | + pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> { |
| 69 | + let cstr = |
| 70 | + core::ffi::CStr::from_bytes_until_nul(bytes).map_err(StringError::MissingNull)?; |
| 71 | + |
| 72 | + cstr.to_str().map_err(StringError::Utf8) |
57 | 73 | } |
58 | 74 | } |
59 | 75 |
|
@@ -128,21 +144,28 @@ mod tests { |
128 | 144 | use super::*; |
129 | 145 |
|
130 | 146 | #[test] |
131 | | - fn test_get_dst_str_slice() { |
132 | | - // unlikely case |
133 | | - assert_eq!(Ok(""), Tag::get_dst_str_slice(&[])); |
134 | | - // also unlikely case |
135 | | - assert_eq!(Ok(""), Tag::get_dst_str_slice(&[b'\0'])); |
136 | | - // unlikely case: missing null byte. but the lib can cope with that |
137 | | - assert_eq!(Ok("foobar"), Tag::get_dst_str_slice(b"foobar")); |
138 | | - // test that the null bytes is not included in the string slice |
139 | | - assert_eq!(Ok("foobar"), Tag::get_dst_str_slice(b"foobar\0")); |
140 | | - // test that C-style null string termination works as expected |
141 | | - assert_eq!(Ok("foo"), Tag::get_dst_str_slice(b"foo\0bar")); |
142 | | - // test invalid utf8 |
| 147 | + fn parse_slice_as_string() { |
| 148 | + // empty slice is invalid |
| 149 | + assert!(matches!( |
| 150 | + Tag::parse_slice_as_string(&[]), |
| 151 | + Err(StringError::MissingNull(_)) |
| 152 | + )); |
| 153 | + // empty string is fine |
| 154 | + assert_eq!(Tag::parse_slice_as_string(&[0x00]), Ok("")); |
| 155 | + // reject invalid utf8 |
| 156 | + assert!(matches!( |
| 157 | + Tag::parse_slice_as_string(&[0xff, 0x00]), |
| 158 | + Err(StringError::Utf8(_)) |
| 159 | + )); |
| 160 | + // reject missing null |
143 | 161 | assert!(matches!( |
144 | | - Tag::get_dst_str_slice(&[0xff, 0xff]), |
145 | | - Err(Utf8Error { .. }) |
| 162 | + Tag::parse_slice_as_string(b"hello"), |
| 163 | + Err(StringError::MissingNull(_)) |
146 | 164 | )); |
| 165 | + // must not include final null |
| 166 | + assert_eq!(Tag::parse_slice_as_string(b"hello\0"), Ok("hello")); |
| 167 | + assert_eq!(Tag::parse_slice_as_string(b"hello\0\0"), Ok("hello")); |
| 168 | + // must skip everytihng after first null |
| 169 | + assert_eq!(Tag::parse_slice_as_string(b"hello\0foo"), Ok("hello")); |
147 | 170 | } |
148 | 171 | } |
0 commit comments