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