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