| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::target_handle::TargetHandle, |
| anyhow::{anyhow, Result}, |
| async_trait::async_trait, |
| chrono::Utc, |
| ffx_daemon_events::{FastbootInterface, TargetConnectionState, TargetInfo}, |
| ffx_daemon_target::manual_targets, |
| ffx_daemon_target::target::{ |
| target_addr_info_to_socketaddr, Target, TargetAddrEntry, TargetAddrType, |
| }, |
| ffx_daemon_target::target_collection::TargetCollection, |
| ffx_stream_util::TryStreamUtilExt, |
| fidl::endpoints::ProtocolMarker, |
| fidl_fuchsia_developer_ffx as ffx, |
| fidl_fuchsia_developer_remotecontrol::RemoteControlMarker, |
| futures::TryStreamExt, |
| protocols::prelude::*, |
| std::net::SocketAddr, |
| std::rc::Rc, |
| std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}, |
| tasks::TaskManager, |
| }; |
| |
| mod reboot; |
| mod target_handle; |
| |
| #[ffx_protocol(ffx::MdnsMarker, ffx::FastbootTargetStreamMarker)] |
| pub struct TargetCollectionProtocol { |
| tasks: TaskManager, |
| |
| // An online cache of configured target entries (the non-discoverable targets represented in the |
| // ffx configuration). |
| // The cache can be updated by calls to AddTarget and RemoveTarget. |
| // With manual_targets, we have access to the targets.manual field of the configuration (a |
| // vector of strings). Each target is defined by an IP address and a port. |
| manual_targets: Rc<dyn manual_targets::ManualTargets>, |
| } |
| |
| impl Default for TargetCollectionProtocol { |
| fn default() -> Self { |
| #[cfg(not(test))] |
| let manual_targets = manual_targets::Config::default(); |
| #[cfg(test)] |
| let manual_targets = manual_targets::Mock::default(); |
| |
| Self { tasks: Default::default(), manual_targets: Rc::new(manual_targets) } |
| } |
| } |
| |
| async fn add_manual_target( |
| manual_targets: Rc<dyn manual_targets::ManualTargets>, |
| tc: &TargetCollection, |
| addr: SocketAddr, |
| lifetime: Option<Duration>, |
| ) -> Rc<Target> { |
| // Expiry is the SystemTime (represented as seconds after the UNIX_EPOCH) at which a manual |
| // target is allowed to expire and enter the Disconnected state. If no lifetime is given, |
| // the target is allowed to persist indefinitely. This is persisted in FFX config. |
| // Timeout is the number of seconds until the expiry is met; it is used in-memory only. |
| let (timeout, expiry, last_seen) = if lifetime.is_none() { |
| (None, None, None) |
| } else { |
| let timeout = SystemTime::now() + lifetime.unwrap(); |
| let expiry = timeout.duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO).as_secs(); |
| (Some(timeout), Some(expiry), Some(Instant::now())) |
| }; |
| |
| let tae = TargetAddrEntry::new(addr.into(), Utc::now(), TargetAddrType::Manual(timeout)); |
| let _ = manual_targets.add(format!("{}", addr), expiry).await.map_err(|e| { |
| log::error!("Unable to persist manual target: {:?}", e); |
| }); |
| let target = Target::new_with_addr_entries(Option::<String>::None, Some(tae).into_iter()); |
| if addr.port() != 0 { |
| target.set_ssh_port(Some(addr.port())); |
| } |
| |
| target.update_connection_state(|_| TargetConnectionState::Manual(last_seen)); |
| let target = tc.merge_insert(target); |
| target.run_host_pipe(); |
| target |
| } |
| |
| async fn remove_manual_target( |
| manual_targets: Rc<dyn manual_targets::ManualTargets>, |
| tc: &TargetCollection, |
| target_id: String, |
| ) -> bool { |
| if let Some(target) = tc.get(target_id.clone()) { |
| let ssh_port = target.ssh_port(); |
| for addr in target.manual_addrs() { |
| let mut sockaddr = SocketAddr::from(addr); |
| ssh_port.map(|p| sockaddr.set_port(p)); |
| let _ = manual_targets.remove(format!("{}", sockaddr)).await.map_err(|e| { |
| log::error!("Unable to persist target removal: {}", e); |
| }); |
| } |
| } |
| tc.remove_target(target_id) |
| } |
| |
| impl TargetCollectionProtocol { |
| async fn load_manual_targets(&self, tc: &TargetCollection) { |
| // The FFX config value for a manual target contains a target ID (typically the IP:PORT |
| // combo) and a timeout (which is None, if the target is indefinitely persistent). |
| for (str, val) in self.manual_targets.get_or_default().await { |
| let sa = match str.parse::<std::net::SocketAddr>() { |
| Ok(sa) => sa, |
| Err(e) => { |
| log::error!("Parse of manual target config failed: {}", e); |
| continue; |
| } |
| }; |
| let secs = val.as_u64(); |
| if secs.is_some() { |
| // If the manual target has a lifetime specified, we need to include it in the |
| // reloaded entry. |
| let lifetime_from_epoch = Duration::from_secs(secs.unwrap()); |
| let now = SystemTime::now(); |
| if let Ok(elapsed) = now.duration_since(UNIX_EPOCH) { |
| let remaining = if lifetime_from_epoch < elapsed { |
| Duration::ZERO |
| } else { |
| lifetime_from_epoch - elapsed |
| }; |
| add_manual_target(self.manual_targets.clone(), tc, sa, Some(remaining)).await; |
| } |
| } else { |
| // Manual targets without a lifetime are always reloaded. |
| add_manual_target(self.manual_targets.clone(), tc, sa, None).await; |
| } |
| } |
| } |
| } |
| |
| #[async_trait(?Send)] |
| impl FidlProtocol for TargetCollectionProtocol { |
| type Protocol = ffx::TargetCollectionMarker; |
| type StreamHandler = FidlStreamHandler<Self>; |
| |
| async fn handle(&self, cx: &Context, req: ffx::TargetCollectionRequest) -> Result<()> { |
| let target_collection = cx.get_target_collection().await?; |
| match req { |
| ffx::TargetCollectionRequest::ListTargets { reader, query, .. } => { |
| let reader = reader.into_proxy()?; |
| let targets = match query.string_matcher.as_deref() { |
| None | Some("") => target_collection |
| .targets() |
| .into_iter() |
| .filter_map( |
| |t| if t.is_connected() { Some(t.as_ref().into()) } else { None }, |
| ) |
| .collect::<Vec<ffx::TargetInfo>>(), |
| q => match target_collection.get_connected(q) { |
| Some(t) => vec![t.as_ref().into()], |
| None => vec![], |
| }, |
| }; |
| // This was chosen arbitrarily. It's possible to determine a |
| // better chunk size using some FIDL constant math. |
| const TARGET_CHUNK_SIZE: usize = 20; |
| let mut iter = targets.into_iter(); |
| loop { |
| let next_chunk = iter.by_ref().take(TARGET_CHUNK_SIZE); |
| let next_chunk_len = next_chunk.len(); |
| reader.next(&mut next_chunk.collect::<Vec<_>>().into_iter()).await?; |
| if next_chunk_len == 0 { |
| break; |
| } |
| } |
| Ok(()) |
| } |
| ffx::TargetCollectionRequest::OpenTarget { query, responder, target_handle } => { |
| let target = match target_collection.wait_for_match(query.string_matcher).await { |
| Ok(t) => t, |
| Err(e) => { |
| return responder |
| .send(&mut match e { |
| ffx::DaemonError::TargetAmbiguous => { |
| Err(ffx::OpenTargetError::QueryAmbiguous) |
| } |
| ffx::DaemonError::TargetNotFound => { |
| Err(ffx::OpenTargetError::TargetNotFound) |
| } |
| e => { |
| // This, so far, will only happen if encountering |
| // TargetCacheError, which is highly unlikely. |
| log::warn!("encountered unhandled error: {:?}", e); |
| Err(ffx::OpenTargetError::TargetNotFound) |
| } |
| }) |
| .map_err(Into::into); |
| } |
| }; |
| self.tasks.spawn(TargetHandle::new(target, cx.clone(), target_handle)?); |
| responder.send(&mut Ok(())).map_err(Into::into) |
| } |
| ffx::TargetCollectionRequest::AddTarget { ip, config, responder } => { |
| let addr = target_addr_info_to_socketaddr(ip); |
| match config.verify_connection { |
| Some(true) => {} |
| _ => { |
| let _ = add_manual_target( |
| self.manual_targets.clone(), |
| &target_collection, |
| addr, |
| None, |
| ) |
| .await; |
| return responder.send(&mut Ok(())).map_err(Into::into); |
| } |
| }; |
| // The drop guard is here for the impatient user: if the user closes their channel |
| // prematurely (before this operation either succeeds or fails), then they will |
| // risk adding a manual target that can never be connected to, and then have to |
| // manually remove the target themselves. |
| struct DropGuard( |
| Option<( |
| Rc<dyn manual_targets::ManualTargets>, |
| Rc<TargetCollection>, |
| SocketAddr, |
| )>, |
| ); |
| impl Drop for DropGuard { |
| fn drop(&mut self) { |
| match self.0.take() { |
| Some((mt, tc, addr)) => fuchsia_async::Task::local(async move { |
| remove_manual_target(mt, &tc, addr.to_string()).await |
| }) |
| .detach(), |
| None => {} |
| } |
| } |
| } |
| let mut drop_guard = DropGuard(Some(( |
| self.manual_targets.clone(), |
| target_collection.clone(), |
| addr.clone(), |
| ))); |
| let target = |
| add_manual_target(self.manual_targets.clone(), &target_collection, addr, None) |
| .await; |
| let rcs = target_handle::wait_for_rcs(&target).await?; |
| match rcs { |
| Ok(mut rcs) => { |
| let (rcs_proxy, server) = |
| fidl::endpoints::create_proxy::<RemoteControlMarker>()?; |
| rcs.copy_to_channel(server.into_channel())?; |
| match rcs::knock_rcs(&rcs_proxy).await { |
| Ok(_) => { |
| let _ = drop_guard.0.take(); |
| } |
| Err(e) => return responder.send(&mut Err(e)).map_err(Into::into), |
| } |
| } |
| Err(e) => { |
| let _ = remove_manual_target( |
| self.manual_targets.clone(), |
| &target_collection, |
| addr.to_string(), |
| ) |
| .await; |
| let _ = drop_guard.0.take(); |
| return responder.send(&mut Err(e)).map_err(Into::into); |
| } |
| } |
| responder.send(&mut Ok(())).map_err(Into::into) |
| } |
| ffx::TargetCollectionRequest::AddEphemeralTarget { |
| ip, |
| connect_timeout_seconds, |
| responder, |
| } => { |
| let addr = target_addr_info_to_socketaddr(ip); |
| add_manual_target( |
| self.manual_targets.clone(), |
| &target_collection, |
| addr, |
| Some(Duration::from_secs(connect_timeout_seconds)), |
| ) |
| .await; |
| responder.send().map_err(Into::into) |
| } |
| ffx::TargetCollectionRequest::RemoveTarget { target_id, responder } => { |
| let result = remove_manual_target( |
| self.manual_targets.clone(), |
| &target_collection, |
| target_id, |
| ) |
| .await; |
| responder.send(result).map_err(Into::into) |
| } |
| } |
| } |
| |
| async fn serve<'a>( |
| &'a self, |
| cx: &'a Context, |
| stream: <Self::Protocol as ProtocolMarker>::RequestStream, |
| ) -> Result<()> { |
| // Necessary to avoid hanging forever when a client drops a connection |
| // during a call to OpenTarget. |
| stream |
| .map_err(|err| anyhow!("{}", err)) |
| .try_for_each_concurrent_while_connected(None, |req| self.handle(cx, req)) |
| .await |
| } |
| |
| async fn stop(&mut self, _cx: &Context) -> Result<()> { |
| drop(self.tasks.drain()); |
| Ok(()) |
| } |
| |
| async fn start(&mut self, cx: &Context) -> Result<()> { |
| let target_collection = cx.get_target_collection().await?; |
| self.load_manual_targets(&target_collection).await; |
| let mdns = self.open_mdns_proxy(cx).await?; |
| let fastboot = self.open_fastboot_target_stream_proxy(cx).await?; |
| let tc = cx.get_target_collection().await?; |
| let tc_clone = tc.clone(); |
| self.tasks.spawn(async move { |
| while let Ok(Some(e)) = mdns.get_next_event().await { |
| match *e { |
| ffx::MdnsEventType::TargetFound(t) |
| | ffx::MdnsEventType::TargetRediscovered(t) => { |
| handle_mdns_event(&tc_clone, t); |
| } |
| _ => {} |
| } |
| } |
| }); |
| self.tasks.spawn(async move { |
| while let Ok(target) = fastboot.get_next().await { |
| handle_fastboot_target(&tc, target); |
| } |
| }); |
| Ok(()) |
| } |
| } |
| |
| fn handle_fastboot_target(tc: &Rc<TargetCollection>, target: ffx::FastbootTarget) { |
| if let Some(ref serial) = target.serial { |
| log::trace!("Found new target via fastboot: {}", serial); |
| } else { |
| log::trace!("Fastboot target has no serial number. Not able to merge."); |
| return; |
| } |
| let t = TargetInfo { serial: target.serial, ..Default::default() }; |
| let target = tc.merge_insert(Target::from_target_info(t.into())); |
| target.update_connection_state(|s| match s { |
| TargetConnectionState::Disconnected | TargetConnectionState::Fastboot(_) => { |
| TargetConnectionState::Fastboot(Instant::now()) |
| } |
| _ => s, |
| }); |
| } |
| |
| fn handle_mdns_event(tc: &Rc<TargetCollection>, t: ffx::TargetInfo) { |
| let t = TargetInfo { |
| nodename: t.nodename, |
| addresses: t |
| .addresses |
| .map(|a| a.into_iter().map(Into::into).collect()) |
| .unwrap_or(Vec::new()), |
| fastboot_interface: if t.target_state == Some(ffx::TargetState::Fastboot) { |
| t.fastboot_interface.map(|v| match v { |
| ffx::FastbootInterface::Usb => FastbootInterface::Usb, |
| ffx::FastbootInterface::Udp => FastbootInterface::Udp, |
| ffx::FastbootInterface::Tcp => FastbootInterface::Tcp, |
| }) |
| } else { |
| None |
| }, |
| ..Default::default() |
| }; |
| if t.fastboot_interface.is_some() { |
| log::trace!( |
| "Found new fastboot target via mdns: {}", |
| t.nodename.clone().unwrap_or("<unknown>".to_string()) |
| ); |
| let target = tc.merge_insert(match Target::from_fastboot_target_info(t) { |
| Ok(ret) => ret, |
| Err(e) => { |
| log::trace!("Error while making target: {:?}", e); |
| return; |
| } |
| }); |
| target.update_connection_state(|s| match s { |
| TargetConnectionState::Disconnected | TargetConnectionState::Fastboot(_) => { |
| TargetConnectionState::Fastboot(Instant::now()) |
| } |
| _ => s, |
| }); |
| } else { |
| log::trace!( |
| "Found new target via mdns: {}", |
| t.nodename.clone().unwrap_or("<unknown>".to_string()) |
| ); |
| let new_target = Target::from_target_info(t); |
| new_target.update_connection_state(|_| TargetConnectionState::Mdns(Instant::now())); |
| let target = tc.merge_insert(new_target); |
| target.run_host_pipe(); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use addr::TargetAddr; |
| use assert_matches::assert_matches; |
| use async_channel::{Receiver, Sender}; |
| use fidl_fuchsia_net::{IpAddress, Ipv6Address}; |
| use protocols::testing::FakeDaemonBuilder; |
| use serde_json::{json, Map, Value}; |
| use std::cell::RefCell; |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_handle_mdns_non_fastboot() { |
| let t = Target::new_named("this-is-a-thing"); |
| let tc = Rc::new(TargetCollection::new()); |
| tc.merge_insert(t.clone()); |
| let before_update = Instant::now(); |
| |
| handle_mdns_event( |
| &tc, |
| ffx::TargetInfo { nodename: Some(t.nodename().unwrap()), ..ffx::TargetInfo::EMPTY }, |
| ); |
| assert!(t.is_host_pipe_running()); |
| assert_matches!(t.get_connection_state(), TargetConnectionState::Mdns(t) if t > before_update); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_handle_mdns_fastboot() { |
| let t = Target::new_named("this-is-a-thing"); |
| let tc = Rc::new(TargetCollection::new()); |
| tc.merge_insert(t.clone()); |
| let before_update = Instant::now(); |
| |
| handle_mdns_event( |
| &tc, |
| ffx::TargetInfo { |
| nodename: Some(t.nodename().unwrap()), |
| target_state: Some(ffx::TargetState::Fastboot), |
| fastboot_interface: Some(ffx::FastbootInterface::Tcp), |
| ..ffx::TargetInfo::EMPTY |
| }, |
| ); |
| assert!(!t.is_host_pipe_running()); |
| assert_matches!(t.get_connection_state(), TargetConnectionState::Fastboot(t) if t > before_update); |
| } |
| |
| struct TestMdns { |
| /// Lets the test know that a call to `GetNextEvent` has started. This |
| /// is just a hack to avoid using timers for races. This is dependent |
| /// on the executor running in a single thread. |
| call_started: Sender<()>, |
| next_event: Receiver<ffx::MdnsEventType>, |
| } |
| |
| impl Default for TestMdns { |
| fn default() -> Self { |
| unimplemented!() |
| } |
| } |
| |
| #[async_trait(?Send)] |
| impl FidlProtocol for TestMdns { |
| type Protocol = ffx::MdnsMarker; |
| type StreamHandler = FidlStreamHandler<Self>; |
| |
| async fn handle(&self, _cx: &Context, req: ffx::MdnsRequest) -> Result<()> { |
| match req { |
| ffx::MdnsRequest::GetNextEvent { responder } => { |
| self.call_started.send(()).await.unwrap(); |
| responder.send(self.next_event.recv().await.ok().as_mut()).map_err(Into::into) |
| } |
| _ => panic!("unsupported"), |
| } |
| } |
| } |
| |
| async fn list_targets( |
| query: Option<&str>, |
| tc: &ffx::TargetCollectionProxy, |
| ) -> Vec<ffx::TargetInfo> { |
| let (reader, server) = |
| fidl::endpoints::create_endpoints::<ffx::TargetCollectionReaderMarker>().unwrap(); |
| tc.list_targets( |
| ffx::TargetQuery { |
| string_matcher: query.map(|s| s.to_owned()), |
| ..ffx::TargetQuery::EMPTY |
| }, |
| reader, |
| ) |
| .unwrap(); |
| let mut res = Vec::new(); |
| let mut stream = server.into_stream().unwrap(); |
| while let Ok(Some(ffx::TargetCollectionReaderRequest::Next { entry, responder })) = |
| stream.try_next().await |
| { |
| responder.send().unwrap(); |
| if entry.len() > 0 { |
| res.extend(entry); |
| } else { |
| break; |
| } |
| } |
| res |
| } |
| |
| #[derive(Default)] |
| struct FakeFastboot {} |
| |
| #[async_trait(?Send)] |
| impl FidlProtocol for FakeFastboot { |
| type Protocol = ffx::FastbootTargetStreamMarker; |
| type StreamHandler = FidlStreamHandler<Self>; |
| |
| async fn handle( |
| &self, |
| _cx: &Context, |
| _req: ffx::FastbootTargetStreamRequest, |
| ) -> Result<()> { |
| futures::future::pending::<()>().await; |
| Ok(()) |
| } |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_protocol_integration() { |
| const NAME: &'static str = "foo"; |
| const NAME2: &'static str = "bar"; |
| const NAME3: &'static str = "baz"; |
| const NON_MATCHING_NAME: &'static str = "mlorp"; |
| const PARTIAL_NAME_MATCH: &'static str = "ba"; |
| let (call_started_sender, call_started_receiver) = async_channel::unbounded::<()>(); |
| let (target_sender, r) = async_channel::unbounded::<ffx::MdnsEventType>(); |
| let mdns_protocol = |
| Rc::new(RefCell::new(TestMdns { call_started: call_started_sender, next_event: r })); |
| let fake_daemon = FakeDaemonBuilder::new() |
| .inject_fidl_protocol(mdns_protocol) |
| .register_fidl_protocol::<FakeFastboot>() |
| .register_fidl_protocol::<TargetCollectionProtocol>() |
| .build(); |
| let tc = fake_daemon.open_proxy::<ffx::TargetCollectionMarker>().await; |
| let res = list_targets(None, &tc).await; |
| assert_eq!(res.len(), 0); |
| call_started_receiver.recv().await.unwrap(); |
| target_sender |
| .send(ffx::MdnsEventType::TargetFound(ffx::TargetInfo { |
| nodename: Some(NAME.to_owned()), |
| ..ffx::TargetInfo::EMPTY |
| })) |
| .await |
| .unwrap(); |
| target_sender |
| .send(ffx::MdnsEventType::TargetFound(ffx::TargetInfo { |
| nodename: Some(NAME2.to_owned()), |
| ..ffx::TargetInfo::EMPTY |
| })) |
| .await |
| .unwrap(); |
| target_sender |
| .send(ffx::MdnsEventType::TargetFound(ffx::TargetInfo { |
| nodename: Some(NAME3.to_owned()), |
| ..ffx::TargetInfo::EMPTY |
| })) |
| .await |
| .unwrap(); |
| call_started_receiver.recv().await.unwrap(); |
| let res = list_targets(None, &tc).await; |
| assert_eq!(res.len(), 3, "received: {:?}", res); |
| assert!(res.iter().any(|t| t.nodename.as_ref().unwrap() == NAME)); |
| assert!(res.iter().any(|t| t.nodename.as_ref().unwrap() == NAME2)); |
| assert!(res.iter().any(|t| t.nodename.as_ref().unwrap() == NAME3)); |
| |
| let res = list_targets(Some(NON_MATCHING_NAME), &tc).await; |
| assert_eq!(res.len(), 0, "received: {:?}", res); |
| |
| let res = list_targets(Some(NAME), &tc).await; |
| assert_eq!(res.len(), 1, "received: {:?}", res); |
| assert_eq!(res[0].nodename.as_ref().unwrap(), NAME); |
| |
| let res = list_targets(Some(NAME2), &tc).await; |
| assert_eq!(res.len(), 1, "received: {:?}", res); |
| assert_eq!(res[0].nodename.as_ref().unwrap(), NAME2); |
| |
| let res = list_targets(Some(NAME3), &tc).await; |
| assert_eq!(res.len(), 1, "received: {:?}", res); |
| assert_eq!(res[0].nodename.as_ref().unwrap(), NAME3); |
| |
| let res = list_targets(Some(PARTIAL_NAME_MATCH), &tc).await; |
| assert_eq!(res.len(), 1, "received: {:?}", res); |
| assert!(res.iter().any(|t| { |
| let name = t.nodename.as_ref().unwrap(); |
| // Check either partial match just in case the backing impl |
| // changes ordering. Possible todo here would be to return multiple |
| // targets when there is a partial match. |
| name == NAME3 || name == NAME2 |
| })); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_handle_fastboot_target_no_serial() { |
| let tc = Rc::new(TargetCollection::new()); |
| handle_fastboot_target(&tc, ffx::FastbootTarget::EMPTY); |
| assert_eq!(tc.targets().len(), 0, "target collection should remain empty"); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_handle_fastboot_target() { |
| let tc = Rc::new(TargetCollection::new()); |
| handle_fastboot_target( |
| &tc, |
| ffx::FastbootTarget { serial: Some("12345".to_string()), ..ffx::FastbootTarget::EMPTY }, |
| ); |
| assert_eq!(tc.targets()[0].serial().as_deref(), Some("12345")); |
| } |
| |
| #[derive(Default)] |
| struct FakeMdns {} |
| |
| #[async_trait(?Send)] |
| impl FidlProtocol for FakeMdns { |
| type Protocol = ffx::MdnsMarker; |
| type StreamHandler = FidlStreamHandler<Self>; |
| |
| async fn handle(&self, _cx: &Context, _req: ffx::MdnsRequest) -> Result<()> { |
| futures::future::pending::<()>().await; |
| Ok(()) |
| } |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_persisted_manual_target_remove() { |
| let tc_impl = Rc::new(RefCell::new(TargetCollectionProtocol::default())); |
| let fake_daemon = FakeDaemonBuilder::new() |
| .register_fidl_protocol::<FakeMdns>() |
| .register_fidl_protocol::<FakeFastboot>() |
| .inject_fidl_protocol(tc_impl.clone()) |
| .build(); |
| // Set one timeout 1 hour in the future; the other will have no timeout. |
| let expiry = (SystemTime::now() + Duration::from_secs(3600)) |
| .duration_since(UNIX_EPOCH) |
| .expect("Problem getting a duration relative to epoch.") |
| .as_secs(); |
| tc_impl.borrow().manual_targets.add("127.0.0.1:8022".to_string(), None).await.unwrap(); |
| tc_impl |
| .borrow() |
| .manual_targets |
| .add("127.0.0.1:8023".to_string(), Some(expiry)) |
| .await |
| .unwrap(); |
| let target_collection = |
| Context::new(fake_daemon.clone()).get_target_collection().await.unwrap(); |
| tc_impl.borrow().load_manual_targets(&target_collection).await; |
| let proxy = fake_daemon.open_proxy::<ffx::TargetCollectionMarker>().await; |
| let res = list_targets(None, &proxy).await; |
| assert_eq!(2, res.len()); |
| assert!(proxy.remove_target("127.0.0.1:8022").await.unwrap()); |
| assert!(proxy.remove_target("127.0.0.1:8023").await.unwrap()); |
| assert_eq!(0, list_targets(None, &proxy).await.len()); |
| assert_eq!( |
| tc_impl.borrow().manual_targets.get_or_default().await, |
| Map::<String, Value>::new() |
| ); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_add_target() { |
| let fake_daemon = FakeDaemonBuilder::new() |
| .register_fidl_protocol::<FakeMdns>() |
| .register_fidl_protocol::<FakeFastboot>() |
| .register_fidl_protocol::<TargetCollectionProtocol>() |
| .build(); |
| let target_addr = TargetAddr::new("[::1]:0").unwrap(); |
| let proxy = fake_daemon.open_proxy::<ffx::TargetCollectionMarker>().await; |
| proxy |
| .add_target(&mut target_addr.into(), ffx::AddTargetConfig::EMPTY) |
| .await |
| .unwrap() |
| .unwrap(); |
| let target_collection = Context::new(fake_daemon).get_target_collection().await.unwrap(); |
| let target = target_collection.get(target_addr.to_string()).unwrap(); |
| assert_eq!(target.addrs().len(), 1); |
| assert_eq!(target.addrs().into_iter().next(), Some(target_addr)); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_add_ephemeral_target() { |
| let fake_daemon = FakeDaemonBuilder::new() |
| .register_fidl_protocol::<FakeMdns>() |
| .register_fidl_protocol::<FakeFastboot>() |
| .register_fidl_protocol::<TargetCollectionProtocol>() |
| .build(); |
| let target_addr = TargetAddr::new("[::1]:0").unwrap(); |
| let proxy = fake_daemon.open_proxy::<ffx::TargetCollectionMarker>().await; |
| proxy.add_ephemeral_target(&mut target_addr.into(), 3600).await.unwrap(); |
| let target_collection = Context::new(fake_daemon).get_target_collection().await.unwrap(); |
| let target = target_collection.get(target_addr.to_string()).unwrap(); |
| assert_eq!(target.addrs().len(), 1); |
| assert_eq!(target.addrs().into_iter().next(), Some(target_addr)); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_add_target_with_port() { |
| let fake_daemon = FakeDaemonBuilder::new() |
| .register_fidl_protocol::<FakeMdns>() |
| .register_fidl_protocol::<FakeFastboot>() |
| .register_fidl_protocol::<TargetCollectionProtocol>() |
| .build(); |
| let target_addr = TargetAddr::new("[::1]:8022").unwrap(); |
| let proxy = fake_daemon.open_proxy::<ffx::TargetCollectionMarker>().await; |
| proxy |
| .add_target(&mut target_addr.into(), ffx::AddTargetConfig::EMPTY) |
| .await |
| .unwrap() |
| .unwrap(); |
| let target_collection = Context::new(fake_daemon).get_target_collection().await.unwrap(); |
| let target = target_collection.get(target_addr.to_string()).unwrap(); |
| assert_eq!(target.addrs().len(), 1); |
| assert_eq!(target.addrs().into_iter().next(), Some(target_addr)); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_add_ephemeral_target_with_port() { |
| let fake_daemon = FakeDaemonBuilder::new() |
| .register_fidl_protocol::<FakeMdns>() |
| .register_fidl_protocol::<FakeFastboot>() |
| .register_fidl_protocol::<TargetCollectionProtocol>() |
| .build(); |
| let target_addr = TargetAddr::new("[::1]:8022").unwrap(); |
| let proxy = fake_daemon.open_proxy::<ffx::TargetCollectionMarker>().await; |
| proxy.add_ephemeral_target(&mut target_addr.into(), 3600).await.unwrap(); |
| let target_collection = Context::new(fake_daemon).get_target_collection().await.unwrap(); |
| let target = target_collection.get(target_addr.to_string()).unwrap(); |
| assert_eq!(target.addrs().len(), 1); |
| assert_eq!(target.addrs().into_iter().next(), Some(target_addr)); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_persisted_manual_target_add() { |
| let tc_impl = Rc::new(RefCell::new(TargetCollectionProtocol::default())); |
| let fake_daemon = FakeDaemonBuilder::new() |
| .register_fidl_protocol::<FakeMdns>() |
| .register_fidl_protocol::<FakeFastboot>() |
| .inject_fidl_protocol(tc_impl.clone()) |
| .build(); |
| let proxy = fake_daemon.open_proxy::<ffx::TargetCollectionMarker>().await; |
| proxy |
| .add_target( |
| &mut ffx::TargetAddrInfo::IpPort(ffx::TargetIpPort { |
| ip: IpAddress::Ipv6(Ipv6Address { |
| addr: [0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], |
| }), |
| port: 8022, |
| scope_id: 1, |
| }), |
| ffx::AddTargetConfig::EMPTY, |
| ) |
| .await |
| .unwrap() |
| .unwrap(); |
| let target_collection = Context::new(fake_daemon).get_target_collection().await.unwrap(); |
| assert_eq!(1, target_collection.targets().len()); |
| let mut map = Map::<String, Value>::new(); |
| map.insert("[fe80::1%1]:8022".to_string(), Value::Null); |
| assert_eq!(tc_impl.borrow().manual_targets.get().await.unwrap(), json!(map)); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_persisted_ephemeral_target_add() { |
| let tc_impl = Rc::new(RefCell::new(TargetCollectionProtocol::default())); |
| let fake_daemon = FakeDaemonBuilder::new() |
| .register_fidl_protocol::<FakeMdns>() |
| .register_fidl_protocol::<FakeFastboot>() |
| .inject_fidl_protocol(tc_impl.clone()) |
| .build(); |
| let proxy = fake_daemon.open_proxy::<ffx::TargetCollectionMarker>().await; |
| proxy |
| .add_ephemeral_target( |
| &mut ffx::TargetAddrInfo::IpPort(ffx::TargetIpPort { |
| ip: IpAddress::Ipv6(Ipv6Address { |
| addr: [0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], |
| }), |
| port: 8022, |
| scope_id: 1, |
| }), |
| 3600, |
| ) |
| .await |
| .unwrap(); |
| let target_collection = Context::new(fake_daemon).get_target_collection().await.unwrap(); |
| assert_eq!(1, target_collection.targets().len()); |
| assert!(tc_impl.borrow().manual_targets.get().await.unwrap().is_object()); |
| let value = tc_impl.borrow().manual_targets.get().await.unwrap(); |
| assert!(value.is_object()); |
| let map = value.as_object().unwrap(); |
| assert!(map.contains_key("[fe80::1%1]:8022")); |
| let target = map.get(&"[fe80::1%1]:8022".to_string()); |
| assert!(target.is_some()); |
| let now = SystemTime::now() |
| .duration_since(UNIX_EPOCH) |
| .expect("Couldn't get duration from epoch.") |
| .as_secs(); |
| assert!(target.unwrap().as_u64().unwrap() > now); |
| } |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_persisted_manual_target_load() { |
| let tc_impl = Rc::new(RefCell::new(TargetCollectionProtocol::default())); |
| let fake_daemon = FakeDaemonBuilder::new() |
| .register_fidl_protocol::<FakeMdns>() |
| .register_fidl_protocol::<FakeFastboot>() |
| .inject_fidl_protocol(tc_impl.clone()) |
| .build(); |
| // We attempt to load three targets: |
| // - One with no timeout, should load, |
| // - One with an expired timeout, should load, and |
| // - One with a future timeout, should load. |
| let now = SystemTime::now() |
| .duration_since(UNIX_EPOCH) |
| .expect("Couldn't load duration since epoch.") |
| .as_secs(); |
| let expired = now - 3600; |
| let future = now + 3600; |
| tc_impl.borrow().manual_targets.add("127.0.0.1:8022".to_string(), None).await.unwrap(); |
| tc_impl |
| .borrow() |
| .manual_targets |
| .add("127.0.0.1:8023".to_string(), Some(expired)) |
| .await |
| .unwrap(); |
| tc_impl |
| .borrow() |
| .manual_targets |
| .add("127.0.0.1:8024".to_string(), Some(future)) |
| .await |
| .unwrap(); |
| |
| let target_collection = Context::new(fake_daemon).get_target_collection().await.unwrap(); |
| // This happens in FidlProtocol::start(), but we want to avoid binding the |
| // network sockets in unit tests, thus not calling start. |
| tc_impl.borrow().load_manual_targets(&target_collection).await; |
| |
| let target = target_collection.get("127.0.0.1:8022".to_string()).unwrap(); |
| assert_eq!(target.ssh_address(), Some("127.0.0.1:8022".parse::<SocketAddr>().unwrap())); |
| let target = target_collection.get("127.0.0.1:8023".to_string()).unwrap(); |
| assert_eq!(target.ssh_address(), Some("127.0.0.1:8023".parse::<SocketAddr>().unwrap())); |
| let target = target_collection.get("127.0.0.1:8024".to_string()).unwrap(); |
| assert_eq!(target.ssh_address(), Some("127.0.0.1:8024".parse::<SocketAddr>().unwrap())); |
| } |
| } |