|
@@ -1,45 +1,29 @@
|
|
|
//! Module for the base tag definitions and helper types.
|
|
|
//!
|
|
|
-//! The relevant exports of this module is [`Tag`].
|
|
|
-
|
|
|
-use crate::{TagTrait, TagType, TagTypeId};
|
|
|
-use core::fmt;
|
|
|
-use core::fmt::{Debug, Display, Formatter};
|
|
|
-use core::marker::PhantomData;
|
|
|
-use core::str::Utf8Error;
|
|
|
-
|
|
|
-/// Error type describing failures when parsing the string from a tag.
|
|
|
-#[derive(Debug, PartialEq, Eq, Clone)]
|
|
|
-pub enum StringError {
|
|
|
- /// There is no terminating NUL character, although the specification
|
|
|
- /// requires one.
|
|
|
- MissingNul(core::ffi::FromBytesUntilNulError),
|
|
|
- /// The sequence until the first NUL character is not valid UTF-8.
|
|
|
- Utf8(Utf8Error),
|
|
|
-}
|
|
|
+//! The relevant exports of this module are [`TagHeader`], [`GenericTag`], and
|
|
|
+//! [`TagIter`].
|
|
|
+//!
|
|
|
+//! The (internal) workflow to parse a tag from bytes is the following:
|
|
|
+//! - `&[u8]` --> [`TagBytesRef`]
|
|
|
+//! - [`TagBytesRef`] --> [`TagHeader`]
|
|
|
+//! - [`TagBytesRef`] + [`TagHeader`] --> [`GenericTag`]
|
|
|
+//! - [`GenericTag`] --> cast to desired tag
|
|
|
|
|
|
-impl Display for StringError {
|
|
|
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
- write!(f, "{:?}", self)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg(feature = "unstable")]
|
|
|
-impl core::error::Error for StringError {
|
|
|
- fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
|
|
|
- match self {
|
|
|
- Self::MissingNul(e) => Some(e),
|
|
|
- Self::Utf8(e) => Some(e),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
+use crate::util::increase_to_alignment;
|
|
|
+use crate::{TagTrait, TagType, TagTypeId, ALIGNMENT};
|
|
|
+use core::fmt::{Debug, Formatter};
|
|
|
+use core::mem;
|
|
|
+use core::ops::Deref;
|
|
|
+use core::ptr;
|
|
|
|
|
|
/// The common header that all tags have in common. This type is ABI compatible.
|
|
|
///
|
|
|
/// Not to be confused with Multiboot header tags, which are something
|
|
|
/// different.
|
|
|
+///
|
|
|
+/// It is the sized counterpart of `GenericTag`, an internal type.
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
|
-#[repr(C)]
|
|
|
+#[repr(C, align(8))] // Alignment also propagates to all tag types using this.
|
|
|
pub struct TagHeader {
|
|
|
/// The ABI-compatible [`TagType`].
|
|
|
pub typ: TagTypeId, /* u32 */
|
|
@@ -58,141 +42,401 @@ impl TagHeader {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// Common base structure for all tags that can be passed via the Multiboot2
|
|
|
-/// Information Structure (MBI) to a Multiboot2 payload/program/kernel.
|
|
|
+/// Wraps a byte slice representing a tag, but guarantees that the memory
|
|
|
+/// requirements are fulfilled.
|
|
|
///
|
|
|
-/// Can be transformed to any other tag (sized or unsized/DST) via
|
|
|
-/// [`Tag::cast_tag`].
|
|
|
+/// This is the only type that can be used to construct a [`GenericTag`].
|
|
|
///
|
|
|
-/// Do not confuse them with the Multiboot2 header tags. They are something
|
|
|
-/// different.
|
|
|
-#[derive(Clone, Copy)]
|
|
|
+/// The main reason for this dedicated type is to create fine-grained unit-tests
|
|
|
+/// for Miri.
|
|
|
+///
|
|
|
+/// # Memory Requirements (for Multiboot and Rust/Miri)
|
|
|
+/// - At least as big as a `size_of<TagHeader>()`
|
|
|
+/// - at least [`ALIGNMENT`]-aligned
|
|
|
+/// - Length is multiple of [`ALIGNMENT`]. In other words, there are enough
|
|
|
+/// padding bytes until so that pointer coming right after the last byte
|
|
|
+/// is [`ALIGNMENT`]-aligned
|
|
|
+#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
+#[repr(transparent)]
|
|
|
+pub struct TagBytesRef<'a>(&'a [u8]);
|
|
|
+
|
|
|
+impl<'a> TryFrom<&'a [u8]> for TagBytesRef<'a> {
|
|
|
+ type Error = MemoryError;
|
|
|
+
|
|
|
+ fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
|
|
|
+ if value.len() < mem::size_of::<TagHeader>() {
|
|
|
+ return Err(MemoryError::MinLengthNotSatisfied);
|
|
|
+ }
|
|
|
+ // Doesn't work as expected: if align_of_val(&value[0]) < ALIGNMENT {
|
|
|
+ if value.as_ptr().align_offset(ALIGNMENT) != 0 {
|
|
|
+ return Err(MemoryError::WrongAlignment);
|
|
|
+ }
|
|
|
+ let padding_bytes = value.len() % ALIGNMENT;
|
|
|
+ if padding_bytes != 0 {
|
|
|
+ return Err(MemoryError::MissingPadding);
|
|
|
+ }
|
|
|
+ Ok(Self(value))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> Deref for TagBytesRef<'a> {
|
|
|
+ type Target = &'a [u8];
|
|
|
+
|
|
|
+ fn deref(&self) -> &Self::Target {
|
|
|
+ &self.0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Errors that occur when constructing a [`TagBytesRef`].
|
|
|
+#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
|
|
+pub enum MemoryError {
|
|
|
+ /// The memory must be at least [`ALIGNMENT`]-aligned.
|
|
|
+ WrongAlignment,
|
|
|
+ /// The memory must cover at least the length of a [`TagHeader`].
|
|
|
+ MinLengthNotSatisfied,
|
|
|
+ /// The buffer misses the terminating padding to the next alignment
|
|
|
+ /// boundary.
|
|
|
+ // This is mainly relevant to satisfy Miri. As the spec also mandates an
|
|
|
+ // alignment, we can rely on this property.
|
|
|
+ MissingPadding,
|
|
|
+}
|
|
|
+
|
|
|
+/// A generic tag serving as base to cast to specific tags. This is a DST
|
|
|
+/// version of [`TagHeader`] that solves various type and memory safety
|
|
|
+/// problems by having a type that owns the whole memory of a tag.
|
|
|
+#[derive(Eq, Ord, PartialEq, PartialOrd, ptr_meta::Pointee)]
|
|
|
#[repr(C)]
|
|
|
-#[allow(missing_docs)] // type will be removed soon anyway in its form
|
|
|
-pub struct Tag {
|
|
|
- pub typ: TagTypeId, // u32
|
|
|
- pub size: u32,
|
|
|
- // followed by additional, tag specific fields
|
|
|
+pub struct GenericTag {
|
|
|
+ header: TagHeader,
|
|
|
+ /// Payload of the tag that is reflected in the `size` attribute, thus, no
|
|
|
+ /// padding bytes!
|
|
|
+ payload: [u8],
|
|
|
}
|
|
|
|
|
|
-impl Tag {
|
|
|
- /// Returns the underlying type of the tag.
|
|
|
- #[must_use]
|
|
|
- pub fn typ(&self) -> TagType {
|
|
|
- self.typ.into()
|
|
|
+impl GenericTag {
|
|
|
+ /// Base size of the DST struct without the dynamic part.
|
|
|
+ const BASE_SIZE: usize = mem::size_of::<TagHeader>();
|
|
|
+
|
|
|
+ /// Creates a reference to a [`GenericTag`] from the provided `bytes`
|
|
|
+ /// [`TagBytesRef`].
|
|
|
+ pub(crate) fn ref_from(bytes: TagBytesRef) -> &Self {
|
|
|
+ let header = bytes.as_ptr().cast::<TagHeader>();
|
|
|
+ let header = unsafe { &*header };
|
|
|
+ let dst_len = Self::dst_len(header);
|
|
|
+ assert_eq!(header.size as usize, Self::BASE_SIZE + dst_len);
|
|
|
+
|
|
|
+ let generic_tag: *const Self = ptr_meta::from_raw_parts(bytes.as_ptr().cast(), dst_len);
|
|
|
+ unsafe { &*generic_tag }
|
|
|
}
|
|
|
|
|
|
- /// Casts the base tag to the specific tag type.
|
|
|
- #[must_use]
|
|
|
- pub fn cast_tag<'a, T: TagTrait + ?Sized + 'a>(&'a self) -> &'a T {
|
|
|
- assert_eq!(self.typ, T::ID);
|
|
|
- // Safety: At this point, we trust that "self.size" and the size hint
|
|
|
- // for DST tags are sane.
|
|
|
- unsafe { TagTrait::from_base_tag(self) }
|
|
|
+ pub const fn header(&self) -> &TagHeader {
|
|
|
+ &self.header
|
|
|
}
|
|
|
|
|
|
- /// Parses the provided byte sequence as Multiboot string, which maps to a
|
|
|
- /// [`str`].
|
|
|
- pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> {
|
|
|
- let cstr = core::ffi::CStr::from_bytes_until_nul(bytes).map_err(StringError::MissingNul)?;
|
|
|
+ #[cfg(all(test, feature = "builder"))]
|
|
|
+ pub const fn payload(&self) -> &[u8] {
|
|
|
+ &self.payload
|
|
|
+ }
|
|
|
|
|
|
- cstr.to_str().map_err(StringError::Utf8)
|
|
|
+ /// Casts the generic tag to a specific [`TagTrait`] implementation which
|
|
|
+ /// may be a ZST or DST typed tag.
|
|
|
+ pub fn cast<T: TagTrait + ?Sized>(&self) -> &T {
|
|
|
+ let base_ptr = ptr::addr_of!(*self);
|
|
|
+ let t_dst_size = T::dst_len(&self.header);
|
|
|
+ let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
|
|
|
+ let t_ref = unsafe { &*t_ptr };
|
|
|
+ assert_eq!(mem::size_of_val(self), mem::size_of_val(t_ref));
|
|
|
+ t_ref
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl Debug for Tag {
|
|
|
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
- let tag_type = TagType::from(self.typ);
|
|
|
-
|
|
|
- let mut debug = f.debug_struct("Tag");
|
|
|
- debug.field("typ", &tag_type);
|
|
|
-
|
|
|
- if !matches!(tag_type, TagType::Custom(_)) {
|
|
|
- debug.field("typ (numeric)", &(u32::from(self.typ)));
|
|
|
- }
|
|
|
+impl Debug for GenericTag {
|
|
|
+ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
|
|
+ f.debug_struct("GenericTag")
|
|
|
+ .field("header", &self.header)
|
|
|
+ .field("payload", &"<bytes>")
|
|
|
+ .finish()
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- debug.field("size", &(self.size));
|
|
|
+impl TagTrait for GenericTag {
|
|
|
+ const ID: TagType = TagType::Custom(0xffffffff);
|
|
|
|
|
|
- debug.finish()
|
|
|
+ fn dst_len(header: &TagHeader) -> usize {
|
|
|
+ assert!(header.size as usize >= Self::BASE_SIZE);
|
|
|
+ header.size as usize - Self::BASE_SIZE
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// Iterates the MBI's tags from the first tag to the end tag.
|
|
|
+/// Iterates the tags of the MBI from the first tag to the end tag. THe end tag
|
|
|
+/// included.
|
|
|
#[derive(Clone, Debug)]
|
|
|
pub struct TagIter<'a> {
|
|
|
- /// Pointer to the next tag. Updated in each iteration.
|
|
|
- pub current: *const Tag,
|
|
|
- /// The pointer right after the MBI. Used for additional bounds checking.
|
|
|
- end_ptr_exclusive: *const u8,
|
|
|
- /// Lifetime capture of the MBI's memory.
|
|
|
- _mem: PhantomData<&'a ()>,
|
|
|
+ /// Absolute offset to next tag and updated in each iteration.
|
|
|
+ next_tag_offset: usize,
|
|
|
+ exclusive_end: *const u8,
|
|
|
+ buffer: &'a [u8],
|
|
|
}
|
|
|
|
|
|
impl<'a> TagIter<'a> {
|
|
|
/// Creates a new iterator
|
|
|
pub fn new(mem: &'a [u8]) -> Self {
|
|
|
+ // Assert alignment.
|
|
|
assert_eq!(mem.as_ptr().align_offset(8), 0);
|
|
|
+
|
|
|
+ let exclusive_end = unsafe { mem.as_ptr().add(mem.len()) };
|
|
|
+
|
|
|
TagIter {
|
|
|
- current: mem.as_ptr().cast(),
|
|
|
- end_ptr_exclusive: unsafe { mem.as_ptr().add(mem.len()) },
|
|
|
- _mem: PhantomData,
|
|
|
+ next_tag_offset: 0,
|
|
|
+ buffer: mem,
|
|
|
+ exclusive_end,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl<'a> Iterator for TagIter<'a> {
|
|
|
- type Item = &'a Tag;
|
|
|
+ type Item = &'a GenericTag;
|
|
|
|
|
|
- fn next(&mut self) -> Option<&'a Tag> {
|
|
|
- // This never failed so far. But better be safe.
|
|
|
- assert!(self.current.cast::<u8>() < self.end_ptr_exclusive);
|
|
|
+ fn next(&mut self) -> Option<Self::Item> {
|
|
|
+ let next_ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) };
|
|
|
|
|
|
- let tag = unsafe { &*self.current };
|
|
|
- match tag.typ() {
|
|
|
- TagType::End => None, // end tag
|
|
|
- _ => {
|
|
|
- // We return the tag and update self.current already to the next
|
|
|
- // tag.
|
|
|
+ if next_ptr == self.exclusive_end {
|
|
|
+ return None;
|
|
|
+ }
|
|
|
+ assert!(next_ptr < self.exclusive_end);
|
|
|
|
|
|
- // next pointer (rounded up to 8-byte alignment)
|
|
|
- let ptr_offset = (tag.size as usize + 7) & !7;
|
|
|
+ let next_tag_ptr = next_ptr.cast::<TagHeader>();
|
|
|
|
|
|
- // go to next tag
|
|
|
- self.current = unsafe { self.current.cast::<u8>().add(ptr_offset).cast::<Tag>() };
|
|
|
+ let tag_hdr = unsafe { &*next_tag_ptr };
|
|
|
|
|
|
- Some(tag)
|
|
|
- }
|
|
|
- }
|
|
|
+ // Get relevant byte portion for the next tag. This includes padding
|
|
|
+ // bytes to fulfill Rust memory guarantees. Otherwise, Miri complains.
|
|
|
+ // See <https://doc.rust-lang.org/reference/type-layout.html>.
|
|
|
+ let bytes = {
|
|
|
+ let from = self.next_tag_offset;
|
|
|
+ let to = from + tag_hdr.size as usize;
|
|
|
+ // The size of [the allocation for] a value is always a multiple of its
|
|
|
+ // alignment.
|
|
|
+ // https://doc.rust-lang.org/reference/type-layout.html
|
|
|
+ let to = increase_to_alignment(to);
|
|
|
+
|
|
|
+ // Update ptr for next iteration.
|
|
|
+ self.next_tag_offset += to - from;
|
|
|
+
|
|
|
+ &self.buffer[from..to]
|
|
|
+ };
|
|
|
+
|
|
|
+ // Should never fail at this point.
|
|
|
+ let tag_bytes = TagBytesRef::try_from(bytes).unwrap();
|
|
|
+
|
|
|
+ Some(GenericTag::ref_from(tag_bytes))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use super::*;
|
|
|
+ use crate::test_util::AlignedBytes;
|
|
|
+ use core::borrow::Borrow;
|
|
|
+ use core::mem;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_new_generic_tag() {
|
|
|
+ let bytes = AlignedBytes::new([
|
|
|
+ /* id: 0xffff_ffff */
|
|
|
+ 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
|
|
|
+ 16, 0, 0, 0, /* field a: 0xdead_beef */
|
|
|
+ 0xde, 0xad, 0xbe, 0xef, /* field b: 0x1337_1337 */
|
|
|
+ 0x13, 0x37, 0x13, 0x37,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
|
|
|
+ let tag = GenericTag::ref_from(bytes);
|
|
|
+ assert_eq!(tag.header.typ, 0xffff_ffff);
|
|
|
+ assert_eq!(tag.header.size, 16);
|
|
|
+ assert_eq!(tag.payload.len(), 8);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_cast_generic_tag_to_sized_tag() {
|
|
|
+ #[repr(C)]
|
|
|
+ struct CustomTag {
|
|
|
+ tag_header: TagHeader,
|
|
|
+ a: u32,
|
|
|
+ b: u32,
|
|
|
+ }
|
|
|
+
|
|
|
+ impl TagTrait for CustomTag {
|
|
|
+ const ID: TagType = TagType::End;
|
|
|
+
|
|
|
+ fn dst_len(_header: &TagHeader) -> Self::Metadata {}
|
|
|
+ }
|
|
|
+
|
|
|
+ let bytes = AlignedBytes([
|
|
|
+ /* id: 0xffff_ffff */
|
|
|
+ 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
|
|
|
+ 16, 0, 0, 0, /* field a: 0xdead_beef */
|
|
|
+ 0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
|
|
|
+ 0x37, 0x13, 0x37, 0x13,
|
|
|
+ ]);
|
|
|
+ let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
|
|
|
+ let tag = GenericTag::ref_from(bytes);
|
|
|
+ let custom_tag = tag.cast::<CustomTag>();
|
|
|
+
|
|
|
+ assert_eq!(mem::size_of_val(custom_tag), 16);
|
|
|
+ assert_eq!(custom_tag.a, 0xdead_beef);
|
|
|
+ assert_eq!(custom_tag.b, 0x1337_1337);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_cast_generic_tag_to_dynamically_sized_tag() {
|
|
|
+ #[repr(C)]
|
|
|
+ #[derive(ptr_meta::Pointee)]
|
|
|
+ struct CustomDstTag {
|
|
|
+ tag_header: TagHeader,
|
|
|
+ a: u32,
|
|
|
+ payload: [u8],
|
|
|
+ }
|
|
|
+
|
|
|
+ impl TagTrait for CustomDstTag {
|
|
|
+ const ID: TagType = TagType::End;
|
|
|
+
|
|
|
+ fn dst_len(header: &TagHeader) -> Self::Metadata {
|
|
|
+ let base_size = mem::size_of::<TagHeader>() + mem::size_of::<u32>();
|
|
|
+ header.size as usize - base_size
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let bytes = AlignedBytes([
|
|
|
+ /* id: 0xffff_ffff */
|
|
|
+ 0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
|
|
|
+ 16, 0, 0, 0, /* field a: 0xdead_beef */
|
|
|
+ 0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
|
|
|
+ 0x37, 0x13, 0x37, 0x13,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
|
|
|
+ let tag = GenericTag::ref_from(bytes);
|
|
|
+ let custom_tag = tag.cast::<CustomDstTag>();
|
|
|
+
|
|
|
+ assert_eq!(mem::size_of_val(custom_tag), 16);
|
|
|
+ assert_eq!(custom_tag.a, 0xdead_beef);
|
|
|
+ assert_eq!(custom_tag.payload.len(), 4);
|
|
|
+ assert_eq!(custom_tag.payload[0], 0x37);
|
|
|
+ assert_eq!(custom_tag.payload[1], 0x13);
|
|
|
+ assert_eq!(custom_tag.payload[2], 0x37);
|
|
|
+ assert_eq!(custom_tag.payload[3], 0x13);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_tag_bytes_ref() {
|
|
|
+ let empty: &[u8] = &[];
|
|
|
+ assert_eq!(
|
|
|
+ TagBytesRef::try_from(empty),
|
|
|
+ Err(MemoryError::MinLengthNotSatisfied)
|
|
|
+ );
|
|
|
+
|
|
|
+ let slice = &[0_u8, 1, 2, 3, 4, 5, 6];
|
|
|
+ assert_eq!(
|
|
|
+ TagBytesRef::try_from(&slice[..]),
|
|
|
+ Err(MemoryError::MinLengthNotSatisfied)
|
|
|
+ );
|
|
|
+
|
|
|
+ let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]);
|
|
|
+ // Guaranteed wrong alignment
|
|
|
+ let unaligned_slice = &slice[3..];
|
|
|
+ assert_eq!(
|
|
|
+ TagBytesRef::try_from(unaligned_slice),
|
|
|
+ Err(MemoryError::WrongAlignment)
|
|
|
+ );
|
|
|
+
|
|
|
+ let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7]);
|
|
|
+ let slice = &slice[..];
|
|
|
+ assert_eq!(TagBytesRef::try_from(slice), Ok(TagBytesRef(slice)));
|
|
|
+ }
|
|
|
|
|
|
#[test]
|
|
|
- fn parse_slice_as_string() {
|
|
|
- // empty slice is invalid
|
|
|
- assert!(matches!(
|
|
|
- Tag::parse_slice_as_string(&[]),
|
|
|
- Err(StringError::MissingNul(_))
|
|
|
- ));
|
|
|
- // empty string is fine
|
|
|
- assert_eq!(Tag::parse_slice_as_string(&[0x00]), Ok(""));
|
|
|
- // reject invalid utf8
|
|
|
- assert!(matches!(
|
|
|
- Tag::parse_slice_as_string(&[0xff, 0x00]),
|
|
|
- Err(StringError::Utf8(_))
|
|
|
- ));
|
|
|
- // reject missing null
|
|
|
- assert!(matches!(
|
|
|
- Tag::parse_slice_as_string(b"hello"),
|
|
|
- Err(StringError::MissingNul(_))
|
|
|
- ));
|
|
|
- // must not include final null
|
|
|
- assert_eq!(Tag::parse_slice_as_string(b"hello\0"), Ok("hello"));
|
|
|
- assert_eq!(Tag::parse_slice_as_string(b"hello\0\0"), Ok("hello"));
|
|
|
- // must skip everytihng after first null
|
|
|
- assert_eq!(Tag::parse_slice_as_string(b"hello\0foo"), Ok("hello"));
|
|
|
+ fn test_create_generic_tag() {
|
|
|
+ #[rustfmt::skip]
|
|
|
+ let bytes = AlignedBytes::new(
|
|
|
+ [
|
|
|
+ TagType::Cmdline.val() as u8, 0, 0, 0,
|
|
|
+ /* Tag size */
|
|
|
+ 18, 0, 0, 0,
|
|
|
+ /* Some payload. */
|
|
|
+ 0, 1, 2, 3,
|
|
|
+ 4, 5, 6, 7,
|
|
|
+ 8, 9,
|
|
|
+ // Padding
|
|
|
+ 0, 0, 0, 0, 0, 0
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
|
|
|
+ let tag = GenericTag::ref_from(bytes);
|
|
|
+ assert_eq!(tag.header.typ, TagType::Cmdline);
|
|
|
+ assert_eq!(tag.header.size, 8 + 10);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_cast_generic_tag_to_generic_tag() {
|
|
|
+ #[rustfmt::skip]
|
|
|
+ let bytes = AlignedBytes::new(
|
|
|
+ [
|
|
|
+ TagType::Cmdline.val() as u8, 0, 0, 0,
|
|
|
+ /* Tag size */
|
|
|
+ 18, 0, 0, 0,
|
|
|
+ /* Some payload. */
|
|
|
+ 0, 1, 2, 3,
|
|
|
+ 4, 5, 6, 7,
|
|
|
+ 8, 9,
|
|
|
+ // Padding
|
|
|
+ 0, 0, 0, 0, 0, 0
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ let bytes = TagBytesRef::try_from(bytes.borrow()).unwrap();
|
|
|
+ let tag = GenericTag::ref_from(bytes);
|
|
|
+
|
|
|
+ // Main objective here is also that this test passes Miri.
|
|
|
+ let tag = tag.cast::<GenericTag>();
|
|
|
+ assert_eq!(tag.header.typ, TagType::Cmdline);
|
|
|
+ assert_eq!(tag.header.size, 8 + 10);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_tag_iter() {
|
|
|
+ #[rustfmt::skip]
|
|
|
+ let bytes = AlignedBytes::new(
|
|
|
+ [
|
|
|
+ /* Some minimal tag. */
|
|
|
+ 0xff, 0, 0, 0,
|
|
|
+ 8, 0, 0, 0,
|
|
|
+ /* Some tag with payload. */
|
|
|
+ 0xfe, 0, 0, 0,
|
|
|
+ 12, 0, 0, 0,
|
|
|
+ 1, 2, 3, 4,
|
|
|
+ // Padding
|
|
|
+ 0, 0, 0, 0,
|
|
|
+ /* End tag */
|
|
|
+ 0, 0, 0, 0,
|
|
|
+ 8, 0, 0, 0,
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ let mut iter = TagIter::new(bytes.borrow());
|
|
|
+ let first = iter.next().unwrap();
|
|
|
+ assert_eq!(first.header.typ, TagType::Custom(0xff));
|
|
|
+ assert_eq!(first.header.size, 8);
|
|
|
+ assert!(first.payload.is_empty());
|
|
|
+
|
|
|
+ let second = iter.next().unwrap();
|
|
|
+ assert_eq!(second.header.typ, TagType::Custom(0xfe));
|
|
|
+ assert_eq!(second.header.size, 12);
|
|
|
+ assert_eq!(&second.payload, &[1, 2, 3, 4]);
|
|
|
+
|
|
|
+ let third = iter.next().unwrap();
|
|
|
+ assert_eq!(third.header.typ, TagType::End);
|
|
|
+ assert_eq!(third.header.size, 8);
|
|
|
+ assert!(first.payload.is_empty());
|
|
|
+
|
|
|
+ assert_eq!(iter.next(), None);
|
|
|
}
|
|
|
}
|