iquery_test_support/
test_support.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use byteorder::{LittleEndian, WriteBytesExt};
6use fdomain_client::fidl::{RequestStream as FRequestStream, ServerEnd as FServerEnd};
7use fdomain_client::AsHandleRef;
8use fidl::endpoints::{create_endpoints, create_proxy, ServerEnd};
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/// Builder struct for `RealmQueryResult`/
28/// This is an builder interface meant to simplify building of test fixtures.
29/// Example usage:
30/// ```
31///   MockRealmQuery.add()
32///   .when("other/component") // when client queries for this string ("other/component").
33///   .moniker("./other/component") // Returns the following.
34///   .exposes(vec![Expose::Protocol(ExposeProtocol {
35///       source: Some(Ref::Self_(SelfRef)),
36///       target: Some(Ref::Self_(SelfRef)),
37///       source_name: Some("src".to_owned()),
38///       target_name: Some("fuchsia.io.SomeOtherThing".to_owned()),
39///       ..Default::default()
40///   })])
41///   .add() // Finish building the result.
42///   .when("some/thing") // Start another build.
43///   ...
44/// ```
45#[derive(Default)]
46pub struct MockRealmQueryBuilder {
47    mapping: HashMap<String, Box<MockRealmQueryBuilderInner>>,
48}
49
50/// Inner struct of `MockRealmQueryBuilder` to provide a builder interface for
51/// RealmQuery protocol responses.
52pub struct MockRealmQueryBuilderInner {
53    when: Moniker,
54    moniker: Moniker,
55    exposes: Vec<Expose>,
56    parent: Option<Box<MockRealmQueryBuilder>>,
57}
58
59impl MockRealmQueryBuilderInner {
60    /// Sets the result moniker.
61    pub fn moniker(mut self, moniker: &str) -> Self {
62        self.moniker = moniker.try_into().unwrap();
63        self
64    }
65
66    /// Sets the result vector of `Expose`s.
67    pub fn exposes(mut self, exposes: Vec<Expose>) -> Self {
68        self.exposes = exposes;
69        self
70    }
71
72    /// Completes the build and returns a `MockRealmQueryBuilder`.
73    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    /// Create a new empty `MockRealmQueryBuilder`.
194    pub fn new() -> Self {
195        Self::default()
196    }
197
198    /// Start a build of `RealmQueryResult` by specifying the
199    /// expected query string.
200    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    /// Finish the build and return servable `MockRealmQuery`.
210    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
299/// Provides a mock `RealmQuery` interface.
300pub struct MockRealmQuery {
301    /// Mapping from Moniker -> Expose.
302    mapping: HashMap<String, Box<MockRealmQueryBuilderInner>>,
303}
304
305/// Creates the default test fixures for `MockRealmQuery`.
306impl Default for MockRealmQuery {
307    fn default() -> Self {
308        MockRealmQueryBuilder::prefilled().build()
309    }
310}
311
312impl MockRealmQuery {
313    /// Serves the `RealmQuery` interface asynchronously and runs until the program terminates.
314    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::Open {
325                    moniker,
326                    dir_type,
327                    object,
328                    responder,
329                    path,
330                    ..
331                } => {
332                    let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
333                    if let Some(res) = self.mapping.get(&query_moniker.to_string()) {
334                        if dir_type == fsys2_f::OpenDirType::ExposedDir {
335                            // Serve the out dir, everything else doesn't get served.
336                            res.serve_exposed_dir_f(object.into_channel().into(), &path);
337                        }
338                        responder.send(Ok(())).unwrap();
339                    } else {
340                        responder.send(Err(fsys2_f::OpenError::InstanceNotFound)).unwrap();
341                    }
342                }
343                fsys2_f::RealmQueryRequest::OpenDirectory {
344                    moniker,
345                    dir_type,
346                    object,
347                    responder,
348                    ..
349                } => {
350                    let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
351                    if let Some(res) = self.mapping.get(&query_moniker.to_string()) {
352                        if dir_type == fsys2_f::OpenDirType::OutgoingDir {
353                            // Serve the out dir, everything else doesn't get served.
354                            res.serve_exposed_dir_f(object, "");
355                        }
356                        responder.send(Ok(())).unwrap();
357                    } else {
358                        responder.send(Err(fsys2_f::OpenError::InstanceNotFound)).unwrap();
359                    }
360                }
361                fsys2_f::RealmQueryRequest::GetManifest { moniker, responder, .. } => {
362                    let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
363                    let res = self.mapping.get(&query_moniker.to_string()).unwrap();
364                    let manifest = res.make_manifest();
365                    let manifest = fidl::persist(&manifest).unwrap();
366                    let (client_end, server_end) =
367                        client.create_endpoints::<fsys2_f::ManifestBytesIteratorMarker>();
368
369                    fuchsia_async::Task::spawn(async move {
370                        let mut stream = server_end.into_stream();
371                        let fsys2_f::ManifestBytesIteratorRequest::Next { responder } =
372                            stream.next().await.unwrap().unwrap();
373                        responder.send(manifest.as_slice()).unwrap();
374                        let fsys2_f::ManifestBytesIteratorRequest::Next { responder } =
375                            stream.next().await.unwrap().unwrap();
376                        responder.send(&[]).unwrap();
377                    })
378                    .detach();
379
380                    responder.send(Ok(client_end)).unwrap();
381                }
382                fsys2_f::RealmQueryRequest::GetResolvedDeclaration {
383                    moniker, responder, ..
384                } => {
385                    let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
386                    let res = self.mapping.get(&query_moniker.to_string()).unwrap();
387                    let manifest = res.make_manifest();
388                    let manifest = fidl::persist(&manifest).unwrap();
389                    let (client_end, server_end) =
390                        client.create_endpoints::<fsys2_f::ManifestBytesIteratorMarker>();
391
392                    fuchsia_async::Task::spawn(async move {
393                        let mut stream = server_end.into_stream();
394                        let fsys2_f::ManifestBytesIteratorRequest::Next { responder } =
395                            stream.next().await.unwrap().unwrap();
396                        responder.send(manifest.as_slice()).unwrap();
397                        let fsys2_f::ManifestBytesIteratorRequest::Next { responder } =
398                            stream.next().await.unwrap().unwrap();
399                        responder.send(&[]).unwrap();
400                    })
401                    .detach();
402
403                    responder.send(Ok(client_end)).unwrap();
404                }
405                fsys2_f::RealmQueryRequest::GetAllInstances { responder } => {
406                    let instances: Vec<fsys2_f::Instance> =
407                        self.mapping.values().map(|m| m.to_instance()).collect();
408
409                    let (client_end, server_end) =
410                        client.create_endpoints::<fsys2_f::InstanceIteratorMarker>();
411
412                    fuchsia_async::Task::spawn(async move {
413                        let mut stream = server_end.into_stream();
414                        let fsys2_f::InstanceIteratorRequest::Next { responder } =
415                            stream.next().await.unwrap().unwrap();
416                        responder.send(&instances).unwrap();
417                        let fsys2_f::InstanceIteratorRequest::Next { responder } =
418                            stream.next().await.unwrap().unwrap();
419                        responder.send(&[]).unwrap();
420                    })
421                    .detach();
422
423                    responder.send(Ok(client_end)).unwrap();
424                }
425                _ => unreachable!("request {:?}", request),
426            }
427        }
428    }
429
430    /// Serves the `RealmQuery` interface asynchronously and runs until the program terminates.
431    pub async fn serve(self: Rc<Self>, object: ServerEnd<fsys2::RealmQueryMarker>) {
432        let mut stream = object.into_stream();
433        while let Ok(Some(request)) = stream.try_next().await {
434            match request {
435                fsys2::RealmQueryRequest::GetInstance { moniker, responder } => {
436                    let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
437                    let res = self.mapping.get(&query_moniker.to_string()).unwrap();
438                    responder.send(Ok(&res.to_instance())).unwrap();
439                }
440                fsys2::RealmQueryRequest::OpenDirectory {
441                    moniker,
442                    dir_type,
443                    object,
444                    responder,
445                    ..
446                } => {
447                    let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
448                    if let Some(res) = self.mapping.get(&query_moniker.to_string()) {
449                        if dir_type == fsys2::OpenDirType::ExposedDir {
450                            // Serve the out dir, everything else doesn't get served.
451                            res.serve_exposed_dir(object, "");
452                        }
453                        responder.send(Ok(())).unwrap();
454                    } else {
455                        responder.send(Err(fsys2::OpenError::InstanceNotFound)).unwrap();
456                    }
457                }
458                fsys2::RealmQueryRequest::GetManifest { moniker, responder, .. } => {
459                    let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
460                    let res = self.mapping.get(&query_moniker.to_string()).unwrap();
461                    let manifest = res.make_manifest();
462                    let manifest = fidl::persist(&manifest).unwrap();
463                    let (client_end, server_end) =
464                        create_endpoints::<fsys2::ManifestBytesIteratorMarker>();
465
466                    fuchsia_async::Task::spawn(async move {
467                        let mut stream = server_end.into_stream();
468                        let fsys2::ManifestBytesIteratorRequest::Next { responder } =
469                            stream.next().await.unwrap().unwrap();
470                        responder.send(manifest.as_slice()).unwrap();
471                        let fsys2::ManifestBytesIteratorRequest::Next { responder } =
472                            stream.next().await.unwrap().unwrap();
473                        responder.send(&[]).unwrap();
474                    })
475                    .detach();
476
477                    responder.send(Ok(client_end)).unwrap();
478                }
479                fsys2::RealmQueryRequest::GetResolvedDeclaration { moniker, responder, .. } => {
480                    let query_moniker = Moniker::from_str(moniker.as_str()).unwrap();
481                    let res = self.mapping.get(&query_moniker.to_string()).unwrap();
482                    let manifest = res.make_manifest();
483                    let manifest = fidl::persist(&manifest).unwrap();
484                    let (client_end, server_end) =
485                        create_endpoints::<fsys2::ManifestBytesIteratorMarker>();
486
487                    fuchsia_async::Task::spawn(async move {
488                        let mut stream = server_end.into_stream();
489                        let fsys2::ManifestBytesIteratorRequest::Next { responder } =
490                            stream.next().await.unwrap().unwrap();
491                        responder.send(manifest.as_slice()).unwrap();
492                        let fsys2::ManifestBytesIteratorRequest::Next { responder } =
493                            stream.next().await.unwrap().unwrap();
494                        responder.send(&[]).unwrap();
495                    })
496                    .detach();
497
498                    responder.send(Ok(client_end)).unwrap();
499                }
500                fsys2::RealmQueryRequest::GetAllInstances { responder } => {
501                    let instances: Vec<fsys2::Instance> =
502                        self.mapping.values().map(|m| m.to_instance()).collect();
503
504                    let (client_end, server_end) =
505                        create_endpoints::<fsys2::InstanceIteratorMarker>();
506
507                    fuchsia_async::Task::spawn(async move {
508                        let mut stream = server_end.into_stream();
509                        let fsys2::InstanceIteratorRequest::Next { responder } =
510                            stream.next().await.unwrap().unwrap();
511                        responder.send(&instances).unwrap();
512                        let fsys2::InstanceIteratorRequest::Next { responder } =
513                            stream.next().await.unwrap().unwrap();
514                        responder.send(&[]).unwrap();
515                    })
516                    .detach();
517
518                    responder.send(Ok(client_end)).unwrap();
519                }
520                _ => unreachable!("request {:?}", request),
521            }
522        }
523    }
524
525    /// Serves the `RealmQuery` interface asynchronously and runs until the program terminates.
526    /// Then, instead of needing the client to discover the protocol, return the proxy for futher
527    /// test use.
528    pub async fn get_proxy(self: Rc<Self>) -> fsys2::RealmQueryProxy {
529        let (proxy, server_end) = create_proxy::<fsys2::RealmQueryMarker>();
530        fuchsia_async::Task::local(async move { self.serve(server_end).await }).detach();
531        proxy
532    }
533}
534
535// Mock directory structure.
536pub trait Entry {
537    fn open(self: Rc<Self>, path: &str, object: fidl::Channel);
538    fn open_f(self: Rc<Self>, path: &str, object: fdomain_client::Channel);
539    fn encode(&self, buf: &mut Vec<u8>);
540    fn name(&self) -> String;
541}
542
543pub struct MockDir {
544    subdirs: HashMap<String, Rc<dyn Entry>>,
545    name: String,
546    at_end: AtomicBool,
547}
548
549impl MockDir {
550    pub fn new(name: String) -> Self {
551        MockDir { name, subdirs: HashMap::new(), at_end: AtomicBool::new(false) }
552    }
553
554    pub fn new_arc(name: String) -> Rc<Self> {
555        Rc::new(Self::new(name))
556    }
557
558    pub fn add_entry(mut self, entry: Rc<dyn Entry>) -> Self {
559        self.subdirs.insert(entry.name(), entry);
560        self
561    }
562
563    async fn serve_f(self: Rc<Self>, object: FServerEnd<fio_f::DirectoryMarker>) {
564        let mut stream = object.into_stream();
565        let _ = stream.control_handle().send_on_open_(
566            Status::OK.into_raw(),
567            Some(fio_f::NodeInfoDeprecated::Directory(fio_f::DirectoryObject {})),
568        );
569        while let Ok(Some(request)) = stream.try_next().await {
570            match request {
571                fio_f::DirectoryRequest::Open { path, object, .. } => {
572                    self.clone().open_f(&path, object);
573                }
574                fio_f::DirectoryRequest::Rewind { responder, .. } => {
575                    self.at_end.store(false, Ordering::Relaxed);
576                    responder.send(Status::OK.into_raw()).unwrap();
577                }
578                fio_f::DirectoryRequest::ReadDirents { max_bytes: _, responder, .. } => {
579                    let entries = match self.at_end.compare_exchange(
580                        false,
581                        true,
582                        Ordering::Relaxed,
583                        Ordering::Relaxed,
584                    ) {
585                        Ok(false) => encode_entries(&self.subdirs),
586                        Err(true) => Vec::new(),
587                        _ => unreachable!(),
588                    };
589                    responder.send(Status::OK.into_raw(), &entries).unwrap();
590                }
591                x => panic!("unsupported request: {x:?}"),
592            }
593        }
594    }
595
596    async fn serve(self: Rc<Self>, object: ServerEnd<fio::DirectoryMarker>) {
597        let mut stream = object.into_stream();
598        while let Ok(Some(request)) = stream.try_next().await {
599            match request {
600                fio::DirectoryRequest::Open { path, object, .. } => {
601                    self.clone().open(&path, object);
602                }
603                fio::DirectoryRequest::Rewind { responder, .. } => {
604                    self.at_end.store(false, Ordering::Relaxed);
605                    responder.send(Status::OK.into_raw()).unwrap();
606                }
607                fio::DirectoryRequest::ReadDirents { max_bytes: _, responder, .. } => {
608                    let entries = match self.at_end.compare_exchange(
609                        false,
610                        true,
611                        Ordering::Relaxed,
612                        Ordering::Relaxed,
613                    ) {
614                        Ok(false) => encode_entries(&self.subdirs),
615                        Err(true) => Vec::new(),
616                        _ => unreachable!(),
617                    };
618                    responder.send(Status::OK.into_raw(), &entries).unwrap();
619                }
620                x => panic!("unsupported request: {x:?}"),
621            }
622        }
623    }
624}
625
626fn encode_entries(subdirs: &HashMap<String, Rc<dyn Entry>>) -> Vec<u8> {
627    let mut buf = Vec::new();
628    let mut data = subdirs.iter().collect::<Vec<(_, _)>>();
629    data.sort_by(|a, b| a.0.cmp(b.0));
630    for (_, entry) in data.iter() {
631        entry.encode(&mut buf);
632    }
633    buf
634}
635
636impl Entry for MockDir {
637    fn open(self: Rc<Self>, path: &str, object: fidl::Channel) {
638        let path = Path::new(path);
639        let mut path_iter = path.iter();
640        let segment = if let Some(segment) = path_iter.next() {
641            if let Some(segment) = segment.to_str() {
642                segment
643            } else {
644                let _ =
645                    ServerEnd::<fio::NodeMarker>::new(object).close_with_epitaph(Status::NOT_FOUND);
646                return;
647            }
648        } else {
649            "."
650        };
651        if segment == "." {
652            fuchsia_async::Task::local(self.serve(ServerEnd::new(object))).detach();
653            return;
654        }
655        if let Some(entry) = self.subdirs.get(segment) {
656            entry.clone().open(path_iter.as_path().to_str().unwrap(), object);
657        } else {
658            let _ = ServerEnd::<fio::NodeMarker>::new(object).close_with_epitaph(Status::NOT_FOUND);
659        }
660    }
661
662    fn open_f(self: Rc<Self>, path: &str, object: fdomain_client::Channel) {
663        let path = Path::new(path);
664        let mut path_iter = path.iter();
665        let segment = if let Some(segment) = path_iter.next() {
666            if let Some(segment) = segment.to_str() {
667                segment
668            } else {
669                let _ = FServerEnd::<fio_f::NodeMarker>::new(object)
670                    .close_with_epitaph(Status::NOT_FOUND);
671                return;
672            }
673        } else {
674            "."
675        };
676        if segment == "." {
677            fuchsia_async::Task::local(self.serve_f(FServerEnd::new(object))).detach();
678            return;
679        }
680        if let Some(entry) = self.subdirs.get(segment) {
681            entry.clone().open_f(path_iter.as_path().to_str().unwrap(), object);
682        } else {
683            let _ =
684                FServerEnd::<fio_f::NodeMarker>::new(object).close_with_epitaph(Status::NOT_FOUND);
685        }
686    }
687
688    fn encode(&self, buf: &mut Vec<u8>) {
689        buf.write_u64::<LittleEndian>(fio::INO_UNKNOWN).expect("writing mockdir ino to work");
690        buf.write_u8(self.name.len() as u8).expect("writing mockdir size to work");
691        buf.write_u8(fio::DirentType::Directory.into_primitive())
692            .expect("writing mockdir type to work");
693        buf.write_all(self.name.as_ref()).expect("writing mockdir name to work");
694    }
695
696    fn name(&self) -> String {
697        self.name.clone()
698    }
699}
700
701struct MockFile {
702    name: String,
703}
704
705impl MockFile {
706    pub fn new(name: String) -> Self {
707        MockFile { name }
708    }
709    pub fn new_arc(name: String) -> Rc<Self> {
710        Rc::new(Self::new(name))
711    }
712}
713
714impl Entry for MockFile {
715    fn open(self: Rc<Self>, _path: &str, _object: fidl::Channel) {
716        unimplemented!();
717    }
718
719    fn open_f(self: Rc<Self>, _path: &str, _object: fdomain_client::Channel) {
720        unimplemented!();
721    }
722
723    fn encode(&self, buf: &mut Vec<u8>) {
724        buf.write_u64::<LittleEndian>(fio::INO_UNKNOWN).expect("writing mockdir ino to work");
725        buf.write_u8(self.name.len() as u8).expect("writing mockdir size to work");
726        buf.write_u8(fio::DirentType::Service.into_primitive())
727            .expect("writing mockdir type to work");
728        buf.write_all(self.name.as_ref()).expect("writing mockdir name to work");
729    }
730
731    fn name(&self) -> String {
732        self.name.clone()
733    }
734}