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 = tree_content.buffer.ok_or(format_err!("Buffer doesn't contain VMO"))?;
167 Ok(buffer.vmo)
168 }
169
170 fn new(fidl: validate::InspectPuppetProxy) -> Self {
171 Self { fidl, root_link_channel: None }
172 }
173
174 async fn initialize_vmo(&mut self) -> Result<Vmo, Error> {
175 self.root_link_channel = Self::fetch_link_channel(&self.fidl).await;
176 match &self.root_link_channel {
177 Some(root_link_channel) => Self::get_vmo_handle(root_link_channel).await,
178 None => {
179 let params = validate::InitializationParams {
180 vmo_size: Some(VMO_SIZE),
181 ..Default::default()
182 };
183 let handle: Option<zx::Handle>;
184 let out = self.fidl.initialize(¶ms).await?;
185 if let (Some(out_handle), _) = out {
186 handle = Some(out_handle);
187 } else {
188 return Err(format_err!("Didn't get a VMO handle"));
189 }
190 match handle {
191 Some(unwrapped_handle) => Ok(Vmo::from(unwrapped_handle)),
192 None => Err(format_err!("Failed to unwrap handle")),
193 }
194 }
195 }
196 }
197}
198
199#[cfg(test)]
200pub(crate) mod tests {
201 use super::*;
202 use crate::create_node;
203 use anyhow::Context as _;
204 use fidl::endpoints::{RequestStream, ServerEnd, create_proxy};
205 use fidl_diagnostics_validate::{
206 Action, CreateNode, CreateNumericProperty, InspectPuppetMarker, InspectPuppetRequest,
207 InspectPuppetRequestStream, Options, ROOT_ID, TestResult, Value,
208 };
209 use fuchsia_async as fasync;
210 use fuchsia_inspect::{Inspector, InspectorConfig, IntProperty, Node};
211 use futures::prelude::*;
212 use log::info;
213 use std::collections::HashMap;
214 use zx::HandleBased;
215
216 #[fuchsia::test]
217 async fn test_fidl_loopback() -> Result<(), Error> {
218 let mut puppet = local_incomplete_puppet().await?;
219 assert_eq!(puppet.vmo.get_size().unwrap(), VMO_SIZE);
220 let tree = puppet.read_data().await?;
221 assert_eq!(tree.to_string(), "root ->".to_string());
222 let mut data = Data::new();
223 tree.compare(&data, DiffType::Full)?;
224 let mut action = create_node!(parent: ROOT_ID, id: 1, name: "child");
225 puppet.apply(&mut action).await?;
226 data.apply(&action)?;
227 let tree = data::Scanner::try_from(&puppet.vmo)?.data();
228 assert_eq!(tree.to_string(), "root ->\n> child ->".to_string());
229 tree.compare(&data, DiffType::Full)?;
230 Ok(())
231 }
232
233 pub(crate) async fn local_incomplete_puppet() -> Result<Puppet, Error> {
238 let (client_end, server_end) = create_proxy();
239 spawn_local_puppet(server_end).await;
240 Puppet::connect_local(client_end).await
241 }
242
243 async fn spawn_local_puppet(server_end: ServerEnd<InspectPuppetMarker>) {
244 fasync::Task::spawn(
245 async move {
246 let mut inspector_maybe: Option<Inspector> = None;
248 let mut nodes: HashMap<u32, Node> = HashMap::new();
249 let mut properties: HashMap<u32, IntProperty> = HashMap::new();
250 let server_chan = fasync::Channel::from_channel(server_end.into_channel());
251 let mut stream = InspectPuppetRequestStream::from_channel(server_chan);
252 while let Some(event) = stream.try_next().await? {
253 match event {
254 InspectPuppetRequest::GetConfig { responder } => {
255 responder.send("*Local*", Options::default()).ok();
256 }
257 InspectPuppetRequest::Initialize { params, responder } => {
258 let inspector = match params.vmo_size {
259 Some(size) => {
260 Inspector::new(InspectorConfig::default().size(size as usize))
261 }
262 None => Inspector::default(),
263 };
264 responder
265 .send(
266 inspector.duplicate_vmo().map(|v| v.into_handle()),
267 TestResult::Ok,
268 )
269 .context("responding to initialize")?;
270 inspector_maybe = Some(inspector);
271 }
272 InspectPuppetRequest::Act { action, responder } => match action {
273 Action::CreateNode(CreateNode { parent, id, name }) => {
274 if let Some(ref inspector) = inspector_maybe {
275 let parent_node = if parent == ROOT_ID {
276 inspector.root()
277 } else {
278 nodes.get(&parent).unwrap()
279 };
280 let new_child = parent_node.create_child(name);
281 nodes.insert(id, new_child);
282 }
283 responder.send(TestResult::Ok)?;
284 }
285 Action::CreateNumericProperty(CreateNumericProperty {
286 parent,
287 id,
288 name,
289 value: Value::IntT(value),
290 }) => {
291 inspector_maybe.as_ref().map(|i| {
292 let parent_node = if parent == 0 {
293 i.root()
294 } else {
295 nodes.get(&parent).unwrap()
296 };
297 properties.insert(id, parent_node.create_int(name, value))
298 });
299 responder.send(TestResult::Ok)?;
300 }
301 Action::CreateNumericProperty(CreateNumericProperty {
302 value: Value::UintT(_),
303 ..
304 }) => {
305 responder.send(TestResult::Unimplemented)?;
306 }
307
308 _ => responder.send(TestResult::Illegal)?,
309 },
310 InspectPuppetRequest::InitializeTree { params: _, responder } => {
311 responder.send(None, TestResult::Unimplemented)?;
312 }
313 InspectPuppetRequest::ActLazy { lazy_action: _, responder } => {
314 responder.send(TestResult::Unimplemented)?;
315 }
316 InspectPuppetRequest::Publish { responder } => {
317 responder.send(TestResult::Unimplemented)?;
318 }
319 InspectPuppetRequest::ActLazyThreadLocal { responder, .. } => {
320 responder.send(TestResult::Unimplemented)?;
321 }
322 InspectPuppetRequest::_UnknownMethod { .. } => {}
323 }
324 }
325 Ok(())
326 }
327 .unwrap_or_else(|e: anyhow::Error| info!("error running validate interface: {:?}", e)),
328 )
329 .detach();
330 }
331}