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