1use 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 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 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(); buf.extend([1, 1, 1, 1]); 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]); buf.extend([1, 1, 1, 1]); 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]; buf.extend(&[0, 0, 0]); buf.extend([1, 1, 1, 1]); 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]); buf.extend([1, 1, 1, 1]); 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}