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