Skip to main content

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