fxt/
blob.rs

1// Copyright 2023 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
5use crate::args::{Arg, RawArg};
6use crate::error::ParseWarning;
7use crate::init::Ticks;
8use crate::session::ResolveCtx;
9use crate::string::StringRef;
10use crate::thread::{ProcessKoid, ProcessRef, ThreadKoid, ThreadRef};
11use crate::{
12    take_n_padded, trace_header, ParseResult, Provider, BLOB_RECORD_TYPE, LARGE_RECORD_TYPE,
13};
14use flyweights::FlyStr;
15use nom::combinator::all_consuming;
16use nom::number::complete::le_u64;
17use nom::Parser;
18
19const BLOB_TYPE_DATA: u8 = 0x01;
20const BLOB_TYPE_LAST_BRANCH: u8 = 0x02;
21const BLOB_TYPE_PERFETTO: u8 = 0x03;
22
23const LARGE_BLOB_WITH_METADATA_TYPE: u8 = 0;
24const LARGE_BLOB_NO_METADATA_TYPE: u8 = 1;
25
26#[derive(Clone, Debug, PartialEq)]
27pub struct BlobRecord {
28    pub provider: Option<Provider>,
29    pub name: FlyStr,
30    pub ty: BlobType,
31    pub bytes: Vec<u8>,
32}
33
34impl BlobRecord {
35    pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawBlobRecord<'_>) -> Self {
36        Self {
37            provider: ctx.current_provider(),
38            name: ctx.resolve_str(raw.name),
39            ty: raw.ty,
40            bytes: raw.bytes.to_owned(),
41        }
42    }
43}
44
45#[derive(Debug, PartialEq)]
46pub(super) struct RawBlobRecord<'a> {
47    name: StringRef<'a>,
48    ty: BlobType,
49    bytes: &'a [u8],
50}
51
52impl<'a> RawBlobRecord<'a> {
53    pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
54        let (buf, header) = BlobHeader::parse(buf)?;
55        let ty = BlobType::from(header.blob_format_type());
56        let (rem, payload) = header.take_payload(buf)?;
57        let (payload, name) = StringRef::parse(header.name_ref(), payload)?;
58
59        // NB: most records are parsed with all_consuming for the last element to ensure we've
60        // used all of the payload reported in the header, but in practice it seems that some
61        // providers emit some trailing words.
62        let (_should_be_empty, bytes) = take_n_padded(header.payload_len() as usize, payload)?;
63        Ok((rem, Self { name, ty, bytes }))
64    }
65}
66
67#[derive(Clone, Debug, PartialEq)]
68pub enum BlobType {
69    Data,
70    LastBranch,
71    Perfetto,
72    Unknown { raw: u8 },
73}
74
75impl From<u8> for BlobType {
76    fn from(raw: u8) -> Self {
77        match raw {
78            BLOB_TYPE_DATA => BlobType::Data,
79            BLOB_TYPE_LAST_BRANCH => BlobType::LastBranch,
80            BLOB_TYPE_PERFETTO => BlobType::Perfetto,
81            raw => BlobType::Unknown { raw },
82        }
83    }
84}
85
86trace_header! {
87    BlobHeader (BLOB_RECORD_TYPE) {
88        u16, name_ref: 16, 31;
89        u16, payload_len: 32, 36;
90        u8, blob_format_type: 48, 55;
91    }
92}
93
94#[derive(Clone, Debug, PartialEq)]
95pub struct LargeBlobRecord {
96    pub provider: Option<Provider>,
97    pub ty: BlobType,
98    pub category: FlyStr,
99    pub name: FlyStr,
100    pub bytes: Vec<u8>,
101    pub metadata: Option<LargeBlobMetadata>,
102}
103
104impl LargeBlobRecord {
105    pub(super) fn resolve(ctx: &mut ResolveCtx, raw: RawLargeBlobRecord<'_>) -> Option<Self> {
106        let (bytes, metadata) = match raw.payload {
107            RawLargeBlobPayload::BytesAndMetadata(bytes, metadata) => {
108                (bytes.to_owned(), Some(LargeBlobMetadata::resolve(ctx, metadata)))
109            }
110            RawLargeBlobPayload::BytesOnly(bytes) => (bytes.to_owned(), None),
111            RawLargeBlobPayload::UnknownLargeBlobType { raw_type, .. } => {
112                ctx.add_warning(ParseWarning::UnknownLargeBlobType(raw_type));
113                return None;
114            }
115        };
116        Some(Self {
117            provider: ctx.current_provider(),
118            ty: raw.ty,
119            category: ctx.resolve_str(raw.category),
120            name: ctx.resolve_str(raw.name),
121            bytes,
122            metadata,
123        })
124    }
125}
126
127#[derive(Debug, PartialEq)]
128pub(super) struct RawLargeBlobRecord<'a> {
129    ty: BlobType,
130    category: StringRef<'a>,
131    name: StringRef<'a>,
132    payload: RawLargeBlobPayload<'a>,
133}
134
135#[derive(Debug, PartialEq)]
136enum RawLargeBlobPayload<'a> {
137    BytesOnly(&'a [u8]),
138    BytesAndMetadata(&'a [u8], RawLargeBlobMetadata<'a>),
139    UnknownLargeBlobType { raw_type: u8, remaining_bytes: &'a [u8] },
140}
141
142impl<'a> RawLargeBlobRecord<'a> {
143    pub(super) fn parse(buf: &'a [u8]) -> ParseResult<'a, Self> {
144        let (buf, header) = LargeBlobHeader::parse(buf)?;
145        let ty = BlobType::from(header.blob_format_type());
146        let (rem, payload) = header.take_payload(buf)?;
147        let (payload, format_header) =
148            nom::combinator::map(le_u64, LargeBlobFormatHeader).parse(payload)?;
149        let (payload, category) = StringRef::parse(format_header.category_ref(), payload)?;
150        let (payload, name) = StringRef::parse(format_header.name_ref(), payload)?;
151        let (payload, metadata) = match header.large_record_type() {
152            LARGE_BLOB_WITH_METADATA_TYPE => {
153                let (payload, ticks) = Ticks::parse(payload)?;
154                let (payload, process) = ProcessRef::parse(format_header.thread_ref(), payload)?;
155                let (payload, thread) = ThreadRef::parse(format_header.thread_ref(), payload)?;
156                let (payload, args) = RawArg::parse_n(format_header.num_args(), payload)?;
157                (payload, Some(RawLargeBlobMetadata { ticks, process, thread, args }))
158            }
159            LARGE_BLOB_NO_METADATA_TYPE => (payload, None),
160            unknown => {
161                // Without knowing which metadata type we have, we don't know where the blob size
162                // and bytes payload start, so we'll put them all together.
163                let payload = RawLargeBlobPayload::UnknownLargeBlobType {
164                    raw_type: unknown,
165                    remaining_bytes: payload,
166                };
167                return Ok((rem, Self { ty, category, name, payload }));
168            }
169        };
170        let (payload, blob_size) = le_u64(payload)?;
171
172        let (empty, bytes) =
173            all_consuming(|p| take_n_padded(blob_size as usize, p)).parse(payload)?;
174        assert_eq!(empty, [] as [u8; 0], "all_consuming must not return any trailing bytes");
175
176        let payload = if let Some(metadata) = metadata {
177            RawLargeBlobPayload::BytesAndMetadata(bytes, metadata)
178        } else {
179            RawLargeBlobPayload::BytesOnly(bytes)
180        };
181
182        Ok((rem, Self { ty, category, name, payload }))
183    }
184}
185
186#[derive(Clone, Debug, PartialEq)]
187pub struct LargeBlobMetadata {
188    pub timestamp: i64,
189    pub process: ProcessKoid,
190    pub thread: ThreadKoid,
191    pub args: Vec<Arg>,
192}
193
194impl LargeBlobMetadata {
195    fn resolve(ctx: &mut ResolveCtx, raw: RawLargeBlobMetadata<'_>) -> Self {
196        Self {
197            timestamp: ctx.resolve_ticks(raw.ticks),
198            process: ctx.resolve_process(raw.process),
199            thread: ctx.resolve_thread(raw.thread),
200            args: Arg::resolve_n(ctx, raw.args),
201        }
202    }
203}
204
205#[derive(Debug, PartialEq)]
206pub(super) struct RawLargeBlobMetadata<'a> {
207    ticks: Ticks,
208    process: ProcessRef,
209    thread: ThreadRef,
210    args: Vec<RawArg<'a>>,
211}
212
213trace_header! {
214    LargeBlobHeader (max_size_bit: 35) (u32) (LARGE_RECORD_TYPE) {
215        u8, large_record_type: 36, 39;
216        u8, blob_format_type: 40, 43;
217    }
218}
219
220bitfield::bitfield! {
221    struct LargeBlobFormatHeader(u64);
222    impl Debug;
223
224    u16, category_ref, set_category_ref: 15, 0;
225    u16, name_ref, set_name_ref: 31, 16;
226
227    // These are only meaningful if large_record_type includes metadata.
228    u8, num_args, set_num_args: 35, 32;
229    u8, thread_ref, set_thread_ref: 43, 36;
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235    use crate::fxt_builder::FxtBuilder;
236    use crate::RawTraceRecord;
237    use std::num::{NonZeroU16, NonZeroU8};
238
239    #[test]
240    fn blob_name_index() {
241        let payload = &[
242            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
243        ][..];
244
245        let mut header = BlobHeader::empty();
246        header.set_name_ref(15);
247        header.set_payload_len(payload.len() as u16);
248        header.set_blob_format_type(BLOB_TYPE_DATA);
249
250        assert_parses_to_record!(
251            FxtBuilder::new(header).atom(payload).build(),
252            RawTraceRecord::Blob(RawBlobRecord {
253                name: StringRef::Index(NonZeroU16::new(15).unwrap()),
254                ty: BlobType::Data,
255                bytes: &payload,
256            }),
257        );
258    }
259
260    #[test]
261    fn blob_name_inline() {
262        let name = "foo_blob";
263        let payload = &[
264            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
265            25,
266        ][..];
267
268        let mut header = BlobHeader::empty();
269        header.set_name_ref(name.len() as u16 | crate::string::STRING_REF_INLINE_BIT);
270        header.set_payload_len(payload.len() as u16);
271        header.set_blob_format_type(BLOB_TYPE_PERFETTO);
272
273        assert_parses_to_record!(
274            FxtBuilder::new(header).atom(name).atom(payload).build(),
275            RawTraceRecord::Blob(RawBlobRecord {
276                name: StringRef::Inline(name),
277                ty: BlobType::Perfetto,
278                bytes: payload,
279            }),
280        );
281    }
282
283    #[test]
284    fn large_blob_no_metadata() {
285        let payload = &[
286            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
287            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
288        ][..];
289
290        let mut header = LargeBlobHeader::empty();
291        header.set_blob_format_type(BLOB_TYPE_DATA);
292        header.set_large_record_type(LARGE_BLOB_NO_METADATA_TYPE);
293
294        let mut format_header = LargeBlobFormatHeader(0);
295        format_header.set_category_ref(7);
296        format_header.set_name_ref(8);
297
298        assert_parses_to_record!(
299            FxtBuilder::new(header)
300                .atom(format_header.0.to_le_bytes())
301                .atom(payload.len().to_le_bytes())
302                .atom(payload)
303                .build(),
304            RawTraceRecord::LargeBlob(RawLargeBlobRecord {
305                ty: BlobType::Data,
306                category: StringRef::Index(NonZeroU16::new(7).unwrap()),
307                name: StringRef::Index(NonZeroU16::new(8).unwrap()),
308                payload: RawLargeBlobPayload::BytesOnly(payload),
309            })
310        );
311    }
312
313    #[test]
314    fn large_blob_with_metadata() {
315        let payload = &[
316            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
317            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
318        ][..];
319
320        let mut header = LargeBlobHeader::empty();
321        header.set_blob_format_type(BLOB_TYPE_DATA);
322        header.set_large_record_type(LARGE_BLOB_WITH_METADATA_TYPE);
323
324        let mut format_header = LargeBlobFormatHeader(0);
325        format_header.set_category_ref(7);
326        format_header.set_name_ref(8);
327        format_header.set_thread_ref(31);
328        format_header.set_num_args(1);
329
330        let mut arg_header = crate::args::U32Header::empty();
331        arg_header.set_name_ref(10);
332        arg_header.set_value(100);
333
334        assert_parses_to_record!(
335            FxtBuilder::new(header)
336                .atom(format_header.0.to_le_bytes())
337                .atom(1024u64.to_le_bytes())
338                .atom(FxtBuilder::new(arg_header).build())
339                .atom(payload.len().to_le_bytes())
340                .atom(payload)
341                .build(),
342            RawTraceRecord::LargeBlob(RawLargeBlobRecord {
343                ty: BlobType::Data,
344                category: StringRef::Index(NonZeroU16::new(7).unwrap()),
345                name: StringRef::Index(NonZeroU16::new(8).unwrap()),
346                payload: RawLargeBlobPayload::BytesAndMetadata(
347                    payload,
348                    RawLargeBlobMetadata {
349                        ticks: Ticks(1024),
350                        process: ProcessRef::Index(NonZeroU8::new(31).unwrap()),
351                        thread: ThreadRef::Index(NonZeroU8::new(31).unwrap()),
352                        args: vec![RawArg {
353                            name: StringRef::Index(NonZeroU16::new(10).unwrap()),
354                            value: crate::args::RawArgValue::Unsigned32(100),
355                        }],
356                    },
357                )
358            })
359        );
360    }
361}