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