diagnostics_log_encoding/
parse.rs

1// Copyright 2020 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
5//! Parse diagnostic records from streams, returning FIDL-generated structs that match expected
6//! diagnostic service APIs.
7
8use crate::{constants, ArgType, Argument, Header, RawSeverity, Record, Value};
9use std::borrow::Cow;
10use thiserror::Error;
11use zerocopy::FromBytes;
12
13/// Extracts the basic information of a log message: timestamp and severity.
14pub 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
25/// Attempt to parse a diagnostic record from the head of this buffer, returning the record and any
26/// unused portion of the buffer if successful.
27pub 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        // Remove two word lengths for header and timestamp.
40        (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
69/// Internal parser state.
70/// Used to support handling of invalid utf-8 in msg fields.
71enum ParseState {
72    /// Initial parsing state
73    Initial,
74    /// We're in a message
75    InMessage,
76    /// We're in arguments (no special Unicode treatment)
77    InArguments,
78}
79
80/// Parses an argument
81pub 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    // zero out the top bit
143    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, after_padding) = after_name.split_at(after_name.len() % 8);
154    Ok((parsed, after_padding))
155}
156
157/// Errors which occur when interacting with streams of diagnostic records.
158#[derive(Debug, Clone, Error)]
159pub enum ParseError {
160    /// We attempted to parse bytes as a type for which the bytes are not a valid pattern.
161    #[error("value out of range")]
162    ValueOutOfValidRange,
163
164    /// We attempted to parse or encode values which are not yet supported by this implementation of
165    /// the Fuchsia Tracing format.
166    #[error("unsupported value type")]
167    Unsupported,
168
169    /// We failed to parse a record header.
170    #[error("found invalid header")]
171    InvalidHeader,
172
173    /// We failed to parse a record header.
174    #[error("found invalid header in an argument")]
175    InvalidArgumentHeader,
176
177    /// We failed to parse a record timestamp.
178    #[error("found invalid timestamp after header")]
179    InvalidTimestamp,
180
181    /// We failed to parse a record argument.
182    #[error("found invalid argument")]
183    InvalidArgument,
184
185    /// The record type wasn't the one we expected: LOGS
186    #[error("found invalid record type")]
187    InvalidRecordType,
188
189    /// We failed to parse a complete item.
190    #[error("parsing terminated early, remaining bytes: {0:?}")]
191    Incomplete(usize),
192}
193
194impl From<std::str::Utf8Error> for ParseError {
195    fn from(_: std::str::Utf8Error) -> Self {
196        ParseError::ValueOutOfValidRange
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use crate::encode::{Encoder, EncoderOpts};
204    use fidl_fuchsia_diagnostics::Severity;
205    use std::io::Cursor;
206
207    #[fuchsia::test]
208    fn basic_structured_info() {
209        let expected_timestamp = zx::BootInstant::from_nanos(72);
210        let record = Record {
211            timestamp: expected_timestamp,
212            severity: Severity::Error as u8,
213            arguments: vec![],
214        };
215        let mut buffer = Cursor::new(vec![0u8; 1000]);
216        let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
217        encoder.write_record(record).unwrap();
218        let encoded = &buffer.get_ref().as_slice()[..buffer.position() as usize];
219
220        let (timestamp, severity) = basic_info(encoded).unwrap();
221        assert_eq!(timestamp, expected_timestamp);
222        assert_eq!(severity, Severity::Error.into_primitive());
223    }
224
225    #[fuchsia::test]
226    fn parse_record_with_zeros() {
227        let expected_timestamp = zx::BootInstant::from_nanos(72);
228        let record = Record {
229            timestamp: expected_timestamp,
230            severity: Severity::Error as u8,
231            arguments: vec![],
232        };
233        let mut buffer = Cursor::new(vec![0u8; 1000]);
234        let mut encoder = Encoder::new(&mut buffer, EncoderOpts::default());
235        encoder.write_record(record.clone()).unwrap();
236
237        // Ensure that some additional padding is ok to parse the record.
238        let encoded = &buffer.get_ref().as_slice()[..buffer.position() as usize + 3];
239
240        let (result_record, rem) = parse_record(encoded).unwrap();
241        assert_eq!(rem.len(), 3);
242        assert_eq!(record, result_record);
243    }
244}