1use byteorder::{LittleEndian, WriteBytesExt};
6use fidl::endpoints::{create_endpoints, create_proxy, ControlHandle, RequestStream, ServerEnd};
7use fidl_fuchsia_component_decl::{
8 Capability, Component, Dictionary, Expose, ExposeDictionary, ExposeProtocol, ParentRef,
9 Protocol, Ref, SelfRef,
10};
11use fidl_fuchsia_io::{self as fio, DirectoryMarker};
12use fidl_fuchsia_sys2 as fsys2;
13use futures::{StreamExt, TryStreamExt};
14use moniker::Moniker;
15use std::collections::HashMap;
16use std::io::Write;
17use std::path::Path;
18use std::rc::Rc;
19use std::str::FromStr;
20use std::sync::atomic::{AtomicBool, Ordering};
21use zx_status::Status;
22
23#[derive(Default)]
42pub struct MockRealmQueryBuilder {
43 mapping: HashMap<String, Box<MockRealmQueryBuilderInner>>,
44}
45
46pub struct MockRealmQueryBuilderInner {
49 when: Moniker,
50 moniker: Moniker,
51 exposes: Vec<Expose>,
52 parent: Option<Box<MockRealmQueryBuilder>>,
53}
54
55impl MockRealmQueryBuilderInner {
56 pub fn moniker(mut self, moniker: &str) -> Self {
58 self.moniker = moniker.try_into().unwrap();
59 self
60 }
61
62 pub fn exposes(mut self, exposes: Vec<Expose>) -> Self {
64 self.exposes = exposes;
65 self
66 }
67
68 pub fn add(mut self) -> MockRealmQueryBuilder {
70 let mut parent = *self.parent.unwrap();
71 self.parent = None;
72
73 parent.mapping.insert(self.when.to_string(), Box::new(self));
74 parent
75 }
76
77 pub fn serve_exposed_dir(&self, server_end: ServerEnd<DirectoryMarker>, path: &str) {
78 let mut mock_dir_top = MockDir::new("expose".to_owned());
79 let mut mock_accessors = MockDir::new("diagnostics-accessors".to_owned());
80 for expose in &self.exposes {
81 let Expose::Protocol(ExposeProtocol {
82 source_name: Some(name), source_dictionary, ..
83 }) = expose
84 else {
85 continue;
86 };
87 if matches!(source_dictionary, Some(d) if d == "diagnostics-accessors") {
88 mock_accessors = mock_accessors.add_entry(MockFile::new_arc(name.to_owned()));
89 }
90 }
91
92 match path {
93 "diagnostics-accessors" => {
94 fuchsia_async::Task::local(async move {
95 Rc::new(mock_accessors).serve(server_end).await
96 })
97 .detach();
98 }
99 _ => {
100 mock_dir_top = mock_dir_top.add_entry(Rc::new(mock_accessors));
101 fuchsia_async::Task::local(
102 async move { Rc::new(mock_dir_top).serve(server_end).await },
103 )
104 .detach();
105 }
106 }
107 }
108
109 fn to_instance(&self) -> fsys2::Instance {
110 fsys2::Instance {
111 moniker: Some(self.moniker.to_string()),
112 url: Some("".to_owned()),
113 instance_id: None,
114 resolved_info: Some(fsys2::ResolvedInfo {
115 resolved_url: Some("".to_owned()),
116 ..Default::default()
117 }),
118 ..Default::default()
119 }
120 }
121
122 fn make_manifest(&self) -> Component {
123 let capabilities = self
124 .exposes
125 .iter()
126 .map(|expose| match expose {
127 Expose::Protocol(ExposeProtocol {
128 source_name: Some(name),
129 source: Some(Ref::Self_(SelfRef)),
130 ..
131 }) => Capability::Protocol(Protocol {
132 name: Some(name.clone()),
133 source_path: Some(format!("/svc/{}", name)),
134 ..Protocol::default()
135 }),
136 Expose::Dictionary(ExposeDictionary {
137 source_name: Some(name),
138 source: Some(Ref::Self_(SelfRef)),
139 ..
140 }) => Capability::Dictionary(Dictionary {
141 name: Some(name.clone()),
142 source: Some(Ref::Self_(SelfRef)),
143 ..Dictionary::default()
144 }),
145 _ => unreachable!("we just add protocols for the test purposes"),
146 })
147 .collect::<Vec<_>>();
148 Component {
149 capabilities: Some(capabilities),
150 exposes: Some(self.exposes.clone()),
151 ..Default::default()
152 }
153 }
154}
155
156impl MockRealmQueryBuilder {
157 pub fn new() -> Self {
159 Self::default()
160 }
161
162 pub fn when(self, at: &str) -> MockRealmQueryBuilderInner {
165 MockRealmQueryBuilderInner {
166 when: at.try_into().unwrap(),
167 moniker: Moniker::root(),
168 exposes: vec![],
169 parent: Some(Box::new(self)),
170 }
171 }
172
173 pub fn build(self) -> MockRealmQuery {
175 MockRealmQuery { mapping: self.mapping }
176 }
177
178 pub fn prefilled() -> Self {
179 Self::new()
180 .when("example/component")
181 .moniker("./example/component")
182 .exposes(vec![
183 Expose::Protocol(ExposeProtocol {
184 source: Some(Ref::Self_(SelfRef)),
185 target: Some(Ref::Parent(ParentRef)),
186 source_name: Some("fuchsia.diagnostics.ArchiveAccessor".to_owned()),
187 target_name: Some("fuchsia.diagnostics.ArchiveAccessor".to_owned()),
188 source_dictionary: Some("diagnostics-accessors".to_owned()),
189 ..Default::default()
190 }),
191 Expose::Dictionary(ExposeDictionary {
192 source_name: Some("diagnostics-accessors".into()),
193 target_name: Some("diagnostics-accessors".into()),
194 source: Some(Ref::Self_(SelfRef)),
195 target: Some(Ref::Parent(ParentRef)),
196 ..Default::default()
197 }),
198 ])
199 .add()
200 .when("other/component")
201 .moniker("./other/component")
202 .exposes(vec![Expose::Protocol(ExposeProtocol {
203 source: Some(Ref::Self_(SelfRef)),
204 target: Some(Ref::Parent(ParentRef)),
205 source_name: Some("src".to_owned()),
206 target_name: Some("fuchsia.io.SomeOtherThing".to_owned()),
207 ..Default::default()
208 })])
209 .add()
210 .when("other/component")
211 .moniker("./other/component")
212 .exposes(vec![Expose::Protocol(ExposeProtocol {
213 source: Some(Ref::Self_(SelfRef)),
214 target: Some(Ref::Parent(ParentRef)),
215 source_name: Some("src".to_owned()),
216 target_name: Some("fuchsia.io.MagicStuff".to_owned()),
217 ..Default::default()
218 })])
219 .add()
220 .when("foo/component")
221 .moniker("./foo/component")
222 .exposes(vec![
223 Expose::Protocol(ExposeProtocol {
224 source: Some(Ref::Self_(SelfRef)),
225 target: Some(Ref::Parent(ParentRef)),
226 source_name: Some("fuchsia.diagnostics.ArchiveAccessor.feedback".to_owned()),
227 target_name: Some("fuchsia.diagnostics.ArchiveAccessor.feedback".to_owned()),
228 source_dictionary: Some("diagnostics-accessors".to_owned()),
229 ..Default::default()
230 }),
231 Expose::Dictionary(ExposeDictionary {
232 source_name: Some("diagnostics-accessors".into()),
233 target_name: Some("diagnostics-accessors".into()),
234 source: Some(Ref::Self_(SelfRef)),
235 target: Some(Ref::Parent(ParentRef)),
236 ..Default::default()
237 }),
238 ])
239 .add()
240 .when("foo/bar/thing:instance")
241 .moniker("./foo/bar/thing:instance")
242 .exposes(vec![
243 Expose::Protocol(ExposeProtocol {
244 source: Some(Ref::Self_(SelfRef)),
245 target: Some(Ref::Parent(ParentRef)),
246 source_name: Some("fuchsia.diagnostics.ArchiveAccessor.feedback".to_owned()),
247 target_name: Some("fuchsia.diagnostics.ArchiveAccessor.feedback".to_owned()),
248 source_dictionary: Some("diagnostics-accessors".to_owned()),
249 ..Default::default()
250 }),
251 Expose::Dictionary(ExposeDictionary {
252 source_name: Some("diagnostics-accessors".into()),
253 target_name: Some("diagnostics-accessors".into()),
254 source: Some(Ref::Self_(SelfRef)),
255 target: Some(Ref::Parent(ParentRef)),
256 ..Default::default()
257 }),
258 ])
259 .add()
260 }
261}
262
263pub struct MockRealmQuery {
265 mapping: HashMap<String, Box<MockRealmQueryBuilderInner>>,
267}
268
269impl Default for MockRealmQuery {
271 fn default() -> Self {
272 MockRealmQueryBuilder::prefilled().build()
273 }
274}
275
276impl MockRealmQuery {
277 pub async fn serve(self: Rc<Self>, object: ServerEnd<fsys2::RealmQueryMarker>) {
279 let mut stream = object.into_stream();
280 while let Ok(Some(request)) = stream.try_next().await {
281 match request {
282 fsys2::RealmQueryRequest::GetInstance { moniker, responder } => {
283 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
284 let res = self.mapping.get(&query_moniker.to_string()).unwrap();
285 responder.send(Ok(&res.to_instance())).unwrap();
286 }
287 fsys2::RealmQueryRequest::DeprecatedOpen {
288 moniker,
289 dir_type,
290 object,
291 responder,
292 path,
293 ..
294 } => {
295 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
296 if let Some(res) = self.mapping.get(&query_moniker.to_string()) {
297 if dir_type == fsys2::OpenDirType::ExposedDir {
298 res.serve_exposed_dir(object.into_channel().into(), &path);
300 }
301 responder.send(Ok(())).unwrap();
302 } else {
303 responder.send(Err(fsys2::OpenError::InstanceNotFound)).unwrap();
304 }
305 }
306 fsys2::RealmQueryRequest::OpenDirectory {
307 moniker,
308 dir_type,
309 object,
310 responder,
311 ..
312 } => {
313 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
314 if let Some(res) = self.mapping.get(&query_moniker.to_string()) {
315 if dir_type == fsys2::OpenDirType::OutgoingDir {
316 res.serve_exposed_dir(object, "");
318 }
319 responder.send(Ok(())).unwrap();
320 } else {
321 responder.send(Err(fsys2::OpenError::InstanceNotFound)).unwrap();
322 }
323 }
324 fsys2::RealmQueryRequest::GetManifest { moniker, responder, .. } => {
325 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
326 let res = self.mapping.get(&query_moniker.to_string()).unwrap();
327 let manifest = res.make_manifest();
328 let manifest = fidl::persist(&manifest).unwrap();
329 let (client_end, server_end) =
330 create_endpoints::<fsys2::ManifestBytesIteratorMarker>();
331
332 fuchsia_async::Task::spawn(async move {
333 let mut stream = server_end.into_stream();
334 let fsys2::ManifestBytesIteratorRequest::Next { responder } =
335 stream.next().await.unwrap().unwrap();
336 responder.send(manifest.as_slice()).unwrap();
337 let fsys2::ManifestBytesIteratorRequest::Next { responder } =
338 stream.next().await.unwrap().unwrap();
339 responder.send(&[]).unwrap();
340 })
341 .detach();
342
343 responder.send(Ok(client_end)).unwrap();
344 }
345 fsys2::RealmQueryRequest::GetResolvedDeclaration { moniker, responder, .. } => {
346 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
347 let res = self.mapping.get(&query_moniker.to_string()).unwrap();
348 let manifest = res.make_manifest();
349 let manifest = fidl::persist(&manifest).unwrap();
350 let (client_end, server_end) =
351 create_endpoints::<fsys2::ManifestBytesIteratorMarker>();
352
353 fuchsia_async::Task::spawn(async move {
354 let mut stream = server_end.into_stream();
355 let fsys2::ManifestBytesIteratorRequest::Next { responder } =
356 stream.next().await.unwrap().unwrap();
357 responder.send(manifest.as_slice()).unwrap();
358 let fsys2::ManifestBytesIteratorRequest::Next { responder } =
359 stream.next().await.unwrap().unwrap();
360 responder.send(&[]).unwrap();
361 })
362 .detach();
363
364 responder.send(Ok(client_end)).unwrap();
365 }
366 fsys2::RealmQueryRequest::GetAllInstances { responder } => {
367 let instances: Vec<fsys2::Instance> =
368 self.mapping.values().map(|m| m.to_instance()).collect();
369
370 let (client_end, server_end) =
371 create_endpoints::<fsys2::InstanceIteratorMarker>();
372
373 fuchsia_async::Task::spawn(async move {
374 let mut stream = server_end.into_stream();
375 let fsys2::InstanceIteratorRequest::Next { responder } =
376 stream.next().await.unwrap().unwrap();
377 responder.send(&instances).unwrap();
378 let fsys2::InstanceIteratorRequest::Next { responder } =
379 stream.next().await.unwrap().unwrap();
380 responder.send(&[]).unwrap();
381 })
382 .detach();
383
384 responder.send(Ok(client_end)).unwrap();
385 }
386 _ => unreachable!("request {:?}", request),
387 }
388 }
389 }
390
391 pub async fn get_proxy(self: Rc<Self>) -> fsys2::RealmQueryProxy {
395 let (proxy, server_end) = create_proxy::<fsys2::RealmQueryMarker>();
396 fuchsia_async::Task::local(async move { self.serve(server_end).await }).detach();
397 proxy
398 }
399}
400
401pub trait Entry {
403 fn open(self: Rc<Self>, flags: fio::OpenFlags, path: &str, object: ServerEnd<fio::NodeMarker>);
404 fn encode(&self, buf: &mut Vec<u8>);
405 fn name(&self) -> String;
406}
407
408pub struct MockDir {
409 subdirs: HashMap<String, Rc<dyn Entry>>,
410 name: String,
411 at_end: AtomicBool,
412}
413
414impl MockDir {
415 pub fn new(name: String) -> Self {
416 MockDir { name, subdirs: HashMap::new(), at_end: AtomicBool::new(false) }
417 }
418
419 pub fn new_arc(name: String) -> Rc<Self> {
420 Rc::new(Self::new(name))
421 }
422
423 pub fn add_entry(mut self, entry: Rc<dyn Entry>) -> Self {
424 self.subdirs.insert(entry.name(), entry);
425 self
426 }
427
428 async fn serve(self: Rc<Self>, object: ServerEnd<fio::DirectoryMarker>) {
429 let mut stream = object.into_stream();
430 let _ = stream.control_handle().send_on_open_(
431 Status::OK.into_raw(),
432 Some(fio::NodeInfoDeprecated::Directory(fio::DirectoryObject {})),
433 );
434 while let Ok(Some(request)) = stream.try_next().await {
435 match request {
436 fio::DirectoryRequest::DeprecatedOpen { flags, mode: _, path, object, .. } => {
437 self.clone().open(flags, &path, object);
438 }
439 fio::DirectoryRequest::Rewind { responder, .. } => {
440 self.at_end.store(false, Ordering::Relaxed);
441 responder.send(Status::OK.into_raw()).unwrap();
442 }
443 fio::DirectoryRequest::ReadDirents { max_bytes: _, responder, .. } => {
444 let entries = match self.at_end.compare_exchange(
445 false,
446 true,
447 Ordering::Relaxed,
448 Ordering::Relaxed,
449 ) {
450 Ok(false) => encode_entries(&self.subdirs),
451 Err(true) => Vec::new(),
452 _ => unreachable!(),
453 };
454 responder.send(Status::OK.into_raw(), &entries).unwrap();
455 }
456 x => panic!("unsupported request: {:?}", x),
457 }
458 }
459 }
460}
461
462fn encode_entries(subdirs: &HashMap<String, Rc<dyn Entry>>) -> Vec<u8> {
463 let mut buf = Vec::new();
464 let mut data = subdirs.iter().collect::<Vec<(_, _)>>();
465 data.sort_by(|a, b| a.0.cmp(b.0));
466 for (_, entry) in data.iter() {
467 entry.encode(&mut buf);
468 }
469 buf
470}
471
472impl Entry for MockDir {
473 fn open(self: Rc<Self>, flags: fio::OpenFlags, path: &str, object: ServerEnd<fio::NodeMarker>) {
474 let path = Path::new(path);
475 let mut path_iter = path.iter();
476 let segment = if let Some(segment) = path_iter.next() {
477 if let Some(segment) = segment.to_str() {
478 segment
479 } else {
480 send_error(object, Status::NOT_FOUND);
481 return;
482 }
483 } else {
484 "."
485 };
486 if segment == "." {
487 fuchsia_async::Task::local(self.serve(ServerEnd::new(object.into_channel()))).detach();
488 return;
489 }
490 if let Some(entry) = self.subdirs.get(segment) {
491 entry.clone().open(flags, path_iter.as_path().to_str().unwrap(), object);
492 } else {
493 send_error(object, Status::NOT_FOUND);
494 }
495 }
496
497 fn encode(&self, buf: &mut Vec<u8>) {
498 buf.write_u64::<LittleEndian>(fio::INO_UNKNOWN).expect("writing mockdir ino to work");
499 buf.write_u8(self.name.len() as u8).expect("writing mockdir size to work");
500 buf.write_u8(fio::DirentType::Directory.into_primitive())
501 .expect("writing mockdir type to work");
502 buf.write_all(self.name.as_ref()).expect("writing mockdir name to work");
503 }
504
505 fn name(&self) -> String {
506 self.name.clone()
507 }
508}
509
510impl Entry for fio::DirectoryProxy {
511 fn open(self: Rc<Self>, flags: fio::OpenFlags, path: &str, object: ServerEnd<fio::NodeMarker>) {
512 let _ = fio::DirectoryProxy::deprecated_open(
513 &self,
514 flags,
515 fio::ModeType::empty(),
516 path,
517 object,
518 );
519 }
520
521 fn encode(&self, _buf: &mut Vec<u8>) {
522 unimplemented!();
523 }
524
525 fn name(&self) -> String {
526 unimplemented!();
527 }
528}
529
530struct MockFile {
531 name: String,
532}
533
534impl MockFile {
535 pub fn new(name: String) -> Self {
536 MockFile { name }
537 }
538 pub fn new_arc(name: String) -> Rc<Self> {
539 Rc::new(Self::new(name))
540 }
541}
542
543impl Entry for MockFile {
544 fn open(
545 self: Rc<Self>,
546 _flags: fio::OpenFlags,
547 _path: &str,
548 _object: ServerEnd<fio::NodeMarker>,
549 ) {
550 unimplemented!();
551 }
552
553 fn encode(&self, buf: &mut Vec<u8>) {
554 buf.write_u64::<LittleEndian>(fio::INO_UNKNOWN).expect("writing mockdir ino to work");
555 buf.write_u8(self.name.len() as u8).expect("writing mockdir size to work");
556 buf.write_u8(fio::DirentType::Service.into_primitive())
557 .expect("writing mockdir type to work");
558 buf.write_all(self.name.as_ref()).expect("writing mockdir name to work");
559 }
560
561 fn name(&self) -> String {
562 self.name.clone()
563 }
564}
565
566fn send_error(object: ServerEnd<fio::NodeMarker>, status: Status) {
567 let stream = object.into_stream();
568 let control_handle = stream.control_handle();
569 let _ = control_handle.send_on_open_(status.into_raw(), None);
570 control_handle.shutdown_with_epitaph(status);
571}