1use byteorder::{LittleEndian, WriteBytesExt};
6use fdomain_client::AsHandleRef;
7use fdomain_client::fidl::{RequestStream as FRequestStream, ServerEnd as FServerEnd};
8use fidl::endpoints::{ServerEnd, create_endpoints, create_proxy};
9use fidl_fuchsia_component_decl::{
10 Capability, Component, Dictionary, Expose, ExposeDictionary, ExposeProtocol, ParentRef,
11 Protocol, Ref, SelfRef,
12};
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;
22use {
23 fdomain_fuchsia_io as fio_f, fdomain_fuchsia_sys2 as fsys2_f, fidl_fuchsia_io as fio,
24 fidl_fuchsia_sys2 as fsys2,
25};
26
27#[derive(Default)]
46pub struct MockRealmQueryBuilder {
47 mapping: HashMap<String, Box<MockRealmQueryBuilderInner>>,
48}
49
50pub struct MockRealmQueryBuilderInner {
53 when: Moniker,
54 moniker: Moniker,
55 exposes: Vec<Expose>,
56 parent: Option<Box<MockRealmQueryBuilder>>,
57}
58
59impl MockRealmQueryBuilderInner {
60 pub fn moniker(mut self, moniker: &str) -> Self {
62 self.moniker = moniker.try_into().unwrap();
63 self
64 }
65
66 pub fn exposes(mut self, exposes: Vec<Expose>) -> Self {
68 self.exposes = exposes;
69 self
70 }
71
72 pub fn add(mut self) -> MockRealmQueryBuilder {
74 let mut parent = *self.parent.unwrap();
75 self.parent = None;
76
77 parent.mapping.insert(self.when.to_string(), Box::new(self));
78 parent
79 }
80
81 pub fn serve_exposed_dir_f(&self, server_end: FServerEnd<fio_f::DirectoryMarker>, path: &str) {
82 let mut mock_dir_top = MockDir::new("expose".to_owned());
83 let mut mock_accessors = MockDir::new("diagnostics-accessors".to_owned());
84 for expose in &self.exposes {
85 let Expose::Protocol(ExposeProtocol {
86 source_name: Some(name), source_dictionary, ..
87 }) = expose
88 else {
89 continue;
90 };
91 if matches!(source_dictionary, Some(d) if d == "diagnostics-accessors") {
92 mock_accessors = mock_accessors.add_entry(MockFile::new_arc(name.to_owned()));
93 }
94 }
95
96 match path {
97 "diagnostics-accessors" => {
98 fuchsia_async::Task::local(async move {
99 Rc::new(mock_accessors).serve_f(server_end).await
100 })
101 .detach();
102 }
103 _ => {
104 mock_dir_top = mock_dir_top.add_entry(Rc::new(mock_accessors));
105 fuchsia_async::Task::local(async move {
106 Rc::new(mock_dir_top).serve_f(server_end).await
107 })
108 .detach();
109 }
110 }
111 }
112
113 pub fn serve_exposed_dir(&self, server_end: ServerEnd<fio::DirectoryMarker>, path: &str) {
114 let mut mock_dir_top = MockDir::new("expose".to_owned());
115 let mut mock_accessors = MockDir::new("diagnostics-accessors".to_owned());
116 for expose in &self.exposes {
117 let Expose::Protocol(ExposeProtocol {
118 source_name: Some(name), source_dictionary, ..
119 }) = expose
120 else {
121 continue;
122 };
123 if matches!(source_dictionary, Some(d) if d == "diagnostics-accessors") {
124 mock_accessors = mock_accessors.add_entry(MockFile::new_arc(name.to_owned()));
125 }
126 }
127
128 match path {
129 "diagnostics-accessors" => {
130 fuchsia_async::Task::local(async move {
131 Rc::new(mock_accessors).serve(server_end).await
132 })
133 .detach();
134 }
135 _ => {
136 mock_dir_top = mock_dir_top.add_entry(Rc::new(mock_accessors));
137 fuchsia_async::Task::local(
138 async move { Rc::new(mock_dir_top).serve(server_end).await },
139 )
140 .detach();
141 }
142 }
143 }
144
145 fn to_instance(&self) -> fsys2::Instance {
146 fsys2::Instance {
147 moniker: Some(self.moniker.to_string()),
148 url: Some("".to_owned()),
149 instance_id: None,
150 resolved_info: Some(fsys2::ResolvedInfo {
151 resolved_url: Some("".to_owned()),
152 ..Default::default()
153 }),
154 ..Default::default()
155 }
156 }
157
158 fn make_manifest(&self) -> Component {
159 let capabilities = self
160 .exposes
161 .iter()
162 .map(|expose| match expose {
163 Expose::Protocol(ExposeProtocol {
164 source_name: Some(name),
165 source: Some(Ref::Self_(SelfRef)),
166 ..
167 }) => Capability::Protocol(Protocol {
168 name: Some(name.clone()),
169 source_path: Some(format!("/svc/{name}")),
170 ..Protocol::default()
171 }),
172 Expose::Dictionary(ExposeDictionary {
173 source_name: Some(name),
174 source: Some(Ref::Self_(SelfRef)),
175 ..
176 }) => Capability::Dictionary(Dictionary {
177 name: Some(name.clone()),
178 source: Some(Ref::Self_(SelfRef)),
179 ..Dictionary::default()
180 }),
181 _ => unreachable!("we just add protocols for the test purposes"),
182 })
183 .collect::<Vec<_>>();
184 Component {
185 capabilities: Some(capabilities),
186 exposes: Some(self.exposes.clone()),
187 ..Default::default()
188 }
189 }
190}
191
192impl MockRealmQueryBuilder {
193 pub fn new() -> Self {
195 Self::default()
196 }
197
198 pub fn when(self, at: &str) -> MockRealmQueryBuilderInner {
201 MockRealmQueryBuilderInner {
202 when: at.try_into().unwrap(),
203 moniker: Moniker::root(),
204 exposes: vec![],
205 parent: Some(Box::new(self)),
206 }
207 }
208
209 pub fn build(self) -> MockRealmQuery {
211 MockRealmQuery { mapping: self.mapping }
212 }
213
214 pub fn prefilled() -> Self {
215 Self::new()
216 .when("example/component")
217 .moniker("./example/component")
218 .exposes(vec![
219 Expose::Protocol(ExposeProtocol {
220 source: Some(Ref::Self_(SelfRef)),
221 target: Some(Ref::Parent(ParentRef)),
222 source_name: Some("fuchsia.diagnostics.ArchiveAccessor".to_owned()),
223 target_name: Some("fuchsia.diagnostics.ArchiveAccessor".to_owned()),
224 source_dictionary: Some("diagnostics-accessors".to_owned()),
225 ..Default::default()
226 }),
227 Expose::Dictionary(ExposeDictionary {
228 source_name: Some("diagnostics-accessors".into()),
229 target_name: Some("diagnostics-accessors".into()),
230 source: Some(Ref::Self_(SelfRef)),
231 target: Some(Ref::Parent(ParentRef)),
232 ..Default::default()
233 }),
234 ])
235 .add()
236 .when("other/component")
237 .moniker("./other/component")
238 .exposes(vec![Expose::Protocol(ExposeProtocol {
239 source: Some(Ref::Self_(SelfRef)),
240 target: Some(Ref::Parent(ParentRef)),
241 source_name: Some("src".to_owned()),
242 target_name: Some("fuchsia.io.SomeOtherThing".to_owned()),
243 ..Default::default()
244 })])
245 .add()
246 .when("other/component")
247 .moniker("./other/component")
248 .exposes(vec![Expose::Protocol(ExposeProtocol {
249 source: Some(Ref::Self_(SelfRef)),
250 target: Some(Ref::Parent(ParentRef)),
251 source_name: Some("src".to_owned()),
252 target_name: Some("fuchsia.io.MagicStuff".to_owned()),
253 ..Default::default()
254 })])
255 .add()
256 .when("foo/component")
257 .moniker("./foo/component")
258 .exposes(vec![
259 Expose::Protocol(ExposeProtocol {
260 source: Some(Ref::Self_(SelfRef)),
261 target: Some(Ref::Parent(ParentRef)),
262 source_name: Some("fuchsia.diagnostics.ArchiveAccessor.feedback".to_owned()),
263 target_name: Some("fuchsia.diagnostics.ArchiveAccessor.feedback".to_owned()),
264 source_dictionary: Some("diagnostics-accessors".to_owned()),
265 ..Default::default()
266 }),
267 Expose::Dictionary(ExposeDictionary {
268 source_name: Some("diagnostics-accessors".into()),
269 target_name: Some("diagnostics-accessors".into()),
270 source: Some(Ref::Self_(SelfRef)),
271 target: Some(Ref::Parent(ParentRef)),
272 ..Default::default()
273 }),
274 ])
275 .add()
276 .when("foo/bar/thing:instance")
277 .moniker("./foo/bar/thing:instance")
278 .exposes(vec![
279 Expose::Protocol(ExposeProtocol {
280 source: Some(Ref::Self_(SelfRef)),
281 target: Some(Ref::Parent(ParentRef)),
282 source_name: Some("fuchsia.diagnostics.ArchiveAccessor.feedback".to_owned()),
283 target_name: Some("fuchsia.diagnostics.ArchiveAccessor.feedback".to_owned()),
284 source_dictionary: Some("diagnostics-accessors".to_owned()),
285 ..Default::default()
286 }),
287 Expose::Dictionary(ExposeDictionary {
288 source_name: Some("diagnostics-accessors".into()),
289 target_name: Some("diagnostics-accessors".into()),
290 source: Some(Ref::Self_(SelfRef)),
291 target: Some(Ref::Parent(ParentRef)),
292 ..Default::default()
293 }),
294 ])
295 .add()
296 }
297}
298
299pub struct MockRealmQuery {
301 mapping: HashMap<String, Box<MockRealmQueryBuilderInner>>,
303}
304
305impl Default for MockRealmQuery {
307 fn default() -> Self {
308 MockRealmQueryBuilder::prefilled().build()
309 }
310}
311
312impl MockRealmQuery {
313 pub async fn serve_f(self: Rc<Self>, object: FServerEnd<fsys2_f::RealmQueryMarker>) {
315 let client = object.domain();
316 let mut stream = object.into_stream();
317 while let Ok(Some(request)) = stream.try_next().await {
318 match request {
319 fsys2_f::RealmQueryRequest::GetInstance { moniker, responder } => {
320 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
321 let res = self.mapping.get(&query_moniker.to_string()).unwrap();
322 responder.send(Ok(&res.to_instance())).unwrap();
323 }
324 fsys2_f::RealmQueryRequest::OpenDirectory {
325 moniker,
326 dir_type,
327 object,
328 responder,
329 ..
330 } => {
331 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
332 if let Some(res) = self.mapping.get(&query_moniker.to_string()) {
333 if dir_type == fsys2_f::OpenDirType::OutgoingDir {
334 res.serve_exposed_dir_f(object, "");
336 }
337 responder.send(Ok(())).unwrap();
338 } else {
339 responder.send(Err(fsys2_f::OpenError::InstanceNotFound)).unwrap();
340 }
341 }
342 fsys2_f::RealmQueryRequest::GetResolvedDeclaration {
343 moniker, responder, ..
344 } => {
345 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
346 let res = self.mapping.get(&query_moniker.to_string()).unwrap();
347 let manifest = res.make_manifest();
348 let manifest = fidl::persist(&manifest).unwrap();
349 let (client_end, server_end) =
350 client.create_endpoints::<fsys2_f::ManifestBytesIteratorMarker>();
351
352 fuchsia_async::Task::spawn(async move {
353 let mut stream = server_end.into_stream();
354 let fsys2_f::ManifestBytesIteratorRequest::Next { responder } =
355 stream.next().await.unwrap().unwrap();
356 responder.send(manifest.as_slice()).unwrap();
357 let fsys2_f::ManifestBytesIteratorRequest::Next { responder } =
358 stream.next().await.unwrap().unwrap();
359 responder.send(&[]).unwrap();
360 })
361 .detach();
362
363 responder.send(Ok(client_end)).unwrap();
364 }
365 fsys2_f::RealmQueryRequest::GetAllInstances { responder } => {
366 let instances: Vec<fsys2_f::Instance> =
367 self.mapping.values().map(|m| m.to_instance()).collect();
368
369 let (client_end, server_end) =
370 client.create_endpoints::<fsys2_f::InstanceIteratorMarker>();
371
372 fuchsia_async::Task::spawn(async move {
373 let mut stream = server_end.into_stream();
374 let fsys2_f::InstanceIteratorRequest::Next { responder } =
375 stream.next().await.unwrap().unwrap();
376 responder.send(&instances).unwrap();
377 let fsys2_f::InstanceIteratorRequest::Next { responder } =
378 stream.next().await.unwrap().unwrap();
379 responder.send(&[]).unwrap();
380 })
381 .detach();
382
383 responder.send(Ok(client_end)).unwrap();
384 }
385 _ => unreachable!("request {:?}", request),
386 }
387 }
388 }
389
390 pub async fn serve(self: Rc<Self>, object: ServerEnd<fsys2::RealmQueryMarker>) {
392 let mut stream = object.into_stream();
393 while let Ok(Some(request)) = stream.try_next().await {
394 match request {
395 fsys2::RealmQueryRequest::GetInstance { moniker, responder } => {
396 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
397 let res = self.mapping.get(&query_moniker.to_string()).unwrap();
398 responder.send(Ok(&res.to_instance())).unwrap();
399 }
400 fsys2::RealmQueryRequest::OpenDirectory {
401 moniker,
402 dir_type,
403 object,
404 responder,
405 ..
406 } => {
407 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
408 if let Some(res) = self.mapping.get(&query_moniker.to_string()) {
409 if dir_type == fsys2::OpenDirType::ExposedDir {
410 res.serve_exposed_dir(object, "");
412 }
413 responder.send(Ok(())).unwrap();
414 } else {
415 responder.send(Err(fsys2::OpenError::InstanceNotFound)).unwrap();
416 }
417 }
418 fsys2::RealmQueryRequest::GetResolvedDeclaration { moniker, responder, .. } => {
419 let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
420 let res = self.mapping.get(&query_moniker.to_string()).unwrap();
421 let manifest = res.make_manifest();
422 let manifest = fidl::persist(&manifest).unwrap();
423 let (client_end, server_end) =
424 create_endpoints::<fsys2::ManifestBytesIteratorMarker>();
425
426 fuchsia_async::Task::spawn(async move {
427 let mut stream = server_end.into_stream();
428 let fsys2::ManifestBytesIteratorRequest::Next { responder } =
429 stream.next().await.unwrap().unwrap();
430 responder.send(manifest.as_slice()).unwrap();
431 let fsys2::ManifestBytesIteratorRequest::Next { responder } =
432 stream.next().await.unwrap().unwrap();
433 responder.send(&[]).unwrap();
434 })
435 .detach();
436
437 responder.send(Ok(client_end)).unwrap();
438 }
439 fsys2::RealmQueryRequest::GetAllInstances { responder } => {
440 let instances: Vec<fsys2::Instance> =
441 self.mapping.values().map(|m| m.to_instance()).collect();
442
443 let (client_end, server_end) =
444 create_endpoints::<fsys2::InstanceIteratorMarker>();
445
446 fuchsia_async::Task::spawn(async move {
447 let mut stream = server_end.into_stream();
448 let fsys2::InstanceIteratorRequest::Next { responder } =
449 stream.next().await.unwrap().unwrap();
450 responder.send(&instances).unwrap();
451 let fsys2::InstanceIteratorRequest::Next { responder } =
452 stream.next().await.unwrap().unwrap();
453 responder.send(&[]).unwrap();
454 })
455 .detach();
456
457 responder.send(Ok(client_end)).unwrap();
458 }
459 _ => unreachable!("request {:?}", request),
460 }
461 }
462 }
463
464 pub async fn get_proxy(self: Rc<Self>) -> fsys2::RealmQueryProxy {
468 let (proxy, server_end) = create_proxy::<fsys2::RealmQueryMarker>();
469 fuchsia_async::Task::local(async move { self.serve(server_end).await }).detach();
470 proxy
471 }
472}
473
474pub trait Entry {
476 fn open(self: Rc<Self>, path: &str, object: fidl::Channel);
477 fn open_f(self: Rc<Self>, path: &str, object: fdomain_client::Channel);
478 fn encode(&self, buf: &mut Vec<u8>);
479 fn name(&self) -> String;
480}
481
482pub struct MockDir {
483 subdirs: HashMap<String, Rc<dyn Entry>>,
484 name: String,
485 at_end: AtomicBool,
486}
487
488impl MockDir {
489 pub fn new(name: String) -> Self {
490 MockDir { name, subdirs: HashMap::new(), at_end: AtomicBool::new(false) }
491 }
492
493 pub fn new_arc(name: String) -> Rc<Self> {
494 Rc::new(Self::new(name))
495 }
496
497 pub fn add_entry(mut self, entry: Rc<dyn Entry>) -> Self {
498 self.subdirs.insert(entry.name(), entry);
499 self
500 }
501
502 async fn serve_f(self: Rc<Self>, object: FServerEnd<fio_f::DirectoryMarker>) {
503 let mut stream = object.into_stream();
504 let _ = stream.control_handle().send_on_open_(
505 Status::OK.into_raw(),
506 Some(fio_f::NodeInfoDeprecated::Directory(fio_f::DirectoryObject {})),
507 );
508 while let Ok(Some(request)) = stream.try_next().await {
509 match request {
510 fio_f::DirectoryRequest::Open { path, object, .. } => {
511 self.clone().open_f(&path, object);
512 }
513 fio_f::DirectoryRequest::Rewind { responder, .. } => {
514 self.at_end.store(false, Ordering::Relaxed);
515 responder.send(Status::OK.into_raw()).unwrap();
516 }
517 fio_f::DirectoryRequest::ReadDirents { max_bytes: _, responder, .. } => {
518 let entries = match self.at_end.compare_exchange(
519 false,
520 true,
521 Ordering::Relaxed,
522 Ordering::Relaxed,
523 ) {
524 Ok(false) => encode_entries(&self.subdirs),
525 Err(true) => Vec::new(),
526 _ => unreachable!(),
527 };
528 responder.send(Status::OK.into_raw(), &entries).unwrap();
529 }
530 x => panic!("unsupported request: {x:?}"),
531 }
532 }
533 }
534
535 async fn serve(self: Rc<Self>, object: ServerEnd<fio::DirectoryMarker>) {
536 let mut stream = object.into_stream();
537 while let Ok(Some(request)) = stream.try_next().await {
538 match request {
539 fio::DirectoryRequest::Open { path, object, .. } => {
540 self.clone().open(&path, object);
541 }
542 fio::DirectoryRequest::Rewind { responder, .. } => {
543 self.at_end.store(false, Ordering::Relaxed);
544 responder.send(Status::OK.into_raw()).unwrap();
545 }
546 fio::DirectoryRequest::ReadDirents { max_bytes: _, responder, .. } => {
547 let entries = match self.at_end.compare_exchange(
548 false,
549 true,
550 Ordering::Relaxed,
551 Ordering::Relaxed,
552 ) {
553 Ok(false) => encode_entries(&self.subdirs),
554 Err(true) => Vec::new(),
555 _ => unreachable!(),
556 };
557 responder.send(Status::OK.into_raw(), &entries).unwrap();
558 }
559 x => panic!("unsupported request: {x:?}"),
560 }
561 }
562 }
563}
564
565fn encode_entries(subdirs: &HashMap<String, Rc<dyn Entry>>) -> Vec<u8> {
566 let mut buf = Vec::new();
567 let mut data = subdirs.iter().collect::<Vec<(_, _)>>();
568 data.sort_by(|a, b| a.0.cmp(b.0));
569 for (_, entry) in data.iter() {
570 entry.encode(&mut buf);
571 }
572 buf
573}
574
575impl Entry for MockDir {
576 fn open(self: Rc<Self>, path: &str, object: fidl::Channel) {
577 let path = Path::new(path);
578 let mut path_iter = path.iter();
579 let segment = if let Some(segment) = path_iter.next() {
580 if let Some(segment) = segment.to_str() {
581 segment
582 } else {
583 let _ =
584 ServerEnd::<fio::NodeMarker>::new(object).close_with_epitaph(Status::NOT_FOUND);
585 return;
586 }
587 } else {
588 "."
589 };
590 if segment == "." {
591 fuchsia_async::Task::local(self.serve(ServerEnd::new(object))).detach();
592 return;
593 }
594 if let Some(entry) = self.subdirs.get(segment) {
595 entry.clone().open(path_iter.as_path().to_str().unwrap(), object);
596 } else {
597 let _ = ServerEnd::<fio::NodeMarker>::new(object).close_with_epitaph(Status::NOT_FOUND);
598 }
599 }
600
601 fn open_f(self: Rc<Self>, path: &str, object: fdomain_client::Channel) {
602 let path = Path::new(path);
603 let mut path_iter = path.iter();
604 let segment = if let Some(segment) = path_iter.next() {
605 if let Some(segment) = segment.to_str() {
606 segment
607 } else {
608 let _ = FServerEnd::<fio_f::NodeMarker>::new(object)
609 .close_with_epitaph(Status::NOT_FOUND);
610 return;
611 }
612 } else {
613 "."
614 };
615 if segment == "." {
616 fuchsia_async::Task::local(self.serve_f(FServerEnd::new(object))).detach();
617 return;
618 }
619 if let Some(entry) = self.subdirs.get(segment) {
620 entry.clone().open_f(path_iter.as_path().to_str().unwrap(), object);
621 } else {
622 let _ =
623 FServerEnd::<fio_f::NodeMarker>::new(object).close_with_epitaph(Status::NOT_FOUND);
624 }
625 }
626
627 fn encode(&self, buf: &mut Vec<u8>) {
628 buf.write_u64::<LittleEndian>(fio::INO_UNKNOWN).expect("writing mockdir ino to work");
629 buf.write_u8(self.name.len() as u8).expect("writing mockdir size to work");
630 buf.write_u8(fio::DirentType::Directory.into_primitive())
631 .expect("writing mockdir type to work");
632 buf.write_all(self.name.as_ref()).expect("writing mockdir name to work");
633 }
634
635 fn name(&self) -> String {
636 self.name.clone()
637 }
638}
639
640struct MockFile {
641 name: String,
642}
643
644impl MockFile {
645 pub fn new(name: String) -> Self {
646 MockFile { name }
647 }
648 pub fn new_arc(name: String) -> Rc<Self> {
649 Rc::new(Self::new(name))
650 }
651}
652
653impl Entry for MockFile {
654 fn open(self: Rc<Self>, _path: &str, _object: fidl::Channel) {
655 unimplemented!();
656 }
657
658 fn open_f(self: Rc<Self>, _path: &str, _object: fdomain_client::Channel) {
659 unimplemented!();
660 }
661
662 fn encode(&self, buf: &mut Vec<u8>) {
663 buf.write_u64::<LittleEndian>(fio::INO_UNKNOWN).expect("writing mockdir ino to work");
664 buf.write_u8(self.name.len() as u8).expect("writing mockdir size to work");
665 buf.write_u8(fio::DirentType::Service.into_primitive())
666 .expect("writing mockdir type to work");
667 buf.write_all(self.name.as_ref()).expect("writing mockdir name to work");
668 }
669
670 fn name(&self) -> String {
671 self.name.clone()
672 }
673}