use crate::{ArgType, Header, StringRef};
use fidl_fuchsia_diagnostics_stream::{Argument, Record, Value};
use nom::{
bytes::complete::take,
multi::many0,
number::complete::{le_f64, le_i64, le_u64},
Err, IResult,
};
use std::{borrow::Cow, cell::RefCell, rc::Rc};
use thiserror::Error;
pub(crate) type ParseResult<'a, T> = IResult<&'a [u8], T, ParseError>;
pub fn parse_record(buf: &[u8]) -> Result<(Record, &[u8]), ParseError> {
match try_parse_record(buf) {
Ok((remainder, record)) => Ok((record, remainder)),
Err(Err::Incomplete(n)) => Err(ParseError::Incomplete(n)),
Err(Err::Error(e)) | Err(Err::Failure(e)) => Err(e),
}
}
enum ParseState {
Initial,
InMessage,
InArguments,
}
pub(crate) fn try_parse_record(buf: &[u8]) -> ParseResult<'_, Record> {
let (after_header, header) = parse_header(buf)?;
if header.raw_type() != crate::TRACING_FORMAT_LOG_RECORD_TYPE {
return Err(nom::Err::Failure(ParseError::ValueOutOfValidRange));
}
let (var_len, timestamp) = le_i64(after_header)?;
let remaining_record_len = if header.size_words() >= 2 {
(header.size_words() - 2) as usize * 8
} else {
return Err(nom::Err::Failure(ParseError::ValueOutOfValidRange));
};
let severity = header.severity();
let (after_record, args_buf) = take(remaining_record_len)(var_len)?;
let state = Rc::new(RefCell::new(ParseState::Initial));
let (_, arguments) =
many0(|input| parse_argument_internal(input, &mut state.borrow_mut()))(args_buf)?;
Ok((after_record, Record { timestamp, severity, arguments }))
}
fn parse_header(buf: &[u8]) -> ParseResult<'_, Header> {
let (after, header) = le_u64(buf)?;
let header = Header(header);
Ok((after, header))
}
pub fn parse_argument(buf: &[u8]) -> ParseResult<'_, Argument> {
let mut state = ParseState::Initial;
parse_argument_internal(buf, &mut state)
}
fn parse_argument_internal<'a>(buf: &'a [u8], state: &mut ParseState) -> ParseResult<'a, Argument> {
let (after_header, header) = parse_header(buf)?;
let arg_ty = ArgType::try_from(header.raw_type()).map_err(nom::Err::Failure)?;
let (after_name, name) = string_ref(header.name_ref(), after_header, false)?;
if matches!(state, ParseState::Initial)
&& matches!(&name, StringRef::Inline(Cow::Borrowed("message")))
{
*state = ParseState::InMessage;
}
let (value, after_value) = match arg_ty {
ArgType::Null => (Value::UnsignedInt(1), after_name),
ArgType::I64 => {
let (rem, n) = le_i64(after_name)?;
(Value::SignedInt(n), rem)
}
ArgType::U64 => {
let (rem, n) = le_u64(after_name)?;
(Value::UnsignedInt(n), rem)
}
ArgType::F64 => {
let (rem, n) = le_f64(after_name)?;
(Value::Floating(n), rem)
}
ArgType::String => {
let (rem, s) =
string_ref(header.value_ref(), after_name, matches!(state, ParseState::InMessage))?;
(Value::Text(s.to_string()), rem)
}
ArgType::Bool => (Value::Boolean(header.bool_val()), after_name),
ArgType::Pointer | ArgType::Koid | ArgType::I32 | ArgType::U32 => {
return Err(Err::Failure(ParseError::Unsupported))
}
};
if matches!(state, ParseState::InMessage) {
*state = ParseState::InArguments;
}
Ok((after_value, Argument { name: name.to_string(), value }))
}
fn string_ref(
ref_mask: u16,
buf: &[u8],
support_invalid_utf8: bool,
) -> ParseResult<'_, StringRef<'_>> {
Ok(if ref_mask == 0 {
(buf, StringRef::Empty)
} else if (ref_mask & 1 << 15) == 0 {
return Err(Err::Failure(ParseError::Unsupported));
} else {
let name_len = (ref_mask & !(1 << 15)) as usize;
let (after_name, name) = take(name_len)(buf)?;
let parsed = if support_invalid_utf8 {
match std::str::from_utf8(name) {
Ok(valid) => Cow::Borrowed(valid),
Err(_) => String::from_utf8_lossy(name),
}
} else {
Cow::Borrowed(
std::str::from_utf8(name).map_err(|e| nom::Err::Error(ParseError::from(e)))?,
)
};
let (_padding, after_padding) = after_name.split_at(after_name.len() % 8);
(after_padding, StringRef::Inline(parsed))
})
}
#[derive(Debug, Clone, Error)]
pub enum ParseError {
#[error("value out of range")]
ValueOutOfValidRange,
#[error("unsupported value type")]
Unsupported,
#[error("nom parsing error: {0:?}")]
Nom(nom::error::ErrorKind),
#[error("parsing terminated early, needed {0:?}")]
Incomplete(nom::Needed),
}
impl From<std::str::Utf8Error> for ParseError {
fn from(_: std::str::Utf8Error) -> Self {
ParseError::ValueOutOfValidRange
}
}
impl nom::error::ParseError<&[u8]> for ParseError {
fn from_error_kind(_input: &[u8], kind: nom::error::ErrorKind) -> Self {
ParseError::Nom(kind)
}
fn append(_input: &[u8], kind: nom::error::ErrorKind, _prev: Self) -> Self {
ParseError::Nom(kind)
}
}