fxt/
string.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{take_n_padded, trace_header, ParseError, ParseResult, STRING_RECORD_TYPE};
6use nom::combinator::all_consuming;
7use nom::Parser;
8use std::num::NonZeroU16;
9
10pub(crate) const STRING_REF_INLINE_BIT: u16 = 1 << 15;
11
12#[derive(Clone, Debug, PartialEq)]
13pub enum StringRef<'a> {
14    Empty,
15    Index(NonZeroU16),
16    Inline(&'a str),
17}
18
19impl<'a> StringRef<'a> {
20    pub(crate) fn parse(str_ref: u16, buf: &'a [u8]) -> ParseResult<'a, Self> {
21        if let Some(nonzero) = NonZeroU16::new(str_ref) {
22            if (nonzero.get() >> 15) & 1 == 0 {
23                // MSB is zero, so this is a string index.
24                Ok((buf, StringRef::Index(nonzero)))
25            } else {
26                // Remove the MSB from the length.
27                let length = str_ref ^ STRING_REF_INLINE_BIT;
28                let (buf, inline) = parse_padded_string(length as usize, buf)?;
29                Ok((buf, StringRef::Inline(inline)))
30            }
31        } else {
32            Ok((buf, StringRef::Empty))
33        }
34    }
35}
36
37#[derive(Debug, PartialEq)]
38pub(super) struct StringRecord<'a> {
39    /// Index should not be 0 but we can't use NonZeroU16 according to the spec:
40    ///
41    /// > String records that contain empty strings must be tolerated but they're pointless since
42    /// > the empty string can simply be encoded as zero in a string ref.
43    pub index: u16,
44    pub value: &'a str,
45}
46
47impl<'a> StringRecord<'a> {
48    pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
49        let (buf, header) = StringHeader::parse(buf)?;
50        let (rem, payload) = header.take_payload(buf)?;
51        let (empty, value) =
52            all_consuming(|p| parse_padded_string(header.string_len() as usize, p))
53                .parse(payload)?;
54        assert!(empty.is_empty(), "all_consuming must not return any remaining buffer");
55        Ok((rem, Self { index: header.string_index(), value }))
56    }
57}
58
59trace_header! {
60    StringHeader (STRING_RECORD_TYPE) {
61        u16, string_index: 16, 30;
62        u16, string_len: 32, 46;
63    }
64}
65
66pub(crate) fn parse_padded_string<'a>(
67    unpadded_len: usize,
68    buf: &'a [u8],
69) -> ParseResult<'a, &'a str> {
70    let (rem, bytes) = take_n_padded(unpadded_len, buf)?;
71    let value =
72        std::str::from_utf8(bytes).map_err(|e| nom::Err::Failure(ParseError::InvalidUtf8(e)))?;
73    Ok((rem, value))
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::RawTraceRecord;
80
81    #[test]
82    fn empty_string() {
83        let (trailing, empty) = parse_padded_string(0, &[1, 1, 1, 1]).unwrap();
84        assert_eq!(empty, "");
85        assert_eq!(trailing, [1, 1, 1, 1]);
86    }
87
88    #[test]
89    fn string_no_padding() {
90        let mut buf = "helloooo".as_bytes().to_vec(); // contents
91        buf.extend([1, 1, 1, 1]); // trailing
92
93        let (trailing, parsed) = parse_padded_string(8, &buf).unwrap();
94        assert_eq!(parsed, "helloooo");
95        assert_eq!(trailing, [1, 1, 1, 1]);
96    }
97
98    #[test]
99    fn string_with_padding() {
100        let mut buf = "hello".as_bytes().to_vec();
101        buf.extend(&[0, 0, 0]); // padding
102        buf.extend([1, 1, 1, 1]); // trailing
103
104        let (trailing, parsed) = parse_padded_string(5, &buf).unwrap();
105        assert_eq!(parsed, "hello");
106        assert_eq!(trailing, [1, 1, 1, 1]);
107    }
108
109    #[test]
110    fn string_ref_index() {
111        let (trailing, parsed) = StringRef::parse(10u16, &[1, 1, 1, 1]).unwrap();
112        assert_eq!(parsed, StringRef::Index(NonZeroU16::new(10).unwrap()));
113        assert_eq!(trailing, [1, 1, 1, 1]);
114    }
115
116    #[test]
117    fn string_ref_inline() {
118        let mut buf = "hello".as_bytes().to_vec();
119        buf.extend(&[0, 0, 0]); // padding
120        buf.extend([1, 1, 1, 1]); // trailing
121
122        let (trailing, parsed) = StringRef::parse(5 | STRING_REF_INLINE_BIT, &buf).unwrap();
123        assert_eq!(parsed, StringRef::Inline("hello"),);
124        assert_eq!(trailing, [1, 1, 1, 1]);
125    }
126
127    #[test]
128    fn string_record() {
129        let mut header = StringHeader::empty();
130        header.set_string_index(10);
131        header.set_string_len(9);
132
133        assert_parses_to_record!(
134            crate::fxt_builder::FxtBuilder::new(header).atom("hellooooo").build(),
135            RawTraceRecord::String(StringRecord { index: 10, value: "hellooooo" }),
136        );
137    }
138}