remote_control/
lib.rs

1// Copyright 2019 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 crate::host_identifier::{DefaultIdentifier, HostIdentifier, Identifier};
6use anyhow::{Context as _, Result};
7use component_debug::dirs::*;
8use component_debug::lifecycle::*;
9use fuchsia_component::client::connect_to_protocol_at_path;
10use futures::channel::oneshot;
11use futures::prelude::*;
12use log::*;
13use moniker::Moniker;
14use std::borrow::Borrow;
15use std::cell::RefCell;
16use std::rc::{Rc, Weak};
17use {
18    fidl_fuchsia_developer_remotecontrol as rcs,
19    fidl_fuchsia_developer_remotecontrol_connector as connector,
20    fidl_fuchsia_diagnostics as diagnostics, fidl_fuchsia_io as fio, fidl_fuchsia_io as io,
21    fidl_fuchsia_sys2 as fsys,
22};
23
24mod host_identifier;
25
26pub struct RemoteControlService {
27    ids: RefCell<Vec<Weak<RefCell<Vec<u64>>>>>,
28    id_allocator: Box<dyn Fn() -> Result<Box<dyn Identifier + 'static>>>,
29    connector: Box<dyn Fn(ConnectionRequest, Weak<RemoteControlService>)>,
30}
31
32struct Client {
33    // Maintain reference-counts to this client's ids.
34    // The ids may be shared (e.g. when Overnet maintains two
35    // connections to the target -- legacy + CSO), so we can't
36    // just maintain a list of RCS's ids and remove when one
37    // disappars.  Instead, when these are freed due to the client
38    // being dropped, the RCS Weak references will become invalid.
39    allocated_ids: Rc<RefCell<Vec<u64>>>,
40}
41
42/// Indicates a connection request to be handled by the `connector` argument of
43/// `RemoteControlService::new`
44pub enum ConnectionRequest {
45    Overnet(fidl::Socket, oneshot::Sender<u64>),
46    FDomain(fidl::Socket),
47}
48
49impl RemoteControlService {
50    pub async fn new(connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static) -> Self {
51        let boot_id = zx::MonotonicInstant::get().into_nanos() as u64;
52        Self::new_with_allocator(connector, move || Ok(Box::new(HostIdentifier::new(boot_id)?)))
53    }
54
55    pub async fn new_with_default_allocator(
56        connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static,
57    ) -> Self {
58        Self::new_with_allocator(connector, || Ok(Box::new(DefaultIdentifier::new())))
59    }
60
61    pub(crate) fn new_with_allocator(
62        connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static,
63        id_allocator: impl Fn() -> Result<Box<dyn Identifier + 'static>> + 'static,
64    ) -> Self {
65        Self {
66            id_allocator: Box::new(id_allocator),
67            ids: Default::default(),
68            connector: Box::new(connector),
69        }
70    }
71
72    // Some of the ID-lists may be gone because old clients have shut down.
73    // They will have a strong_count of 0.  Drop 'em.
74    fn remove_old_ids(self: &Rc<Self>) {
75        self.ids.borrow_mut().retain(|wirc| wirc.strong_count() > 0);
76    }
77
78    async fn handle_connector(
79        self: &Rc<Self>,
80        client: &Client,
81        request: connector::ConnectorRequest,
82    ) -> Result<()> {
83        match request {
84            connector::ConnectorRequest::EstablishCircuit { id, socket, responder } => {
85                let (nodeid_sender, nodeid_receiver) = oneshot::channel();
86                (self.connector)(
87                    ConnectionRequest::Overnet(socket, nodeid_sender),
88                    Rc::downgrade(self),
89                );
90                let node_id = nodeid_receiver.await?;
91                client.allocated_ids.borrow_mut().push(id);
92                responder.send(node_id)?;
93                Ok(())
94            }
95            connector::ConnectorRequest::FdomainToolboxSocket { socket, responder } => {
96                (self.connector)(ConnectionRequest::FDomain(socket), Rc::downgrade(self));
97                responder.send()?;
98                Ok(())
99            }
100        }
101    }
102
103    async fn handle(self: &Rc<Self>, request: rcs::RemoteControlRequest) -> Result<()> {
104        match request {
105            rcs::RemoteControlRequest::EchoString { value, responder } => {
106                info!("Received echo string {}", value);
107                responder.send(&value)?;
108                Ok(())
109            }
110            rcs::RemoteControlRequest::LogMessage { tag, message, severity, responder } => {
111                match severity {
112                    diagnostics::Severity::Trace => trace!(tag:%; "{}", message),
113                    diagnostics::Severity::Debug => debug!(tag:%; "{}", message),
114                    diagnostics::Severity::Info => info!(tag:%; "{}", message),
115                    diagnostics::Severity::Warn => warn!(tag:%; "{}", message),
116                    diagnostics::Severity::Error => error!(tag:%; "{}", message),
117                    // Tracing crate doesn't have a Fatal level, just log an error with a FATAL message embedded.
118                    diagnostics::Severity::Fatal => error!(tag:%; "<FATAL> {}", message),
119                }
120                responder.send()?;
121                Ok(())
122            }
123            rcs::RemoteControlRequest::IdentifyHost { responder } => {
124                self.clone().identify_host(responder).await?;
125                Ok(())
126            }
127            rcs::RemoteControlRequest::ConnectCapability {
128                moniker,
129                capability_set,
130                capability_name,
131                server_channel,
132                responder,
133            } => {
134                responder.send(
135                    self.clone()
136                        .open_capability(moniker, capability_set, capability_name, server_channel)
137                        .await,
138                )?;
139                Ok(())
140            }
141            rcs::RemoteControlRequest::DeprecatedOpenCapability {
142                moniker,
143                capability_set,
144                capability_name,
145                server_channel,
146                flags: _,
147                responder,
148            } => {
149                responder.send(
150                    self.clone()
151                        .open_capability(moniker, capability_set, capability_name, server_channel)
152                        .await,
153                )?;
154                Ok(())
155            }
156            rcs::RemoteControlRequest::GetTime { responder } => {
157                responder.send(zx::MonotonicInstant::get())?;
158                Ok(())
159            }
160            rcs::RemoteControlRequest::GetBootTime { responder } => {
161                responder.send(zx::BootInstant::get())?;
162                Ok(())
163            }
164            rcs::RemoteControlRequest::_UnknownMethod { ordinal, .. } => {
165                warn!("Received unknown request with ordinal {ordinal}");
166                Ok(())
167            }
168        }
169    }
170
171    pub async fn serve_connector_stream(self: Rc<Self>, stream: connector::ConnectorRequestStream) {
172        // When the stream ends, the client (and its ids) will drop
173        let allocated_ids = Rc::new(RefCell::new(vec![]));
174        self.ids.borrow_mut().push(Rc::downgrade(&allocated_ids));
175        let client = Client { allocated_ids };
176        stream
177            .for_each_concurrent(None, |request| async {
178                match request {
179                    Ok(request) => {
180                        let _ = self
181                            .handle_connector(&client, request)
182                            .await
183                            .map_err(|e| warn!("stream request handling error: {:?}", e));
184                    }
185                    Err(e) => warn!("stream error: {:?}", e),
186                }
187            })
188            .await;
189    }
190
191    pub async fn serve_stream(self: Rc<Self>, stream: rcs::RemoteControlRequestStream) {
192        stream
193            .for_each_concurrent(None, |request| async {
194                match request {
195                    Ok(request) => {
196                        let _ = self
197                            .handle(request)
198                            .await
199                            .map_err(|e| warn!("stream request handling error: {:?}", e));
200                    }
201                    Err(e) => warn!("stream error: {:?}", e),
202                }
203            })
204            .await;
205    }
206
207    pub async fn identify_host(
208        self: &Rc<Self>,
209        responder: rcs::RemoteControlIdentifyHostResponder,
210    ) -> Result<()> {
211        let identifier = match (self.id_allocator)() {
212            Ok(i) => i,
213            Err(e) => {
214                error!(e:%; "Allocating host identifier");
215                return responder
216                    .send(Err(rcs::IdentifyHostError::ProxyConnectionFailed))
217                    .context("responding to client");
218            }
219        };
220
221        // We need to clean up the ids at some point. Let's do
222        // it when those IDs are asked for.
223        self.remove_old_ids();
224        // Now the only vecs should be ones which are still held with a strong
225        // Rc reference. Extract those.
226        let ids: Vec<u64> = self
227            .ids
228            .borrow()
229            .iter()
230            .flat_map(|w| -> Vec<u64> {
231                // This is all sadmac's fault. Grr. (Because he suggested, correctly, that
232                // we use a Rc<Vec<_>> instead of Vec<Rc<_>>)
233                <Rc<RefCell<Vec<u64>>> as Borrow<RefCell<Vec<u64>>>>::borrow(
234                    &w.upgrade().expect("Didn't we just clear out refs with expired values??"),
235                )
236                .borrow()
237                .clone()
238            })
239            .collect();
240        let target_identity = identifier.identify().await.map(move |mut i| {
241            i.ids = Some(ids);
242            i
243        });
244        responder.send(target_identity.as_ref().map_err(|e| *e)).context("responding to client")?;
245        Ok(())
246    }
247
248    /// Connects to a capability identified by the given moniker in the specified set of
249    /// capabilities at the given capability name.
250    async fn open_capability(
251        self: &Rc<Self>,
252        moniker: String,
253        capability_set: fsys::OpenDirType,
254        capability_name: String,
255        server_end: zx::Channel,
256    ) -> Result<(), rcs::ConnectCapabilityError> {
257        // Connect to the root LifecycleController protocol
258        let lifecycle = connect_to_protocol_at_path::<fsys::LifecycleControllerMarker>(
259            "/svc/fuchsia.sys2.LifecycleController.root",
260        )
261        .map_err(|err| {
262            error!(err:%; "could not connect to lifecycle controller");
263            rcs::ConnectCapabilityError::CapabilityConnectFailed
264        })?;
265
266        // Connect to the root RealmQuery protocol
267        let query = connect_to_protocol_at_path::<fsys::RealmQueryMarker>(
268            "/svc/fuchsia.sys2.RealmQuery.root",
269        )
270        .map_err(|err| {
271            error!(err:%; "could not connect to realm query");
272            rcs::ConnectCapabilityError::CapabilityConnectFailed
273        })?;
274
275        let moniker = Moniker::try_from(moniker.as_str())
276            .map_err(|_| rcs::ConnectCapabilityError::InvalidMoniker)?;
277        connect_to_capability_at_moniker(
278            moniker,
279            capability_set,
280            capability_name,
281            server_end,
282            lifecycle,
283            query,
284        )
285        .await
286    }
287
288    pub async fn open_toolboox(
289        self: &Rc<Self>,
290        server_end: zx::Channel,
291    ) -> Result<(), rcs::ConnectCapabilityError> {
292        // Connect to the root LifecycleController protocol
293        let controller = connect_to_protocol_at_path::<fsys::LifecycleControllerMarker>(
294            "/svc/fuchsia.sys2.LifecycleController.root",
295        )
296        .map_err(|err| {
297            error!(err:%; "could not connect to lifecycle controller");
298            rcs::ConnectCapabilityError::CapabilityConnectFailed
299        })?;
300
301        // Connect to the root RealmQuery protocol
302        let query = connect_to_protocol_at_path::<fsys::RealmQueryMarker>(
303            "/svc/fuchsia.sys2.RealmQuery.root",
304        )
305        .map_err(|err| {
306            error!(err:%; "could not connect to realm query");
307            rcs::ConnectCapabilityError::CapabilityConnectFailed
308        })?;
309
310        // Attempt to resolve both the modern and legacy locations concurrently and use the one that
311        // resolves successfully
312        let moniker =
313            moniker::Moniker::try_from("toolbox").expect("Moniker 'toolbox' did not parse!");
314        let legacy_moniker = moniker::Moniker::try_from("core/toolbox")
315            .expect("Moniker 'core/toolbox' did not parse!");
316        let (modern, legacy) = futures::join!(
317            resolve_instance(&controller, &moniker),
318            resolve_instance(&controller, &legacy_moniker)
319        );
320
321        let moniker = if modern.is_ok() {
322            moniker
323        } else if legacy.is_ok() {
324            legacy_moniker
325        } else {
326            error!("Unable to resolve toolbox component in either toolbox or core/toolbox");
327            return Err(rcs::ConnectCapabilityError::NoMatchingComponent);
328        };
329
330        let dir = component_debug::dirs::open_instance_dir_root_readable(
331            &moniker,
332            fsys::OpenDirType::NamespaceDir.into(),
333            &query,
334        )
335        .map_err(|err| {
336            error!(err:?; "error opening exposed dir");
337            rcs::ConnectCapabilityError::CapabilityConnectFailed
338        })
339        .await?;
340
341        dir.open("svc", io::PERM_READABLE, &Default::default(), server_end).map_err(|err| {
342            error!(err:?; "error opening svc dir in toolbox");
343            rcs::ConnectCapabilityError::CapabilityConnectFailed
344        })?;
345        Ok(())
346    }
347}
348
349/// Connect to the capability at the provided moniker in the specified set of capabilities under
350/// the provided capability name.
351async fn connect_to_capability_at_moniker(
352    moniker: Moniker,
353    capability_set: fsys::OpenDirType,
354    capability_name: String,
355    server_end: zx::Channel,
356    lifecycle: fsys::LifecycleControllerProxy,
357    query: fsys::RealmQueryProxy,
358) -> Result<(), rcs::ConnectCapabilityError> {
359    // This is a no-op if already resolved.
360    resolve_instance(&lifecycle, &moniker)
361        .map_err(|err| match err {
362            ResolveError::ActionError(ActionError::InstanceNotFound) => {
363                rcs::ConnectCapabilityError::NoMatchingComponent
364            }
365            err => {
366                error!(err:?; "error resolving component");
367                rcs::ConnectCapabilityError::CapabilityConnectFailed
368            }
369        })
370        .await?;
371
372    let dir = open_instance_dir_root_readable(&moniker, capability_set.into(), &query)
373        .map_err(|err| {
374            error!(err:?; "error opening exposed dir");
375            rcs::ConnectCapabilityError::CapabilityConnectFailed
376        })
377        .await?;
378
379    connect_to_capability_in_dir(&dir, &capability_name, server_end).await?;
380    Ok(())
381}
382
383async fn connect_to_capability_in_dir(
384    dir: &io::DirectoryProxy,
385    capability_name: &str,
386    server_end: zx::Channel,
387) -> Result<(), rcs::ConnectCapabilityError> {
388    check_entry_exists(dir, capability_name).await?;
389    // Connect to the capability
390    dir.open(capability_name, io::Flags::PROTOCOL_SERVICE, &Default::default(), server_end).map_err(
391        |err| {
392            error!(err:%; "error opening capability from exposed dir");
393            rcs::ConnectCapabilityError::CapabilityConnectFailed
394        },
395    )
396}
397
398// Checks that the given directory contains an entry with the given name.
399async fn check_entry_exists(
400    dir: &io::DirectoryProxy,
401    capability_name: &str,
402) -> Result<(), rcs::ConnectCapabilityError> {
403    let dir_idx = capability_name.rfind('/');
404    let (capability_name, entries) = match dir_idx {
405        Some(dir_idx) => {
406            let dirname = &capability_name[0..dir_idx];
407            let basename = &capability_name[dir_idx + 1..];
408            let nested_dir =
409                fuchsia_fs::directory::open_directory(dir, dirname, fio::PERM_READABLE)
410                    .await
411                    .map_err(|_| rcs::ConnectCapabilityError::NoMatchingCapabilities)?;
412            let entries = fuchsia_fs::directory::readdir(&nested_dir)
413                .await
414                .map_err(|_| rcs::ConnectCapabilityError::CapabilityConnectFailed)?;
415            (basename, entries)
416        }
417        None => {
418            let entries = fuchsia_fs::directory::readdir(dir)
419                .await
420                .map_err(|_| rcs::ConnectCapabilityError::CapabilityConnectFailed)?;
421            (capability_name, entries)
422        }
423    };
424    if entries.iter().any(|e| e.name == capability_name) {
425        Ok(())
426    } else {
427        Err(rcs::ConnectCapabilityError::NoMatchingCapabilities)
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use super::*;
434    use fidl::endpoints::ServerEnd;
435    use fuchsia_component::server::ServiceFs;
436    use {
437        fidl_fuchsia_buildinfo as buildinfo, fidl_fuchsia_developer_remotecontrol as rcs,
438        fidl_fuchsia_device as fdevice, fidl_fuchsia_hwinfo as hwinfo, fidl_fuchsia_io as fio,
439        fidl_fuchsia_net as fnet, fidl_fuchsia_net_interfaces as fnet_interfaces,
440        fidl_fuchsia_sysinfo as sysinfo, fuchsia_async as fasync,
441    };
442
443    const NODENAME: &'static str = "thumb-set-human-shred";
444    const BOOT_TIME: u64 = 123456789000000000;
445    const SYSINFO_SERIAL: &'static str = "test_sysinfo_serial";
446    const SERIAL: &'static str = "test_serial";
447    const BOARD_CONFIG: &'static str = "test_board_name";
448    const PRODUCT_CONFIG: &'static str = "core";
449
450    const IPV4_ADDR: [u8; 4] = [127, 0, 0, 1];
451    const IPV6_ADDR: [u8; 16] = [127, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6];
452
453    fn setup_fake_device_service() -> hwinfo::DeviceProxy {
454        let (proxy, mut stream) =
455            fidl::endpoints::create_proxy_and_stream::<hwinfo::DeviceMarker>();
456        fasync::Task::spawn(async move {
457            while let Ok(Some(req)) = stream.try_next().await {
458                match req {
459                    hwinfo::DeviceRequest::GetInfo { responder } => {
460                        let _ = responder.send(&hwinfo::DeviceInfo {
461                            serial_number: Some(String::from(SERIAL)),
462                            ..Default::default()
463                        });
464                    }
465                }
466            }
467        })
468        .detach();
469
470        proxy
471    }
472
473    fn setup_fake_sysinfo_service(status: zx::Status) -> sysinfo::SysInfoProxy {
474        let (proxy, mut stream) =
475            fidl::endpoints::create_proxy_and_stream::<sysinfo::SysInfoMarker>();
476        fasync::Task::spawn(async move {
477            while let Ok(Some(req)) = stream.try_next().await {
478                match req {
479                    sysinfo::SysInfoRequest::GetSerialNumber { responder } => {
480                        let _ = responder.send(
481                            Result::from(status)
482                                .map(|_| SYSINFO_SERIAL)
483                                .map_err(zx::Status::into_raw),
484                        );
485                    }
486                    _ => panic!("unexpected request: {req:?}"),
487                }
488            }
489        })
490        .detach();
491
492        proxy
493    }
494
495    fn setup_fake_build_info_service() -> buildinfo::ProviderProxy {
496        let (proxy, mut stream) =
497            fidl::endpoints::create_proxy_and_stream::<buildinfo::ProviderMarker>();
498        fasync::Task::spawn(async move {
499            while let Ok(Some(req)) = stream.try_next().await {
500                match req {
501                    buildinfo::ProviderRequest::GetBuildInfo { responder } => {
502                        let _ = responder.send(&buildinfo::BuildInfo {
503                            board_config: Some(String::from(BOARD_CONFIG)),
504                            product_config: Some(String::from(PRODUCT_CONFIG)),
505                            ..Default::default()
506                        });
507                    }
508                }
509            }
510        })
511        .detach();
512
513        proxy
514    }
515
516    fn setup_fake_name_provider_service() -> fdevice::NameProviderProxy {
517        let (proxy, mut stream) =
518            fidl::endpoints::create_proxy_and_stream::<fdevice::NameProviderMarker>();
519
520        fasync::Task::spawn(async move {
521            while let Ok(Some(req)) = stream.try_next().await {
522                match req {
523                    fdevice::NameProviderRequest::GetDeviceName { responder } => {
524                        let _ = responder.send(Ok(NODENAME));
525                    }
526                }
527            }
528        })
529        .detach();
530
531        proxy
532    }
533
534    fn setup_fake_interface_state_service() -> fnet_interfaces::StateProxy {
535        let (proxy, mut stream) =
536            fidl::endpoints::create_proxy_and_stream::<fnet_interfaces::StateMarker>();
537
538        fasync::Task::spawn(async move {
539            while let Ok(Some(req)) = stream.try_next().await {
540                match req {
541                    fnet_interfaces::StateRequest::GetWatcher {
542                        options: _,
543                        watcher,
544                        control_handle: _,
545                    } => {
546                        let mut stream = watcher.into_stream();
547                        let mut first = true;
548                        while let Ok(Some(req)) = stream.try_next().await {
549                            match req {
550                                fnet_interfaces::WatcherRequest::Watch { responder } => {
551                                    let event = if first {
552                                        first = false;
553                                        fnet_interfaces::Event::Existing(
554                                            fnet_interfaces::Properties {
555                                                id: Some(1),
556                                                addresses: Some(
557                                                    IntoIterator::into_iter([
558                                                        fnet::Subnet {
559                                                            addr: fnet::IpAddress::Ipv4(
560                                                                fnet::Ipv4Address {
561                                                                    addr: IPV4_ADDR,
562                                                                },
563                                                            ),
564                                                            prefix_len: 4,
565                                                        },
566                                                        fnet::Subnet {
567                                                            addr: fnet::IpAddress::Ipv6(
568                                                                fnet::Ipv6Address {
569                                                                    addr: IPV6_ADDR,
570                                                                },
571                                                            ),
572                                                            prefix_len: 110,
573                                                        },
574                                                    ])
575                                                    .map(Some)
576                                                    .map(|addr| fnet_interfaces::Address {
577                                                        addr,
578                                                        assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
579                                                        ..Default::default()
580                                                    })
581                                                    .collect(),
582                                                ),
583                                                online: Some(true),
584                                                port_class: Some(
585                                                    fnet_interfaces::PortClass::Loopback(
586                                                        fnet_interfaces::Empty {},
587                                                    ),
588                                                ),
589                                                has_default_ipv4_route: Some(false),
590                                                has_default_ipv6_route: Some(false),
591                                                name: Some(String::from("eth0")),
592                                                ..Default::default()
593                                            },
594                                        )
595                                    } else {
596                                        fnet_interfaces::Event::Idle(fnet_interfaces::Empty {})
597                                    };
598                                    let () = responder.send(&event).unwrap();
599                                }
600                            }
601                        }
602                    }
603                }
604            }
605        })
606        .detach();
607
608        proxy
609    }
610
611    #[derive(Default)]
612    #[non_exhaustive]
613    struct RcsEnv {
614        system_info_proxy: Option<sysinfo::SysInfoProxy>,
615        use_default_identifier: bool,
616    }
617
618    fn make_rcs_from_env(env: RcsEnv) -> Rc<RemoteControlService> {
619        let RcsEnv { system_info_proxy, use_default_identifier } = env;
620        if use_default_identifier {
621            Rc::new(RemoteControlService::new_with_allocator(
622                |req, _| match req {
623                    ConnectionRequest::Overnet(_, sender) => sender.send(0u64).unwrap(),
624                    _ => (),
625                },
626                move || Ok(Box::new(DefaultIdentifier { boot_timestamp_nanos: BOOT_TIME })),
627            ))
628        } else {
629            Rc::new(RemoteControlService::new_with_allocator(
630                |req, _| match req {
631                    ConnectionRequest::Overnet(_, sender) => sender.send(0u64).unwrap(),
632                    _ => (),
633                },
634                move || {
635                    Ok(Box::new(HostIdentifier {
636                        interface_state_proxy: setup_fake_interface_state_service(),
637                        name_provider_proxy: setup_fake_name_provider_service(),
638                        device_info_proxy: setup_fake_device_service(),
639                        system_info_proxy: system_info_proxy
640                            .clone()
641                            .unwrap_or_else(|| setup_fake_sysinfo_service(zx::Status::INTERNAL)),
642                        build_info_proxy: setup_fake_build_info_service(),
643                        boot_timestamp_nanos: BOOT_TIME,
644                        boot_id: 0,
645                    }))
646                },
647            ))
648        }
649    }
650
651    fn setup_rcs_proxy_from_env(
652        env: RcsEnv,
653    ) -> (rcs::RemoteControlProxy, connector::ConnectorProxy) {
654        let service = make_rcs_from_env(env);
655
656        let (rcs_proxy, stream) =
657            fidl::endpoints::create_proxy_and_stream::<rcs::RemoteControlMarker>();
658        fasync::Task::local({
659            let service = Rc::clone(&service);
660            async move {
661                service.serve_stream(stream).await;
662            }
663        })
664        .detach();
665        let (connector_proxy, stream) =
666            fidl::endpoints::create_proxy_and_stream::<connector::ConnectorMarker>();
667        fasync::Task::local(async move {
668            service.serve_connector_stream(stream).await;
669        })
670        .detach();
671
672        (rcs_proxy, connector_proxy)
673    }
674
675    fn setup_rcs_proxy() -> rcs::RemoteControlProxy {
676        setup_rcs_proxy_from_env(Default::default()).0
677    }
678
679    fn setup_rcs_proxy_with_connector() -> (rcs::RemoteControlProxy, connector::ConnectorProxy) {
680        setup_rcs_proxy_from_env(Default::default())
681    }
682
683    fn setup_fake_lifecycle_controller() -> fsys::LifecycleControllerProxy {
684        fidl::endpoints::spawn_stream_handler(
685            move |request: fsys::LifecycleControllerRequest| async move {
686                match request {
687                    fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
688                        assert_eq!(moniker, "core/my_component");
689                        responder.send(Ok(())).unwrap()
690                    }
691                    _ => panic!("unexpected request: {:?}", request),
692                }
693            },
694        )
695    }
696
697    fn setup_exposed_dir(server: ServerEnd<fio::DirectoryMarker>) {
698        let mut fs = ServiceFs::new();
699        fs.add_fidl_service(move |_: hwinfo::BoardRequestStream| {});
700        fs.dir("svc").add_fidl_service(move |_: hwinfo::BoardRequestStream| {});
701        fs.serve_connection(server).unwrap();
702        fasync::Task::spawn(fs.collect::<()>()).detach();
703    }
704
705    /// Set up a fake realm query which asserts a requests coming in have the
706    /// right options set, including which of a component's capability sets
707    /// (ie. incoming namespace, outgoing directory, etc) the capability is
708    /// expected to be requested from.
709    fn setup_fake_realm_query(capability_set: fsys::OpenDirType) -> fsys::RealmQueryProxy {
710        fidl::endpoints::spawn_stream_handler(move |request: fsys::RealmQueryRequest| async move {
711            match request {
712                fsys::RealmQueryRequest::DeprecatedOpen {
713                    moniker,
714                    dir_type,
715                    flags,
716                    mode,
717                    path,
718                    object,
719                    responder,
720                } => {
721                    assert_eq!(moniker, "core/my_component");
722                    assert_eq!(dir_type, capability_set);
723                    assert_eq!(flags, fio::OpenFlags::RIGHT_READABLE);
724                    assert_eq!(mode, fio::ModeType::empty());
725                    assert_eq!(path, ".");
726
727                    setup_exposed_dir(object.into_channel().into());
728
729                    responder.send(Ok(())).unwrap()
730                }
731                fsys::RealmQueryRequest::OpenDirectory { moniker, dir_type, object, responder } => {
732                    assert_eq!(moniker, "core/my_component");
733                    assert_eq!(dir_type, capability_set);
734                    setup_exposed_dir(object);
735                    responder.send(Ok(())).unwrap()
736                }
737                _ => panic!("unexpected request: {:?}", request),
738            }
739        })
740    }
741
742    #[fuchsia::test]
743    async fn test_connect_to_component_capability() -> Result<()> {
744        for dir_type in vec![
745            fsys::OpenDirType::ExposedDir,
746            fsys::OpenDirType::NamespaceDir,
747            fsys::OpenDirType::OutgoingDir,
748        ] {
749            let (_client, server) = zx::Channel::create();
750            let lifecycle = setup_fake_lifecycle_controller();
751            let query = setup_fake_realm_query(dir_type);
752            connect_to_capability_at_moniker(
753                Moniker::try_from("./core/my_component").unwrap(),
754                dir_type,
755                "fuchsia.hwinfo.Board".to_string(),
756                server,
757                lifecycle,
758                query,
759            )
760            .await
761            .unwrap();
762        }
763        Ok(())
764    }
765
766    #[fuchsia::test]
767    async fn test_connect_to_component_capability_in_subdirectory() -> Result<()> {
768        for dir_type in vec![
769            fsys::OpenDirType::ExposedDir,
770            fsys::OpenDirType::NamespaceDir,
771            fsys::OpenDirType::OutgoingDir,
772        ] {
773            let (_client, server) = zx::Channel::create();
774            let lifecycle = setup_fake_lifecycle_controller();
775            let query = setup_fake_realm_query(dir_type);
776            connect_to_capability_at_moniker(
777                Moniker::try_from("./core/my_component").unwrap(),
778                dir_type,
779                "svc/fuchsia.hwinfo.Board".to_string(),
780                server,
781                lifecycle,
782                query,
783            )
784            .await
785            .unwrap();
786        }
787        Ok(())
788    }
789
790    #[fuchsia::test]
791    async fn test_connect_to_capability_not_available() -> Result<()> {
792        for dir_type in vec![
793            fsys::OpenDirType::ExposedDir,
794            fsys::OpenDirType::NamespaceDir,
795            fsys::OpenDirType::OutgoingDir,
796        ] {
797            let (_client, server) = zx::Channel::create();
798            let lifecycle = setup_fake_lifecycle_controller();
799            let query = setup_fake_realm_query(dir_type);
800            let error = connect_to_capability_at_moniker(
801                Moniker::try_from("./core/my_component").unwrap(),
802                dir_type,
803                "fuchsia.not.exposed".to_string(),
804                server,
805                lifecycle,
806                query,
807            )
808            .await
809            .unwrap_err();
810            assert_eq!(error, rcs::ConnectCapabilityError::NoMatchingCapabilities);
811        }
812        Ok(())
813    }
814
815    #[fuchsia::test]
816    async fn test_connect_to_capability_not_available_in_subdirectory() -> Result<()> {
817        for dir_type in vec![
818            fsys::OpenDirType::ExposedDir,
819            fsys::OpenDirType::NamespaceDir,
820            fsys::OpenDirType::OutgoingDir,
821        ] {
822            let (_client, server) = zx::Channel::create();
823            let lifecycle = setup_fake_lifecycle_controller();
824            let query = setup_fake_realm_query(dir_type);
825            let error = connect_to_capability_at_moniker(
826                Moniker::try_from("./core/my_component").unwrap(),
827                dir_type,
828                "svc/fuchsia.not.exposed".to_string(),
829                server,
830                lifecycle,
831                query,
832            )
833            .await
834            .unwrap_err();
835            assert_eq!(error, rcs::ConnectCapabilityError::NoMatchingCapabilities);
836        }
837        Ok(())
838    }
839
840    #[fuchsia::test]
841    async fn test_identify_host() -> Result<()> {
842        let rcs_proxy = setup_rcs_proxy();
843
844        let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
845
846        assert_eq!(resp.serial_number.unwrap(), SERIAL);
847        assert_eq!(resp.board_config.unwrap(), BOARD_CONFIG);
848        assert_eq!(resp.product_config.unwrap(), PRODUCT_CONFIG);
849        assert_eq!(resp.nodename.unwrap(), NODENAME);
850
851        let addrs = resp.addresses.unwrap();
852        assert_eq!(
853            addrs[..],
854            [
855                fnet::Subnet {
856                    addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: IPV4_ADDR }),
857                    prefix_len: 4,
858                },
859                fnet::Subnet {
860                    addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: IPV6_ADDR }),
861                    prefix_len: 110,
862                }
863            ]
864        );
865
866        assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
867
868        Ok(())
869    }
870
871    #[fuchsia::test]
872    async fn test_identify_host_sysinfo_serial() -> Result<()> {
873        let (rcs_proxy, _) = setup_rcs_proxy_from_env(RcsEnv {
874            system_info_proxy: Some(setup_fake_sysinfo_service(zx::Status::OK)),
875            ..Default::default()
876        });
877
878        let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
879
880        assert_eq!(resp.serial_number.unwrap(), SYSINFO_SERIAL);
881        assert_eq!(resp.board_config.unwrap(), BOARD_CONFIG);
882        assert_eq!(resp.product_config.unwrap(), PRODUCT_CONFIG);
883        assert_eq!(resp.nodename.unwrap(), NODENAME);
884
885        let addrs = resp.addresses.unwrap();
886        assert_eq!(
887            addrs[..],
888            [
889                fnet::Subnet {
890                    addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: IPV4_ADDR }),
891                    prefix_len: 4,
892                },
893                fnet::Subnet {
894                    addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: IPV6_ADDR }),
895                    prefix_len: 110,
896                }
897            ]
898        );
899
900        assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
901
902        Ok(())
903    }
904
905    #[fuchsia::test]
906    async fn test_ids_in_host_identify() -> Result<()> {
907        let (rcs_proxy, connector_proxy) = setup_rcs_proxy_with_connector();
908
909        let ident = rcs_proxy.identify_host().await.unwrap().unwrap();
910        assert_eq!(ident.ids, Some(vec![]));
911
912        let (pumpkin_a, _) = fidl::Socket::create_stream();
913        let (pumpkin_b, _) = fidl::Socket::create_stream();
914        let _node_ida = connector_proxy.establish_circuit(1234, pumpkin_a).await.unwrap();
915        let _node_idb = connector_proxy.establish_circuit(4567, pumpkin_b).await.unwrap();
916
917        let ident = rcs_proxy.identify_host().await.unwrap().unwrap();
918        let ids = ident.ids.unwrap();
919        assert_eq!(ids.len(), 2);
920        assert_eq!(1234u64, ids[0]);
921        assert_eq!(4567u64, ids[1]);
922
923        Ok(())
924    }
925
926    #[fuchsia::test]
927    async fn test_identify_default() -> Result<()> {
928        let (rcs_proxy, _) =
929            setup_rcs_proxy_from_env(RcsEnv { use_default_identifier: true, ..Default::default() });
930
931        let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
932
933        assert_eq!(resp.nodename.unwrap(), "fuchsia-default-nodename");
934        assert_eq!(resp.serial_number.unwrap(), "fuchsia-default-serial-number");
935        assert_eq!(resp.board_config, None);
936        assert_eq!(resp.product_config, None);
937        assert_eq!(resp.addresses, None);
938        assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
939
940        Ok(())
941    }
942}