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