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