1use crate::host_identifier::{DefaultIdentifier, HostIdentifier, Identifier};
6use anyhow::{Context as _, Result};
7use component_debug::dirs::*;
8use component_debug::lifecycle::*;
9use fidl::endpoints::Proxy;
10use fidl_fuchsia_developer_remotecontrol as rcs;
11use fidl_fuchsia_developer_remotecontrol_connector as connector;
12use fidl_fuchsia_diagnostics_types as diagnostics;
13use fidl_fuchsia_io as fio;
14use fidl_fuchsia_io as io;
15use fidl_fuchsia_sys2 as fsys;
16use fuchsia_component::client::connect_to_protocol_at_path;
17use futures::channel::oneshot;
18use futures::prelude::*;
19use log::*;
20use moniker::Moniker;
21use std::borrow::Borrow;
22use std::cell::RefCell;
23use std::rc::{Rc, Weak};
24
25mod host_identifier;
26pub mod http;
27
28pub struct RemoteControlService {
29 ids: RefCell<Vec<Weak<RefCell<Vec<u64>>>>>,
30 id_allocator: Box<dyn Fn() -> Result<Box<dyn Identifier + 'static>>>,
31 connector: Box<dyn Fn(ConnectionRequest, Weak<RemoteControlService>)>,
32 toolbox_dir: RefCell<Option<fio::DirectoryProxy>>,
33}
34
35struct Client {
36 allocated_ids: Rc<RefCell<Vec<u64>>>,
43}
44
45pub enum ConnectionRequest {
48 Overnet(fidl::Socket, oneshot::Sender<u64>),
49 FDomain(fidl::Socket),
50}
51
52impl RemoteControlService {
53 pub async fn new(connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static) -> Self {
54 let mut bytes = [0u8; 8];
61 zx::cprng_draw(&mut bytes);
62 let boot_id = u64::from_ne_bytes(bytes);
63 Self::new_with_allocator(connector, move || Ok(Box::new(HostIdentifier::new(boot_id)?)))
64 }
65
66 pub async fn new_with_default_allocator(
67 connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static,
68 ) -> Self {
69 Self::new_with_allocator(connector, || Ok(Box::new(DefaultIdentifier::new())))
70 }
71
72 pub(crate) fn new_with_allocator(
73 connector: impl Fn(ConnectionRequest, Weak<Self>) + 'static,
74 id_allocator: impl Fn() -> Result<Box<dyn Identifier + 'static>> + 'static,
75 ) -> Self {
76 Self {
77 id_allocator: Box::new(id_allocator),
78 ids: Default::default(),
79 connector: Box::new(connector),
80 toolbox_dir: RefCell::new(None),
81 }
82 }
83
84 fn remove_old_ids(self: &Rc<Self>) {
87 self.ids.borrow_mut().retain(|wirc| wirc.strong_count() > 0);
88 }
89
90 async fn handle_connector(
91 self: &Rc<Self>,
92 client: &Client,
93 request: connector::ConnectorRequest,
94 ) -> Result<()> {
95 match request {
96 connector::ConnectorRequest::EstablishCircuit { id, socket, responder } => {
97 let (nodeid_sender, nodeid_receiver) = oneshot::channel();
98 (self.connector)(
99 ConnectionRequest::Overnet(socket, nodeid_sender),
100 Rc::downgrade(self),
101 );
102 let node_id = nodeid_receiver.await?;
103 client.allocated_ids.borrow_mut().push(id);
104 responder.send(node_id)?;
105 Ok(())
106 }
107 connector::ConnectorRequest::FdomainToolboxSocket { socket, responder } => {
108 (self.connector)(ConnectionRequest::FDomain(socket), Rc::downgrade(self));
109 responder.send()?;
110 Ok(())
111 }
112 }
113 }
114
115 async fn handle(self: &Rc<Self>, request: rcs::RemoteControlRequest) -> Result<()> {
116 match request {
117 rcs::RemoteControlRequest::EchoString { value, responder } => {
118 debug!("Received echo string {}", value);
119 responder.send(&value)?;
120 Ok(())
121 }
122 rcs::RemoteControlRequest::LogMessage { tag, message, severity, responder } => {
123 match severity {
124 diagnostics::Severity::Trace => trace!(tag:%; "{}", message),
125 diagnostics::Severity::Debug => debug!(tag:%; "{}", message),
126 diagnostics::Severity::Info => info!(tag:%; "{}", message),
127 diagnostics::Severity::Warn => warn!(tag:%; "{}", message),
128 diagnostics::Severity::Error => error!(tag:%; "{}", message),
129 diagnostics::Severity::Fatal => error!(tag:%; "<FATAL> {}", message),
131 diagnostics::Severity::__SourceBreaking { .. } => {
132 error!(tag:%; "<UNKNOWN> {message}")
133 }
134 }
135 responder.send()?;
136 Ok(())
137 }
138 rcs::RemoteControlRequest::IdentifyHost { responder } => {
139 self.clone().identify_host(responder).await?;
140 Ok(())
141 }
142 rcs::RemoteControlRequest::ConnectCapability {
143 moniker,
144 capability_set,
145 capability_name,
146 server_channel,
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 get_host_identity(
208 self: &Rc<Self>,
209 ) -> Result<rcs::IdentifyHostResponse, rcs::IdentifyHostError> {
210 let identifier = match (self.id_allocator)() {
211 Ok(i) => i,
212 Err(e) => {
213 error!(e:%; "Allocating host identifier");
214 return Err(rcs::IdentifyHostError::ProxyConnectionFailed);
215 }
216 };
217
218 self.remove_old_ids();
221 let ids: Vec<u64> = self
224 .ids
225 .borrow()
226 .iter()
227 .flat_map(|w| -> Vec<u64> {
228 <Rc<RefCell<Vec<u64>>> as Borrow<RefCell<Vec<u64>>>>::borrow(
231 &w.upgrade().expect("Didn't we just clear out refs with expired values??"),
232 )
233 .borrow()
234 .clone()
235 })
236 .collect();
237 let target_identity = identifier.identify().await.map(move |mut i| {
238 i.ids = Some(ids);
239 i
240 });
241 target_identity
242 }
243
244 pub async fn identify_host(
245 self: &Rc<Self>,
246 responder: rcs::RemoteControlIdentifyHostResponder,
247 ) -> Result<()> {
248 responder
249 .send(self.get_host_identity().await.as_ref().map_err(|e| *e))
250 .context("responding to client")?;
251 Ok(())
252 }
253
254 async fn open_capability(
257 self: &Rc<Self>,
258 moniker: String,
259 capability_set: fsys::OpenDirType,
260 capability_name: String,
261 server_end: zx::Channel,
262 ) -> Result<(), rcs::ConnectCapabilityError> {
263 let lifecycle = connect_to_protocol_at_path::<fsys::LifecycleControllerMarker>(
265 "/svc/fuchsia.sys2.LifecycleController.root",
266 )
267 .map_err(|err| {
268 error!(err:%; "could not connect to lifecycle controller");
269 rcs::ConnectCapabilityError::CapabilityConnectFailed
270 })?;
271
272 let query = connect_to_protocol_at_path::<fsys::RealmQueryMarker>(
274 "/svc/fuchsia.sys2.RealmQuery.root",
275 )
276 .map_err(|err| {
277 error!(err:%; "could not connect to realm query");
278 rcs::ConnectCapabilityError::CapabilityConnectFailed
279 })?;
280
281 let moniker = Moniker::try_from(moniker.as_str())
282 .map_err(|_| rcs::ConnectCapabilityError::InvalidMoniker)?;
283 connect_to_capability_at_moniker(
284 moniker,
285 capability_set,
286 capability_name,
287 server_end,
288 lifecycle,
289 query,
290 )
291 .await
292 }
293
294 async fn get_or_open_toolbox_dir(
303 self: &Rc<Self>,
304 ) -> Result<fio::DirectoryProxy, rcs::ConnectCapabilityError> {
305 {
306 let borrowed = self.toolbox_dir.borrow();
309 if let Some(dir) = borrowed.as_ref() {
310 let is_alive = match dir
313 .as_channel()
314 .as_ref()
315 .wait_one(zx::Signals::CHANNEL_PEER_CLOSED, zx::MonotonicInstant::from_nanos(0))
316 .to_result()
317 {
318 Ok(_) => false,
319 Err(zx::Status::TIMED_OUT) => true,
320 Err(_) => false,
321 };
322
323 if is_alive {
324 if let Ok(reopened) =
325 fuchsia_fs::directory::open_directory_async(dir, ".", fio::PERM_READABLE)
326 {
327 return Ok(reopened);
328 }
329 }
330 }
331 }
332 *self.toolbox_dir.borrow_mut() = None;
333
334 let controller = connect_to_protocol_at_path::<fsys::LifecycleControllerMarker>(
336 "/svc/fuchsia.sys2.LifecycleController.root",
337 )
338 .map_err(|err| {
339 error!(err:%; "could not connect to lifecycle controller");
340 rcs::ConnectCapabilityError::CapabilityConnectFailed
341 })?;
342
343 let query = connect_to_protocol_at_path::<fsys::RealmQueryMarker>(
345 "/svc/fuchsia.sys2.RealmQuery.root",
346 )
347 .map_err(|err| {
348 error!(err:%; "could not connect to realm query");
349 rcs::ConnectCapabilityError::CapabilityConnectFailed
350 })?;
351
352 let moniker =
355 moniker::Moniker::try_from("toolbox").expect("Moniker 'toolbox' did not parse!");
356 let legacy_moniker = moniker::Moniker::try_from("core/toolbox")
357 .expect("Moniker 'core/toolbox' did not parse!");
358 let (modern, legacy) = futures::join!(
359 resolve_instance(&controller, &moniker),
360 resolve_instance(&controller, &legacy_moniker)
361 );
362
363 let moniker = if modern.is_ok() {
364 moniker
365 } else if legacy.is_ok() {
366 legacy_moniker
367 } else {
368 error!("Unable to resolve toolbox component in either toolbox or core/toolbox");
369 return Err(rcs::ConnectCapabilityError::NoMatchingComponent);
370 };
371
372 let dir = component_debug::dirs::open_instance_directory(
373 &moniker,
374 fsys::OpenDirType::NamespaceDir.into(),
375 &query,
376 )
377 .map_err(|err| {
378 error!(err:?; "error opening exposed dir");
379 rcs::ConnectCapabilityError::CapabilityConnectFailed
380 })
381 .await?;
382
383 let reopened = fuchsia_fs::directory::open_directory_async(&dir, ".", fio::PERM_READABLE)
384 .map_err(|err| {
385 error!(err:?; "error reopening toolbox dir");
386 rcs::ConnectCapabilityError::CapabilityConnectFailed
387 })?;
388 *self.toolbox_dir.borrow_mut() = Some(dir);
389 Ok(reopened)
390 }
391
392 pub async fn open_toolbox(
393 self: &Rc<Self>,
394 server_end: zx::Channel,
395 ) -> Result<(), rcs::ConnectCapabilityError> {
396 let dir = self.get_or_open_toolbox_dir().await?;
397 dir.open("svc", io::PERM_READABLE, &Default::default(), server_end).map_err(|err| {
398 error!(err:?; "error opening svc dir in toolbox");
399 *self.toolbox_dir.borrow_mut() = None;
400 rcs::ConnectCapabilityError::CapabilityConnectFailed
401 })?;
402 Ok(())
403 }
404}
405
406async fn connect_to_capability_at_moniker(
409 moniker: Moniker,
410 capability_set: fsys::OpenDirType,
411 capability_name: String,
412 server_end: zx::Channel,
413 lifecycle: fsys::LifecycleControllerProxy,
414 query: fsys::RealmQueryProxy,
415) -> Result<(), rcs::ConnectCapabilityError> {
416 resolve_instance(&lifecycle, &moniker)
418 .map_err(|err| match err {
419 ResolveError::ActionError(ActionError::InstanceNotFound) => {
420 rcs::ConnectCapabilityError::NoMatchingComponent
421 }
422 err => {
423 error!(err:?; "error resolving component");
424 rcs::ConnectCapabilityError::CapabilityConnectFailed
425 }
426 })
427 .await?;
428
429 let dir = open_instance_directory(&moniker, capability_set.into(), &query)
430 .map_err(|err| {
431 error!(err:?; "error opening exposed dir");
432 rcs::ConnectCapabilityError::CapabilityConnectFailed
433 })
434 .await?;
435
436 connect_to_capability_in_dir(&dir, &capability_name, server_end).await?;
437 Ok(())
438}
439
440async fn connect_to_capability_in_dir(
441 dir: &io::DirectoryProxy,
442 capability_name: &str,
443 server_end: zx::Channel,
444) -> Result<(), rcs::ConnectCapabilityError> {
445 check_entry_exists(dir, capability_name).await?;
446 dir.open(capability_name, io::Flags::PROTOCOL_SERVICE, &Default::default(), server_end).map_err(
448 |err| {
449 error!(err:%; "error opening capability from exposed dir");
450 rcs::ConnectCapabilityError::CapabilityConnectFailed
451 },
452 )
453}
454
455async fn check_entry_exists(
457 dir: &io::DirectoryProxy,
458 capability_name: &str,
459) -> Result<(), rcs::ConnectCapabilityError> {
460 let dir_idx = capability_name.rfind('/');
461 let (capability_name, entries) = match dir_idx {
462 Some(dir_idx) => {
463 let dirname = &capability_name[0..dir_idx];
464 let basename = &capability_name[dir_idx + 1..];
465 let nested_dir =
466 fuchsia_fs::directory::open_directory(dir, dirname, fio::PERM_READABLE)
467 .await
468 .map_err(|_| rcs::ConnectCapabilityError::NoMatchingCapabilities)?;
469 let entries = fuchsia_fs::directory::readdir(&nested_dir)
470 .await
471 .map_err(|_| rcs::ConnectCapabilityError::CapabilityConnectFailed)?;
472 (basename, entries)
473 }
474 None => {
475 let entries = fuchsia_fs::directory::readdir(dir)
476 .await
477 .map_err(|_| rcs::ConnectCapabilityError::CapabilityConnectFailed)?;
478 (capability_name, entries)
479 }
480 };
481 if entries.iter().any(|e| e.name == capability_name) {
482 Ok(())
483 } else {
484 Err(rcs::ConnectCapabilityError::NoMatchingCapabilities)
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491 use fidl::endpoints::ServerEnd;
492 use fidl_fuchsia_buildinfo as buildinfo;
493 use fidl_fuchsia_developer_remotecontrol as rcs;
494 use fidl_fuchsia_device as fdevice;
495 use fidl_fuchsia_hwinfo as hwinfo;
496 use fidl_fuchsia_io as fio;
497 use fidl_fuchsia_net as fnet;
498 use fidl_fuchsia_net_interfaces as fnet_interfaces;
499 use fidl_fuchsia_sysinfo as sysinfo;
500 use fuchsia_async as fasync;
501 use fuchsia_component::server::ServiceFs;
502
503 const NODENAME: &'static str = "thumb-set-human-shred";
504 const BOOT_TIME: u64 = 123456789000000000;
505 const SYSINFO_SERIAL: &'static str = "test_sysinfo_serial";
506 const SERIAL: &'static str = "test_serial";
507 const BOARD_CONFIG: &'static str = "test_board_name";
508 const PRODUCT_CONFIG: &'static str = "core";
509
510 const IPV4_ADDR: [u8; 4] = [127, 0, 0, 1];
511 const IPV6_ADDR: [u8; 16] = [127, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6];
512
513 fn setup_fake_device_service() -> hwinfo::DeviceProxy {
514 let (proxy, mut stream) =
515 fidl::endpoints::create_proxy_and_stream::<hwinfo::DeviceMarker>();
516 fasync::Task::spawn(async move {
517 while let Ok(Some(req)) = stream.try_next().await {
518 match req {
519 hwinfo::DeviceRequest::GetInfo { responder } => {
520 let _ = responder.send(&hwinfo::DeviceInfo {
521 serial_number: Some(String::from(SERIAL)),
522 ..Default::default()
523 });
524 }
525 }
526 }
527 })
528 .detach();
529
530 proxy
531 }
532
533 fn setup_fake_sysinfo_service(status: Result<(), zx::Status>) -> sysinfo::SysInfoProxy {
534 let (proxy, mut stream) =
535 fidl::endpoints::create_proxy_and_stream::<sysinfo::SysInfoMarker>();
536 fasync::Task::spawn(async move {
537 while let Ok(Some(req)) = stream.try_next().await {
538 match req {
539 sysinfo::SysInfoRequest::GetSerialNumber { responder } => {
540 let _ = responder
541 .send(status.map(|_| SYSINFO_SERIAL).map_err(zx::Status::into_raw));
542 }
543 _ => panic!("unexpected request: {req:?}"),
544 }
545 }
546 })
547 .detach();
548
549 proxy
550 }
551
552 fn setup_fake_build_info_service() -> buildinfo::ProviderProxy {
553 let (proxy, mut stream) =
554 fidl::endpoints::create_proxy_and_stream::<buildinfo::ProviderMarker>();
555 fasync::Task::spawn(async move {
556 while let Ok(Some(req)) = stream.try_next().await {
557 match req {
558 buildinfo::ProviderRequest::GetBuildInfo { responder } => {
559 let _ = responder.send(&buildinfo::BuildInfo {
560 board_config: Some(String::from(BOARD_CONFIG)),
561 product_config: Some(String::from(PRODUCT_CONFIG)),
562 ..Default::default()
563 });
564 }
565 }
566 }
567 })
568 .detach();
569
570 proxy
571 }
572
573 fn setup_fake_name_provider_service() -> fdevice::NameProviderProxy {
574 let (proxy, mut stream) =
575 fidl::endpoints::create_proxy_and_stream::<fdevice::NameProviderMarker>();
576
577 fasync::Task::spawn(async move {
578 while let Ok(Some(req)) = stream.try_next().await {
579 match req {
580 fdevice::NameProviderRequest::GetDeviceName { responder } => {
581 let _ = responder.send(Ok(NODENAME));
582 }
583 }
584 }
585 })
586 .detach();
587
588 proxy
589 }
590
591 fn setup_fake_interface_state_service() -> fnet_interfaces::StateProxy {
592 let (proxy, mut stream) =
593 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces::StateMarker>();
594
595 fasync::Task::spawn(async move {
596 while let Ok(Some(req)) = stream.try_next().await {
597 match req {
598 fnet_interfaces::StateRequest::GetWatcher {
599 options: _,
600 watcher,
601 control_handle: _,
602 } => {
603 let mut stream = watcher.into_stream();
604 let mut first = true;
605 while let Ok(Some(req)) = stream.try_next().await {
606 match req {
607 fnet_interfaces::WatcherRequest::Watch { responder } => {
608 let event = if first {
609 first = false;
610 fnet_interfaces::Event::Existing(
611 fnet_interfaces::Properties {
612 id: Some(1),
613 addresses: Some(
614 IntoIterator::into_iter([
615 fnet::Subnet {
616 addr: fnet::IpAddress::Ipv4(
617 fnet::Ipv4Address {
618 addr: IPV4_ADDR,
619 },
620 ),
621 prefix_len: 4,
622 },
623 fnet::Subnet {
624 addr: fnet::IpAddress::Ipv6(
625 fnet::Ipv6Address {
626 addr: IPV6_ADDR,
627 },
628 ),
629 prefix_len: 110,
630 },
631 ])
632 .map(Some)
633 .map(|addr| fnet_interfaces::Address {
634 addr,
635 assignment_state: Some(fnet_interfaces::AddressAssignmentState::Assigned),
636 ..Default::default()
637 })
638 .collect(),
639 ),
640 online: Some(true),
641 port_class: Some(
642 fnet_interfaces::PortClass::Loopback(
643 fnet_interfaces::Empty {},
644 ),
645 ),
646 has_default_ipv4_route: Some(false),
647 has_default_ipv6_route: Some(false),
648 name: Some(String::from("eth0")),
649 ..Default::default()
650 },
651 )
652 } else {
653 fnet_interfaces::Event::Idle(fnet_interfaces::Empty {})
654 };
655 let () = responder.send(&event).unwrap();
656 }
657 }
658 }
659 }
660 }
661 }
662 })
663 .detach();
664
665 proxy
666 }
667
668 #[derive(Default)]
669 #[non_exhaustive]
670 struct RcsEnv {
671 system_info_proxy: Option<sysinfo::SysInfoProxy>,
672 use_default_identifier: bool,
673 }
674
675 fn make_rcs_from_env(env: RcsEnv) -> Rc<RemoteControlService> {
676 let RcsEnv { system_info_proxy, use_default_identifier } = env;
677 if use_default_identifier {
678 Rc::new(RemoteControlService::new_with_allocator(
679 |req, _| match req {
680 ConnectionRequest::Overnet(_, sender) => sender.send(0u64).unwrap(),
681 _ => (),
682 },
683 move || Ok(Box::new(DefaultIdentifier { boot_timestamp_nanos: BOOT_TIME })),
684 ))
685 } else {
686 Rc::new(RemoteControlService::new_with_allocator(
687 |req, _| match req {
688 ConnectionRequest::Overnet(_, sender) => sender.send(0u64).unwrap(),
689 _ => (),
690 },
691 move || {
692 Ok(Box::new(HostIdentifier {
693 interface_state_proxy: setup_fake_interface_state_service(),
694 name_provider_proxy: setup_fake_name_provider_service(),
695 device_info_proxy: setup_fake_device_service(),
696 system_info_proxy: system_info_proxy.clone().unwrap_or_else(|| {
697 setup_fake_sysinfo_service(Err(zx::Status::INTERNAL))
698 }),
699 build_info_proxy: setup_fake_build_info_service(),
700 boot_timestamp_nanos: BOOT_TIME,
701 boot_id: 0,
702 }))
703 },
704 ))
705 }
706 }
707
708 fn setup_rcs_proxy_from_env(
709 env: RcsEnv,
710 ) -> (rcs::RemoteControlProxy, connector::ConnectorProxy) {
711 let service = make_rcs_from_env(env);
712
713 let (rcs_proxy, stream) =
714 fidl::endpoints::create_proxy_and_stream::<rcs::RemoteControlMarker>();
715 fasync::Task::local({
716 let service = Rc::clone(&service);
717 async move {
718 service.serve_stream(stream).await;
719 }
720 })
721 .detach();
722 let (connector_proxy, stream) =
723 fidl::endpoints::create_proxy_and_stream::<connector::ConnectorMarker>();
724 fasync::Task::local(async move {
725 service.serve_connector_stream(stream).await;
726 })
727 .detach();
728
729 (rcs_proxy, connector_proxy)
730 }
731
732 fn setup_rcs_proxy() -> rcs::RemoteControlProxy {
733 setup_rcs_proxy_from_env(Default::default()).0
734 }
735
736 fn setup_rcs_proxy_with_connector() -> (rcs::RemoteControlProxy, connector::ConnectorProxy) {
737 setup_rcs_proxy_from_env(Default::default())
738 }
739
740 fn setup_fake_lifecycle_controller() -> fsys::LifecycleControllerProxy {
741 fidl_test_util::spawn_stream_handler(
742 move |request: fsys::LifecycleControllerRequest| async move {
743 match request {
744 fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
745 assert_eq!(moniker, "core/my_component");
746 responder.send(Ok(())).unwrap()
747 }
748 _ => panic!("unexpected request: {:?}", request),
749 }
750 },
751 )
752 }
753
754 fn setup_exposed_dir(server: ServerEnd<fio::DirectoryMarker>) {
755 let mut fs = ServiceFs::new();
756 fs.add_fidl_service(move |_: hwinfo::BoardRequestStream| {});
757 fs.dir("svc").add_fidl_service(move |_: hwinfo::BoardRequestStream| {});
758 fs.serve_connection(server).unwrap();
759 fasync::Task::spawn(fs.collect::<()>()).detach();
760 }
761
762 fn setup_fake_realm_query(capability_set: fsys::OpenDirType) -> fsys::RealmQueryProxy {
767 fidl_test_util::spawn_stream_handler(move |request: fsys::RealmQueryRequest| async move {
768 match request {
769 fsys::RealmQueryRequest::OpenDirectory { moniker, dir_type, object, responder } => {
770 assert_eq!(moniker, "core/my_component");
771 assert_eq!(dir_type, capability_set);
772 setup_exposed_dir(object);
773 responder.send(Ok(())).unwrap()
774 }
775 _ => panic!("unexpected request: {:?}", request),
776 }
777 })
778 }
779
780 #[fuchsia::test]
781 async fn test_connect_to_component_capability() -> Result<()> {
782 for dir_type in vec![
783 fsys::OpenDirType::ExposedDir,
784 fsys::OpenDirType::NamespaceDir,
785 fsys::OpenDirType::OutgoingDir,
786 ] {
787 let (_client, server) = zx::Channel::create();
788 let lifecycle = setup_fake_lifecycle_controller();
789 let query = setup_fake_realm_query(dir_type);
790 connect_to_capability_at_moniker(
791 Moniker::try_from("./core/my_component").unwrap(),
792 dir_type,
793 "fuchsia.hwinfo.Board".to_string(),
794 server,
795 lifecycle,
796 query,
797 )
798 .await
799 .unwrap();
800 }
801 Ok(())
802 }
803
804 #[fuchsia::test]
805 async fn test_connect_to_component_capability_in_subdirectory() -> Result<()> {
806 for dir_type in vec![
807 fsys::OpenDirType::ExposedDir,
808 fsys::OpenDirType::NamespaceDir,
809 fsys::OpenDirType::OutgoingDir,
810 ] {
811 let (_client, server) = zx::Channel::create();
812 let lifecycle = setup_fake_lifecycle_controller();
813 let query = setup_fake_realm_query(dir_type);
814 connect_to_capability_at_moniker(
815 Moniker::try_from("./core/my_component").unwrap(),
816 dir_type,
817 "svc/fuchsia.hwinfo.Board".to_string(),
818 server,
819 lifecycle,
820 query,
821 )
822 .await
823 .unwrap();
824 }
825 Ok(())
826 }
827
828 #[fuchsia::test]
829 async fn test_connect_to_capability_not_available() -> Result<()> {
830 for dir_type in vec![
831 fsys::OpenDirType::ExposedDir,
832 fsys::OpenDirType::NamespaceDir,
833 fsys::OpenDirType::OutgoingDir,
834 ] {
835 let (_client, server) = zx::Channel::create();
836 let lifecycle = setup_fake_lifecycle_controller();
837 let query = setup_fake_realm_query(dir_type);
838 let error = connect_to_capability_at_moniker(
839 Moniker::try_from("./core/my_component").unwrap(),
840 dir_type,
841 "fuchsia.not.exposed".to_string(),
842 server,
843 lifecycle,
844 query,
845 )
846 .await
847 .unwrap_err();
848 assert_eq!(error, rcs::ConnectCapabilityError::NoMatchingCapabilities);
849 }
850 Ok(())
851 }
852
853 #[fuchsia::test]
854 async fn test_connect_to_capability_not_available_in_subdirectory() -> Result<()> {
855 for dir_type in vec![
856 fsys::OpenDirType::ExposedDir,
857 fsys::OpenDirType::NamespaceDir,
858 fsys::OpenDirType::OutgoingDir,
859 ] {
860 let (_client, server) = zx::Channel::create();
861 let lifecycle = setup_fake_lifecycle_controller();
862 let query = setup_fake_realm_query(dir_type);
863 let error = connect_to_capability_at_moniker(
864 Moniker::try_from("./core/my_component").unwrap(),
865 dir_type,
866 "svc/fuchsia.not.exposed".to_string(),
867 server,
868 lifecycle,
869 query,
870 )
871 .await
872 .unwrap_err();
873 assert_eq!(error, rcs::ConnectCapabilityError::NoMatchingCapabilities);
874 }
875 Ok(())
876 }
877
878 #[fuchsia::test]
879 async fn test_identify_host() -> Result<()> {
880 let rcs_proxy = setup_rcs_proxy();
881
882 let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
883
884 assert_eq!(resp.serial_number.unwrap(), SERIAL);
885 assert_eq!(resp.board_config.unwrap(), BOARD_CONFIG);
886 assert_eq!(resp.product_config.unwrap(), PRODUCT_CONFIG);
887 assert_eq!(resp.nodename.unwrap(), NODENAME);
888
889 let addrs = resp.addresses.unwrap();
890 assert_eq!(
891 addrs[..],
892 [
893 fnet::Subnet {
894 addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: IPV4_ADDR }),
895 prefix_len: 4,
896 },
897 fnet::Subnet {
898 addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: IPV6_ADDR }),
899 prefix_len: 110,
900 }
901 ]
902 );
903
904 assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
905 assert_eq!(resp.f_release, crate::host_identifier::current_f_release());
906
907 Ok(())
908 }
909
910 #[fuchsia::test]
911 async fn test_identify_host_sysinfo_serial() -> Result<()> {
912 let (rcs_proxy, _) = setup_rcs_proxy_from_env(RcsEnv {
913 system_info_proxy: Some(setup_fake_sysinfo_service(Ok(()))),
914 ..Default::default()
915 });
916
917 let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
918
919 assert_eq!(resp.serial_number.unwrap(), SYSINFO_SERIAL);
920 assert_eq!(resp.board_config.unwrap(), BOARD_CONFIG);
921 assert_eq!(resp.product_config.unwrap(), PRODUCT_CONFIG);
922 assert_eq!(resp.nodename.unwrap(), NODENAME);
923
924 let addrs = resp.addresses.unwrap();
925 assert_eq!(
926 addrs[..],
927 [
928 fnet::Subnet {
929 addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: IPV4_ADDR }),
930 prefix_len: 4,
931 },
932 fnet::Subnet {
933 addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: IPV6_ADDR }),
934 prefix_len: 110,
935 }
936 ]
937 );
938
939 assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
940 assert_eq!(resp.f_release, crate::host_identifier::current_f_release());
941
942 Ok(())
943 }
944
945 #[fuchsia::test]
946 async fn test_ids_in_host_identify() -> Result<()> {
947 let (rcs_proxy, connector_proxy) = setup_rcs_proxy_with_connector();
948
949 let ident = rcs_proxy.identify_host().await.unwrap().unwrap();
950 assert_eq!(ident.ids, Some(vec![]));
951
952 let (pumpkin_a, _) = fidl::Socket::create_stream();
953 let (pumpkin_b, _) = fidl::Socket::create_stream();
954 let _node_ida = connector_proxy.establish_circuit(1234, pumpkin_a).await.unwrap();
955 let _node_idb = connector_proxy.establish_circuit(4567, pumpkin_b).await.unwrap();
956
957 let ident = rcs_proxy.identify_host().await.unwrap().unwrap();
958 let ids = ident.ids.unwrap();
959 assert_eq!(ids.len(), 2);
960 assert_eq!(1234u64, ids[0]);
961 assert_eq!(4567u64, ids[1]);
962
963 Ok(())
964 }
965
966 #[fuchsia::test]
967 async fn test_identify_default() -> Result<()> {
968 let (rcs_proxy, _) =
969 setup_rcs_proxy_from_env(RcsEnv { use_default_identifier: true, ..Default::default() });
970
971 let resp = rcs_proxy.identify_host().await.unwrap().unwrap();
972
973 assert_eq!(resp.nodename.unwrap(), "fuchsia-default-nodename");
974 assert_eq!(resp.serial_number.unwrap(), "fuchsia-default-serial-number");
975 assert_eq!(resp.board_config, None);
976 assert_eq!(resp.product_config, None);
977 assert_eq!(resp.addresses, None);
978 assert_eq!(resp.boot_timestamp_nanos.unwrap(), BOOT_TIME);
979 assert_eq!(resp.f_release, crate::host_identifier::current_f_release());
980
981 Ok(())
982 }
983}