1use crate::host_identifier::{DefaultIdentifier, HostIdentifier, Identifier};
6use anyhow::{Context as _, Result};
7use component_debug::dirs::*;
8use component_debug::lifecycle::*;
9use fidl_fuchsia_developer_remotecontrol as rcs;
10use fidl_fuchsia_developer_remotecontrol_connector as connector;
11use fidl_fuchsia_diagnostics_types as diagnostics;
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_io as io;
14use fidl_fuchsia_sys2 as fsys;
15use fuchsia_component::client::connect_to_protocol_at_path;
16use futures::channel::oneshot;
17use futures::prelude::*;
18use log::*;
19use moniker::Moniker;
20use std::borrow::Borrow;
21use std::cell::RefCell;
22use std::rc::{Rc, Weak};
23
24mod host_identifier;
25pub mod http;
26
27pub struct RemoteControlService {
28 ids: RefCell<Vec<Weak<RefCell<Vec<u64>>>>>,
29 id_allocator: Box<dyn Fn() -> Result<Box<dyn Identifier + 'static>>>,
30 connector: Box<dyn Fn(ConnectionRequest, Weak<RemoteControlService>)>,
31}
32
33struct Client {
34 allocated_ids: Rc<RefCell<Vec<u64>>>,
41}
42
43pub enum ConnectionRequest {
46 Overnet(fidl::Socket, oneshot::Sender<u64>),
47 FDomain(fidl::Socket),
48}
49
50impl RemoteControlService {
51 pub async fn new(connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static) -> Self {
52 let mut bytes = [0u8; 8];
59 zx::cprng_draw(&mut bytes);
60 let boot_id = u64::from_ne_bytes(bytes);
61 Self::new_with_allocator(connector, move || Ok(Box::new(HostIdentifier::new(boot_id)?)))
62 }
63
64 pub async fn new_with_default_allocator(
65 connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static,
66 ) -> Self {
67 Self::new_with_allocator(connector, || Ok(Box::new(DefaultIdentifier::new())))
68 }
69
70 pub(crate) fn new_with_allocator(
71 connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static,
72 id_allocator: impl Fn() -> Result<Box<dyn Identifier + 'static>> + 'static,
73 ) -> Self {
74 Self {
75 id_allocator: Box::new(id_allocator),
76 ids: Default::default(),
77 connector: Box::new(connector),
78 }
79 }
80
81 fn remove_old_ids(self: &Rc<Self>) {
84 self.ids.borrow_mut().retain(|wirc| wirc.strong_count() > 0);
85 }
86
87 async fn handle_connector(
88 self: &Rc<Self>,
89 client: &Client,
90 request: connector::ConnectorRequest,
91 ) -> Result<()> {
92 match request {
93 connector::ConnectorRequest::EstablishCircuit { id, socket, responder } => {
94 let (nodeid_sender, nodeid_receiver) = oneshot::channel();
95 (self.connector)(
96 ConnectionRequest::Overnet(socket, nodeid_sender),
97 Rc::downgrade(self),
98 );
99 let node_id = nodeid_receiver.await?;
100 client.allocated_ids.borrow_mut().push(id);
101 responder.send(node_id)?;
102 Ok(())
103 }
104 connector::ConnectorRequest::FdomainToolboxSocket { socket, responder } => {
105 (self.connector)(ConnectionRequest::FDomain(socket), Rc::downgrade(self));
106 responder.send()?;
107 Ok(())
108 }
109 }
110 }
111
112 async fn handle(self: &Rc<Self>, request: rcs::RemoteControlRequest) -> Result<()> {
113 match request {
114 rcs::RemoteControlRequest::EchoString { value, responder } => {
115 debug!("Received echo string {}", value);
116 responder.send(&value)?;
117 Ok(())
118 }
119 rcs::RemoteControlRequest::LogMessage { tag, message, severity, responder } => {
120 match severity {
121 diagnostics::Severity::Trace => trace!(tag:%; "{}", message),
122 diagnostics::Severity::Debug => debug!(tag:%; "{}", message),
123 diagnostics::Severity::Info => info!(tag:%; "{}", message),
124 diagnostics::Severity::Warn => warn!(tag:%; "{}", message),
125 diagnostics::Severity::Error => error!(tag:%; "{}", message),
126 diagnostics::Severity::Fatal => error!(tag:%; "<FATAL> {}", message),
128 diagnostics::Severity::__SourceBreaking { .. } => {
129 error!(tag:%; "<UNKNOWN> {message}")
130 }
131 }
132 responder.send()?;
133 Ok(())
134 }
135 rcs::RemoteControlRequest::IdentifyHost { responder } => {
136 self.clone().identify_host(responder).await?;
137 Ok(())
138 }
139 rcs::RemoteControlRequest::ConnectCapability {
140 moniker,
141 capability_set,
142 capability_name,
143 server_channel,
144 responder,
145 } => {
146 responder.send(
147 self.clone()
148 .open_capability(moniker, capability_set, capability_name, server_channel)
149 .await,
150 )?;
151 Ok(())
152 }
153 rcs::RemoteControlRequest::GetTime { responder } => {
154 responder.send(zx::MonotonicInstant::get())?;
155 Ok(())
156 }
157 rcs::RemoteControlRequest::GetBootTime { responder } => {
158 responder.send(zx::BootInstant::get())?;
159 Ok(())
160 }
161 rcs::RemoteControlRequest::_UnknownMethod { ordinal, .. } => {
162 warn!("Received unknown request with ordinal {ordinal}");
163 Ok(())
164 }
165 }
166 }
167
168 pub async fn serve_connector_stream(self: Rc<Self>, stream: connector::ConnectorRequestStream) {
169 let allocated_ids = Rc::new(RefCell::new(vec![]));
171 self.ids.borrow_mut().push(Rc::downgrade(&allocated_ids));
172 let client = Client { allocated_ids };
173 stream
174 .for_each_concurrent(None, |request| async {
175 match request {
176 Ok(request) => {
177 let _ = self
178 .handle_connector(&client, request)
179 .await
180 .map_err(|e| warn!("stream request handling error: {:?}", e));
181 }
182 Err(e) => warn!("stream error: {:?}", e),
183 }
184 })
185 .await;
186 }
187
188 pub async fn serve_stream(self: Rc<Self>, stream: rcs::RemoteControlRequestStream) {
189 stream
190 .for_each_concurrent(None, |request| async {
191 match request {
192 Ok(request) => {
193 let _ = self
194 .handle(request)
195 .await
196 .map_err(|e| warn!("stream request handling error: {:?}", e));
197 }
198 Err(e) => warn!("stream error: {:?}", e),
199 }
200 })
201 .await;
202 }
203
204 pub async fn get_host_identity(
205 self: &Rc<Self>,
206 ) -> Result<rcs::IdentifyHostResponse, rcs::IdentifyHostError> {
207 let identifier = match (self.id_allocator)() {
208 Ok(i) => i,
209 Err(e) => {
210 error!(e:%; "Allocating host identifier");
211 return Err(rcs::IdentifyHostError::ProxyConnectionFailed);
212 }
213 };
214
215 self.remove_old_ids();
218 let ids: Vec<u64> = self
221 .ids
222 .borrow()
223 .iter()
224 .flat_map(|w| -> Vec<u64> {
225 <Rc<RefCell<Vec<u64>>> as Borrow<RefCell<Vec<u64>>>>::borrow(
228 &w.upgrade().expect("Didn't we just clear out refs with expired values??"),
229 )
230 .borrow()
231 .clone()
232 })
233 .collect();
234 let target_identity = identifier.identify().await.map(move |mut i| {
235 i.ids = Some(ids);
236 i
237 });
238 target_identity
239 }
240
241 pub async fn identify_host(
242 self: &Rc<Self>,
243 responder: rcs::RemoteControlIdentifyHostResponder,
244 ) -> Result<()> {
245 responder
246 .send(self.get_host_identity().await.as_ref().map_err(|e| *e))
247 .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_toolbox(
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_directory(
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_directory(&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 fidl_fuchsia_buildinfo as buildinfo;
439 use fidl_fuchsia_developer_remotecontrol as rcs;
440 use fidl_fuchsia_device as fdevice;
441 use fidl_fuchsia_hwinfo as hwinfo;
442 use fidl_fuchsia_io as fio;
443 use fidl_fuchsia_net as fnet;
444 use fidl_fuchsia_net_interfaces as fnet_interfaces;
445 use fidl_fuchsia_sysinfo as sysinfo;
446 use fuchsia_async as fasync;
447 use fuchsia_component::server::ServiceFs;
448
449 const NODENAME: &'static str = "thumb-set-human-shred";
450 const BOOT_TIME: u64 = 123456789000000000;
451 const SYSINFO_SERIAL: &'static str = "test_sysinfo_serial";
452 const SERIAL: &'static str = "test_serial";
453 const BOARD_CONFIG: &'static str = "test_board_name";
454 const PRODUCT_CONFIG: &'static str = "core";
455
456 const IPV4_ADDR: [u8; 4] = [127, 0, 0, 1];
457 const IPV6_ADDR: [u8; 16] = [127, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6];
458
459 fn setup_fake_device_service() -> hwinfo::DeviceProxy {
460 let (proxy, mut stream) =
461 fidl::endpoints::create_proxy_and_stream::<hwinfo::DeviceMarker>();
462 fasync::Task::spawn(async move {
463 while let Ok(Some(req)) = stream.try_next().await {
464 match req {
465 hwinfo::DeviceRequest::GetInfo { responder } => {
466 let _ = responder.send(&hwinfo::DeviceInfo {
467 serial_number: Some(String::from(SERIAL)),
468 ..Default::default()
469 });
470 }
471 }
472 }
473 })
474 .detach();
475
476 proxy
477 }
478
479 fn setup_fake_sysinfo_service(status: zx::Status) -> sysinfo::SysInfoProxy {
480 let (proxy, mut stream) =
481 fidl::endpoints::create_proxy_and_stream::<sysinfo::SysInfoMarker>();
482 fasync::Task::spawn(async move {
483 while let Ok(Some(req)) = stream.try_next().await {
484 match req {
485 sysinfo::SysInfoRequest::GetSerialNumber { responder } => {
486 let _ = responder.send(
487 Result::from(status)
488 .map(|_| SYSINFO_SERIAL)
489 .map_err(zx::Status::into_raw),
490 );
491 }
492 _ => panic!("unexpected request: {req:?}"),
493 }
494 }
495 })
496 .detach();
497
498 proxy
499 }
500
501 fn setup_fake_build_info_service() -> buildinfo::ProviderProxy {
502 let (proxy, mut stream) =
503 fidl::endpoints::create_proxy_and_stream::<buildinfo::ProviderMarker>();
504 fasync::Task::spawn(async move {
505 while let Ok(Some(req)) = stream.try_next().await {
506 match req {
507 buildinfo::ProviderRequest::GetBuildInfo { responder } => {
508 let _ = responder.send(&buildinfo::BuildInfo {
509 board_config: Some(String::from(BOARD_CONFIG)),
510 product_config: Some(String::from(PRODUCT_CONFIG)),
511 ..Default::default()
512 });
513 }
514 }
515 }
516 })
517 .detach();
518
519 proxy
520 }
521
522 fn setup_fake_name_provider_service() -> fdevice::NameProviderProxy {
523 let (proxy, mut stream) =
524 fidl::endpoints::create_proxy_and_stream::<fdevice::NameProviderMarker>();
525
526 fasync::Task::spawn(async move {
527 while let Ok(Some(req)) = stream.try_next().await {
528 match req {
529 fdevice::NameProviderRequest::GetDeviceName { responder } => {
530 let _ = responder.send(Ok(NODENAME));
531 }
532 }
533 }
534 })
535 .detach();
536
537 proxy
538 }
539
540 fn setup_fake_interface_state_service() -> fnet_interfaces::StateProxy {
541 let (proxy, mut stream) =
542 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces::StateMarker>();
543
544 fasync::Task::spawn(async move {
545 while let Ok(Some(req)) = stream.try_next().await {
546 match req {
547 fnet_interfaces::StateRequest::GetWatcher {
548 options: _,
549 watcher,
550 control_handle: _,
551 } => {
552 let mut stream = watcher.into_stream();
553 let mut first = true;
554 while let Ok(Some(req)) = stream.try_next().await {
555 match req {
556 fnet_interfaces::WatcherRequest::Watch { responder } => {
557 let event = if first {
558 first = false;
559 fnet_interfaces::Event::Existing(
560 fnet_interfaces::Properties {
561 id: Some(1),
562 addresses: Some(
563 IntoIterator::into_iter([
564 fnet::Subnet {
565 addr: fnet::IpAddress::Ipv4(
566 fnet::Ipv4Address {
567 addr: IPV4_ADDR,
568 },
569 ),
570 prefix_len: 4,
571 },
572 fnet::Subnet {
573 addr: fnet::IpAddress::Ipv6(
574 fnet::Ipv6Address {
575 addr: IPV6_ADDR,
576 },
577 ),
578 prefix_len: 110,
579 },
580 ])
581 .map(Some)
582 .map(|addr| fnet_interfaces::Address {
583 addr,
584 assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
585 ..Default::default()
586 })
587 .collect(),
588 ),
589 online: Some(true),
590 port_class: Some(
591 fnet_interfaces::PortClass::Loopback(
592 fnet_interfaces::Empty {},
593 ),
594 ),
595 has_default_ipv4_route: Some(false),
596 has_default_ipv6_route: Some(false),
597 name: Some(String::from("eth0")),
598 ..Default::default()
599 },
600 )
601 } else {
602 fnet_interfaces::Event::Idle(fnet_interfaces::Empty {})
603 };
604 let () = responder.send(&event).unwrap();
605 }
606 }
607 }
608 }
609 }
610 }
611 })
612 .detach();
613
614 proxy
615 }
616
617 #[derive(Default)]
618 #[non_exhaustive]
619 struct RcsEnv {
620 system_info_proxy: Option<sysinfo::SysInfoProxy>,
621 use_default_identifier: bool,
622 }
623
624 fn make_rcs_from_env(env: RcsEnv) -> Rc<RemoteControlService> {
625 let RcsEnv { system_info_proxy, use_default_identifier } = env;
626 if use_default_identifier {
627 Rc::new(RemoteControlService::new_with_allocator(
628 |req, _| match req {
629 ConnectionRequest::Overnet(_, sender) => sender.send(0u64).unwrap(),
630 _ => (),
631 },
632 move || Ok(Box::new(DefaultIdentifier { boot_timestamp_nanos: BOOT_TIME })),
633 ))
634 } else {
635 Rc::new(RemoteControlService::new_with_allocator(
636 |req, _| match req {
637 ConnectionRequest::Overnet(_, sender) => sender.send(0u64).unwrap(),
638 _ => (),
639 },
640 move || {
641 Ok(Box::new(HostIdentifier {
642 interface_state_proxy: setup_fake_interface_state_service(),
643 name_provider_proxy: setup_fake_name_provider_service(),
644 device_info_proxy: setup_fake_device_service(),
645 system_info_proxy: system_info_proxy
646 .clone()
647 .unwrap_or_else(|| setup_fake_sysinfo_service(zx::Status::INTERNAL)),
648 build_info_proxy: setup_fake_build_info_service(),
649 boot_timestamp_nanos: BOOT_TIME,
650 boot_id: 0,
651 }))
652 },
653 ))
654 }
655 }
656
657 fn setup_rcs_proxy_from_env(
658 env: RcsEnv,
659 ) -> (rcs::RemoteControlProxy, connector::ConnectorProxy) {
660 let service = make_rcs_from_env(env);
661
662 let (rcs_proxy, stream) =
663 fidl::endpoints::create_proxy_and_stream::<rcs::RemoteControlMarker>();
664 fasync::Task::local({
665 let service = Rc::clone(&service);
666 async move {
667 service.serve_stream(stream).await;
668 }
669 })
670 .detach();
671 let (connector_proxy, stream) =
672 fidl::endpoints::create_proxy_and_stream::<connector::ConnectorMarker>();
673 fasync::Task::local(async move {
674 service.serve_connector_stream(stream).await;
675 })
676 .detach();
677
678 (rcs_proxy, connector_proxy)
679 }
680
681 fn setup_rcs_proxy() -> rcs::RemoteControlProxy {
682 setup_rcs_proxy_from_env(Default::default()).0
683 }
684
685 fn setup_rcs_proxy_with_connector() -> (rcs::RemoteControlProxy, connector::ConnectorProxy) {
686 setup_rcs_proxy_from_env(Default::default())
687 }
688
689 fn setup_fake_lifecycle_controller() -> fsys::LifecycleControllerProxy {
690 fidl_test_util::spawn_stream_handler(
691 move |request: fsys::LifecycleControllerRequest| async move {
692 match request {
693 fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
694 assert_eq!(moniker, "core/my_component");
695 responder.send(Ok(())).unwrap()
696 }
697 _ => panic!("unexpected request: {:?}", request),
698 }
699 },
700 )
701 }
702
703 fn setup_exposed_dir(server: ServerEnd<fio::DirectoryMarker>) {
704 let mut fs = ServiceFs::new();
705 fs.add_fidl_service(move |_: hwinfo::BoardRequestStream| {});
706 fs.dir("svc").add_fidl_service(move |_: hwinfo::BoardRequestStream| {});
707 fs.serve_connection(server).unwrap();
708 fasync::Task::spawn(fs.collect::<()>()).detach();
709 }
710
711 fn setup_fake_realm_query(capability_set: fsys::OpenDirType) -> fsys::RealmQueryProxy {
716 fidl_test_util::spawn_stream_handler(move |request: fsys::RealmQueryRequest| async move {
717 match request {
718 fsys::RealmQueryRequest::OpenDirectory { moniker, dir_type, object, responder } => {
719 assert_eq!(moniker, "core/my_component");
720 assert_eq!(dir_type, capability_set);
721 setup_exposed_dir(object);
722 responder.send(Ok(())).unwrap()
723 }
724 _ => panic!("unexpected request: {:?}", request),
725 }
726 })
727 }
728
729 #[fuchsia::test]
730 async fn test_connect_to_component_capability() -> Result<()> {
731 for dir_type in vec![
732 fsys::OpenDirType::ExposedDir,
733 fsys::OpenDirType::NamespaceDir,
734 fsys::OpenDirType::OutgoingDir,
735 ] {
736 let (_client, server) = zx::Channel::create();
737 let lifecycle = setup_fake_lifecycle_controller();
738 let query = setup_fake_realm_query(dir_type);
739 connect_to_capability_at_moniker(
740 Moniker::try_from("./core/my_component").unwrap(),
741 dir_type,
742 "fuchsia.hwinfo.Board".to_string(),
743 server,
744 lifecycle,
745 query,
746 )
747 .await
748 .unwrap();
749 }
750 Ok(())
751 }
752
753 #[fuchsia::test]
754 async fn test_connect_to_component_capability_in_subdirectory() -> Result<()> {
755 for dir_type in vec![
756 fsys::OpenDirType::ExposedDir,
757 fsys::OpenDirType::NamespaceDir,
758 fsys::OpenDirType::OutgoingDir,
759 ] {
760 let (_client, server) = zx::Channel::create();
761 let lifecycle = setup_fake_lifecycle_controller();
762 let query = setup_fake_realm_query(dir_type);
763 connect_to_capability_at_moniker(
764 Moniker::try_from("./core/my_component").unwrap(),
765 dir_type,
766 "svc/fuchsia.hwinfo.Board".to_string(),
767 server,
768 lifecycle,
769 query,
770 )
771 .await
772 .unwrap();
773 }
774 Ok(())
775 }
776
777 #[fuchsia::test]
778 async fn test_connect_to_capability_not_available() -> Result<()> {
779 for dir_type in vec![
780 fsys::OpenDirType::ExposedDir,
781 fsys::OpenDirType::NamespaceDir,
782 fsys::OpenDirType::OutgoingDir,
783 ] {
784 let (_client, server) = zx::Channel::create();
785 let lifecycle = setup_fake_lifecycle_controller();
786 let query = setup_fake_realm_query(dir_type);
787 let error = connect_to_capability_at_moniker(
788 Moniker::try_from("./core/my_component").unwrap(),
789 dir_type,
790 "fuchsia.not.exposed".to_string(),
791 server,
792 lifecycle,
793 query,
794 )
795 .await
796 .unwrap_err();
797 assert_eq!(error, rcs::ConnectCapabilityError::NoMatchingCapabilities);
798 }
799 Ok(())
800 }
801
802 #[fuchsia::test]
803 async fn test_connect_to_capability_not_available_in_subdirectory() -> Result<()> {
804 for dir_type in vec![
805 fsys::OpenDirType::ExposedDir,
806 fsys::OpenDirType::NamespaceDir,
807 fsys::OpenDirType::OutgoingDir,
808 ] {
809 let (_client, server) = zx::Channel::create();
810 let lifecycle = setup_fake_lifecycle_controller();
811 let query = setup_fake_realm_query(dir_type);
812 let error = connect_to_capability_at_moniker(
813 Moniker::try_from("./core/my_component").unwrap(),
814 dir_type,
815 "svc/fuchsia.not.exposed".to_string(),
816 server,
817 lifecycle,
818 query,
819 )
820 .await
821 .unwrap_err();
822 assert_eq!(error, rcs::ConnectCapabilityError::NoMatchingCapabilities);
823 }
824 Ok(())
825 }
826
827 #[fuchsia::test]
828 async fn test_identify_host() -> Result<()> {
829 let rcs_proxy = setup_rcs_proxy();
830
831 let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
832
833 assert_eq!(resp.serial_number.unwrap(), SERIAL);
834 assert_eq!(resp.board_config.unwrap(), BOARD_CONFIG);
835 assert_eq!(resp.product_config.unwrap(), PRODUCT_CONFIG);
836 assert_eq!(resp.nodename.unwrap(), NODENAME);
837
838 let addrs = resp.addresses.unwrap();
839 assert_eq!(
840 addrs[..],
841 [
842 fnet::Subnet {
843 addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: IPV4_ADDR }),
844 prefix_len: 4,
845 },
846 fnet::Subnet {
847 addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: IPV6_ADDR }),
848 prefix_len: 110,
849 }
850 ]
851 );
852
853 assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
854
855 Ok(())
856 }
857
858 #[fuchsia::test]
859 async fn test_identify_host_sysinfo_serial() -> Result<()> {
860 let (rcs_proxy, _) = setup_rcs_proxy_from_env(RcsEnv {
861 system_info_proxy: Some(setup_fake_sysinfo_service(zx::Status::OK)),
862 ..Default::default()
863 });
864
865 let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
866
867 assert_eq!(resp.serial_number.unwrap(), SYSINFO_SERIAL);
868 assert_eq!(resp.board_config.unwrap(), BOARD_CONFIG);
869 assert_eq!(resp.product_config.unwrap(), PRODUCT_CONFIG);
870 assert_eq!(resp.nodename.unwrap(), NODENAME);
871
872 let addrs = resp.addresses.unwrap();
873 assert_eq!(
874 addrs[..],
875 [
876 fnet::Subnet {
877 addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: IPV4_ADDR }),
878 prefix_len: 4,
879 },
880 fnet::Subnet {
881 addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: IPV6_ADDR }),
882 prefix_len: 110,
883 }
884 ]
885 );
886
887 assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
888
889 Ok(())
890 }
891
892 #[fuchsia::test]
893 async fn test_ids_in_host_identify() -> Result<()> {
894 let (rcs_proxy, connector_proxy) = setup_rcs_proxy_with_connector();
895
896 let ident = rcs_proxy.identify_host().await.unwrap().unwrap();
897 assert_eq!(ident.ids, Some(vec![]));
898
899 let (pumpkin_a, _) = fidl::Socket::create_stream();
900 let (pumpkin_b, _) = fidl::Socket::create_stream();
901 let _node_ida = connector_proxy.establish_circuit(1234, pumpkin_a).await.unwrap();
902 let _node_idb = connector_proxy.establish_circuit(4567, pumpkin_b).await.unwrap();
903
904 let ident = rcs_proxy.identify_host().await.unwrap().unwrap();
905 let ids = ident.ids.unwrap();
906 assert_eq!(ids.len(), 2);
907 assert_eq!(1234u64, ids[0]);
908 assert_eq!(4567u64, ids[1]);
909
910 Ok(())
911 }
912
913 #[fuchsia::test]
914 async fn test_identify_default() -> Result<()> {
915 let (rcs_proxy, _) =
916 setup_rcs_proxy_from_env(RcsEnv { use_default_identifier: true, ..Default::default() });
917
918 let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
919
920 assert_eq!(resp.nodename.unwrap(), "fuchsia-default-nodename");
921 assert_eq!(resp.serial_number.unwrap(), "fuchsia-default-serial-number");
922 assert_eq!(resp.board_config, None);
923 assert_eq!(resp.product_config, None);
924 assert_eq!(resp.addresses, None);
925 assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
926
927 Ok(())
928 }
929}