1use super::PUPPET_MONIKER;
6use super::data::{self, Data, LazyNode};
7use super::metrics::Metrics;
8use anyhow::{Error, format_err};
9use fuchsia_component::client as fclient;
10use serde::Serialize;
11use zx::{self as zx, Vmo};
12use {fidl_diagnostics_validate as validate, fidl_fuchsia_inspect as fidl_inspect};
13
14pub const VMO_SIZE: u64 = 4096;
15
16#[derive(Debug)]
17pub struct Config {
18 pub diff_type: DiffType,
19 pub printable_name: String,
20 pub has_runner_node: bool,
21 pub test_archive: bool,
22}
23
24#[derive(Clone, Copy, Debug, Default, Serialize)]
29pub enum DiffType {
30 #[default]
31 Full,
32 Diff,
33 Both,
34}
35
36impl From<Option<validate::DiffType>> for DiffType {
37 fn from(original: Option<validate::DiffType>) -> Self {
38 match original {
39 Some(validate::DiffType::Diff) => Self::Diff,
40 Some(validate::DiffType::Both) => Self::Both,
41 _ => Self::Full,
42 }
43 }
44}
45
46pub struct Puppet {
47 pub vmo: Vmo,
48 connection: Connection,
50 pub config: Config,
52}
53
54impl Puppet {
55 pub async fn apply(
56 &mut self,
57 action: &mut validate::Action,
58 ) -> Result<validate::TestResult, Error> {
59 Ok(self.connection.fidl.act(action).await?)
60 }
61
62 pub async fn apply_lazy(
63 &mut self,
64 lazy_action: &mut validate::LazyAction,
65 ) -> Result<validate::TestResult, Error> {
66 match &self.connection.root_link_channel {
67 Some(_) => Ok(self.connection.fidl.act_lazy(lazy_action).await?),
68 None => Ok(validate::TestResult::Unimplemented),
69 }
70 }
71
72 pub async fn act_lazy_thread_local(
73 &mut self,
74 lazy_action: &mut validate::LazyAction,
75 ) -> Result<validate::TestResult, Error> {
76 Ok(self.connection.fidl.act_lazy_thread_local(lazy_action).await?)
77 }
78
79 pub async fn publish(&mut self) -> Result<validate::TestResult, Error> {
80 Ok(self.connection.fidl.publish().await?)
81 }
82
83 pub async fn connect() -> Result<Self, Error> {
84 Puppet::initialize_with_connection(Connection::connect().await?).await
85 }
86
87 pub(crate) async fn shutdown(self) {
88 let lifecycle_controller =
89 fclient::connect_to_protocol::<fidl_fuchsia_sys2::LifecycleControllerMarker>().unwrap();
90 lifecycle_controller.stop_instance(&format!("./{PUPPET_MONIKER}")).await.unwrap().unwrap();
91 }
92
93 pub fn printable_name(&self) -> &str {
95 &self.config.printable_name
96 }
97
98 #[cfg(test)]
99 pub async fn connect_local(local_fidl: validate::InspectPuppetProxy) -> Result<Puppet, Error> {
100 let mut puppet = Puppet::initialize_with_connection(Connection::new(local_fidl)).await?;
101 puppet.config.test_archive = false;
102 Ok(puppet)
103 }
104
105 async fn initialize_with_connection(mut connection: Connection) -> Result<Puppet, Error> {
106 Ok(Puppet {
107 vmo: connection.initialize_vmo().await?,
108 config: connection.get_config().await?,
109 connection,
110 })
111 }
112
113 pub async fn read_data(&self) -> Result<Data, Error> {
114 Ok(match &self.connection.root_link_channel {
115 None => data::Scanner::try_from(&self.vmo)?.data(),
116 Some(root_link_channel) => {
117 let vmo_tree = LazyNode::new(root_link_channel.clone()).await?;
118 data::Scanner::try_from(vmo_tree)?.data()
119 }
120 })
121 }
122
123 pub fn metrics(&self) -> Result<Metrics, Error> {
124 Ok(data::Scanner::try_from(&self.vmo)?.metrics())
125 }
126}
127
128struct Connection {
129 fidl: validate::InspectPuppetProxy,
130 root_link_channel: Option<fidl_inspect::TreeProxy>,
133}
134
135impl Connection {
136 async fn connect() -> Result<Self, Error> {
137 let puppet_fidl = fclient::connect_to_protocol::<validate::InspectPuppetMarker>().unwrap();
138 Ok(Self::new(puppet_fidl))
139 }
140
141 async fn get_config(&self) -> Result<Config, Error> {
142 let (printable_name, opts) = self.fidl.get_config().await?;
143 Ok(Config {
144 diff_type: opts.diff_type.into(),
145 printable_name,
146 has_runner_node: opts.has_runner_node.unwrap_or(false),
147 test_archive: true,
148 })
149 }
150
151 async fn fetch_link_channel(
152 fidl: &validate::InspectPuppetProxy,
153 ) -> Option<fidl_inspect::TreeProxy> {
154 let params =
155 validate::InitializationParams { vmo_size: Some(VMO_SIZE), ..Default::default() };
156 let response = fidl.initialize_tree(¶ms).await;
157 if let Ok((Some(tree_client_end), validate::TestResult::Ok)) = response {
158 Some(tree_client_end.into_proxy())
159 } else {
160 None
161 }
162 }
163
164 async fn get_vmo_handle(channel: &fidl_inspect::TreeProxy) -> Result<Vmo, Error> {
165 let tree_content = channel.get_content().await?;
166 let buffer =
167 tree_content.buffer.ok_or_else(|| format_err!("Buffer doesn't contain VMO"))?;
168 Ok(buffer.vmo)
169 }
170
171 fn new(fidl: validate::InspectPuppetProxy) -> Self {
172 Self { fidl, root_link_channel: None }
173 }
174
175 async fn initialize_vmo(&mut self) -> Result<Vmo, Error> {
176 self.root_link_channel = Self::fetch_link_channel(&self.fidl).await;
177 match &self.root_link_channel {
178 Some(root_link_channel) => Self::get_vmo_handle(root_link_channel).await,
179 None => {
180 let params = validate::InitializationParams {
181 vmo_size: Some(VMO_SIZE),
182 ..Default::default()
183 };
184 let handle: Option<zx::Handle>;
185 let out = self.fidl.initialize(¶ms).await?;
186 if let (Some(out_handle), _) = out {
187 handle = Some(out_handle);
188 } else {
189 return Err(format_err!("Didn't get a VMO handle"));
190 }
191 match handle {
192 Some(unwrapped_handle) => Ok(Vmo::from(unwrapped_handle)),
193 None => Err(format_err!("Failed to unwrap handle")),
194 }
195 }
196 }
197 }
198}
199
200#[cfg(test)]
201pub(crate) mod tests {
202 use super::*;
203 use crate::create_node;
204 use anyhow::Context as _;
205 use fidl::endpoints::{RequestStream, ServerEnd, create_proxy};
206 use fidl_diagnostics_validate::{
207 Action, CreateNode, CreateNumericProperty, InspectPuppetMarker, InspectPuppetRequest,
208 InspectPuppetRequestStream, Options, ROOT_ID, TestResult, Value,
209 };
210 use fuchsia_async as fasync;
211 use fuchsia_inspect::{Inspector, InspectorConfig, IntProperty, Node};
212 use futures::prelude::*;
213 use log::info;
214 use std::collections::HashMap;
215 use zx::HandleBased;
216
217 #[fuchsia::test]
218 async fn test_fidl_loopback() -> Result<(), Error> {
219 let mut puppet = local_incomplete_puppet().await?;
220 assert_eq!(puppet.vmo.get_size().unwrap(), VMO_SIZE);
221 let tree = puppet.read_data().await?;
222 assert_eq!(tree.to_string(), "root ->".to_string());
223 let mut data = Data::new();
224 tree.compare(&data, DiffType::Full)?;
225 let mut action = create_node!(parent: ROOT_ID, id: 1, name: "child");
226 puppet.apply(&mut action).await?;
227 data.apply(&action)?;
228 let tree = data::Scanner::try_from(&puppet.vmo)?.data();
229 assert_eq!(tree.to_string(), "root ->\n> child ->".to_string());
230 tree.compare(&data, DiffType::Full)?;
231 Ok(())
232 }
233
234 pub(crate) async fn local_incomplete_puppet() -> Result<Puppet, Error> {
239 let (client_end, server_end) = create_proxy();
240 spawn_local_puppet(server_end).await;
241 Puppet::connect_local(client_end).await
242 }
243
244 async fn spawn_local_puppet(server_end: ServerEnd<InspectPuppetMarker>) {
245 fasync::Task::spawn(
246 async move {
247 let mut inspector_maybe: Option<Inspector> = None;
249 let mut nodes: HashMap<u32, Node> = HashMap::new();
250 let mut properties: HashMap<u32, IntProperty> = HashMap::new();
251 let server_chan = fasync::Channel::from_channel(server_end.into_channel());
252 let mut stream = InspectPuppetRequestStream::from_channel(server_chan);
253 while let Some(event) = stream.try_next().await? {
254 match event {
255 InspectPuppetRequest::GetConfig { responder } => {
256 responder.send("*Local*", Options::default()).ok();
257 }
258 InspectPuppetRequest::Initialize { params, responder } => {
259 let inspector = match params.vmo_size {
260 Some(size) => {
261 Inspector::new(InspectorConfig::default().size(size as usize))
262 }
263 None => Inspector::default(),
264 };
265 responder
266 .send(
267 inspector.duplicate_vmo().map(|v| v.into_handle()),
268 TestResult::Ok,
269 )
270 .context("responding to initialize")?;
271 inspector_maybe = Some(inspector);
272 }
273 InspectPuppetRequest::Act { action, responder } => match action {
274 Action::CreateNode(CreateNode { parent, id, name }) => {
275 if let Some(ref inspector) = inspector_maybe {
276 let parent_node = if parent == ROOT_ID {
277 inspector.root()
278 } else {
279 nodes.get(&parent).unwrap()
280 };
281 let new_child = parent_node.create_child(name);
282 nodes.insert(id, new_child);
283 }
284 responder.send(TestResult::Ok)?;
285 }
286 Action::CreateNumericProperty(CreateNumericProperty {
287 parent,
288 id,
289 name,
290 value: Value::IntT(value),
291 }) => {
292 inspector_maybe.as_ref().map(|i| {
293 let parent_node = if parent == 0 {
294 i.root()
295 } else {
296 nodes.get(&parent).unwrap()
297 };
298 properties.insert(id, parent_node.create_int(name, value))
299 });
300 responder.send(TestResult::Ok)?;
301 }
302 Action::CreateNumericProperty(CreateNumericProperty {
303 value: Value::UintT(_),
304 ..
305 }) => {
306 responder.send(TestResult::Unimplemented)?;
307 }
308
309 _ => responder.send(TestResult::Illegal)?,
310 },
311 InspectPuppetRequest::InitializeTree { params: _, responder } => {
312 responder.send(None, TestResult::Unimplemented)?;
313 }
314 InspectPuppetRequest::ActLazy { lazy_action: _, responder } => {
315 responder.send(TestResult::Unimplemented)?;
316 }
317 InspectPuppetRequest::Publish { responder } => {
318 responder.send(TestResult::Unimplemented)?;
319 }
320 InspectPuppetRequest::ActLazyThreadLocal { responder, .. } => {
321 responder.send(TestResult::Unimplemented)?;
322 }
323 InspectPuppetRequest::_UnknownMethod { .. } => {}
324 }
325 }
326 Ok(())
327 }
328 .unwrap_or_else(|e: anyhow::Error| info!("error running validate interface: {:?}", e)),
329 )
330 .detach();
331 }
332}