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