1use super::PUPPET_MONIKER;
6use super::data::{self, Data, LazyNode};
7use super::metrics::Metrics;
8use anyhow::{Error, format_err};
9use fidl_diagnostics_validate as validate;
10use fidl_fuchsia_inspect as fidl_inspect;
11use fuchsia_component::client as fclient;
12use serde::Serialize;
13use zx::{self as zx, Vmo};
14
15pub const VMO_SIZE: u64 = 4096;
16
17#[derive(Debug)]
18pub struct Config {
19 pub diff_type: DiffType,
20 pub printable_name: String,
21 pub has_runner_node: bool,
22 pub test_archive: bool,
23}
24
25#[derive(Clone, Copy, Debug, Default, Serialize)]
30pub enum DiffType {
31 #[default]
32 Full,
33 Diff,
34 Both,
35}
36
37impl From<Option<validate::DiffType>> for DiffType {
38 fn from(original: Option<validate::DiffType>) -> Self {
39 match original {
40 Some(validate::DiffType::Diff) => Self::Diff,
41 Some(validate::DiffType::Both) => Self::Both,
42 _ => Self::Full,
43 }
44 }
45}
46
47pub struct Puppet {
48 pub vmo: Vmo,
49 connection: Connection,
51 pub config: Config,
53}
54
55impl Puppet {
56 pub async fn apply(
57 &mut self,
58 action: &mut validate::Action,
59 ) -> Result<validate::TestResult, Error> {
60 Ok(self.connection.fidl.act(action).await?)
61 }
62
63 pub async fn apply_lazy(
64 &mut self,
65 lazy_action: &mut validate::LazyAction,
66 ) -> Result<validate::TestResult, Error> {
67 match &self.connection.root_link_channel {
68 Some(_) => Ok(self.connection.fidl.act_lazy(lazy_action).await?),
69 None => Ok(validate::TestResult::Unimplemented),
70 }
71 }
72
73 pub async fn act_lazy_thread_local(
74 &mut self,
75 lazy_action: &mut validate::LazyAction,
76 ) -> Result<validate::TestResult, Error> {
77 Ok(self.connection.fidl.act_lazy_thread_local(lazy_action).await?)
78 }
79
80 pub async fn publish(&mut self) -> Result<validate::TestResult, Error> {
81 Ok(self.connection.fidl.publish().await?)
82 }
83
84 pub async fn connect() -> Result<Self, Error> {
85 Puppet::initialize_with_connection(Connection::connect().await?).await
86 }
87
88 pub(crate) async fn shutdown(self) {
89 let lifecycle_controller =
90 fclient::connect_to_protocol::<fidl_fuchsia_sys2::LifecycleControllerMarker>().unwrap();
91 lifecycle_controller.stop_instance(&format!("./{PUPPET_MONIKER}")).await.unwrap().unwrap();
92 }
93
94 pub fn printable_name(&self) -> &str {
96 &self.config.printable_name
97 }
98
99 #[cfg(test)]
100 pub async fn connect_local(local_fidl: validate::InspectPuppetProxy) -> Result<Puppet, Error> {
101 let mut puppet = Puppet::initialize_with_connection(Connection::new(local_fidl)).await?;
102 puppet.config.test_archive = false;
103 Ok(puppet)
104 }
105
106 async fn initialize_with_connection(mut connection: Connection) -> Result<Puppet, Error> {
107 Ok(Puppet {
108 vmo: connection.initialize_vmo().await?,
109 config: connection.get_config().await?,
110 connection,
111 })
112 }
113
114 pub async fn read_data(&self) -> Result<Data, Error> {
115 Ok(match &self.connection.root_link_channel {
116 None => data::Scanner::try_from(&self.vmo)?.data(),
117 Some(root_link_channel) => {
118 let vmo_tree = LazyNode::new(root_link_channel.clone()).await?;
119 data::Scanner::try_from(vmo_tree)?.data()
120 }
121 })
122 }
123
124 pub fn metrics(&self) -> Result<Metrics, Error> {
125 Ok(data::Scanner::try_from(&self.vmo)?.metrics())
126 }
127}
128
129struct Connection {
130 fidl: validate::InspectPuppetProxy,
131 root_link_channel: Option<fidl_inspect::TreeProxy>,
134}
135
136impl Connection {
137 async fn connect() -> Result<Self, Error> {
138 let puppet_fidl = fclient::connect_to_protocol::<validate::InspectPuppetMarker>().unwrap();
139 Ok(Self::new(puppet_fidl))
140 }
141
142 async fn get_config(&self) -> Result<Config, Error> {
143 let (printable_name, opts) = self.fidl.get_config().await?;
144 Ok(Config {
145 diff_type: opts.diff_type.into(),
146 printable_name,
147 has_runner_node: opts.has_runner_node.unwrap_or(false),
148 test_archive: true,
149 })
150 }
151
152 async fn fetch_link_channel(
153 fidl: &validate::InspectPuppetProxy,
154 ) -> Option<fidl_inspect::TreeProxy> {
155 let params =
156 validate::InitializationParams { vmo_size: Some(VMO_SIZE), ..Default::default() };
157 let response = fidl.initialize_tree(¶ms).await;
158 if let Ok((Some(tree_client_end), validate::TestResult::Ok)) = response {
159 Some(tree_client_end.into_proxy())
160 } else {
161 None
162 }
163 }
164
165 async fn get_vmo_handle(channel: &fidl_inspect::TreeProxy) -> Result<Vmo, Error> {
166 let tree_content = channel.get_content().await?;
167 let buffer =
168 tree_content.buffer.ok_or_else(|| format_err!("Buffer doesn't contain VMO"))?;
169 Ok(buffer.vmo)
170 }
171
172 fn new(fidl: validate::InspectPuppetProxy) -> Self {
173 Self { fidl, root_link_channel: None }
174 }
175
176 async fn initialize_vmo(&mut self) -> Result<Vmo, Error> {
177 self.root_link_channel = Self::fetch_link_channel(&self.fidl).await;
178 match &self.root_link_channel {
179 Some(root_link_channel) => Self::get_vmo_handle(root_link_channel).await,
180 None => {
181 let params = validate::InitializationParams {
182 vmo_size: Some(VMO_SIZE),
183 ..Default::default()
184 };
185 let handle: Option<zx::NullableHandle>;
186 let out = self.fidl.initialize(¶ms).await?;
187 if let (Some(out_handle), _) = out {
188 handle = Some(out_handle);
189 } else {
190 return Err(format_err!("Didn't get a VMO handle"));
191 }
192 match handle {
193 Some(unwrapped_handle) => Ok(Vmo::from(unwrapped_handle)),
194 None => Err(format_err!("Failed to unwrap handle")),
195 }
196 }
197 }
198 }
199}
200
201#[cfg(test)]
202pub(crate) mod tests {
203 use super::*;
204 use crate::create_node;
205 use anyhow::Context as _;
206 use fidl::endpoints::{RequestStream, ServerEnd, create_proxy};
207 use fidl_diagnostics_validate::{
208 Action, CreateNode, CreateNumericProperty, InspectPuppetMarker, InspectPuppetRequest,
209 InspectPuppetRequestStream, Options, ROOT_ID, TestResult, Value,
210 };
211 use fuchsia_async as fasync;
212 use fuchsia_inspect::{Inspector, InspectorConfig, IntProperty, Node};
213 use futures::prelude::*;
214 use log::info;
215 use std::collections::HashMap;
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}