Skip to main content

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