flatdata/
rawdata.rs

1use std::borrow::Cow;
2use std::str;
3
4/// Exposes blocks of raw data, providing auxiliary functionality like
5/// extracting substrings.
6#[derive(Debug, Clone, Copy)]
7pub struct RawData<'a> {
8    data: &'a [u8],
9}
10
11impl<'a> std::ops::Deref for RawData<'a> {
12    type Target = [u8];
13
14    #[inline]
15    fn deref(&self) -> &[u8] {
16        self.data
17    }
18}
19
20impl<'a> RawData<'a> {
21    /// Creates a new object from raw memory reference.
22    #[inline]
23    pub fn new(data: &'a [u8]) -> Self {
24        Self { data }
25    }
26
27    /// Reads a \0 terminated substring starting at specified offset.
28    #[inline]
29    pub fn substring(&self, start: usize) -> Result<&'a str, str::Utf8Error> {
30        self.substring_with(start, str::from_utf8)
31    }
32
33    /// Reads a \0 terminated substring starting at specified offset, including invalid characters.
34    #[inline]
35    pub fn substring_lossy(&self, start: usize) -> Cow<'a, str> {
36        self.substring_with(start, String::from_utf8_lossy)
37    }
38
39    /// Reads a \0 terminated substring starting at specified offset as raw bytes.
40    #[inline]
41    pub fn substring_raw(&self, start: usize) -> &'a [u8] {
42        self.substring_with(start, std::convert::identity)
43    }
44
45    /// Reads a \0 terminated substring starting at specified offset without checking that the
46    /// string contains valid UTF-8.
47    ///
48    /// # Safety
49    /// Same as str::from_utf8_unchecked
50    #[inline]
51    pub unsafe fn substring_unchecked(&self, start: usize) -> &'a str {
52        self.substring_with(start, |bytes| str::from_utf8_unchecked(bytes))
53    }
54
55    fn substring_with<T>(&self, start: usize, f: impl FnOnce(&'a [u8]) -> T) -> T {
56        let suffix = &self.data[start..];
57        match suffix.iter().position(|&c| c == 0) {
58            Some(idx) => f(&suffix[..idx]),
59            None => f(suffix),
60        }
61    }
62
63    /// Converts RawData back into bytes.
64    #[inline]
65    pub fn as_bytes(&self) -> &'a [u8] {
66        self.data
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn empty() {
76        let data: &[u8] = b"";
77        let raw_data = RawData::new(data);
78        assert_eq!(raw_data.substring(0), Ok(""));
79        assert_eq!(raw_data.substring_lossy(0), "");
80        assert_eq!(raw_data.substring_raw(0), b"");
81        assert_eq!(unsafe { raw_data.substring_unchecked(0) }, "");
82    }
83
84    #[test]
85    fn last_without_terminator() {
86        let data: &[u8] = b"abc";
87        let raw_data = RawData::new(data);
88        assert_eq!(raw_data.substring(1), Ok("bc"));
89        assert_eq!(raw_data.substring_lossy(1), "bc");
90        assert_eq!(raw_data.substring_raw(1), b"bc");
91        assert_eq!(unsafe { raw_data.substring_unchecked(1) }, "bc");
92    }
93
94    #[test]
95    fn until_terminator() {
96        let data: &[u8] = b"ab\0c";
97        let raw_data = RawData::new(data);
98        assert_eq!(raw_data.substring(1), Ok("b"));
99        assert_eq!(raw_data.substring_lossy(1), "b");
100        assert_eq!(raw_data.substring_raw(1), b"b");
101        assert_eq!(unsafe { raw_data.substring_unchecked(1) }, "b");
102    }
103
104    #[test]
105    fn invalid_utf8() {
106        let data: &[u8] = b"ab\xF0\x90\x80\0c";
107        let raw_data = RawData::new(data);
108        assert!(raw_data.substring(1).is_err());
109        assert_eq!(raw_data.substring_lossy(1), "b�");
110        assert_eq!(raw_data.substring_raw(1), b"b\xF0\x90\x80");
111    }
112}