use crate::{take_n_padded, trace_header, ParseError, ParseResult, STRING_RECORD_TYPE};
use nom::combinator::all_consuming;
use std::num::NonZeroU16;
pub(crate) const STRING_REF_INLINE_BIT: u16 = 1 << 15;
#[derive(Clone, Debug, PartialEq)]
pub enum StringRef<'a> {
Empty,
Index(NonZeroU16),
Inline(&'a str),
}
impl<'a> StringRef<'a> {
pub(crate) fn parse(str_ref: u16, buf: &'a [u8]) -> ParseResult<'a, Self> {
if let Some(nonzero) = NonZeroU16::new(str_ref) {
if (nonzero.get() >> 15) & 1 == 0 {
Ok((buf, StringRef::Index(nonzero)))
} else {
let length = str_ref ^ STRING_REF_INLINE_BIT;
let (buf, inline) = parse_padded_string(length as usize, buf)?;
Ok((buf, StringRef::Inline(inline)))
}
} else {
Ok((buf, StringRef::Empty))
}
}
}
#[derive(Debug, PartialEq)]
pub(super) struct StringRecord<'a> {
pub index: u16,
pub value: &'a str,
}
impl<'a> StringRecord<'a> {
pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
let (buf, header) = StringHeader::parse(buf)?;
let (rem, payload) = header.take_payload(buf)?;
let (empty, value) =
all_consuming(|p| parse_padded_string(header.string_len() as usize, p))(payload)?;
assert!(empty.is_empty(), "all_consuming must not return any remaining buffer");
Ok((rem, Self { index: header.string_index(), value }))
}
}
trace_header! {
StringHeader (STRING_RECORD_TYPE) {
u16, string_index: 16, 30;
u16, string_len: 32, 46;
}
}
pub(crate) fn parse_padded_string<'a>(
unpadded_len: usize,
buf: &'a [u8],
) -> ParseResult<'a, &'a str> {
let (rem, bytes) = take_n_padded(unpadded_len, buf)?;
let value =
std::str::from_utf8(bytes).map_err(|e| nom::Err::Failure(ParseError::InvalidUtf8(e)))?;
Ok((rem, value))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::RawTraceRecord;
#[test]
fn empty_string() {
let (trailing, empty) = parse_padded_string(0, &[1, 1, 1, 1]).unwrap();
assert_eq!(empty, "");
assert_eq!(trailing, [1, 1, 1, 1]);
}
#[test]
fn string_no_padding() {
let mut buf = "helloooo".as_bytes().to_vec(); buf.extend([1, 1, 1, 1]); let (trailing, parsed) = parse_padded_string(8, &buf).unwrap();
assert_eq!(parsed, "helloooo");
assert_eq!(trailing, [1, 1, 1, 1]);
}
#[test]
fn string_with_padding() {
let mut buf = "hello".as_bytes().to_vec();
buf.extend(&[0, 0, 0]); buf.extend([1, 1, 1, 1]); let (trailing, parsed) = parse_padded_string(5, &buf).unwrap();
assert_eq!(parsed, "hello");
assert_eq!(trailing, [1, 1, 1, 1]);
}
#[test]
fn string_ref_index() {
let (trailing, parsed) = StringRef::parse(10u16, &[1, 1, 1, 1]).unwrap();
assert_eq!(parsed, StringRef::Index(NonZeroU16::new(10).unwrap()));
assert_eq!(trailing, [1, 1, 1, 1]);
}
#[test]
fn string_ref_inline() {
let mut buf = "hello".as_bytes().to_vec();
buf.extend(&[0, 0, 0]); buf.extend([1, 1, 1, 1]); let (trailing, parsed) = StringRef::parse(5 | STRING_REF_INLINE_BIT, &buf).unwrap();
assert_eq!(parsed, StringRef::Inline("hello"),);
assert_eq!(trailing, [1, 1, 1, 1]);
}
#[test]
fn string_record() {
let mut header = StringHeader::empty();
header.set_string_index(10);
header.set_string_len(9);
assert_parses_to_record!(
crate::fxt_builder::FxtBuilder::new(header).atom("hellooooo").build(),
RawTraceRecord::String(StringRecord { index: 10, value: "hellooooo" }),
);
}
}