Skip to main content

heapdump_snapshot/
snapshot.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 fidl::MonotonicInstant;
6use flex_fuchsia_memory_heapdump_client as fheapdump_client;
7use futures::StreamExt;
8use std::collections::{HashMap, HashSet};
9use std::rc::Rc;
10
11use crate::Error;
12
13/// Contains a snapshot along with metadata from its header.
14#[derive(Debug)]
15pub struct SnapshotWithHeader {
16    pub process_name: String,
17    pub process_koid: u64,
18    pub snapshot: Snapshot,
19}
20
21/// Contains all the data received over a `SnapshotReceiver` channel.
22#[derive(Debug)]
23pub struct Snapshot {
24    /// All the live allocations in the analyzed process, indexed by memory address.
25    pub allocations: Vec<Allocation>,
26
27    /// All the executable memory regions in the analyzed process, indexed by start address.
28    pub executable_regions: HashMap<u64, ExecutableRegion>,
29}
30
31/// Information about one or more allocated memory blocks.
32#[derive(Debug)]
33pub struct Allocation {
34    pub address: Option<u64>,
35
36    /// Number of allocations that have been aggregated into this `Allocation` instance.
37    pub count: u64,
38
39    /// Total size, in bytes.
40    pub size: u64,
41
42    /// Allocating thread.
43    pub thread_info: Option<Rc<ThreadInfo>>,
44
45    /// Stack trace of the allocation site.
46    pub stack_trace: Rc<StackTrace>,
47
48    /// Allocation timestamp, in nanoseconds.
49    pub timestamp: Option<MonotonicInstant>,
50
51    /// Memory dump of this block's contents.
52    pub contents: Option<Vec<u8>>,
53}
54
55/// A stack trace.
56#[derive(Debug)]
57pub struct StackTrace {
58    /// Code addresses at each call frame. The first entry corresponds to the leaf call.
59    pub program_addresses: Vec<u64>,
60}
61
62/// A memory region containing code loaded from an ELF file.
63#[derive(Debug)]
64pub struct ExecutableRegion {
65    /// Region name for human consumption (usually either the ELF soname or the VMO name), if known.
66    pub name: String,
67
68    /// Region size, in bytes.
69    pub size: u64,
70
71    /// The corresponding offset in the ELF file.
72    pub file_offset: u64,
73
74    /// The corresponding relative address in the ELF file.
75    ///
76    /// Note: this field is not populated if the snapshot was generated by a build from before the
77    /// corresponding FIDL field was introduced.
78    pub vaddr: Option<u64>,
79
80    /// The Build ID of the ELF file.
81    pub build_id: Vec<u8>,
82}
83
84/// Information identifying a specific thread.
85#[derive(Debug, PartialEq)]
86pub struct ThreadInfo {
87    /// The thread's koid.
88    pub koid: zx_types::zx_koid_t,
89
90    /// The thread's name.
91    pub name: String,
92}
93
94/// Gets the value of a field in a FIDL table as a `Result<T, Error>`.
95///
96/// An `Err(Error::MissingField { .. })` is returned if the field's value is `None`.
97///
98/// Usage: `read_field!(container_expression => ContainerType, field_name)`
99///
100/// # Example
101///
102/// ```
103/// struct MyFidlTable { field: Option<u32>, .. }
104/// let table = MyFidlTable { field: Some(44), .. };
105///
106/// let val = read_field!(table => MyFidlTable, field)?;
107/// ```
108macro_rules! read_field {
109    ($e:expr => $c:ident, $f:ident) => {
110        $e.$f.ok_or(Error::MissingField {
111            container: std::stringify!($c),
112            field: std::stringify!($f),
113        })
114    };
115}
116
117impl Snapshot {
118    /// Receives a snapshot over a `SnapshotReceiver` channel and reassembles it.
119    pub async fn receive_single_from(
120        mut stream: fheapdump_client::SnapshotReceiverRequestStream,
121    ) -> Result<Snapshot, Error> {
122        Snapshot::receive_inner(&mut stream).await
123    }
124
125    /// Receives multiple header-prefixed snapshots over a `SnapshotReceiver` channel and
126    /// reassemble them.
127    #[cfg(fuchsia_api_level_at_least = "HEAD")]
128    pub async fn receive_multi_from(
129        mut stream: fheapdump_client::SnapshotReceiverRequestStream,
130    ) -> Result<Vec<SnapshotWithHeader>, Error> {
131        let mut snapshots = vec![];
132        loop {
133            // Wait for a batch of elements containing either just a header element or an empty
134            // batch (to signal the end of the stream).
135            match stream.next().await.transpose()? {
136                Some(fheapdump_client::SnapshotReceiverRequest::Batch { batch, responder }) => {
137                    match &batch[..] {
138                        [fheapdump_client::SnapshotElement::SnapshotHeader(header)] => {
139                            responder.send()?;
140
141                            // Receive the actual snapshot.
142                            let snapshot = Snapshot::receive_inner(&mut stream).await?;
143
144                            let header = header.clone();
145                            snapshots.push(SnapshotWithHeader {
146                                process_name: read_field!(header => SnapshotHeader, process_name)?,
147                                process_koid: read_field!(header => SnapshotHeader, process_koid)?,
148                                snapshot,
149                            });
150                        }
151                        [] => {
152                            responder.send()?;
153                            return Ok(snapshots);
154                        }
155                        _ => return Err(Error::HeaderExpected),
156                    }
157                }
158                Some(fheapdump_client::SnapshotReceiverRequest::ReportError {
159                    error,
160                    responder,
161                }) => {
162                    let _ = responder.send(); // Ignore the result of the acknowledgment.
163                    return Err(Error::CollectorError(error));
164                }
165                None => return Err(Error::UnexpectedEndOfStream),
166            };
167        }
168    }
169
170    async fn receive_inner(
171        stream: &mut fheapdump_client::SnapshotReceiverRequestStream,
172    ) -> Result<Snapshot, Error> {
173        struct AllocationValue {
174            address: Option<u64>,
175            count: u64,
176            size: u64,
177            thread_info_key: Option<u64>,
178            stack_trace_key: u64,
179            timestamp: Option<MonotonicInstant>,
180        }
181        let mut allocation_addresses: HashSet<u64> = HashSet::new();
182        let mut allocations: Vec<AllocationValue> = vec![];
183        let mut thread_infos: HashMap<u64, Rc<ThreadInfo>> = HashMap::new();
184        let mut stack_traces: HashMap<u64, Vec<u64>> = HashMap::new();
185        let mut executable_regions: HashMap<u64, ExecutableRegion> = HashMap::new();
186        let mut contents: HashMap<u64, Vec<u8>> = HashMap::new();
187
188        loop {
189            // Wait for the next batch of elements.
190            let batch = match stream.next().await.transpose()? {
191                Some(fheapdump_client::SnapshotReceiverRequest::Batch { batch, responder }) => {
192                    // Send acknowledgment as quickly as possible, then keep processing the received batch.
193                    responder.send()?;
194                    batch
195                }
196                Some(fheapdump_client::SnapshotReceiverRequest::ReportError {
197                    error,
198                    responder,
199                }) => {
200                    let _ = responder.send(); // Ignore the result of the acknowledgment.
201                    return Err(Error::CollectorError(error));
202                }
203                None => return Err(Error::UnexpectedEndOfStream),
204            };
205
206            // Process data. An empty batch signals the end of the stream.
207            if !batch.is_empty() {
208                for element in batch {
209                    match element {
210                        fheapdump_client::SnapshotElement::Allocation(allocation) => {
211                            if let Some(address) = allocation.address {
212                                if !allocation_addresses.insert(address) {
213                                    return Err(Error::ConflictingElement {
214                                        element_type: "Allocation",
215                                    });
216                                }
217                            }
218
219                            #[cfg(not(fuchsia_api_level_at_least = "29"))]
220                            let count = 1;
221                            #[cfg(fuchsia_api_level_at_least = "29")]
222                            let count = allocation.count.unwrap_or(1);
223
224                            let size = read_field!(allocation => Allocation, size)?;
225                            let stack_trace_key =
226                                read_field!(allocation => Allocation, stack_trace_key)?;
227                            allocations.push(AllocationValue {
228                                address: allocation.address,
229                                count,
230                                size,
231                                thread_info_key: allocation.thread_info_key,
232                                stack_trace_key,
233                                timestamp: allocation.timestamp,
234                            });
235                        }
236                        fheapdump_client::SnapshotElement::StackTrace(stack_trace) => {
237                            let stack_trace_key =
238                                read_field!(stack_trace => StackTrace, stack_trace_key)?;
239                            let mut program_addresses =
240                                read_field!(stack_trace => StackTrace, program_addresses)?;
241                            stack_traces
242                                .entry(stack_trace_key)
243                                .or_default()
244                                .append(&mut program_addresses);
245                        }
246                        fheapdump_client::SnapshotElement::ThreadInfo(thread_info) => {
247                            let thread_info_key =
248                                read_field!(thread_info => ThreadInfo, thread_info_key)?;
249                            let koid = read_field!(thread_info => ThreadInfo, koid)?;
250                            let name = read_field!(thread_info => ThreadInfo, name)?;
251                            if thread_infos
252                                .insert(thread_info_key, Rc::new(ThreadInfo { koid, name }))
253                                .is_some()
254                            {
255                                return Err(Error::ConflictingElement {
256                                    element_type: "ThreadInfo",
257                                });
258                            }
259                        }
260                        fheapdump_client::SnapshotElement::ExecutableRegion(region) => {
261                            let address = read_field!(region => ExecutableRegion, address)?;
262                            #[cfg(fuchsia_api_level_at_least = "27")]
263                            let name = region.name.unwrap_or_else(|| String::new());
264                            #[cfg(not(fuchsia_api_level_at_least = "27"))]
265                            let name = String::new();
266                            let size = read_field!(region => ExecutableRegion, size)?;
267                            let file_offset = read_field!(region => ExecutableRegion, file_offset)?;
268                            #[cfg(fuchsia_api_level_at_least = "27")]
269                            let vaddr = region.vaddr;
270                            #[cfg(not(fuchsia_api_level_at_least = "27"))]
271                            let vaddr = None;
272                            let build_id = read_field!(region => ExecutableRegion, build_id)?.value;
273                            let region =
274                                ExecutableRegion { name, size, file_offset, vaddr, build_id };
275                            if executable_regions.insert(address, region).is_some() {
276                                return Err(Error::ConflictingElement {
277                                    element_type: "ExecutableRegion",
278                                });
279                            }
280                        }
281                        fheapdump_client::SnapshotElement::BlockContents(block_contents) => {
282                            let address = read_field!(block_contents => BlockContents, address)?;
283                            let mut chunk = read_field!(block_contents => BlockContents, contents)?;
284                            contents.entry(address).or_default().append(&mut chunk);
285                        }
286                        _ => return Err(Error::UnexpectedElementType),
287                    }
288                }
289            } else {
290                // We are at the end of the stream. Convert to the final types and resolve
291                // cross-references.
292                let final_stack_traces: HashMap<u64, Rc<StackTrace>> = stack_traces
293                    .into_iter()
294                    .map(|(key, program_addresses)| {
295                        (key, Rc::new(StackTrace { program_addresses }))
296                    })
297                    .collect();
298                let mut final_allocations = vec![];
299                for AllocationValue {
300                    address,
301                    count,
302                    size,
303                    thread_info_key,
304                    stack_trace_key,
305                    timestamp,
306                } in allocations
307                {
308                    let thread_info = match thread_info_key {
309                        Some(key) => Some(
310                            thread_infos
311                                .get(&key)
312                                .ok_or(Error::InvalidCrossReference { element_type: "ThreadInfo" })?
313                                .clone(),
314                        ),
315                        None => None,
316                    };
317                    let stack_trace = final_stack_traces
318                        .get(&stack_trace_key)
319                        .ok_or(Error::InvalidCrossReference { element_type: "StackTrace" })?
320                        .clone();
321                    let contents = address.and_then(|address| contents.remove(&address));
322                    if let Some(data) = &contents {
323                        if data.len() as u64 != size {
324                            return Err(Error::ConflictingElement {
325                                element_type: "BlockContents",
326                            });
327                        }
328                    }
329                    final_allocations.push(Allocation {
330                        address,
331                        count,
332                        size,
333                        thread_info,
334                        stack_trace,
335                        timestamp,
336                        contents,
337                    });
338                }
339
340                return Ok(Snapshot { allocations: final_allocations, executable_regions });
341            }
342        }
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use assert_matches::assert_matches;
350    use fuchsia_async as fasync;
351    use test_case::test_case;
352
353    // Constants used by some of the tests below:
354    const FAKE_ALLOCATION_1_ADDRESS: u64 = 1234;
355    const FAKE_ALLOCATION_1_SIZE: u64 = 8;
356    const FAKE_ALLOCATION_1_TIMESTAMP: MonotonicInstant = MonotonicInstant::from_nanos(888888888);
357    const FAKE_ALLOCATION_1_CONTENTS: [u8; FAKE_ALLOCATION_1_SIZE as usize] = *b"12345678";
358    const FAKE_ALLOCATION_2_ADDRESS: u64 = 5678;
359    const FAKE_ALLOCATION_2_SIZE: u64 = 4;
360    const FAKE_ALLOCATION_2_TIMESTAMP: MonotonicInstant = MonotonicInstant::from_nanos(-777777777); // test negative value too
361    const FAKE_THREAD_1_KOID: u64 = 1212;
362    const FAKE_THREAD_1_NAME: &str = "fake-thread-1-name";
363    const FAKE_THREAD_1_KEY: u64 = 4567;
364    const FAKE_THREAD_2_KOID: u64 = 1213;
365    const FAKE_THREAD_2_NAME: &str = "fake-thread-2-name";
366    const FAKE_THREAD_2_KEY: u64 = 7654;
367    const FAKE_STACK_TRACE_1_ADDRESSES: [u64; 6] = [11111, 22222, 33333, 22222, 44444, 55555];
368    const FAKE_STACK_TRACE_1_KEY: u64 = 9876;
369    const FAKE_STACK_TRACE_2_ADDRESSES: [u64; 4] = [11111, 22222, 11111, 66666];
370    const FAKE_STACK_TRACE_2_KEY: u64 = 6789;
371    const FAKE_REGION_1_ADDRESS: u64 = 0x10000000;
372    const FAKE_REGION_1_NAME: &str = "region-1";
373    const FAKE_REGION_1_SIZE: u64 = 0x80000;
374    const FAKE_REGION_1_FILE_OFFSET: u64 = 0x1000;
375    const FAKE_REGION_1_VADDR: u64 = 0x3000;
376    const FAKE_REGION_1_BUILD_ID: &[u8] = &[0xaa; 20];
377    const FAKE_REGION_2_ADDRESS: u64 = 0x7654300000;
378    const FAKE_REGION_2_SIZE: u64 = 0x200000;
379    const FAKE_REGION_2_FILE_OFFSET: u64 = 0x2000;
380    const FAKE_REGION_2_BUILD_ID: &[u8] = &[0x55; 32];
381
382    #[fasync::run_singlethreaded(test)]
383    async fn test_empty() {
384        #[cfg(feature = "fdomain")]
385        let client = fdomain_local::local_client_empty();
386        #[cfg(not(feature = "fdomain"))]
387        let client = fidl::endpoints::ZirconClient;
388        let (receiver_proxy, receiver_stream) =
389            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
390        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
391
392        // Send the end of stream marker.
393        let fut = receiver_proxy.batch(&[]);
394        fut.await.unwrap();
395
396        // Receive the snapshot we just transmitted and verify that it is empty.
397        let received_snapshot = receive_worker.await.unwrap();
398        assert!(received_snapshot.allocations.is_empty());
399        assert!(received_snapshot.executable_regions.is_empty());
400    }
401
402    #[fasync::run_singlethreaded(test)]
403    async fn test_one_batch() {
404        #[cfg(feature = "fdomain")]
405        let client = fdomain_local::local_client_empty();
406        #[cfg(not(feature = "fdomain"))]
407        let client = fidl::endpoints::ZirconClient;
408        let (receiver_proxy, receiver_stream) =
409            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
410        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
411
412        // Send a batch containing two allocations - whose threads, stack traces and contents can be
413        // listed before or after the allocation(s) that reference them - and two executable
414        // regions.
415        let fut = receiver_proxy.batch(&[
416            fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
417                address: Some(FAKE_ALLOCATION_1_ADDRESS),
418                contents: Some(FAKE_ALLOCATION_1_CONTENTS.to_vec()),
419                ..Default::default()
420            }),
421            fheapdump_client::SnapshotElement::ExecutableRegion(
422                fheapdump_client::ExecutableRegion {
423                    address: Some(FAKE_REGION_1_ADDRESS),
424                    name: Some(FAKE_REGION_1_NAME.to_string()),
425                    size: Some(FAKE_REGION_1_SIZE),
426                    file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
427                    vaddr: Some(FAKE_REGION_1_VADDR),
428                    build_id: Some(fheapdump_client::BuildId {
429                        value: FAKE_REGION_1_BUILD_ID.to_vec(),
430                    }),
431                    ..Default::default()
432                },
433            ),
434            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
435                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
436                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
437                ..Default::default()
438            }),
439            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
440                address: Some(FAKE_ALLOCATION_1_ADDRESS),
441                size: Some(FAKE_ALLOCATION_1_SIZE),
442                thread_info_key: Some(FAKE_THREAD_1_KEY),
443                stack_trace_key: Some(FAKE_STACK_TRACE_2_KEY),
444                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
445                ..Default::default()
446            }),
447            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
448                thread_info_key: Some(FAKE_THREAD_1_KEY),
449                koid: Some(FAKE_THREAD_1_KOID),
450                name: Some(FAKE_THREAD_1_NAME.to_string()),
451                ..Default::default()
452            }),
453            fheapdump_client::SnapshotElement::ExecutableRegion(
454                fheapdump_client::ExecutableRegion {
455                    address: Some(FAKE_REGION_2_ADDRESS),
456                    size: Some(FAKE_REGION_2_SIZE),
457                    file_offset: Some(FAKE_REGION_2_FILE_OFFSET),
458                    build_id: Some(fheapdump_client::BuildId {
459                        value: FAKE_REGION_2_BUILD_ID.to_vec(),
460                    }),
461                    ..Default::default()
462                },
463            ),
464            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
465                thread_info_key: Some(FAKE_THREAD_2_KEY),
466                koid: Some(FAKE_THREAD_2_KOID),
467                name: Some(FAKE_THREAD_2_NAME.to_string()),
468                ..Default::default()
469            }),
470            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
471                address: Some(FAKE_ALLOCATION_2_ADDRESS),
472                size: Some(FAKE_ALLOCATION_2_SIZE),
473                thread_info_key: Some(FAKE_THREAD_2_KEY),
474                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
475                timestamp: Some(FAKE_ALLOCATION_2_TIMESTAMP),
476                ..Default::default()
477            }),
478            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
479                stack_trace_key: Some(FAKE_STACK_TRACE_2_KEY),
480                program_addresses: Some(FAKE_STACK_TRACE_2_ADDRESSES.to_vec()),
481                ..Default::default()
482            }),
483        ]);
484        fut.await.unwrap();
485
486        // Send the end of stream marker.
487        let fut = receiver_proxy.batch(&[]);
488        fut.await.unwrap();
489
490        // Receive the snapshot we just transmitted and verify its contents.
491        let mut received_snapshot = receive_worker.await.unwrap();
492        let allocation1 = received_snapshot.allocations.swap_remove(
493            received_snapshot
494                .allocations
495                .iter()
496                .position(|alloc| alloc.address == Some(FAKE_ALLOCATION_1_ADDRESS))
497                .unwrap(),
498        );
499        assert_eq!(allocation1.size, FAKE_ALLOCATION_1_SIZE);
500        assert_eq!(
501            allocation1.thread_info,
502            Some(Rc::new(ThreadInfo {
503                koid: FAKE_THREAD_1_KOID,
504                name: FAKE_THREAD_1_NAME.to_owned()
505            }))
506        );
507        assert_eq!(allocation1.stack_trace.program_addresses, FAKE_STACK_TRACE_2_ADDRESSES);
508        assert_eq!(allocation1.timestamp, Some(FAKE_ALLOCATION_1_TIMESTAMP));
509        assert_eq!(
510            allocation1.contents.as_ref().expect("contents must be set"),
511            &FAKE_ALLOCATION_1_CONTENTS.to_vec()
512        );
513        let allocation2 = received_snapshot.allocations.swap_remove(
514            received_snapshot
515                .allocations
516                .iter()
517                .position(|alloc| alloc.address == Some(FAKE_ALLOCATION_2_ADDRESS))
518                .unwrap(),
519        );
520        assert_eq!(allocation2.size, FAKE_ALLOCATION_2_SIZE);
521        assert_eq!(
522            allocation2.thread_info,
523            Some(Rc::new(ThreadInfo {
524                koid: FAKE_THREAD_2_KOID,
525                name: FAKE_THREAD_2_NAME.to_owned()
526            }))
527        );
528        assert_eq!(allocation2.stack_trace.program_addresses, FAKE_STACK_TRACE_1_ADDRESSES);
529        assert_eq!(allocation2.timestamp, Some(FAKE_ALLOCATION_2_TIMESTAMP));
530        assert_matches!(allocation2.contents, None, "no contents are sent for this allocation");
531        assert!(received_snapshot.allocations.is_empty(), "all the entries have been removed");
532        let region1 = received_snapshot.executable_regions.remove(&FAKE_REGION_1_ADDRESS).unwrap();
533        assert_eq!(region1.name, FAKE_REGION_1_NAME);
534        assert_eq!(region1.size, FAKE_REGION_1_SIZE);
535        assert_eq!(region1.file_offset, FAKE_REGION_1_FILE_OFFSET);
536        assert_eq!(region1.vaddr.unwrap(), FAKE_REGION_1_VADDR);
537        assert_eq!(region1.build_id, FAKE_REGION_1_BUILD_ID);
538        let region2 = received_snapshot.executable_regions.remove(&FAKE_REGION_2_ADDRESS).unwrap();
539        assert_eq!(region2.size, FAKE_REGION_2_SIZE);
540        assert_eq!(region2.file_offset, FAKE_REGION_2_FILE_OFFSET);
541        assert_eq!(region2.build_id, FAKE_REGION_2_BUILD_ID);
542        assert!(received_snapshot.executable_regions.is_empty(), "all entries have been removed");
543    }
544
545    #[fasync::run_singlethreaded(test)]
546    async fn test_two_batches() {
547        #[cfg(feature = "fdomain")]
548        let client = fdomain_local::local_client_empty();
549        #[cfg(not(feature = "fdomain"))]
550        let client = fidl::endpoints::ZirconClient;
551        let (receiver_proxy, receiver_stream) =
552            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
553        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
554
555        // Send a first batch.
556        let fut = receiver_proxy.batch(&[
557            fheapdump_client::SnapshotElement::ExecutableRegion(
558                fheapdump_client::ExecutableRegion {
559                    address: Some(FAKE_REGION_2_ADDRESS),
560                    size: Some(FAKE_REGION_2_SIZE),
561                    file_offset: Some(FAKE_REGION_2_FILE_OFFSET),
562                    build_id: Some(fheapdump_client::BuildId {
563                        value: FAKE_REGION_2_BUILD_ID.to_vec(),
564                    }),
565                    ..Default::default()
566                },
567            ),
568            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
569                address: Some(FAKE_ALLOCATION_1_ADDRESS),
570                size: Some(FAKE_ALLOCATION_1_SIZE),
571                thread_info_key: Some(FAKE_THREAD_1_KEY),
572                stack_trace_key: Some(FAKE_STACK_TRACE_2_KEY),
573                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
574                ..Default::default()
575            }),
576            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
577                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
578                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
579                ..Default::default()
580            }),
581            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
582                thread_info_key: Some(FAKE_THREAD_2_KEY),
583                koid: Some(FAKE_THREAD_2_KOID),
584                name: Some(FAKE_THREAD_2_NAME.to_string()),
585                ..Default::default()
586            }),
587        ]);
588        fut.await.unwrap();
589
590        // Send another batch.
591        let fut = receiver_proxy.batch(&[
592            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
593                thread_info_key: Some(FAKE_THREAD_1_KEY),
594                koid: Some(FAKE_THREAD_1_KOID),
595                name: Some(FAKE_THREAD_1_NAME.to_string()),
596                ..Default::default()
597            }),
598            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
599                address: Some(FAKE_ALLOCATION_2_ADDRESS),
600                size: Some(FAKE_ALLOCATION_2_SIZE),
601                thread_info_key: Some(FAKE_THREAD_2_KEY),
602                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
603                timestamp: Some(FAKE_ALLOCATION_2_TIMESTAMP),
604                ..Default::default()
605            }),
606            fheapdump_client::SnapshotElement::ExecutableRegion(
607                fheapdump_client::ExecutableRegion {
608                    address: Some(FAKE_REGION_1_ADDRESS),
609                    name: Some(FAKE_REGION_1_NAME.to_string()),
610                    size: Some(FAKE_REGION_1_SIZE),
611                    file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
612                    vaddr: Some(FAKE_REGION_1_VADDR),
613                    build_id: Some(fheapdump_client::BuildId {
614                        value: FAKE_REGION_1_BUILD_ID.to_vec(),
615                    }),
616                    ..Default::default()
617                },
618            ),
619            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
620                stack_trace_key: Some(FAKE_STACK_TRACE_2_KEY),
621                program_addresses: Some(FAKE_STACK_TRACE_2_ADDRESSES.to_vec()),
622                ..Default::default()
623            }),
624            fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
625                address: Some(FAKE_ALLOCATION_1_ADDRESS),
626                contents: Some(FAKE_ALLOCATION_1_CONTENTS.to_vec()),
627                ..Default::default()
628            }),
629        ]);
630        fut.await.unwrap();
631
632        // Send the end of stream marker.
633        let fut = receiver_proxy.batch(&[]);
634        fut.await.unwrap();
635
636        // Receive the snapshot we just transmitted and verify its contents.
637        let mut received_snapshot = receive_worker.await.unwrap();
638        let allocation1 = received_snapshot.allocations.swap_remove(
639            received_snapshot
640                .allocations
641                .iter()
642                .position(|alloc| alloc.address == Some(FAKE_ALLOCATION_1_ADDRESS))
643                .unwrap(),
644        );
645        assert_eq!(allocation1.size, FAKE_ALLOCATION_1_SIZE);
646        assert_eq!(
647            allocation1.thread_info,
648            Some(Rc::new(ThreadInfo {
649                koid: FAKE_THREAD_1_KOID,
650                name: FAKE_THREAD_1_NAME.to_owned()
651            }))
652        );
653        assert_eq!(allocation1.stack_trace.program_addresses, FAKE_STACK_TRACE_2_ADDRESSES);
654        assert_eq!(allocation1.timestamp, Some(FAKE_ALLOCATION_1_TIMESTAMP));
655        assert_eq!(
656            allocation1.contents.as_ref().expect("contents must be set"),
657            &FAKE_ALLOCATION_1_CONTENTS.to_vec()
658        );
659        let allocation2 = received_snapshot.allocations.swap_remove(
660            received_snapshot
661                .allocations
662                .iter()
663                .position(|alloc| alloc.address == Some(FAKE_ALLOCATION_2_ADDRESS))
664                .unwrap(),
665        );
666        assert_eq!(allocation2.size, FAKE_ALLOCATION_2_SIZE);
667        assert_eq!(
668            allocation2.thread_info,
669            Some(Rc::new(ThreadInfo {
670                koid: FAKE_THREAD_2_KOID,
671                name: FAKE_THREAD_2_NAME.to_owned()
672            }))
673        );
674        assert_eq!(allocation2.stack_trace.program_addresses, FAKE_STACK_TRACE_1_ADDRESSES);
675        assert_eq!(allocation2.timestamp, Some(FAKE_ALLOCATION_2_TIMESTAMP));
676        assert_matches!(allocation2.contents, None, "no contents are sent for this allocation");
677        assert!(received_snapshot.allocations.is_empty(), "all the entries have been removed");
678        let region1 = received_snapshot.executable_regions.remove(&FAKE_REGION_1_ADDRESS).unwrap();
679        assert_eq!(region1.name, FAKE_REGION_1_NAME);
680        assert_eq!(region1.size, FAKE_REGION_1_SIZE);
681        assert_eq!(region1.file_offset, FAKE_REGION_1_FILE_OFFSET);
682        assert_eq!(region1.vaddr.unwrap(), FAKE_REGION_1_VADDR);
683        assert_eq!(region1.build_id, FAKE_REGION_1_BUILD_ID);
684        let region2 = received_snapshot.executable_regions.remove(&FAKE_REGION_2_ADDRESS).unwrap();
685        assert_eq!(region2.size, FAKE_REGION_2_SIZE);
686        assert_eq!(region2.file_offset, FAKE_REGION_2_FILE_OFFSET);
687        assert_eq!(region2.build_id, FAKE_REGION_2_BUILD_ID);
688        assert!(received_snapshot.executable_regions.is_empty(), "all entries have been removed");
689    }
690
691    #[test_case(|allocation| allocation.size = None => matches
692        Err(Error::MissingField { container: "Allocation", field: "size" }) ; "size")]
693    #[test_case(|allocation| allocation.stack_trace_key = None => matches
694        Err(Error::MissingField { container: "Allocation", field: "stack_trace_key" }) ; "stack_trace_key")]
695    #[test_case(|allocation| allocation.address = None => matches
696        Ok(_) ; "address_is_optional")]
697    #[test_case(|allocation| allocation.thread_info_key = None => matches
698        Ok(_) ; "thread_info_is_optional")]
699    #[test_case(|allocation| allocation.timestamp = None => matches
700        Ok(_) ; "timestamp_is_optional")]
701    #[test_case(|_| () /* if we do not set any field to None, the result should be Ok */ => matches
702        Ok(_) ; "success")]
703    #[fasync::run_singlethreaded(test)]
704    async fn test_allocation_required_fields(
705        set_one_field_to_none: fn(&mut fheapdump_client::Allocation),
706    ) -> Result<Snapshot, Error> {
707        #[cfg(feature = "fdomain")]
708        let client = fdomain_local::local_client_empty();
709        #[cfg(not(feature = "fdomain"))]
710        let client = fidl::endpoints::ZirconClient;
711        let (receiver_proxy, receiver_stream) =
712            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
713        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
714
715        // Start with an Allocation with all the required fields set.
716        let mut allocation = fheapdump_client::Allocation {
717            address: Some(FAKE_ALLOCATION_1_ADDRESS),
718            size: Some(FAKE_ALLOCATION_1_SIZE),
719            thread_info_key: Some(FAKE_THREAD_1_KEY),
720            stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
721            timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
722            ..Default::default()
723        };
724
725        // Set one of the fields to None, according to the case being tested.
726        set_one_field_to_none(&mut allocation);
727
728        // Send it to the SnapshotReceiver along with the thread info and stack trace it references.
729        let fut = receiver_proxy.batch(&[
730            fheapdump_client::SnapshotElement::Allocation(allocation),
731            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
732                thread_info_key: Some(FAKE_THREAD_1_KEY),
733                koid: Some(FAKE_THREAD_1_KOID),
734                name: Some(FAKE_THREAD_1_NAME.to_string()),
735                ..Default::default()
736            }),
737            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
738                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
739                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
740                ..Default::default()
741            }),
742        ]);
743        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
744
745        // Send the end of stream marker.
746        let fut = receiver_proxy.batch(&[]);
747        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
748
749        // Return the result.
750        receive_worker.await
751    }
752
753    #[test_case(|thread_info| thread_info.thread_info_key = None => matches
754        Err(Error::MissingField { container: "ThreadInfo", field: "thread_info_key" }) ; "thread_info_key")]
755    #[test_case(|thread_info| thread_info.koid = None => matches
756        Err(Error::MissingField { container: "ThreadInfo", field: "koid" }) ; "koid")]
757    #[test_case(|thread_info| thread_info.name = None => matches
758        Err(Error::MissingField { container: "ThreadInfo", field: "name" }) ; "name")]
759    #[test_case(|_| () /* if we do not set any field to None, the result should be Ok */ => matches
760        Ok(_) ; "success")]
761    #[fasync::run_singlethreaded(test)]
762    async fn test_thread_info_required_fields(
763        set_one_field_to_none: fn(&mut fheapdump_client::ThreadInfo),
764    ) -> Result<Snapshot, Error> {
765        #[cfg(feature = "fdomain")]
766        let client = fdomain_local::local_client_empty();
767        #[cfg(not(feature = "fdomain"))]
768        let client = fidl::endpoints::ZirconClient;
769        let (receiver_proxy, receiver_stream) =
770            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
771        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
772
773        // Start with a ThreadInfo with all the required fields set.
774        let mut thread_info = fheapdump_client::ThreadInfo {
775            thread_info_key: Some(FAKE_THREAD_1_KEY),
776            koid: Some(FAKE_THREAD_1_KOID),
777            name: Some(FAKE_THREAD_1_NAME.to_string()),
778            ..Default::default()
779        };
780
781        // Set one of the fields to None, according to the case being tested.
782        set_one_field_to_none(&mut thread_info);
783
784        // Send it to the SnapshotReceiver.
785        let fut =
786            receiver_proxy.batch(&[fheapdump_client::SnapshotElement::ThreadInfo(thread_info)]);
787        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
788
789        // Send the end of stream marker.
790        let fut = receiver_proxy.batch(&[]);
791        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
792
793        // Return the result.
794        receive_worker.await
795    }
796
797    #[test_case(|stack_trace| stack_trace.stack_trace_key = None => matches
798        Err(Error::MissingField { container: "StackTrace", field: "stack_trace_key" }) ; "stack_trace_key")]
799    #[test_case(|stack_trace| stack_trace.program_addresses = None => matches
800        Err(Error::MissingField { container: "StackTrace", field: "program_addresses" }) ; "program_addresses")]
801    #[test_case(|_| () /* if we do not set any field to None, the result should be Ok */ => matches
802        Ok(_) ; "success")]
803    #[fasync::run_singlethreaded(test)]
804    async fn test_stack_trace_required_fields(
805        set_one_field_to_none: fn(&mut fheapdump_client::StackTrace),
806    ) -> Result<Snapshot, Error> {
807        #[cfg(feature = "fdomain")]
808        let client = fdomain_local::local_client_empty();
809        #[cfg(not(feature = "fdomain"))]
810        let client = fidl::endpoints::ZirconClient;
811        let (receiver_proxy, receiver_stream) =
812            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
813        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
814
815        // Start with a StackTrace with all the required fields set.
816        let mut stack_trace = fheapdump_client::StackTrace {
817            stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
818            program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
819            ..Default::default()
820        };
821
822        // Set one of the fields to None, according to the case being tested.
823        set_one_field_to_none(&mut stack_trace);
824
825        // Send it to the SnapshotReceiver.
826        let fut =
827            receiver_proxy.batch(&[fheapdump_client::SnapshotElement::StackTrace(stack_trace)]);
828        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
829
830        // Send the end of stream marker.
831        let fut = receiver_proxy.batch(&[]);
832        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
833
834        // Return the result.
835        receive_worker.await
836    }
837
838    #[test_case(|region| region.address = None => matches
839        Err(Error::MissingField { container: "ExecutableRegion", field: "address" }) ; "address")]
840    #[test_case(|region| region.size = None => matches
841        Err(Error::MissingField { container: "ExecutableRegion", field: "size" }) ; "size")]
842    #[test_case(|region| region.file_offset = None => matches
843        Err(Error::MissingField { container: "ExecutableRegion", field: "file_offset" }) ; "file_offset")]
844    #[test_case(|region| region.build_id = None => matches
845        Err(Error::MissingField { container: "ExecutableRegion", field: "build_id" }) ; "build_id")]
846    #[test_case(|_| () /* if we do not set any field to None, the result should be Ok */ => matches
847        Ok(_) ; "success")]
848    #[fasync::run_singlethreaded(test)]
849    async fn test_executable_region_required_fields(
850        set_one_field_to_none: fn(&mut fheapdump_client::ExecutableRegion),
851    ) -> Result<Snapshot, Error> {
852        #[cfg(feature = "fdomain")]
853        let client = fdomain_local::local_client_empty();
854        #[cfg(not(feature = "fdomain"))]
855        let client = fidl::endpoints::ZirconClient;
856        let (receiver_proxy, receiver_stream) =
857            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
858        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
859
860        // Start with an ExecutableRegion with all the required fields set.
861        let mut region = fheapdump_client::ExecutableRegion {
862            address: Some(FAKE_REGION_1_ADDRESS),
863            size: Some(FAKE_REGION_1_SIZE),
864            file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
865            build_id: Some(fheapdump_client::BuildId { value: FAKE_REGION_1_BUILD_ID.to_vec() }),
866            ..Default::default()
867        };
868
869        // Set one of the fields to None, according to the case being tested.
870        set_one_field_to_none(&mut region);
871
872        // Send it to the SnapshotReceiver.
873        let fut =
874            receiver_proxy.batch(&[fheapdump_client::SnapshotElement::ExecutableRegion(region)]);
875        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
876
877        // Send the end of stream marker.
878        let fut = receiver_proxy.batch(&[]);
879        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
880
881        // Return the result.
882        receive_worker.await
883    }
884
885    #[test_case(|block_contents| block_contents.address = None => matches
886        Err(Error::MissingField { container: "BlockContents", field: "address" }) ; "address")]
887    #[test_case(|block_contents| block_contents.contents = None => matches
888        Err(Error::MissingField { container: "BlockContents", field: "contents" }) ; "contents")]
889    #[test_case(|_| () /* if we do not set any field to None, the result should be Ok */ => matches
890        Ok(_) ; "success")]
891    #[fasync::run_singlethreaded(test)]
892    async fn test_block_contents_required_fields(
893        set_one_field_to_none: fn(&mut fheapdump_client::BlockContents),
894    ) -> Result<Snapshot, Error> {
895        #[cfg(feature = "fdomain")]
896        let client = fdomain_local::local_client_empty();
897        #[cfg(not(feature = "fdomain"))]
898        let client = fidl::endpoints::ZirconClient;
899        let (receiver_proxy, receiver_stream) =
900            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
901        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
902
903        // Start with a BlockContents with all the required fields set.
904        let mut block_contents = fheapdump_client::BlockContents {
905            address: Some(FAKE_ALLOCATION_1_ADDRESS),
906            contents: Some(FAKE_ALLOCATION_1_CONTENTS.to_vec()),
907            ..Default::default()
908        };
909
910        // Set one of the fields to None, according to the case being tested.
911        set_one_field_to_none(&mut block_contents);
912
913        // Send it to the SnapshotReceiver along with the allocation it references.
914        let fut = receiver_proxy.batch(&[
915            fheapdump_client::SnapshotElement::BlockContents(block_contents),
916            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
917                address: Some(FAKE_ALLOCATION_1_ADDRESS),
918                size: Some(FAKE_ALLOCATION_1_SIZE),
919                thread_info_key: Some(FAKE_THREAD_1_KEY),
920                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
921                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
922                ..Default::default()
923            }),
924            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
925                thread_info_key: Some(FAKE_THREAD_1_KEY),
926                koid: Some(FAKE_THREAD_1_KOID),
927                name: Some(FAKE_THREAD_1_NAME.to_string()),
928                ..Default::default()
929            }),
930            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
931                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
932                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
933                ..Default::default()
934            }),
935        ]);
936        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
937
938        // Send the end of stream marker.
939        let fut = receiver_proxy.batch(&[]);
940        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
941
942        // Return the result.
943        receive_worker.await
944    }
945
946    #[fasync::run_singlethreaded(test)]
947    async fn test_conflicting_allocations() {
948        #[cfg(feature = "fdomain")]
949        let client = fdomain_local::local_client_empty();
950        #[cfg(not(feature = "fdomain"))]
951        let client = fidl::endpoints::ZirconClient;
952        let (receiver_proxy, receiver_stream) =
953            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
954        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
955
956        // Send two allocations with the same address along with the stack trace they reference.
957        let fut = receiver_proxy.batch(&[
958            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
959                address: Some(FAKE_ALLOCATION_1_ADDRESS),
960                size: Some(FAKE_ALLOCATION_1_SIZE),
961                thread_info_key: Some(FAKE_THREAD_1_KEY),
962                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
963                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
964                ..Default::default()
965            }),
966            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
967                address: Some(FAKE_ALLOCATION_1_ADDRESS),
968                size: Some(FAKE_ALLOCATION_1_SIZE),
969                thread_info_key: Some(FAKE_THREAD_1_KEY),
970                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
971                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
972                ..Default::default()
973            }),
974            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
975                thread_info_key: Some(FAKE_THREAD_1_KEY),
976                koid: Some(FAKE_THREAD_1_KOID),
977                name: Some(FAKE_THREAD_1_NAME.to_string()),
978                ..Default::default()
979            }),
980            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
981                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
982                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
983                ..Default::default()
984            }),
985        ]);
986        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
987
988        // Send the end of stream marker.
989        let fut = receiver_proxy.batch(&[]);
990        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
991
992        // Verify expected error.
993        assert_matches!(
994            receive_worker.await,
995            Err(Error::ConflictingElement { element_type: "Allocation" })
996        );
997    }
998
999    #[fasync::run_singlethreaded(test)]
1000    async fn test_conflicting_executable_regions() {
1001        #[cfg(feature = "fdomain")]
1002        let client = fdomain_local::local_client_empty();
1003        #[cfg(not(feature = "fdomain"))]
1004        let client = fidl::endpoints::ZirconClient;
1005        let (receiver_proxy, receiver_stream) =
1006            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1007        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1008
1009        // Send two executable regions with the same address.
1010        let fut = receiver_proxy.batch(&[
1011            fheapdump_client::SnapshotElement::ExecutableRegion(
1012                fheapdump_client::ExecutableRegion {
1013                    address: Some(FAKE_REGION_1_ADDRESS),
1014                    size: Some(FAKE_REGION_1_SIZE),
1015                    file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
1016                    build_id: Some(fheapdump_client::BuildId {
1017                        value: FAKE_REGION_1_BUILD_ID.to_vec(),
1018                    }),
1019                    ..Default::default()
1020                },
1021            ),
1022            fheapdump_client::SnapshotElement::ExecutableRegion(
1023                fheapdump_client::ExecutableRegion {
1024                    address: Some(FAKE_REGION_1_ADDRESS),
1025                    size: Some(FAKE_REGION_1_SIZE),
1026                    file_offset: Some(FAKE_REGION_1_FILE_OFFSET),
1027                    build_id: Some(fheapdump_client::BuildId {
1028                        value: FAKE_REGION_1_BUILD_ID.to_vec(),
1029                    }),
1030                    ..Default::default()
1031                },
1032            ),
1033        ]);
1034        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
1035
1036        // Send the end of stream marker.
1037        let fut = receiver_proxy.batch(&[]);
1038        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
1039
1040        // Verify expected error.
1041        assert_matches!(
1042            receive_worker.await,
1043            Err(Error::ConflictingElement { element_type: "ExecutableRegion" })
1044        );
1045    }
1046
1047    #[fasync::run_singlethreaded(test)]
1048    async fn test_block_contents_wrong_size() {
1049        #[cfg(feature = "fdomain")]
1050        let client = fdomain_local::local_client_empty();
1051        #[cfg(not(feature = "fdomain"))]
1052        let client = fidl::endpoints::ZirconClient;
1053        let (receiver_proxy, receiver_stream) =
1054            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1055        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1056
1057        // Send an allocation whose BlockContents has the wrong size.
1058        let contents_with_wrong_size = vec![0; FAKE_ALLOCATION_1_SIZE as usize + 1];
1059        let fut = receiver_proxy.batch(&[
1060            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1061                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1062                size: Some(FAKE_ALLOCATION_1_SIZE),
1063                thread_info_key: Some(FAKE_THREAD_1_KEY),
1064                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1065                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1066                ..Default::default()
1067            }),
1068            fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
1069                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1070                contents: Some(contents_with_wrong_size),
1071                ..Default::default()
1072            }),
1073            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1074                thread_info_key: Some(FAKE_THREAD_1_KEY),
1075                koid: Some(FAKE_THREAD_1_KOID),
1076                name: Some(FAKE_THREAD_1_NAME.to_string()),
1077                ..Default::default()
1078            }),
1079            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1080                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1081                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1082                ..Default::default()
1083            }),
1084        ]);
1085        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
1086
1087        // Send the end of stream marker.
1088        let fut = receiver_proxy.batch(&[]);
1089        let _ = fut.await; // ignore result, as the peer may detect the error and close the channel
1090
1091        // Verify expected error.
1092        assert_matches!(
1093            receive_worker.await,
1094            Err(Error::ConflictingElement { element_type: "BlockContents" })
1095        );
1096    }
1097
1098    #[fasync::run_singlethreaded(test)]
1099    async fn test_empty_stack_trace() {
1100        #[cfg(feature = "fdomain")]
1101        let client = fdomain_local::local_client_empty();
1102        #[cfg(not(feature = "fdomain"))]
1103        let client = fidl::endpoints::ZirconClient;
1104        let (receiver_proxy, receiver_stream) =
1105            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1106        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1107
1108        // Send an allocation that references an empty stack trace.
1109        let fut = receiver_proxy.batch(&[
1110            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1111                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1112                size: Some(FAKE_ALLOCATION_1_SIZE),
1113                thread_info_key: Some(FAKE_THREAD_1_KEY),
1114                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1115                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1116                ..Default::default()
1117            }),
1118            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1119                thread_info_key: Some(FAKE_THREAD_1_KEY),
1120                koid: Some(FAKE_THREAD_1_KOID),
1121                name: Some(FAKE_THREAD_1_NAME.to_string()),
1122                ..Default::default()
1123            }),
1124            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1125                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1126                program_addresses: Some(vec![]),
1127                ..Default::default()
1128            }),
1129        ]);
1130        fut.await.unwrap();
1131
1132        // Send the end of stream marker.
1133        let fut = receiver_proxy.batch(&[]);
1134        fut.await.unwrap();
1135
1136        // Verify that the stack trace has been reconstructed correctly.
1137        let received_snapshot = receive_worker.await.unwrap();
1138        let allocation1 = received_snapshot
1139            .allocations
1140            .iter()
1141            .find(|a| a.address == Some(FAKE_ALLOCATION_1_ADDRESS))
1142            .unwrap();
1143        assert_eq!(allocation1.stack_trace.program_addresses, []);
1144    }
1145
1146    #[fasync::run_singlethreaded(test)]
1147    async fn test_chunked_stack_trace() {
1148        #[cfg(feature = "fdomain")]
1149        let client = fdomain_local::local_client_empty();
1150        #[cfg(not(feature = "fdomain"))]
1151        let client = fidl::endpoints::ZirconClient;
1152        let (receiver_proxy, receiver_stream) =
1153            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1154        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1155
1156        // Send an allocation and the first chunk of its stack trace.
1157        let fut = receiver_proxy.batch(&[
1158            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1159                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1160                size: Some(FAKE_ALLOCATION_1_SIZE),
1161                thread_info_key: Some(FAKE_THREAD_1_KEY),
1162                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1163                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1164                ..Default::default()
1165            }),
1166            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1167                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1168                program_addresses: Some(vec![1111, 2222]),
1169                ..Default::default()
1170            }),
1171            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1172                thread_info_key: Some(FAKE_THREAD_1_KEY),
1173                koid: Some(FAKE_THREAD_1_KOID),
1174                name: Some(FAKE_THREAD_1_NAME.to_string()),
1175                ..Default::default()
1176            }),
1177        ]);
1178        fut.await.unwrap();
1179
1180        // Send the second chunk.
1181        let fut = receiver_proxy.batch(&[fheapdump_client::SnapshotElement::StackTrace(
1182            fheapdump_client::StackTrace {
1183                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1184                program_addresses: Some(vec![3333]),
1185                ..Default::default()
1186            },
1187        )]);
1188        fut.await.unwrap();
1189
1190        // Send the end of stream marker.
1191        let fut = receiver_proxy.batch(&[]);
1192        fut.await.unwrap();
1193
1194        // Verify that the stack trace has been reconstructed correctly.
1195        let received_snapshot = receive_worker.await.unwrap();
1196        let allocation1 = received_snapshot
1197            .allocations
1198            .iter()
1199            .find(|alloc| alloc.address == Some(FAKE_ALLOCATION_1_ADDRESS))
1200            .unwrap();
1201        assert_eq!(allocation1.stack_trace.program_addresses, [1111, 2222, 3333]);
1202    }
1203
1204    #[fasync::run_singlethreaded(test)]
1205    async fn test_empty_block_contents() {
1206        #[cfg(feature = "fdomain")]
1207        let client = fdomain_local::local_client_empty();
1208        #[cfg(not(feature = "fdomain"))]
1209        let client = fidl::endpoints::ZirconClient;
1210        let (receiver_proxy, receiver_stream) =
1211            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1212        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1213
1214        // Send a zero-sized allocation and its empty contents.
1215        let fut = receiver_proxy.batch(&[
1216            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1217                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1218                size: Some(0),
1219                thread_info_key: Some(FAKE_THREAD_1_KEY),
1220                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1221                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1222                ..Default::default()
1223            }),
1224            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1225                thread_info_key: Some(FAKE_THREAD_1_KEY),
1226                koid: Some(FAKE_THREAD_1_KOID),
1227                name: Some(FAKE_THREAD_1_NAME.to_string()),
1228                ..Default::default()
1229            }),
1230            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1231                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1232                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1233                ..Default::default()
1234            }),
1235            fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
1236                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1237                contents: Some(vec![]),
1238                ..Default::default()
1239            }),
1240        ]);
1241        fut.await.unwrap();
1242
1243        // Send the end of stream marker.
1244        let fut = receiver_proxy.batch(&[]);
1245        fut.await.unwrap();
1246
1247        // Verify that the allocation has been reconstructed correctly.
1248        let received_snapshot = receive_worker.await.unwrap();
1249        let allocation1 = received_snapshot
1250            .allocations
1251            .iter()
1252            .find(|alloc| alloc.address == Some(FAKE_ALLOCATION_1_ADDRESS))
1253            .unwrap();
1254        assert_eq!(allocation1.contents.as_ref().expect("contents must be set"), &vec![]);
1255    }
1256
1257    #[fasync::run_singlethreaded(test)]
1258    async fn test_chunked_block_contents() {
1259        #[cfg(feature = "fdomain")]
1260        let client = fdomain_local::local_client_empty();
1261        #[cfg(not(feature = "fdomain"))]
1262        let client = fidl::endpoints::ZirconClient;
1263        let (receiver_proxy, receiver_stream) =
1264            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1265        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1266
1267        // Split the contents in two halves.
1268        let (content_first_chunk, contents_second_chunk) =
1269            FAKE_ALLOCATION_1_CONTENTS.split_at(FAKE_ALLOCATION_1_CONTENTS.len() / 2);
1270
1271        // Send an allocation and the first chunk of its contents.
1272        let fut = receiver_proxy.batch(&[
1273            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1274                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1275                size: Some(FAKE_ALLOCATION_1_SIZE),
1276                thread_info_key: Some(FAKE_THREAD_1_KEY),
1277                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1278                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1279                ..Default::default()
1280            }),
1281            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1282                thread_info_key: Some(FAKE_THREAD_1_KEY),
1283                koid: Some(FAKE_THREAD_1_KOID),
1284                name: Some(FAKE_THREAD_1_NAME.to_string()),
1285                ..Default::default()
1286            }),
1287            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1288                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1289                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1290                ..Default::default()
1291            }),
1292            fheapdump_client::SnapshotElement::BlockContents(fheapdump_client::BlockContents {
1293                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1294                contents: Some(content_first_chunk.to_vec()),
1295                ..Default::default()
1296            }),
1297        ]);
1298        fut.await.unwrap();
1299
1300        // Send the second chunk.
1301        let fut = receiver_proxy.batch(&[fheapdump_client::SnapshotElement::BlockContents(
1302            fheapdump_client::BlockContents {
1303                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1304                contents: Some(contents_second_chunk.to_vec()),
1305                ..Default::default()
1306            },
1307        )]);
1308        fut.await.unwrap();
1309
1310        // Send the end of stream marker.
1311        let fut = receiver_proxy.batch(&[]);
1312        fut.await.unwrap();
1313
1314        // Verify that the allocation's block contents have been reconstructed correctly.
1315        let received_snapshot = receive_worker.await.unwrap();
1316        let allocation1 = received_snapshot
1317            .allocations
1318            .iter()
1319            .find(|a| a.address == Some(FAKE_ALLOCATION_1_ADDRESS))
1320            .unwrap();
1321        assert_eq!(allocation1.contents, Some(FAKE_ALLOCATION_1_CONTENTS.to_vec()));
1322    }
1323
1324    #[fasync::run_singlethreaded(test)]
1325    async fn test_missing_end_of_stream() {
1326        #[cfg(feature = "fdomain")]
1327        let client = fdomain_local::local_client_empty();
1328        #[cfg(not(feature = "fdomain"))]
1329        let client = fidl::endpoints::ZirconClient;
1330        let (receiver_proxy, receiver_stream) =
1331            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1332        let receive_worker = fasync::Task::local(Snapshot::receive_single_from(receiver_stream));
1333
1334        // Send an allocation and its stack trace.
1335        let fut = receiver_proxy.batch(&[
1336            fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1337                address: Some(FAKE_ALLOCATION_1_ADDRESS),
1338                size: Some(FAKE_ALLOCATION_1_SIZE),
1339                thread_info_key: Some(FAKE_THREAD_1_KEY),
1340                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1341                timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1342                ..Default::default()
1343            }),
1344            fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1345                thread_info_key: Some(FAKE_THREAD_1_KEY),
1346                koid: Some(FAKE_THREAD_1_KOID),
1347                name: Some(FAKE_THREAD_1_NAME.to_string()),
1348                ..Default::default()
1349            }),
1350            fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1351                stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1352                program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1353                ..Default::default()
1354            }),
1355        ]);
1356        fut.await.unwrap();
1357
1358        // Close the channel without sending an end of stream marker.
1359        std::mem::drop(receiver_proxy);
1360
1361        // Expect an UnexpectedEndOfStream error.
1362        assert_matches!(receive_worker.await, Err(Error::UnexpectedEndOfStream));
1363    }
1364
1365    #[fasync::run_singlethreaded(test)]
1366    async fn test_multi_contents() {
1367        #[cfg(feature = "fdomain")]
1368        let client = fdomain_local::local_client_empty();
1369        #[cfg(not(feature = "fdomain"))]
1370        let client = fidl::endpoints::ZirconClient;
1371        let (receiver_proxy, receiver_stream) =
1372            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1373        let receive_worker = fasync::Task::local(Snapshot::receive_multi_from(receiver_stream));
1374
1375        // Send two snapshots with different KOIDs.
1376        for koid in [1111, 2222] {
1377            receiver_proxy
1378                .batch(&[fheapdump_client::SnapshotElement::SnapshotHeader(
1379                    fheapdump_client::SnapshotHeader {
1380                        process_name: Some(format!("test-process-{koid}")),
1381                        process_koid: Some(koid),
1382                        ..Default::default()
1383                    },
1384                )])
1385                .await
1386                .unwrap();
1387
1388            receiver_proxy
1389                .batch(&[
1390                    fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1391                        address: Some(FAKE_ALLOCATION_1_ADDRESS),
1392                        size: Some(FAKE_ALLOCATION_1_SIZE),
1393                        thread_info_key: Some(FAKE_THREAD_1_KEY),
1394                        stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1395                        timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1396                        ..Default::default()
1397                    }),
1398                    fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1399                        thread_info_key: Some(FAKE_THREAD_1_KEY),
1400                        koid: Some(FAKE_THREAD_1_KOID),
1401                        name: Some(FAKE_THREAD_1_NAME.to_string()),
1402                        ..Default::default()
1403                    }),
1404                    fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1405                        stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1406                        program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1407                        ..Default::default()
1408                    }),
1409                ])
1410                .await
1411                .unwrap();
1412
1413            receiver_proxy.batch(&[]).await.unwrap(); // end of snapshot
1414        }
1415
1416        // Send end of stream marker.
1417        receiver_proxy.batch(&[]).await.unwrap();
1418
1419        // Validate the received snapshots.
1420        let received_snapshots = receive_worker.await.unwrap();
1421        assert_eq!(received_snapshots.len(), 2);
1422        assert_eq!(received_snapshots[0].process_name, "test-process-1111");
1423        assert_eq!(received_snapshots[0].process_koid, 1111);
1424        assert_eq!(received_snapshots[1].process_name, "test-process-2222");
1425        assert_eq!(received_snapshots[1].process_koid, 2222);
1426    }
1427
1428    #[fasync::run_singlethreaded(test)]
1429    async fn test_multi_missing_end_of_stream() {
1430        #[cfg(feature = "fdomain")]
1431        let client = fdomain_local::local_client_empty();
1432        #[cfg(not(feature = "fdomain"))]
1433        let client = fidl::endpoints::ZirconClient;
1434        let (receiver_proxy, receiver_stream) =
1435            client.create_proxy_and_stream::<fheapdump_client::SnapshotReceiverMarker>();
1436        let receive_worker = fasync::Task::local(Snapshot::receive_multi_from(receiver_stream));
1437
1438        // Send two snapshots with different KOIDs.
1439        for koid in [1111, 2222] {
1440            receiver_proxy
1441                .batch(&[fheapdump_client::SnapshotElement::SnapshotHeader(
1442                    fheapdump_client::SnapshotHeader {
1443                        process_name: Some("test-process-name".to_string()),
1444                        process_koid: Some(koid),
1445                        ..Default::default()
1446                    },
1447                )])
1448                .await
1449                .unwrap();
1450
1451            receiver_proxy
1452                .batch(&[
1453                    fheapdump_client::SnapshotElement::Allocation(fheapdump_client::Allocation {
1454                        address: Some(FAKE_ALLOCATION_1_ADDRESS),
1455                        size: Some(FAKE_ALLOCATION_1_SIZE),
1456                        thread_info_key: Some(FAKE_THREAD_1_KEY),
1457                        stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1458                        timestamp: Some(FAKE_ALLOCATION_1_TIMESTAMP),
1459                        ..Default::default()
1460                    }),
1461                    fheapdump_client::SnapshotElement::ThreadInfo(fheapdump_client::ThreadInfo {
1462                        thread_info_key: Some(FAKE_THREAD_1_KEY),
1463                        koid: Some(FAKE_THREAD_1_KOID),
1464                        name: Some(FAKE_THREAD_1_NAME.to_string()),
1465                        ..Default::default()
1466                    }),
1467                    fheapdump_client::SnapshotElement::StackTrace(fheapdump_client::StackTrace {
1468                        stack_trace_key: Some(FAKE_STACK_TRACE_1_KEY),
1469                        program_addresses: Some(FAKE_STACK_TRACE_1_ADDRESSES.to_vec()),
1470                        ..Default::default()
1471                    }),
1472                ])
1473                .await
1474                .unwrap();
1475
1476            receiver_proxy.batch(&[]).await.unwrap(); // end of snapshot
1477        }
1478
1479        // Close the channel without sending an end of stream marker.
1480        std::mem::drop(receiver_proxy);
1481
1482        // Expect an UnexpectedEndOfStream error.
1483        assert_matches!(receive_worker.await, Err(Error::UnexpectedEndOfStream));
1484    }
1485}