Skip to main content

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::{ParseError, ParseResult, STRING_RECORD_TYPE, take_n_padded, trace_header};
6use nom::Parser;
7use nom::combinator::all_consuming;
8use std::num::NonZeroU16;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub enum StringRef<'a> {
12    Empty,
13    Index(NonZeroU16),
14    Inline(&'a str),
15}
16
17impl<'a> StringRef<'a> {
18    pub(crate) fn parse(str_ref: u16, buf: &'a [u8]) -> ParseResult<'a, Self> {
19        let hdr = fxt_layout::StringRefHeader::from(str_ref);
20        if let Some(nonzero) = NonZeroU16::new(hdr.id_or_len()) {
21            if !hdr.is_inline() {
22                // MSB is zero, so this is a string index.
23                Ok((buf, StringRef::Index(nonzero)))
24            } else {
25                let length = hdr.id_or_len();
26                let (buf, inline) = parse_padded_string(length as usize, buf)?;
27                Ok((buf, StringRef::Inline(inline)))
28            }
29        } else {
30            Ok((buf, StringRef::Empty))
31        }
32    }
33}
34
35#[derive(Debug, PartialEq)]
36pub(super) struct StringRecord<'a> {
37    /// Index should not be 0 but we can't use NonZeroU16 according to the spec:
38    ///
39    /// > String records that contain empty strings must be tolerated but they're pointless since
40    /// > the empty string can simply be encoded as zero in a string ref.
41    pub index: u16,
42    pub value: &'a str,
43}
44
45impl<'a> StringRecord<'a> {
46    pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
47        let (buf, header) = StringHeader::parse(buf)?;
48        let (rem, payload) = header.take_payload(buf)?;
49        let (empty, value) =
50            all_consuming(|p| parse_padded_string(header.string_len() as usize, p))
51                .parse(payload)?;
52        assert!(empty.is_empty(), "all_consuming must not return any remaining buffer");
53        Ok((rem, Self { index: header.string_index(), value }))
54    }
55}
56
57trace_header! {
58    StringHeader (STRING_RECORD_TYPE) {
59        u16, string_index: 16, 30;
60        u16, string_len: 32, 46;
61    }
62}
63
64pub(crate) fn parse_padded_string<'a>(
65    unpadded_len: usize,
66    buf: &'a [u8],
67) -> ParseResult<'a, &'a str> {
68    let (rem, bytes) = take_n_padded(unpadded_len, buf)?;
69    let value =
70        std::str::from_utf8(bytes).map_err(|e| nom::Err::Failure(ParseError::InvalidUtf8(e)))?;
71    Ok((rem, value))
72}
73
74pub(crate) fn parse_padded_bstr<'a>(
75    unpadded_len: usize,
76    buf: &'a [u8],
77) -> ParseResult<'a, &'a bstr::BStr> {
78    let (rem, bytes) = take_n_padded(unpadded_len, buf)?;
79    Ok((rem, bstr::BStr::new(bytes)))
80}
81
82#[derive(Clone, Copy, Debug, PartialEq)]
83pub(crate) enum RawByteStringRef<'a> {
84    Empty,
85    Index(NonZeroU16),
86    Inline(&'a bstr::BStr),
87}
88
89impl<'a> RawByteStringRef<'a> {
90    pub(crate) fn parse(str_ref: u16, buf: &'a [u8]) -> ParseResult<'a, Self> {
91        let hdr = fxt_layout::StringRefHeader::from(str_ref);
92        if let Some(nonzero) = NonZeroU16::new(hdr.id_or_len()) {
93            if !hdr.is_inline() {
94                Ok((buf, RawByteStringRef::Index(nonzero)))
95            } else {
96                let length = hdr.id_or_len();
97                let (buf, inline) = parse_padded_bstr(length as usize, buf)?;
98                Ok((buf, RawByteStringRef::Inline(inline)))
99            }
100        } else {
101            Ok((buf, RawByteStringRef::Empty))
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::RawTraceRecord;
110    use bstr::ByteSlice;
111
112    #[test]
113    fn empty_string() {
114        let (trailing, empty) = parse_padded_string(0, &[1, 1, 1, 1]).unwrap();
115        assert_eq!(empty, "");
116        assert_eq!(trailing, [1, 1, 1, 1]);
117    }
118
119    #[test]
120    fn string_no_padding() {
121        let mut buf = "helloooo".as_bytes().to_vec(); // contents
122        buf.extend([1, 1, 1, 1]); // trailing
123
124        let (trailing, parsed) = parse_padded_string(8, &buf).unwrap();
125        assert_eq!(parsed, "helloooo");
126        assert_eq!(trailing, [1, 1, 1, 1]);
127    }
128
129    #[test]
130    fn string_with_padding() {
131        let mut buf = "hello".as_bytes().to_vec();
132        buf.extend(&[0, 0, 0]); // padding
133        buf.extend([1, 1, 1, 1]); // trailing
134
135        let (trailing, parsed) = parse_padded_string(5, &buf).unwrap();
136        assert_eq!(parsed, "hello");
137        assert_eq!(trailing, [1, 1, 1, 1]);
138    }
139
140    #[test]
141    fn string_invalid_utf8_verbatim() {
142        let mut buf = vec![0x68, 0x65, 0xff, 0x6c, 0x6f]; // "he\xfflo"
143        buf.extend(&[0, 0, 0]); // padding
144        buf.extend([1, 1, 1, 1]); // trailing
145
146        let (trailing, parsed) = parse_padded_bstr(5, &buf).unwrap();
147        assert_eq!(parsed.as_bytes(), &[0x68, 0x65, 0xff, 0x6c, 0x6f]);
148        assert_eq!(trailing, [1, 1, 1, 1]);
149    }
150
151    #[test]
152    fn string_ref_index() {
153        let (trailing, parsed) = StringRef::parse(10u16, &[1, 1, 1, 1]).unwrap();
154        assert_eq!(parsed, StringRef::Index(NonZeroU16::new(10).unwrap()));
155        assert_eq!(trailing, [1, 1, 1, 1]);
156    }
157
158    #[test]
159    fn string_ref_inline() {
160        let mut buf = "hello".as_bytes().to_vec();
161        buf.extend(&[0, 0, 0]); // padding
162        buf.extend([1, 1, 1, 1]); // trailing
163
164        let (trailing, parsed) =
165            StringRef::parse(fxt_layout::StringRefHeader::inline(5).bits(), &buf).unwrap();
166        assert_eq!(parsed, StringRef::Inline("hello"),);
167        assert_eq!(trailing, [1, 1, 1, 1]);
168    }
169
170    #[test]
171    fn string_record() {
172        let mut header = StringHeader::empty();
173        header.set_string_index(10);
174        header.set_string_len(9);
175
176        assert_parses_to_record!(
177            crate::fxt_builder::FxtBuilder::new(header).atom("hellooooo").build(),
178            RawTraceRecord::String(StringRecord { index: 10, value: "hellooooo" }),
179        );
180    }
181}