diagnostics_log_encoding/
parse.rs1use crate::{ArgType, Argument, Header, RawSeverity, Record, Value, constants, zx};
9use std::borrow::Cow;
10use thiserror::Error;
11use zerocopy::FromBytes;
12
13pub fn basic_info(buf: &[u8]) -> Result<(zx::BootInstant, RawSeverity), ParseError> {
15 let (header, after_header) =
16 Header::read_from_prefix(buf).map_err(|_| ParseError::InvalidHeader)?;
17 if header.raw_type() != crate::TRACING_FORMAT_LOG_RECORD_TYPE {
18 return Err(ParseError::InvalidRecordType);
19 }
20 let (timestamp, _) =
21 i64::read_from_prefix(after_header).map_err(|_| ParseError::InvalidTimestamp)?;
22 Ok((zx::BootInstant::from_nanos(timestamp), header.severity()))
23}
24
25pub fn parse_record(buf: &[u8]) -> Result<(Record<'_>, &[u8]), ParseError> {
28 let (header, after_header) =
29 Header::read_from_prefix(buf).map_err(|_| ParseError::InvalidHeader)?;
30
31 if header.raw_type() != crate::TRACING_FORMAT_LOG_RECORD_TYPE {
32 return Err(ParseError::ValueOutOfValidRange);
33 }
34
35 let (timestamp, remaining_buffer) =
36 i64::read_from_prefix(after_header).map_err(|_| ParseError::InvalidTimestamp)?;
37
38 let arguments_length = if header.size_words() >= 2 {
39 (header.size_words() - 2) as usize * 8
41 } else {
42 return Err(ParseError::ValueOutOfValidRange);
43 };
44
45 let Some((mut arguments_buffer, remaining)) =
46 remaining_buffer.split_at_checked(arguments_length)
47 else {
48 return Err(ParseError::ValueOutOfValidRange);
49 };
50
51 let mut arguments = vec![];
52 let mut state = ParseState::Initial;
53 while !arguments_buffer.is_empty() {
54 let (argument, rem) = parse_argument_internal(arguments_buffer, &mut state)?;
55 arguments_buffer = rem;
56 arguments.push(argument);
57 }
58
59 Ok((
60 Record {
61 timestamp: zx::BootInstant::from_nanos(timestamp),
62 severity: header.severity(),
63 arguments,
64 },
65 remaining,
66 ))
67}
68
69enum ParseState {
72 Initial,
74 InMessage,
76 InArguments,
78}
79
80pub fn parse_argument(buf: &[u8]) -> Result<(Argument<'_>, &[u8]), ParseError> {
82 parse_argument_internal(buf, &mut ParseState::Initial)
83}
84
85fn parse_argument_internal<'a>(
86 buf: &'a [u8],
87 state: &mut ParseState,
88) -> Result<(Argument<'a>, &'a [u8]), ParseError> {
89 let (header, after_header) =
90 Header::read_from_prefix(buf).map_err(|_| ParseError::InvalidArgumentHeader)?;
91 let arg_ty = ArgType::try_from(header.raw_type())?;
92
93 let (name, after_name) = string_ref(header.name_ref(), after_header, false)?;
94 if matches!(state, ParseState::Initial) && name == Cow::Borrowed(constants::MESSAGE) {
95 *state = ParseState::InMessage;
96 }
97 let (value, after_value) = match arg_ty {
98 ArgType::Null => (Value::UnsignedInt(1), after_name),
99 ArgType::I64 => {
100 let (n, rem) =
101 i64::read_from_prefix(after_name).map_err(|_| ParseError::InvalidArgument)?;
102 (Value::SignedInt(n), rem)
103 }
104 ArgType::U64 => {
105 let (n, rem) =
106 u64::read_from_prefix(after_name).map_err(|_| ParseError::InvalidArgument)?;
107 (Value::UnsignedInt(n), rem)
108 }
109 ArgType::F64 => {
110 let (n, rem) =
111 f64::read_from_prefix(after_name).map_err(|_| ParseError::InvalidArgument)?;
112 (Value::Floating(n), rem)
113 }
114 ArgType::String => {
115 let (s, rem) =
116 string_ref(header.value_ref(), after_name, matches!(state, ParseState::InMessage))?;
117 (Value::Text(s), rem)
118 }
119 ArgType::Bool => (Value::Boolean(header.bool_val()), after_name),
120 ArgType::Pointer | ArgType::Koid | ArgType::I32 | ArgType::U32 => {
121 return Err(ParseError::Unsupported);
122 }
123 };
124 if matches!(state, ParseState::InMessage) {
125 *state = ParseState::InArguments;
126 }
127
128 Ok((Argument::new(name, value), after_value))
129}
130
131fn string_ref(
132 ref_mask: u16,
133 buf: &[u8],
134 support_invalid_utf8: bool,
135) -> Result<(Cow<'_, str>, &[u8]), ParseError> {
136 if ref_mask == 0 {
137 return Ok((Cow::Borrowed(""), buf));
138 }
139 if (ref_mask & (1 << 15)) == 0 {
140 return Err(ParseError::Unsupported);
141 }
142 let name_len = (ref_mask & !(1 << 15)) as usize;
144 let Some((name, after_name)) = buf.split_at_checked(name_len) else {
145 return Err(ParseError::ValueOutOfValidRange);
146 };
147 let parsed = if support_invalid_utf8 {
148 String::from_utf8_lossy(name)
149 } else {
150 let name = std::str::from_utf8(name)?;
151 Cow::Borrowed(name)
152 };
153 let padding_len = (8 - (name_len % 8)) % 8;
159 let Some((_padding, after_padding)) = after_name.split_at_checked(padding_len) else {
160 return Err(ParseError::ValueOutOfValidRange);
161 };
162 Ok((parsed, after_padding))
163}
164
165#[derive(Debug, Clone, Error)]
167pub enum ParseError {
168 #[error("value out of range")]
170 ValueOutOfValidRange,
171
172 #[error("unsupported value type")]
175 Unsupported,
176
177 #[error("found invalid header")]
179 InvalidHeader,
180
181 #[error("found invalid header in an argument")]
183 InvalidArgumentHeader,
184
185 #[error("found invalid timestamp after header")]
187 InvalidTimestamp,
188
189 #[error("found invalid argument")]
191 InvalidArgument,
192
193 #[error("found invalid record type")]
195 InvalidRecordType,
196
197 #[error("parsing terminated early, remaining bytes: {0:?}")]
199 Incomplete(usize),
200}
201
202impl From<std::str::Utf8Error> for ParseError {
203 fn from(_: std::str::Utf8Error) -> Self {
204 ParseError::ValueOutOfValidRange
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::encode::{Encoder, EncoderOpts};
212 use fidl_fuchsia_diagnostics_types::Severity;
213 use std::io::Cursor;
214
215 #[fuchsia::test]
216 fn basic_structured_info() {
217 let expected_timestamp = zx::BootInstant::from_nanos(72);
218 let record = Record {
219 timestamp: expected_timestamp,
220 severity: Severity::Error.into_primitive(),
221 arguments: vec![],
222 };
223 let mut buffer = Cursor::new(vec![0u8; 1000]);
224 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
225 encoder.write_record(record).unwrap();
226 let encoded = &buffer.get_ref().as_slice()[..buffer.position() as usize];
227
228 let (timestamp, severity) = basic_info(encoded).unwrap();
229 assert_eq!(timestamp, expected_timestamp);
230 assert_eq!(severity, Severity::Error.into_primitive());
231 }
232
233 #[fuchsia::test]
234 fn parse_record_with_zeros() {
235 let expected_timestamp = zx::BootInstant::from_nanos(72);
236 let record = Record {
237 timestamp: expected_timestamp,
238 severity: Severity::Error.into_primitive(),
239 arguments: vec![],
240 };
241 let mut buffer = Cursor::new(vec![0u8; 1000]);
242 let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
243 encoder.write_record(record.clone()).unwrap();
244
245 let encoded = &buffer.get_ref().as_slice()[..buffer.position() as usize + 3];
247
248 let (result_record, rem) = parse_record(encoded).unwrap();
249 assert_eq!(rem.len(), 3);
250 assert_eq!(record, result_record);
251 }
252
253 #[fuchsia::test]
254 fn test_string_ref_padding() {
255 let mut buf = vec![b'f', b'o', b'o', 0, 0, 0, 0, 0];
256 buf.extend_from_slice(b"extra");
257 let ref_mask = (1 << 15) | 3;
258 let (parsed, remaining) = string_ref(ref_mask, &buf, false).unwrap();
259 assert_eq!(parsed, "foo");
260 assert_eq!(remaining, b"extra");
261 }
262}