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