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