1use fidl_fuchsia_component as fcomponent;
6use fidl_fuchsia_memory_attribution as fattribution;
7use futures::stream::{BoxStream, SelectAll};
8use futures::{FutureExt, Stream, StreamExt};
9use pin_project::pin_project;
10use std::collections::HashMap;
11use std::pin::Pin;
12use std::task::{Context, Poll};
13
14#[derive(Debug, Clone, Eq, PartialEq, Hash)]
15pub struct PrincipalIdentifier(pub u64);
16
17#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
18pub enum Resource {
19 KernelObject(zx::Koid),
20 Vmar { process: zx::Koid, base: usize, len: usize },
21}
22
23#[derive(Debug, Clone)]
25pub struct Principal {
26 pub identifier: PrincipalIdentifier,
28
29 pub name: String,
31
32 pub resources: Vec<Resource>,
34
35 pub children: Vec<Principal>,
37}
38
39impl Principal {
40 pub fn new(identifier: PrincipalIdentifier, name: String) -> Principal {
41 Principal { identifier, name, resources: vec![], children: vec![] }
42 }
43
44 pub fn child_by_name(&self, name: &str) -> Option<&Principal> {
45 self.children.iter().find(|c| c.name == name)
46 }
47}
48
49pub fn attribute_memory(
58 identifier: PrincipalIdentifier,
59 name: String,
60 attribution_provider: fattribution::ProviderProxy,
61 introspector: fcomponent::IntrospectorProxy,
62) -> BoxStream<'static, Principal> {
63 futures::stream::unfold(
64 StreamState::new(identifier, name, introspector, attribution_provider),
65 get_next,
66 )
67 .boxed()
68}
69
70async fn get_next(mut state: StreamState) -> Option<(Principal, StreamState)> {
72 let mut node = state
73 .node
74 .clone()
75 .unwrap_or_else(|| Principal::new(state.identifier.clone(), state.name.clone()));
76 let mut children: HashMap<PrincipalIdentifier, Principal> =
77 node.children.clone().into_iter().map(|n| (n.identifier.clone(), n)).collect();
78
79 match state.next().await {
81 Some(event) => {
82 match event {
83 Event::Node(attributions) => {
85 for attribution in attributions {
86 handle_update(attribution, &mut state, &mut children).await;
87 }
88 }
89 Event::Child(child) => {
91 children.insert(child.identifier.clone(), child);
92 }
93 }
94 }
95 None => return None,
96 }
97
98 node.children = children.into_values().collect();
99 state.node = Some(node.clone());
100 Some((node, state))
101}
102
103async fn handle_update(
104 attribution: fattribution::AttributionUpdate,
105 state: &mut StreamState,
106 children: &mut HashMap<PrincipalIdentifier, Principal>,
107) {
108 match attribution {
109 fattribution::AttributionUpdate::Add(new_principal) => {
110 let identifier_id = PrincipalIdentifier(new_principal.identifier.unwrap());
111 let principal_name =
112 get_identifier_string(new_principal.description.unwrap(), &state.introspector)
113 .await;
114
115 if let Some(client) = new_principal.detailed_attribution {
117 state.child_update.push(
118 attribute_memory(
119 identifier_id.clone(),
120 principal_name.clone(),
121 client.into_proxy(),
122 state.introspector.clone(),
123 )
124 .boxed(),
125 );
126 }
127 children.insert(identifier_id.clone(), Principal::new(identifier_id, principal_name));
128 }
129 fattribution::AttributionUpdate::Update(updated_principal) => {
130 let identifier = PrincipalIdentifier(updated_principal.identifier.unwrap());
131
132 let child = children.get_mut(&identifier).unwrap();
133 let raw_resources = match updated_principal.resources.unwrap() {
134 fattribution::Resources::Data(d) => d.resources,
135 fattribution::Resources::Buffer(b) => {
136 let mapping = mapped_vmo::ImmutableMapping::create_from_vmo(&b, false).unwrap();
137 let resource_vector: fattribution::Data = fidl::unpersist(&mapping).unwrap();
138 resource_vector.resources
139 }
140 fattribution::ResourcesUnknown!() => {
141 unimplemented!()
142 }
143 };
144 child.resources = raw_resources
145 .into_iter()
146 .filter_map(|r| match r {
147 fattribution::Resource::KernelObject(koid) => {
148 Some(Resource::KernelObject(zx::Koid::from_raw(koid)))
149 }
150 fattribution::Resource::ProcessMapped(vmar) => Some(Resource::Vmar {
151 process: zx::Koid::from_raw(vmar.process),
152 base: vmar.base as usize,
153 len: vmar.len as usize,
154 }),
155 _ => todo!("unimplemented"),
156 })
157 .collect();
158 }
159 fattribution::AttributionUpdate::Remove(identifier_ref) => {
160 let identifier = PrincipalIdentifier(identifier_ref);
161 children.remove(&identifier);
162 }
163 x @ _ => panic!("unimplemented {x:?}"),
164 }
165}
166
167async fn get_identifier_string(
168 description: fattribution::Description,
169 introspector: &fcomponent::IntrospectorProxy,
170) -> String {
171 match description {
172 fattribution::Description::Component(c) => introspector
173 .get_moniker(c)
174 .await
175 .expect("Inspector call failed")
176 .expect("Inspector::GetMoniker call failed"),
177 fattribution::Description::Part(sc) => sc.clone(),
178 fattribution::DescriptionUnknown!() => todo!(),
179 }
180}
181
182#[pin_project]
191struct StreamState {
192 identifier: PrincipalIdentifier,
194
195 name: String,
197
198 introspector: fcomponent::IntrospectorProxy,
200
201 node: Option<Principal>,
203
204 hanging_get_update: Option<BoxStream<'static, Vec<fattribution::AttributionUpdate>>>,
208
209 #[pin]
214 child_update: SelectAll<BoxStream<'static, Principal>>,
215}
216
217impl StreamState {
218 fn new(
219 identifier: PrincipalIdentifier,
220 name: String,
221 introspector: fcomponent::IntrospectorProxy,
222 attribution_provider: fattribution::ProviderProxy,
223 ) -> Self {
224 Self {
225 identifier,
226 name: name.clone(),
227 introspector,
228 node: None,
229 hanging_get_update: Some(Box::pin(hanging_get_stream(name, attribution_provider))),
230 child_update: SelectAll::new(),
231 }
232 }
233}
234
235fn hanging_get_stream(
236 name: String,
237 proxy: fattribution::ProviderProxy,
238) -> impl Stream<Item = Vec<fattribution::AttributionUpdate>> + 'static {
239 futures::stream::unfold(proxy, move |proxy| {
240 let name = name.clone();
241 proxy.get().map(move |get_result| {
242 let attributions = match get_result {
243 Ok(application_result) => application_result
244 .unwrap_or_else(|e| {
245 panic!("Failed call to AttributionResponse for {name}: {e:?}")
246 })
247 .attributions
248 .unwrap_or_else(|| panic!("Failed memory attribution for {name}")),
249 Err(fidl::Error::ClientChannelClosed {
250 epitaph: fidl::Epitaph::PeerClosed,
251 ..
252 }) => {
253 return None;
259 }
260 Err(e) => {
261 panic!("Failed to get AttributionResponse for {name}: {e:?}");
262 }
263 };
264 Some((attributions, proxy))
265 })
266 })
267}
268
269enum Event {
270 Node(Vec<fattribution::AttributionUpdate>),
271 Child(Principal),
272}
273
274impl Stream for StreamState {
275 type Item = Event;
276
277 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
278 let this = self.get_mut();
279 match this.child_update.poll_next_unpin(cx) {
280 Poll::Ready(Some(node)) => {
281 return Poll::Ready(Some(Event::Child(node)));
282 }
283 Poll::Ready(None) => {}
284 Poll::Pending => {}
285 }
286 match this.hanging_get_update.as_mut() {
287 Some(hanging_get_update) => match hanging_get_update.poll_next_unpin(cx) {
288 Poll::Ready(Some(attributions)) => {
289 return Poll::Ready(Some(Event::Node(attributions)));
290 }
291 Poll::Ready(None) => {
292 this.hanging_get_update = None;
293 return Poll::Ready(None);
295 }
296 Poll::Pending => {}
297 },
298 None => {}
299 }
300 return Poll::Pending;
301 }
302}