1use 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 Ok((buf, StringRef::Index(nonzero)))
25 } else {
26 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 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(); buf.extend([1, 1, 1, 1]); 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]); buf.extend([1, 1, 1, 1]); 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]); buf.extend([1, 1, 1, 1]); 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}