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, 46;
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_long_blob() {
262        let payload = &[0xEFu8; 1024][..];
263
264        let mut header = BlobHeader::empty();
265        header.set_name_ref(15);
266        header.set_payload_len(payload.len() as u16);
267        header.set_blob_format_type(BLOB_TYPE_DATA);
268
269        assert_parses_to_record!(
270            FxtBuilder::new(header).atom(payload).build(),
271            RawTraceRecord::Blob(RawBlobRecord {
272                name: StringRef::Index(NonZeroU16::new(15).unwrap()),
273                ty: BlobType::Data,
274                bytes: &payload,
275            }),
276        );
277    }
278
279    #[test]
280    fn blob_name_inline() {
281        let name = "foo_blob";
282        let payload = &[
283            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
284            25,
285        ][..];
286
287        let mut header = BlobHeader::empty();
288        header.set_name_ref(name.len() as u16 | crate::string::STRING_REF_INLINE_BIT);
289        header.set_payload_len(payload.len() as u16);
290        header.set_blob_format_type(BLOB_TYPE_PERFETTO);
291
292        assert_parses_to_record!(
293            FxtBuilder::new(header).atom(name).atom(payload).build(),
294            RawTraceRecord::Blob(RawBlobRecord {
295                name: StringRef::Inline(name),
296                ty: BlobType::Perfetto,
297                bytes: payload,
298            }),
299        );
300    }
301
302    #[test]
303    fn large_blob_no_metadata() {
304        let payload = &[
305            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
306            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
307        ][..];
308
309        let mut header = LargeBlobHeader::empty();
310        header.set_blob_format_type(BLOB_TYPE_DATA);
311        header.set_large_record_type(LARGE_BLOB_NO_METADATA_TYPE);
312
313        let mut format_header = LargeBlobFormatHeader(0);
314        format_header.set_category_ref(7);
315        format_header.set_name_ref(8);
316
317        assert_parses_to_record!(
318            FxtBuilder::new(header)
319                .atom(format_header.0.to_le_bytes())
320                .atom(payload.len().to_le_bytes())
321                .atom(payload)
322                .build(),
323            RawTraceRecord::LargeBlob(RawLargeBlobRecord {
324                ty: BlobType::Data,
325                category: StringRef::Index(NonZeroU16::new(7).unwrap()),
326                name: StringRef::Index(NonZeroU16::new(8).unwrap()),
327                payload: RawLargeBlobPayload::BytesOnly(payload),
328            })
329        );
330    }
331
332    #[test]
333    fn large_blob_with_metadata() {
334        let payload = &[
335            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
336            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
337        ][..];
338
339        let mut header = LargeBlobHeader::empty();
340        header.set_blob_format_type(BLOB_TYPE_DATA);
341        header.set_large_record_type(LARGE_BLOB_WITH_METADATA_TYPE);
342
343        let mut format_header = LargeBlobFormatHeader(0);
344        format_header.set_category_ref(7);
345        format_header.set_name_ref(8);
346        format_header.set_thread_ref(31);
347        format_header.set_num_args(1);
348
349        let mut arg_header = crate::args::U32Header::empty();
350        arg_header.set_name_ref(10);
351        arg_header.set_value(100);
352
353        assert_parses_to_record!(
354            FxtBuilder::new(header)
355                .atom(format_header.0.to_le_bytes())
356                .atom(1024u64.to_le_bytes())
357                .atom(FxtBuilder::new(arg_header).build())
358                .atom(payload.len().to_le_bytes())
359                .atom(payload)
360                .build(),
361            RawTraceRecord::LargeBlob(RawLargeBlobRecord {
362                ty: BlobType::Data,
363                category: StringRef::Index(NonZeroU16::new(7).unwrap()),
364                name: StringRef::Index(NonZeroU16::new(8).unwrap()),
365                payload: RawLargeBlobPayload::BytesAndMetadata(
366                    payload,
367                    RawLargeBlobMetadata {
368                        ticks: Ticks(1024),
369                        process: ProcessRef::Index(NonZeroU8::new(31).unwrap()),
370                        thread: ThreadRef::Index(NonZeroU8::new(31).unwrap()),
371                        args: vec![RawArg {
372                            name: StringRef::Index(NonZeroU16::new(10).unwrap()),
373                            value: crate::args::RawArgValue::Unsigned32(100),
374                        }],
375                    },
376                )
377            })
378        );
379    }
380}