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